aboutsummaryrefslogtreecommitdiff
path: root/project/dclib
diff options
context:
space:
mode:
Diffstat (limited to 'project/dclib')
-rw-r--r--project/dclib/Makefile.inc67
-rw-r--r--project/dclib/dclib-basics.c8065
-rw-r--r--project/dclib/dclib-basics.h5252
-rw-r--r--project/dclib/dclib-color.c4351
-rw-r--r--project/dclib/dclib-color.h828
-rw-r--r--project/dclib/dclib-debug.c1891
-rw-r--r--project/dclib/dclib-debug.h714
-rw-r--r--project/dclib/dclib-file.c5360
-rw-r--r--project/dclib/dclib-file.h1278
-rw-r--r--project/dclib/dclib-gen-ui.h162
-rw-r--r--project/dclib/dclib-gen-ui.inc1303
-rw-r--r--project/dclib/dclib-mysql.c1560
-rw-r--r--project/dclib/dclib-mysql.h544
-rw-r--r--project/dclib/dclib-network-linux.c461
-rw-r--r--project/dclib/dclib-network-linux.h171
-rw-r--r--project/dclib/dclib-network.c4546
-rw-r--r--project/dclib/dclib-network.h1486
-rw-r--r--project/dclib/dclib-numeric.c6102
-rw-r--r--project/dclib/dclib-numeric.h839
-rw-r--r--project/dclib/dclib-option.c1012
-rw-r--r--project/dclib/dclib-option.h383
-rw-r--r--project/dclib/dclib-regex.c465
-rw-r--r--project/dclib/dclib-regex.h131
-rw-r--r--project/dclib/dclib-shift-jis.c1408
-rw-r--r--project/dclib/dclib-system.h101
-rw-r--r--project/dclib/dclib-tables.c417
-rw-r--r--project/dclib/dclib-types.h403
-rw-r--r--project/dclib/dclib-ui.c950
-rw-r--r--project/dclib/dclib-ui.h215
-rw-r--r--project/dclib/dclib-utf8.c1910
-rw-r--r--project/dclib/dclib-utf8.h130
-rw-r--r--project/dclib/dclib-vector.c2582
-rw-r--r--project/dclib/dclib-xdump.c2214
-rw-r--r--project/dclib/dclib-xdump.h348
-rwxr-xr-xproject/dclib/lib-bmg.c6346
-rwxr-xr-xproject/dclib/lib-bmg.h1125
-rw-r--r--project/dclib/lib-dol.c1208
-rw-r--r--project/dclib/lib-dol.h517
-rw-r--r--project/dclib/lib-mkw.c1107
-rw-r--r--project/dclib/lib-mkw.h308
40 files changed, 68260 insertions, 0 deletions
diff --git a/project/dclib/Makefile.inc b/project/dclib/Makefile.inc
new file mode 100644
index 0000000..6ab04d8
--- /dev/null
+++ b/project/dclib/Makefile.inc
@@ -0,0 +1,67 @@
+
+#------------------------------------------------------------------------------
+# standard object files
+
+DCLIB_O = dclib-basics.o dclib-color.o dclib-numeric.o \
+ dclib-file.o dclib-option.o dclib-regex.o \
+ dclib-utf8.o dclib-shift-jis.o \
+ dclib-vector.o dclib-tables.o dclib-ui.o \
+ dclib-xdump.o dclib-debug.o
+DCLIB_O1 := lib-dol.o lib-bmg.o lib-mkw.o
+DCLIB_O2 := lib-wiimmfi.o lib-gamespy.o lib-natneg.o
+
+DCLIB_PRIVATE := dclib-terminal* dclib-command* lib-wiimmfi* lib-gamespy* lib-natneg*
+
+#------------------------------------------------------------------------------
+# if DCLIB_MYSQL not set: enable it if file 'mysql.h' is found
+
+ifeq ($(origin DCLIB_MYSQL), undefined)
+ DCLIB_MYSQL := $(shell [[ -f /usr/include/mysql/mysql.h ]]; echo $$((!$$?)))
+endif
+
+#------------------------------------------------------------------------------
+# if DCLIB_TERMINAL not set: disable it
+
+ifeq ($(origin DCLIB_TERMINAL), undefined)
+ DCLIB_TERMINAL := 0
+endif
+
+#------------------------------------------------------------------------------
+# if DCLIB_NETWORK not set: enable it if DCLIB_MYSQL is also set
+
+ifeq ($(origin DCLIB_NETWORK), undefined)
+ DCLIB_NETWORK := $(DCLIB_MYSQL)
+endif
+
+#------------------------------------------------------------------------------
+# some more setting
+
+ifeq ($(origin DCLIB_HAVE_USABLE_SIZE), undefined)
+ DCLIB_HAVE_USABLE_SIZE := $(shell grep -sqw malloc_usable_size /usr/include/malloc.h; echo $$((!$$?)))
+endif
+
+#------------------------------------------------------------------------------
+# add libraries and guardian macros
+
+ifeq ($(DCLIB_NETWORK),1)
+ DCLIB_O += dclib-network.o
+ DCLIB_FLAGS += -DDCLIB_NETWORK=1
+else
+ DCLIB_MYSQL := 0
+endif
+
+ifeq ($(DCLIB_MYSQL),1)
+ DCLIB_O += dclib-mysql.o
+ DCLIB_FLAGS += -DDCLIB_MYSQL=1
+endif
+
+ifeq ($(DCLIB_TERMINAL),1)
+ DCLIB_O += dclib-terminal.o dclib-command.o
+ DCLIB_FLAGS += -DDCLIB_TERMINAL=1
+endif
+
+ifeq ($(DCLIB_HAVE_USABLE_SIZE),1)
+ DCLIB_FLAGS += -DDCLIB_HAVE_USABLE_SIZE=1
+endif
+
+#------------------------------------------------------------------------------
diff --git a/project/dclib/dclib-basics.c b/project/dclib/dclib-basics.c
new file mode 100644
index 0000000..74676d0
--- /dev/null
+++ b/project/dclib/dclib-basics.c
@@ -0,0 +1,8065 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#ifndef WIN_SZS_LIB
+ // needed for GetTermWidthFD()
+ #include <sys/ioctl.h>
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+#include "dclib-basics.h"
+#include "dclib-debug.h"
+#include "dclib-file.h"
+#include "dclib-utf8.h"
+#include "dclib-network.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// some basic string functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const char EmptyQuote[] = "\"\""; // Ignored by FreeString()
+const char EmptyString[4] = "\0\0\0"; // Ignored by FreeString()
+const char MinusString[4] = "-\0\0"; // Ignored by FreeString()
+
+const char LoDigits[0x41] =
+ "0123456789"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ ".+";
+
+const char HiDigits[0x41] =
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ ".+";
+
+const char Tabs20[21] = // 20 * TABS + NULL
+ "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+
+const char LF20[21] = // 20 * LF + NULL
+ "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
+
+const char Space200[201] = // 200 * ' ' + NULL
+ " "
+ " "
+ " "
+ " ";
+
+const char Minus300[301] = // 300 * '-' + NULL
+ "--------------------------------------------------"
+ "--------------------------------------------------"
+ "--------------------------------------------------"
+ "--------------------------------------------------"
+ "--------------------------------------------------"
+ "--------------------------------------------------";
+
+const char Underline200[201] = // 200 * '_' + NULL
+ "__________________________________________________"
+ "__________________________________________________"
+ "__________________________________________________"
+ "__________________________________________________";
+
+const char Hash200[201] = // 200 * '#' + NULL
+ "##################################################"
+ "##################################################"
+ "##################################################"
+ "##################################################";
+
+const char Slash200[201] = // 200 * '/' + NULL
+ "//////////////////////////////////////////////////"
+ "//////////////////////////////////////////////////"
+ "//////////////////////////////////////////////////"
+ "//////////////////////////////////////////////////";
+
+// UTF-8 => 3 bytes per char
+const char ThinLine300_3[901] = // 300 * '─' (U+2500) + NULL
+ "──────────────────────────────────────────────────"
+ "──────────────────────────────────────────────────"
+ "──────────────────────────────────────────────────"
+ "──────────────────────────────────────────────────"
+ "──────────────────────────────────────────────────"
+ "──────────────────────────────────────────────────";
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintSettingsDCLIB ( FILE *f, int indent )
+{
+ DASSERT(f);
+ indent = NormalizeIndent(indent);
+ MARK_USED(indent); // for the case, that all other conditions fail!
+
+ #ifdef DCLIB_NETWORK
+ fprintf(f,"%*s" "DCLIB_NETWORK\n",indent,"");
+ #endif
+
+ #ifdef DCLIB_TERMINAL
+ fprintf(f,"%*s" "DCLIB_TERMINAL\n",indent,"");
+ #endif
+
+ #ifdef DCLIB_MYSQL
+ fprintf(f,"%*s" "DCLIB_MYSQL\n",indent,"");
+ #endif
+
+ #ifdef DCLIB_HAVE_USABLE_SIZE
+ fprintf(f,"%*s" "DCLIB_HAVE_USABLE_SIZE\n",indent,"");
+ #endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupMultiProcessing()
+{
+ multi_processing = true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupProgname ( int argc, char ** argv, ccp tname, ccp tvers, ccp ttitle )
+{
+ if (tname)
+ toolname = tname;
+ if (tvers)
+ toolversion = tvers;
+ if (ttitle)
+ tooltitle = ttitle;
+
+ if (!progpath)
+ {
+ char path[PATH_MAX];
+ int res = GetProgramPath( path, sizeof(path), true, argc > 0 ? argv[0] : 0 );
+ if ( res > 0 )
+ progpath = STRDUP(path);
+ }
+
+ if ( argc > 0 && *argv && **argv )
+ {
+ ccp slash = strrchr(*argv,'/');
+ progname = slash ? slash + 1 : *argv;
+ }
+ else
+ progname = toolname && *toolname ? toolname : "?";
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp ProgramPath()
+{
+ static bool done = false;
+ if ( !progpath && !done )
+ {
+ done = true;
+
+ char path[PATH_MAX];
+ int res = GetProgramPath( path, sizeof(path), true, 0 );
+ if ( res > 0 )
+ progpath = STRDUP(path);
+ }
+ return progpath;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp ProgramDirectory()
+{
+ if (!progdir)
+ {
+ ccp path = ProgramPath();
+ if (path)
+ {
+ ccp end = strrchr(path,'/');
+ const uint len = end ? end - path : strlen(path);
+ progdir = MEMDUP(path,len);
+ }
+ }
+ return progdir;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __CYGWIN__
+
+ccp ProgramPathNoExt()
+{
+ static ccp res = 0;
+ if (!res)
+ {
+ res = ProgramPath();
+ if (res)
+ {
+ ccp slash = strrchr(res,'/');
+ ccp ext = strrchr( slash ? slash + 1 : res, '.' );
+ if (ext)
+ res = MEMDUP(res,ext-res);
+ }
+ }
+ return res;
+}
+
+#endif // __CYGWIN__
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetProgramPath
+(
+ // returns strlen(buf) on success, -1 on failure
+
+ char *buf, // buffer for the result; empty string on failure.
+ uint buf_size, // size of buffer
+ bool check_proc, // true: read files of /proc/... to find path
+ ccp argv0 // not NULL: analyse this argument (argv[0])
+)
+{
+ char path[PATH_MAX], path2[PATH_MAX];
+
+
+ //--- read files of /proc/...
+
+ if (check_proc)
+ {
+ static ccp tab[] =
+ {
+ "/proc/self/exe",
+ "/proc/curproc/file",
+ "/proc/self/path/a.out",
+ 0
+ };
+
+ ccp *ptr;
+ for ( ptr = tab; *ptr; ptr++ )
+ if ( realpath(*ptr,path) && *path )
+ return StringCopyS(buf,buf_size,path) - buf;
+ }
+
+
+ //--- analyze argv0
+
+ if (argv0)
+ {
+ if ( *argv0 == '/' )
+ {
+ // absolute path
+ if ( realpath(argv0,path) && *path )
+ return StringCopyS(buf,buf_size,path) - buf;
+ }
+ else if (strchr(argv0,'/'))
+ {
+ // relative path
+ if ( getcwd(path2,sizeof(path2)) > 0 )
+ {
+ ccp p2 = PathCatPP(path2,sizeof(path2),path,argv0);
+ if ( realpath(p2,path) && *path )
+ return StringCopyS(buf,buf_size,path) - buf;
+ }
+ }
+ else
+ {
+ // simple name => search in path environment
+ uint arglen = strlen(argv0);
+ if ( arglen < sizeof(path2) - 2 )
+ {
+ ccp dest_end = path2 + sizeof(path2) - 1 - arglen;
+ ccp env = getenv("PATH");
+ while ( env && *env )
+ {
+ while ( *env == ':' )
+ env++;
+ char *dest = path2;
+ while ( *env && *env != ':' && dest < dest_end )
+ *dest++ = *env++;
+ *dest++ = '/';
+ memcpy(dest,argv0,arglen+1);
+ if ( realpath(path2,path) && *path )
+ return StringCopyS(buf,buf_size,path) - buf;
+ }
+ }
+ }
+ }
+
+
+ //--- failed
+
+ *buf = 0;
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FreeString ( ccp str )
+{
+ noTRACE("FreeString(%p) EmptyString=%p MinusString=%p\n",
+ str, EmptyString, MinusString );
+ if ( str != EmptyString && str != EmptyQuote && str != MinusString )
+ FREE((char*)str);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * StringCopyE ( char * buf, ccp buf_end, ccp src )
+{
+ // RESULT: end of copied string pointing to NULL
+ // 'src' may be a NULL pointer.
+
+ if ( buf >= buf_end )
+ return (char*)buf_end - 1;
+
+ DASSERT(buf);
+ DASSERT(buf<buf_end);
+ buf_end--;
+
+ if (src)
+ while( buf < buf_end && *src )
+ *buf++ = *src++;
+
+ *buf = 0;
+ return buf;
+}
+
+//-----------------------------------------------------------------------------
+
+char * StringCopyS ( char * buf, size_t buf_size, ccp src )
+{
+ return StringCopyE(buf,buf+buf_size,src);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * StringCopyEM ( char * buf, ccp buf_end, ccp src, size_t max_copy )
+{
+ // RESULT: end of copied string pointing to NULL
+ // 'src' may be a NULL pointer.
+
+ ASSERT(buf);
+ ASSERT(buf<buf_end);
+ buf_end--;
+ if ( max_copy >= 0 && buf + max_copy < buf_end )
+ buf_end = buf + max_copy;
+
+ if (src)
+ while( buf < buf_end && *src )
+ *buf++ = *src++;
+
+ *buf = 0;
+ return buf;
+}
+
+//-----------------------------------------------------------------------------
+
+char * StringCopySM ( char * buf, size_t buf_size, ccp src, size_t max_copy )
+{
+ return StringCopyEM(buf,buf+buf_size,src,max_copy);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * StringCat2E ( char * buf, ccp buf_end, ccp src1, ccp src2 )
+{
+ // RESULT: end of copied string pointing to NULL
+ // 'src*' may be a NULL pointer.
+
+ ASSERT(buf);
+ ASSERT(buf<buf_end);
+ buf_end--;
+
+ if (src1)
+ while( buf < buf_end && *src1 )
+ *buf++ = *src1++;
+
+ if (src2)
+ while( buf < buf_end && *src2 )
+ *buf++ = *src2++;
+
+ *buf = 0;
+ return buf;
+}
+
+//-----------------------------------------------------------------------------
+
+char * StringCat2S ( char * buf, size_t buf_size, ccp src1, ccp src2 )
+{
+ return StringCat2E(buf,buf+buf_size,src1,src2);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * StringCat3E ( char * buf, ccp buf_end, ccp src1, ccp src2, ccp src3 )
+{
+ // RESULT: end of copied string pointing to NULL
+ // 'src*' may be a NULL pointer.
+
+ ASSERT(buf);
+ ASSERT(buf<buf_end);
+ buf_end--;
+
+ if (src1)
+ while( buf < buf_end && *src1 )
+ *buf++ = *src1++;
+
+ if (src2)
+ while( buf < buf_end && *src2 )
+ *buf++ = *src2++;
+
+ if (src3)
+ while( buf < buf_end && *src3 )
+ *buf++ = *src3++;
+
+ *buf = 0;
+ return buf;
+}
+
+//-----------------------------------------------------------------------------
+
+char * StringCat3S ( char * buf, size_t buf_size, ccp src1, ccp src2, ccp src3 )
+{
+ return StringCat3E(buf,buf+buf_size,src1,src2,src3);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// [[?]]
+
+char * StringCat2A ( ccp src1, ccp src2 )
+{
+ mem_t mem = MemCat2A( MemByString(src1), MemByString(src2) );
+ return (char*)mem.ptr;
+}
+
+//-----------------------------------------------------------------------------
+
+char * StringCat3A ( ccp src1, ccp src2, ccp src3 )
+{
+ mem_t mem = MemCat3A( MemByString(src1), MemByString(src2), MemByString(src3) );
+ return (char*)mem.ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * StringLowerE ( char * buf, ccp buf_end, ccp src )
+{
+ // RESULT: end of copied string pointing to NULL
+ // 'src' may be a NULL pointer.
+
+ if ( buf >= buf_end )
+ return (char*)buf_end - 1;
+
+ DASSERT(buf);
+ DASSERT(buf<buf_end);
+ buf_end--;
+
+ if (src)
+ while( buf < buf_end && *src )
+ *buf++ = tolower((int)*src++);
+
+ *buf = 0;
+ return buf;
+}
+
+//-----------------------------------------------------------------------------
+
+char * StringLowerS ( char * buf, size_t buf_size, ccp src )
+{
+ return StringLowerE(buf,buf+buf_size,src);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * StringUpperE ( char * buf, ccp buf_end, ccp src )
+{
+ // RESULT: end of copied string pointing to NULL
+ // 'src' may be a NULL pointer.
+
+ if ( buf >= buf_end )
+ return (char*)buf_end - 1;
+
+ DASSERT(buf);
+ DASSERT(buf<buf_end);
+ buf_end--;
+
+ if (src)
+ while( buf < buf_end && *src )
+ *buf++ = toupper((int)*src++);
+
+ *buf = 0;
+ return buf;
+}
+
+//-----------------------------------------------------------------------------
+
+char * StringUpperS ( char * buf, size_t buf_size, ccp src )
+{
+ return StringUpperE(buf,buf+buf_size,src);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// PathCat*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PathCatBufPP ( char * buf, size_t bufsize, ccp path1, ccp path2 )
+{
+ // concatenate path + path; returns buf
+
+ DASSERT(buf);
+ DASSERT(bufsize);
+
+ if ( !path1 || !*path1 )
+ {
+ if (path2)
+ StringCopyS(buf,bufsize,path2);
+ else
+ *buf = 0;
+ return buf;
+ }
+
+ char * ptr = StringCopyS(buf,bufsize-1,path1);
+ DASSERT( ptr > buf );
+ if ( ptr[-1] != '/' )
+ *ptr++ = '/';
+ if (path2)
+ {
+ while ( *path2 == '/' )
+ path2++;
+ StringCopyE(ptr,buf+bufsize,path2);
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathCatPP ( char * buf, size_t bufsize, ccp path1, ccp path2 )
+{
+ // concatenate path + path; returns path1 | path2 | buf
+
+ if ( !path1 || !*path1 )
+ return path2 ? path2 : "";
+
+ if ( !path2 || !*path2 )
+ return path1;
+
+ char * ptr = StringCopyS(buf,bufsize-1,path1);
+ DASSERT( ptr > buf );
+ if ( ptr[-1] != '/' )
+ *ptr++ = '/';
+ while ( *path2 == '/' )
+ path2++;
+ StringCopyE(ptr,buf+bufsize,path2);
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathAllocPP ( ccp path1, ccp path2 )
+{
+ char buf[PATH_MAX];
+ return STRDUP(PathCatPP(buf,sizeof(buf),path1,path2));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PathCatBufPPE ( char * buf, size_t bufsize, ccp path1, ccp path2, ccp ext )
+{
+ // concatenate path + path; returns (by definition) path1 | path2 | buf
+
+ DASSERT(buf);
+ DASSERT(bufsize);
+
+ char *ptr = path1 ? StringCopyS(buf,bufsize-1,path1) : buf;
+ *ptr = 0;
+
+ if (path2)
+ {
+ if ( ptr > buf && ptr[-1] != '/' )
+ *ptr++ = '/';
+ while ( *path2 == '/' )
+ path2++;
+ ptr = StringCopyE(ptr,buf+bufsize,path2);
+ }
+
+ if (ext)
+ StringCopyE(ptr,buf+bufsize,ext);
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// PathCatPPE() is inline
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathAllocPPE ( ccp path1, ccp path2, ccp ext )
+{
+ char buf[PATH_MAX];
+ return STRDUP(PathCatPPE(buf,sizeof(buf),path1,path2,ext));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PathCatBufBP ( char *buf, size_t bufsize, ccp base, ccp path )
+{
+ // PathCatBP() is special: path can be part of buf
+
+ if (!path)
+ path = "";
+ if ( *path == '/' || base && !*base )
+ {
+ // already absolute | no base to add
+ StringCopyS(buf,bufsize,path);
+ return buf;
+ }
+
+
+ //--- now copy base or cwd
+
+ while ( path[0] == '.' && path[1] == '/' )
+ path += 2;
+
+ char buf2[PATH_MAX];
+ StringCopyS(buf2,sizeof(buf2),path);
+
+ char *dest;
+ if (base)
+ dest = StringCopyS(buf,bufsize,base);
+ else
+ {
+ char *cwd = getcwd(buf,bufsize);
+ if (cwd)
+ dest = buf + strlen(buf);
+ else
+ {
+ // on error: return fallback
+ dest = StringCopyS(buf,bufsize,"./");
+ }
+ }
+ StringCat2E(dest,buf+bufsize,"/",buf2);
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathCatBP ( char *buf, size_t bufsize, ccp base, ccp path )
+{
+ // PathCatBP() is special: path can be part of buf
+
+ if ( path && *path == '/' || base && !*base )
+ return path; // already absolute | no base to add
+
+ return PathCatBufBP(buf,bufsize,base,path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathAllocBP ( ccp base, ccp path )
+{
+ char buf[PATH_MAX];
+ return STRDUP(PathCatBP(buf,sizeof(buf),base,path));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PathCatBufBPP ( char * buf, size_t bufsize, ccp base, ccp path1, ccp path2 )
+{
+ ccp path = PathCatPP(buf,bufsize,path1,path2);
+ return PathCatBufBP(buf,bufsize,base,path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathCatBPP ( char * buf, size_t bufsize, ccp base, ccp path1, ccp path2 )
+{
+ ccp path = PathCatPP(buf,bufsize,path1,path2);
+ return PathCatBP(buf,bufsize,base,path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathAllocBPP ( ccp base, ccp path1, ccp path2 )
+{
+ char buf[PATH_MAX];
+ return STRDUP(PathCatBPP(buf,sizeof(buf),base,path1,path2));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PathCatBufBPPE ( char * buf, size_t bufsize, ccp base, ccp path1, ccp path2, ccp ext )
+{
+ ccp path = PathCatPPE(buf,bufsize,path1,path2,ext);
+ return PathCatBufBP(buf,bufsize,base,path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathCatBPPE ( char * buf, size_t bufsize, ccp base, ccp path1, ccp path2, ccp ext )
+{
+ ccp path = PathCatPPE(buf,bufsize,path1,path2,ext);
+ return PathCatBP(buf,bufsize,base,path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PathAllocBPPE ( ccp base, ccp path1, ccp path2, ccp ext )
+{
+ char buf[PATH_MAX];
+ return STRDUP(PathCatBPPE(buf,sizeof(buf),base,path1,path2,ext));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PathCombine ( char *buf, uint buf_size, ccp path, ccp base_path )
+{
+ DASSERT(buf);
+ DASSERT( buf_size > 10 );
+
+ if (!path)
+ path = "";
+
+ char *dest = buf;
+ if ( base_path && *base_path && *path != '/' )
+ {
+ StringCopyS(buf,buf_size,base_path);
+ char *p = strrchr(buf,'/');
+ if (p)
+ dest = p+1;
+ }
+
+ StringCopyE(dest,buf+buf_size,path);
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PathCombineFast ( char *buf, uint buf_size, ccp path, ccp base_path )
+{
+ DASSERT(buf);
+ DASSERT( buf_size > 10 );
+
+ if (!path)
+ path = "";
+
+ if ( !base_path || !*base_path || *path == '/' )
+ return (char*)path;
+
+ StringCopyS(buf,buf_size,base_path);
+ char *p = strrchr(buf,'/');
+ if (!p)
+ return (char*)path;
+
+ StringCopyE(p+1,buf+buf_size,path);
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * NewFileExtE ( char * buf, ccp buf_end, ccp path, ccp ext )
+{
+ DASSERT( buf);
+ DASSERT( buf_end);
+ DASSERT( buf_end > buf );
+
+ char * end = path && path != buf
+ ? StringCopyE(buf,buf_end-1,path)
+ : buf + strlen(buf);
+ DASSERT( end < buf_end );
+
+ char * ptr = end;
+ while ( ptr > buf && *--ptr != '/' )
+ if ( *ptr == '.' )
+ {
+ end = ptr;
+ break;
+ }
+
+ if ( ext && *ext )
+ {
+ if ( *ext != '.' )
+ *end++ = '.';
+ end = StringCopyE(end,buf_end,ext);
+ }
+
+ *end = 0;
+ return end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * NewFileExtS ( char * buf, size_t bufsize, ccp path, ccp ext )
+{
+ return NewFileExtE(buf,buf+bufsize,path,ext);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int StrPathCmp ( ccp path1, ccp path2 )
+{
+ if ( path1[0] == '.' && path1[1] == '/' )
+ path1 += 2;
+ if ( path2[0] == '.' && path2[1] == '/' )
+ path2 += 2;
+ return strcmp(path1,path2);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int PathCmp
+(
+ ccp path1, // NULL or first path
+ ccp path2, // NULL or second path
+ uint mode // bit field:
+ // 1: skip (multiple) leading './'
+ // 2: compare case insensitive
+ // 4: sort with respect for unsigned decimal numbers
+)
+{
+ if (!path2)
+ return path1 != 0;
+ if (!path1)
+ return -1;
+
+ noPRINT("PathCmp( | %s | %s | %u )\n",path1,path2,mode);
+
+ //--- skip './'
+
+ if ( mode & 1 )
+ {
+ while ( path1[0] == '.' && path1[1] == '/' )
+ path1 += 2;
+ while ( path2[0] == '.' && path2[1] == '/' )
+ path2 += 2;
+ }
+
+
+ //--- eliminate equal characters
+
+ while ( *path1 == *path2 )
+ {
+ if (!*path1)
+ return 0;
+ path1++;
+ path2++;
+ }
+
+
+ //--- start the case+path test
+
+ ccp p1 = path1;
+ ccp p2 = path2;
+
+ const uint ignore_case = mode & 2;
+
+ while ( *p1 || *p2 )
+ {
+ int ch1, ch2;
+ if (ignore_case)
+ {
+ ch1 = (uchar)tolower((int)*p1++);
+ ch2 = (uchar)tolower((int)*p2++);
+ }
+ else
+ {
+ ch1 = (uchar)*p1++;
+ ch2 = (uchar)*p2++;
+ }
+ int stat = ch1 - ch2;
+ noPRINT("%02x,%02x,%c %02x,%02x,%c -> %2d\n",
+ p1[-1], ch1, ch1, p2[-1], ch2, ch2, stat );
+
+ if (stat)
+ {
+ if ( mode & 4 )
+ {
+ p1--;
+ while ( *p1 == '0' )
+ p1++;
+ ccp q1 = p1;
+ while ( *q1 >= '0' && *q1 <= '9' )
+ q1++;
+ const int len1 = q1 - p1;
+
+ if (len1)
+ {
+ p2--;
+ while ( *p2 == '0' )
+ p2++;
+ ccp q2 = p2;
+ while ( *q2 >= '0' && *q2 <= '9' )
+ q2++;
+ const int len2 = q2 - p2;
+
+ if (len2)
+ {
+ int stat2 = len1 - len2;
+ if (!stat2)
+ stat2 = memcmp(p1,p2,len1);
+ if (stat2)
+ return stat2;
+ }
+ }
+ }
+ return ch1 == '/' ? -1 : ch2 == '/' ? 1 : stat;
+ }
+
+ if ( ch1 == '/' )
+ break;
+ }
+
+ return strcmp(path1,path2);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int StrNumCmp ( ccp a, ccp b )
+{
+ if (!b)
+ return a != 0;
+ if (!a)
+ return -1;
+
+ for(;;)
+ {
+ const int va = *a++ & 0xff;
+ const int vb = *b++ & 0xff;
+ const int delta = va - vb;
+
+ if (delta)
+ {
+ if ( va < '0' || va > '9' || vb < '0' || vb > '9' )
+ return delta; // no number
+
+ //--- both are numbers
+
+ a--;
+ while ( *a == '0' )
+ a++;
+ ccp start_a = a;
+ while ( *a >= '0' && *a <= '9' )
+ a++;
+
+ b--;
+ while ( *b == '0' )
+ b++;
+ ccp start_b = b;
+ while ( *b >= '0' && *b <= '9' )
+ b++;
+
+ // compare num length
+ int num_delta = ( a - start_a ) - ( b - start_b );
+ if (num_delta)
+ return num_delta;
+
+ // compare numeric values
+ while ( start_a < a )
+ {
+ num_delta = *start_a++ - *start_b++;
+ if (num_delta)
+ return num_delta;
+ }
+
+ // number identical, but not string
+ return delta;
+ }
+ else if (!va)
+ return 0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CountEqual ( cvp m1, cvp m2, uint size )
+{
+ const u8 *p1 = m1;
+ const u8 *p2 = m2;
+ while ( size-- )
+ if ( *p1++ != *p2++ )
+ return p1 - (u8*)m1 - 1;
+ return p1 - (u8*)m1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ReplaceToBuf
+(
+ // returns -1 on error (buf to small) or the length of the resulting string.
+ // In the later case, a 0 byte is appended, but not counting for the length.
+
+ char *buf, // pointer to buf
+ uint buf_size, // size of 'buf'
+
+ ccp src1, // NULL or source, maybe part of 'buf'
+ int src1_len, // length of 'src1', if -1: determine by strlen()
+ int pos, // robust replace position, <0: relative to end
+ int rm_len, // robust num of chars to remove, <0: rm before 'pos'
+
+ ccp src2, // NULL or source to insert, must not be part of 'buf'
+ int src2_len // lengh of 'src2', if -1: determine by strlen()
+)
+{
+ DASSERT(buf);
+
+ //--- normalize source #2
+
+ if (!src2)
+ src2_len = 0;
+ else if ( src2_len < 0 )
+ src2_len = strlen(src2);
+
+
+ //--- if source #1 available
+
+ if (src1)
+ {
+ //--- check src1 length
+
+ if ( src1_len < 0 )
+ src1_len = strlen(src1);
+
+ if (!src1_len)
+ goto no_src1;
+
+
+ //--- normalize 'pos'
+
+ if ( pos < 0 )
+ {
+ pos += src1_len;
+ if ( pos < 0 )
+ {
+ if ( rm_len > 0 )
+ {
+ rm_len += pos;
+ if ( rm_len < 0 )
+ rm_len = 0;
+ }
+ pos = 0;
+ }
+ }
+ else if ( pos > src1_len )
+ pos = src1_len;
+
+
+ //--- normalize 'rm_len'
+
+ if ( rm_len < 0 )
+ {
+ rm_len = -rm_len;
+ if ( rm_len < pos )
+ pos -= rm_len;
+ else
+ {
+ rm_len = pos;
+ pos = 0;
+ }
+ }
+ else
+ {
+ const int max = src1_len - pos;
+ if ( rm_len > max )
+ rm_len = max;
+ }
+
+ const int res_len = src1_len + src2_len - rm_len;
+ if ( res_len >= buf_size )
+ return -1;
+
+
+ //--- copy src1
+
+ if ( src1 > buf )
+ {
+ memmove( buf, src1, pos );
+ const int pos2 = pos + rm_len;
+ memmove( buf+pos+src2_len, src1+pos2, src1_len-pos2 );
+ }
+ else
+ {
+ const int pos2 = pos + rm_len;
+ memmove( buf+pos+src2_len, src1+pos2, src1_len-pos2 );
+ memmove( buf, src1, pos );
+ }
+
+
+ //--- copy src2 & terminate
+
+ memcpy( buf+pos, src2, src2_len );
+ buf[res_len] = 0;
+ return res_len;
+ }
+
+ no_src1:
+ if ( src2_len < buf_size )
+ {
+ memcpy(buf,src2,src2_len);
+ buf[src2_len] = 0;
+ return src2_len;
+ }
+
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int NormalizeIndent ( int indent )
+{
+ return indent < 0 ? 0 : indent < 50 ? indent : 50;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * SkipControls ( ccp src )
+{
+ // skips all character 1..SPACE
+ // 'src' can be NULL
+
+ if (src)
+ while ( *src > 0 && *src <= ' ' )
+ src++;
+
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * SkipControlsE ( ccp src, ccp end )
+{
+ // skips all character 0..SPACE
+ // if end==NULL: Use SkipControls()
+ // 'src' can be NULL
+
+ if (!end)
+ return SkipControls(src);
+
+ if (src)
+ while ( src < end && (uchar)*src <= ' ' )
+ src++;
+
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * SkipControls1 ( ccp src, char ch1 )
+{
+ // skips all character 1..SPACE and 'ch1'
+ // 'src' can be NULL
+
+ if (src)
+ while ( *src > 0 && *src <= ' ' || *src == ch1 )
+ src++;
+
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * SkipControlsE1 ( ccp src, ccp end, char ch1 )
+{
+ // skips all character 0..SPACE and 'ch1'
+ // if end==NULL: Use SkipControls1()
+ // 'src' can be NULL
+
+ if (!end)
+ return SkipControls1(src,ch1);
+
+ if (src)
+ while ( src < end && ( (uchar)*src <= ' ' || *src == ch1 ))
+ src++;
+
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * SkipControls2 ( ccp src, char ch1, char ch2 )
+{
+ // skips all character 1..SPACE and 'ch1' and 'ch2'
+ // 'src' can be NULL
+
+ if (src)
+ while ( *src > 0 && *src <= ' ' || *src == ch1 || *src == ch2 )
+ src++;
+
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * SkipControlsE2 ( ccp src, ccp end, char ch1, char ch2 )
+{
+ // skips all character 0..SPACE and 'ch1' and 'ch2'
+ // if end==NULL: Use SkipControls2()
+ // 'src' can be NULL
+
+ if (!end)
+ return SkipControls2(src,ch1,ch2);
+
+ if (src)
+ while ( src < end && ( (uchar)*src <= ' ' || *src == ch1 || *src == ch2 ))
+ src++;
+
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// return the number of chars until first EOL ( \0 | \r | \n );
+// pointer are NULL save
+
+uint LineLen ( ccp ptr )
+{
+ if (!ptr)
+ return 0;
+
+ ccp p = ptr;
+ while ( *p && *p != '\n' && *p != '\r' )
+ p++;
+ return p - ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint LineLenE ( ccp ptr, ccp end )
+{
+ if (!ptr)
+ return 0;
+ if (!end)
+ return LineLen(ptr);
+
+ ccp p = ptr;
+ while ( p < end && *p && *p != '\n' && *p != '\r' )
+ p++;
+ return p - ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint TrimBlanks
+(
+ char *str, // pointer to string, modified
+ ccp end // NULL or end of string
+)
+{
+ char *src = str;
+ char *dest = str;
+
+ bool space = false;
+ while ( end ? src < end : *src )
+ {
+ DASSERT( dest <= src );
+ uchar ch = *src++;
+ if ( ch > ' ' )
+ {
+ if (space)
+ {
+ space = false;
+ if ( dest > str )
+ *dest++ = ' ';
+ }
+ *dest++ = ch;
+ }
+ else if ( ch == ' ' )
+ space = true;
+ }
+
+ if ( !end || dest < end )
+ *dest = 0;
+
+ return dest - str;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanName
+(
+ // return a pointer to the first unused character
+ char *buf, // destination buffer, terminated with NULL
+ uint buf_size, // size of 'buf', must be >0
+ ccp str, // source string
+ ccp end, // NULL or end of 'str'
+ bool allow_signs // true: allow '-' and '+'
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size);
+ if (!str)
+ {
+ *buf = 0;
+ return 0;
+ }
+
+ //--- skip leading blanks
+
+ if (end)
+ while ( str < end && (uchar)*str <= ' ' )
+ str++;
+ else
+ while ( str < end && *str && (uchar)*str <= ' ' )
+ str++;
+
+ char *dest = buf, *dest_end = buf + buf_size - 1;
+ while ( end ? str < end : *str )
+ {
+ const int ch = *str;
+ if ( isalnum(ch) || ch == '_' || allow_signs && ( ch =='-' || ch == '+' ) )
+ {
+ str++;
+ if ( dest < dest_end )
+ *dest++ = toupper(ch);
+ }
+ else
+ break;
+ }
+ *dest = 0;
+
+ if (end)
+ while ( str < end && (uchar)*str <= ' ' )
+ str++;
+ else
+ while ( str < end && *str && (uchar)*str <= ' ' )
+ str++;
+
+ return (char*)str;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintVersion
+(
+ char * buf, // result buffer
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 version // version number to print
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 20 );
+
+ version = htonl(version); // we use big endian here
+ const u8 * v = (const u8 *)&version;
+
+ if (v[2])
+ {
+ if ( !v[3] || v[3] == 0xff )
+ snprintf(buf,buf_size,"%u.%02x.%02x",v[0],v[1],v[2]);
+ else
+ snprintf(buf,buf_size,"%u.%02x.%02x.beta%u",v[0],v[1],v[2],v[3]);
+ }
+ else
+ {
+ if ( !v[3] || v[3] == 0xff )
+ snprintf(buf,buf_size,"%u.%02x",v[0],v[1]);
+ else
+ snprintf(buf,buf_size,"%u.%02x.beta%u",v[0],v[1],v[3]);
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintID
+(
+ const void * id, // ID to convert in printable format
+ size_t id_len, // len of 'id'
+ void * buf // Pointer to buffer, size >= id_len + 1
+ // If NULL, a local circulary static buffer is used
+)
+{
+ if (!buf)
+ buf = GetCircBuf( id_len + 1);
+
+ ccp src = id;
+ char * dest = buf;
+ while ( id_len-- > 0 )
+ {
+ const char ch = *src++;
+ *dest++ = ch >= ' ' && ch < 0x7f ? ch : '.';
+ }
+ *dest = 0;
+
+ return buf;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// circ buf ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static char circ_buf[CIRC_BUF_SIZE];
+static char *circ_ptr = circ_buf;
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * GetCircBuf
+(
+ // never returns NULL, but always ALIGN(4)
+
+ u32 buf_size // wanted buffer size, caller must add 1 for NULL-term
+ // if buf_size > CIRC_BUF_MAX_ALLOC:
+ // ==> ERROR0(ERR_OUT_OF_MEMORY)
+)
+{
+ DASSERT( circ_ptr >= circ_buf && circ_ptr <= circ_buf + sizeof(circ_buf) );
+
+ buf_size = buf_size + 3 & ~3;
+ if ( buf_size > CIRC_BUF_MAX_ALLOC )
+ {
+ ERROR0(ERR_OUT_OF_MEMORY,
+ "Circulary buffer too small: needed=%u, half-size=%zu\n",
+ buf_size, sizeof(circ_buf)/2 );
+ ASSERT(0);
+ exit(ERR_OUT_OF_MEMORY);
+ }
+
+ if ( circ_buf + sizeof(circ_buf) - circ_ptr < buf_size )
+ circ_ptr = circ_buf;
+
+ char *result = circ_ptr;
+ circ_ptr = result + buf_size;
+ DASSERT( circ_ptr >= circ_buf && circ_ptr <= circ_buf + sizeof(circ_buf) );
+
+ noPRINT("CIRC-BUF: %3u -> %3zu / %3zu / %3zu\n",
+ buf_size, result-circ_buf, circ_ptr-circ_buf, sizeof(circ_buf) );
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * CopyCircBuf
+(
+ // never returns NULL, but always ALIGN(4)
+
+ cvp data, // source to copy
+ u32 data_size // see GetCircBuf()
+)
+{
+ char *dest = GetCircBuf(data_size);
+ memcpy(dest,data,data_size);
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * CopyCircBuf0
+(
+ // never returns NULL, but always ALIGN(4)
+ // an additional char is alloced and set to NULL
+
+ cvp data, // source to copy
+ u32 data_size // see GetCircBuf()
+)
+{
+ char *dest = GetCircBuf(data_size+1);
+ memcpy(dest,data,data_size);
+ dest[data_size] = 0;
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintCircBuf
+(
+ ccp format, // format string for vsnprintf()
+ ... // arguments for 'format'
+)
+{
+ char buf[CIRC_BUF_MAX_ALLOC-1];
+ va_list arg;
+ va_start(arg,format);
+ const int len = vsnprintf(buf,sizeof(buf),format,arg);
+ va_end(arg);
+
+ return len > 0 ? CopyCircBuf0(buf,len) : (char*)EmptyString;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ReleaseCircBuf
+(
+ ccp end_buf, // pointer to end of previous alloced buffer
+ uint release_size // number of bytes to give back from end
+)
+{
+ DASSERT( circ_ptr >= circ_buf && circ_ptr <= circ_buf + sizeof(circ_buf) );
+
+ if ( end_buf == circ_ptr
+ && release_size <= CIRC_BUF_MAX_ALLOC
+ && circ_ptr >= circ_buf + release_size )
+ {
+ circ_ptr -= release_size;
+ DASSERT( circ_ptr >= circ_buf && circ_ptr <= circ_buf + sizeof(circ_buf) );
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct FastBuf_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+FastBuf_t * InitializeFastBuf ( cvp mem, uint size )
+{
+ DASSERT(mem);
+ DASSERT( size >= sizeof(FastBuf_t) );
+
+ FastBuf_t *fb = (FastBuf_t*)mem;
+
+ int buf_size = (int)size - offsetof(FastBuf_t,fast_buf);
+ if ( buf_size < sizeof(fb->fast_buf) )
+ {
+ PrintError(__FUNCTION__,__FILE__,__LINE__,0,ERR_OUT_OF_MEMORY,
+ "Out of memory, FastBuf_t is at least %zd bytes to short.",
+ sizeof(fb->fast_buf) - size );
+ ASSERT(0);
+ return 0;
+ }
+
+ fb->fast_buf_size = buf_size;
+ fb->buf = fb->ptr = fb->fast_buf;
+ fb->end = fb->buf + buf_size - 1;
+ DASSERT( fb->buf < fb->end );
+ return fb;
+}
+
+//-----------------------------------------------------------------------------
+
+FastBuf_t * InitializeFastBufAlloc ( FastBuf_t *fb, uint size )
+{
+ DASSERT(fb);
+ InitializeFastBuf(fb,sizeof(*fb));
+
+ const uint new_size = GetGoodAllocSize(size+10);
+ fb->buf = MALLOC(new_size);
+ fb->ptr = fb->buf;
+ fb->end = fb->buf + new_size - 1;
+ return fb;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define RESET_FASTBUF(fb) fb->buf = fb->ptr = fb->fast_buf; \
+ fb->end = fb->buf + fb->fast_buf_size - 1;
+
+void ResetFastBuf ( FastBuf_t *fb )
+{
+ DASSERT(fb);
+ if ( fb->buf != fb->fast_buf )
+ FREE(fb->buf);
+
+ RESET_FASTBUF(fb)
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetFastBufStatus ( const FastBuf_t *fb )
+{
+ char info[100];
+ int len = snprintf(info,sizeof(info),"%s=%u/%u",
+ fb->buf == fb->fast_buf ? "FastBuf" : "Alloc",
+ (uint)(fb->ptr - fb->buf), (uint)(fb->end - fb->buf) );
+ return CopyCircBuf(info,len+1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * MoveFromFastBufString ( FastBuf_t *fb )
+{
+ DASSERT(fb);
+ *fb->ptr = 0;
+
+ char *res;
+ if ( fb->buf == fb->fast_buf )
+ {
+ res = MEMDUP(fb->buf,fb->ptr-fb->buf);
+ fb->ptr = fb->buf;
+ }
+ else
+ {
+ res = fb->buf;
+ RESET_FASTBUF(fb)
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t MoveFromFastBufMem ( FastBuf_t *fb )
+{
+ DASSERT(fb);
+ *fb->ptr = 0;
+
+ mem_t res;
+ res.len = fb->ptr - fb->buf;
+
+ if ( fb->buf == fb->fast_buf )
+ {
+ res.ptr = MEMDUP(fb->buf,res.len);
+ fb->ptr = fb->buf;
+ }
+ else
+ {
+ res.ptr = fb->buf;
+ RESET_FASTBUF(fb)
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * GetSpaceFastBuf ( FastBuf_t *fb, uint size )
+{
+ DASSERT(fb);
+ if ( size > fb->end - fb->ptr )
+ {
+ const uint used = fb->ptr - fb->buf;
+ const uint new_size = GetGoodAllocSize( used + used/4 + size + 1000 );
+ if ( fb->buf == fb->fast_buf )
+ {
+ fb->buf = MALLOC(new_size);
+ memcpy(fb->buf,fb->fast_buf,used);
+ }
+ else
+ fb->buf = REALLOC(fb->buf,new_size);
+ fb->ptr = fb->buf + used;
+ fb->end = fb->buf + new_size - 1;
+ }
+
+ char *res = fb->ptr;
+ fb->ptr += size;
+ return res;
+}
+
+//-----------------------------------------------------------------------------
+
+int ReserveSpaceFastBuf ( FastBuf_t *fb, uint size )
+{
+ DASSERT(fb);
+ if ( size > 0 )
+ fb->ptr = GetSpaceFastBuf(fb,size);
+ return fb->end - fb->ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint InsertFastBuf ( FastBuf_t *fb, int index, cvp source, int size )
+{
+ DASSERT(fb);
+ DASSERT(source||!size);
+
+ if ( size < 0 )
+ size = strlen(source);
+
+ const uint len = fb->ptr - fb->buf;
+ index = CheckIndex1(len,index);
+ noPRINT("index=%d/%d/%zd, size=%d\n",index,len,fb->end-fb->buf,size);
+
+ char *ptr = GetSpaceFastBuf(fb,size);
+ char *dest = fb->buf + index;
+ DASSERT( dest >= fb->buf );
+ DASSERT( dest + size + len <= fb->end );
+
+ memmove(dest+size,dest,ptr-dest);
+ memcpy(dest,source,size);
+ return index;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * AppendFastBuf ( FastBuf_t *fb, cvp source, int size )
+{
+ DASSERT(fb);
+ DASSERT(source||!size);
+
+ if ( size < 0 )
+ size = strlen(source);
+
+ char *dest = GetSpaceFastBuf(fb,size);
+ memcpy(dest,source,size);
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AppendUTF8CharFastBuf ( FastBuf_t *fb, u32 code )
+{
+ char buf[8];
+ const uint len = PrintUTF8Char(buf,code) - buf;
+
+ char *dest = GetSpaceFastBuf(fb,len);
+ memcpy(dest,buf,len);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AppendBE16FastBuf ( FastBuf_t *fb, u16 num )
+{
+ DASSERT(fb);
+
+ char *dest = GetSpaceFastBuf(fb,sizeof(num));
+ write_be16(dest,num);
+}
+
+//-----------------------------------------------------------------------------
+
+void AppendBE32FastBuf ( FastBuf_t *fb, u32 num )
+{
+ DASSERT(fb);
+
+ char *dest = GetSpaceFastBuf(fb,sizeof(num));
+ write_be32(dest,num);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AppendInt64FastBuf ( FastBuf_t *fb, u64 val, IntMode_t mode )
+{
+ DASSERT(fb);
+
+ switch(mode)
+ {
+ case IMD_UNSET:
+ AppendFastBuf(fb,&val,sizeof(val));
+ break;
+
+ case IMD_BE1:
+ case IMD_LE1: AppendCharFastBuf(fb,val); break;
+
+ case IMD_BE2: write_be16(GetSpaceFastBuf(fb,2),val); break;
+ case IMD_BE3: write_be24(GetSpaceFastBuf(fb,3),val); break;
+ case IMD_BE4: write_be32(GetSpaceFastBuf(fb,4),val); break;
+ case IMD_BE5: write_be40(GetSpaceFastBuf(fb,5),val); break;
+ case IMD_BE6: write_be48(GetSpaceFastBuf(fb,6),val); break;
+ case IMD_BE7: write_be56(GetSpaceFastBuf(fb,7),val); break;
+ case IMD_BE0:
+ case IMD_BE8: write_be64(GetSpaceFastBuf(fb,8),val); break;
+
+ case IMD_LE2: write_le16(GetSpaceFastBuf(fb,2),val); break;
+ case IMD_LE3: write_le24(GetSpaceFastBuf(fb,3),val); break;
+ case IMD_LE4: write_le32(GetSpaceFastBuf(fb,4),val); break;
+ case IMD_LE5: write_le40(GetSpaceFastBuf(fb,5),val); break;
+ case IMD_LE6: write_le48(GetSpaceFastBuf(fb,6),val); break;
+ case IMD_LE7: write_le56(GetSpaceFastBuf(fb,7),val); break;
+ case IMD_LE0:
+ case IMD_LE8: write_le64(GetSpaceFastBuf(fb,8),val); break;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * WriteFastBuf ( FastBuf_t *fb, uint offset, cvp source, int size )
+{
+ DASSERT(fb);
+
+ uint oldlen = fb->ptr - fb->buf;
+ uint minlen = offset + size;
+ if ( minlen > oldlen )
+ {
+ char *ptr = GetSpaceFastBuf(fb,minlen-oldlen);
+ memset(ptr,0,minlen-oldlen);
+ }
+ DASSERT( fb->ptr - fb->buf >= minlen );
+ char *dest = fb->buf + offset;
+ if (source)
+ memcpy( dest, source, size );
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint AssignFastBuf ( FastBuf_t *fb, cvp source, int size )
+{
+ DASSERT(fb);
+
+ if ( size < 0 )
+ size = strlen(source);
+
+ if ( source == fb->buf )
+ {
+ const uint used = fb->ptr - fb->buf;
+ if ( size < used )
+ fb->ptr = fb->buf + size;
+ else
+ size = used;
+ }
+ else
+ {
+ fb->ptr = fb->buf;
+ if (source)
+ AppendFastBuf(fb,source,size);
+ *fb->ptr = 0;
+ }
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint DropFastBuf ( FastBuf_t *fb, int index, int size )
+{
+ DASSERT(fb);
+ if (size)
+ {
+ const uint len = fb->ptr - fb->buf;
+ index = CheckIndex1(len,index);
+ if ( size < 0 )
+ {
+ size = -size;
+ if ( size > index )
+ size = index;
+ index -= size;
+ }
+ int index2 = CheckIndex1(len,index+size);
+ size = index2 - index;
+ if (size)
+ {
+ memmove(fb->buf+index,fb->buf+index2,len-index2+1);
+ fb->ptr -= size;
+ }
+ }
+
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void CopyFastBuf ( FastBuf_t *dest, const FastBuf_t *src )
+{
+ if ( dest && dest != src )
+ {
+ if (!src)
+ ClearFastBuf(dest);
+ else
+ AssignFastBuf( dest, src->buf, src->ptr - src->buf );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MoveFastBuf ( FastBuf_t *dest, FastBuf_t *src )
+{
+ if ( dest && src && dest != src )
+ {
+ if ( src->buf != src->fast_buf )
+ {
+ // src is allocated
+ if ( dest->buf != dest->fast_buf )
+ FREE(dest->buf);
+ dest->buf = src->buf;
+ dest->ptr = src->ptr;
+ dest->end = src->end;
+ InitializeFastBuf( src, offsetof(FastBuf_t,fast_buf) + src->fast_buf_size );
+ }
+ else
+ AssignFastBuf( dest, src->buf, src->ptr - src->buf );
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// alloc info ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool alloc_info_calced = false;
+uint alloc_info_overhead = 8; // default values
+uint alloc_info_block_size = 16;
+uint alloc_info_add1 = 7;
+uint alloc_info_add2 = 8;
+uint alloc_info_mask = ~15;
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SetupAllocInfo()
+{
+ if (!alloc_info_calced)
+ {
+ alloc_info_calced = true;
+
+ enum { MAX = 200 };
+
+ char *alloc[MAX+1];
+ memset(alloc,0,sizeof(alloc));
+
+ uint i, mask_found = ~0;
+ for ( i = 1; i <= MAX; i++ )
+ {
+ alloc[i] = (char*)MALLOC(i);
+ noPRINT("%4x: %p [%zx]\n",i,alloc[i],alloc[i]-alloc[ i>0 ? i-1 : 0 ] );
+ if ( i > 1 )
+ {
+ int diff = abs ( alloc[i-1] - alloc[i] );
+ mask_found &= diff - 1;
+ }
+ }
+ const int lo0 = FindLowest0Bit(&mask_found,1);
+ const int hi1 = FindHighest1Bit(&mask_found,1);
+ PRINT("mask: %08x, lo0=%d, hi1=%d\n",mask_found,lo0,hi1);
+
+ if ( hi1 + 1 == lo0 )
+ {
+ // -> maske besteht aus hi1 '1' gefolgt von ausnahmslos '0'
+
+ alloc_info_mask = ~mask_found;
+ alloc_info_block_size = mask_found + 1;
+ PRINT("alloc_info_block_size = %d, alloc_info_mask = 0x%x\n",
+ alloc_info_block_size, alloc_info_mask );
+
+ int found = 0x100;
+ for ( i = 2; i <= MAX; i++ )
+ {
+ const int diff = abs ( alloc[i-1] - alloc[i] ) - i;
+ if ( diff >= 0 && found > diff )
+ found = diff;
+ }
+
+ if ( found < MAX )
+ {
+ alloc_info_overhead = found;
+ found %= alloc_info_block_size;
+ alloc_info_add1 = found - 1;
+ alloc_info_add2 = alloc_info_block_size - found;
+ PRINT("alloc_info_overhead = %d, alloc_info_add = %d, %d\n",
+ alloc_info_overhead, alloc_info_add1, alloc_info_add2 );
+ }
+ }
+
+ for ( i = 1; i <= MAX; i++ )
+ FREE(alloc[i]);
+ }
+
+ return alloc_info_block_size;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// SplitArg_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetSplitArg ( SplitArg_t *sa )
+{
+ DASSERT(sa);
+ FREE(sa->argv);
+ FREE(sa->temp);
+ InitializeSplitArg(sa);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanSplitArg
+(
+ // returns arg->argc;
+
+ SplitArg_t *arg, // pointer to object
+ bool init_arg, // true: call InitializeSplitArg()
+ ccp src, // NULL or source string
+ ccp src_end, // end of 'src'; if NULL: calculated by strlen(src)
+ char *work_buf // pointer to a buffer for strings, may be 'src'
+ // size must be: length of 'src' + 1
+ // if NULL, a temporary buffer is alloced.
+)
+{
+ DASSERT(arg);
+ if (init_arg)
+ InitializeSplitArg(arg);
+ else
+ ResetSplitArg(arg);
+
+ if (!src)
+ src = EmptyString;
+ if (!src_end)
+ src_end = src + strlen(src);
+
+ #ifdef TEST0
+ enum { N_PREDEF = 5 };
+ #else
+ enum { N_PREDEF = 250 };
+ #endif
+ char *argv_predef[N_PREDEF+1],
+ **argv_buf = argv_predef,
+ **argv_dest = argv_predef,
+ **argv_end = argv_predef + N_PREDEF;
+ *argv_dest++ = progname ? (char*)progname : "";
+ uint argv_size = 0;
+
+ if (!work_buf)
+ {
+ arg->temp_size = src_end - src + 1;
+ arg->temp = work_buf = MALLOC(arg->temp_size);
+ }
+ char *dest = work_buf;
+
+
+ //--- main loop
+
+ for(;;)
+ {
+ while ( src < src_end
+ && ( !*src || *src == ' ' || *src == '\t' || *src == '\r' || *src == '\n' ))
+ {
+ src++;
+ }
+ if ( src == src_end )
+ break;
+
+ if ( argv_dest == argv_end )
+ {
+ PRINT("NEED MORE: %zu/%zu size=%u: |%s|\n",
+ argv_dest-argv_buf, argv_end-argv_buf, argv_size, src );
+ const uint idx = argv_dest - argv_buf;
+ if (argv_size)
+ {
+ argv_size += N_PREDEF;
+ argv_buf = REALLOC(argv_buf,(argv_size+1)*sizeof(*argv_buf));
+ }
+ else
+ {
+ argv_size = 2*N_PREDEF;
+ char **temp = MALLOC((argv_size+1)*sizeof(*argv_buf));
+ memcpy(temp,argv_buf,idx*sizeof(*argv_buf));
+ argv_buf = temp;
+ }
+ argv_dest = argv_buf + idx;
+ argv_end = argv_buf + argv_size;
+ }
+
+ *argv_dest++ = dest;
+ while ( src < src_end )
+ {
+ const char ch = *src;
+ if ( !ch || ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' )
+ break;
+ src++;
+ if ( ch == '\\' && src < src_end )
+ *dest++ = *src++;
+ else if ( ch == '\'' )
+ {
+ while ( src < src_end && *src != '\'' )
+ *dest++ = *src++;
+ if ( src < src_end )
+ src++;
+ }
+ else if ( ch == '"' )
+ {
+ while ( src < src_end && *src != '"' )
+ {
+ char ch = *src++;
+ if ( ch == '\\' )
+ {
+ uint code;
+ src = ScanEscape(&code,src,src_end);
+ ch = code;
+ }
+ *dest++ = ch;
+ }
+ if ( src < src_end )
+ src++;
+ }
+ else
+ *dest++ = ch;
+ }
+ *dest++ = 0;
+ }
+
+ *argv_dest = 0;
+ arg->argc = argv_dest - argv_buf;
+ if ( argv_size )
+ {
+ DASSERT( argv_buf != argv_predef );
+ arg->argv_size = argv_size;
+ arg->argv = argv_buf;
+ }
+ else
+ {
+ DASSERT( argv_buf == argv_predef );
+ arg->argv_size = arg->argc;
+ arg->argv = MEMDUP(argv_buf,(arg->argv_size+1)*sizeof(*argv_dest));
+ }
+
+ return arg->argc;
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct mem_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t EmptyMem = {EmptyString,0};
+mem_t NullMem = {0,0};
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t MidMem ( const mem_t src, int begin, int count )
+{
+ begin = CheckIndex1(src.len,begin);
+ if ( count < 0 )
+ {
+ count = -count;
+ begin -= count;
+ if ( begin < 0 )
+ {
+ count += begin;
+ begin = 0;
+ }
+ }
+
+ mem_t res;
+ res.ptr = src.ptr + begin;
+ const int max = src.len - begin;
+ res.len = count < max ? count : max;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t BeforeMem ( const mem_t src, ccp ref )
+{
+ mem_t res;
+ res.ptr = src.ptr;
+ res.len = ref < src.ptr ? 0
+ : ref <= src.ptr + src.len ? ref - src.ptr
+ : src.len;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t BehindMem ( const mem_t src, ccp ref )
+{
+ mem_t res;
+ if ( ref <= src.ptr )
+ res = src;
+ else if ( ref <= src.ptr + src.len )
+ {
+ res.ptr = ref;
+ res.len = src.ptr + src.len - ref;
+ }
+ else
+ {
+ res.ptr = src.ptr + src.len;
+ res.len = 0;
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CmpMem ( const mem_t s1, const mem_t s2 )
+{
+ if ( s1.len < s2.len )
+ {
+ const int stat = memcmp(s1.ptr,s2.ptr,s1.len);
+ return stat ? stat : -1;
+ }
+
+ const int stat = memcmp(s1.ptr,s2.ptr,s2.len);
+ return stat ? stat : s1.len > s2.len;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int StrCmpMem ( const mem_t mem, ccp str )
+{
+ const uint slen = str ? strlen(str) : 0;
+
+ if ( mem.len < slen )
+ {
+ const int stat = memcmp(mem.ptr,str,mem.len);
+ return stat ? stat : -1;
+ }
+
+ const int stat = memcmp(mem.ptr,str,slen);
+ return stat ? stat : mem.len > slen;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool LeftStrCmpMemEQ ( const mem_t mem, ccp str )
+{
+ const uint slen = str ? strlen(str) : 0;
+ return mem.len <= slen && !memcmp(mem.ptr,str,mem.len);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t MemCat2A ( const mem_t m1, const mem_t m2 )
+{
+ const uint len1 = m1.len >= 0 ? m1.len : strlen(m1.ptr);
+ const uint len2 = m2.len >= 0 ? m2.len : strlen(m2.ptr);
+
+ char *dest;
+ mem_t res;
+ res.len = len1 + len2;
+ res.ptr = dest = MALLOC(res.len+1);
+ dest[res.len] = 0;
+
+ if (len1)
+ memcpy(dest,m1.ptr,len1);
+ if (len2)
+ memcpy(dest+len1,m2.ptr,len2);
+
+ return res;
+}
+
+//-----------------------------------------------------------------------------
+
+mem_t MemCat3A ( const mem_t m1, const mem_t m2, const mem_t m3 )
+{
+ const uint len1 = m1.len >= 0 ? m1.len : strlen(m1.ptr);
+ const uint len2 = m2.len >= 0 ? m2.len : strlen(m2.ptr);
+ const uint len3 = m3.len >= 0 ? m3.len : strlen(m3.ptr);
+
+ char *dest;
+ mem_t res;
+ res.len = len1 + len2 + len3;
+ res.ptr = dest = MALLOC(res.len+1);
+ dest[res.len] = 0;
+
+ if (len1)
+ memcpy(dest,m1.ptr,len1);
+ if (len2)
+ memcpy(dest+len1,m2.ptr,len2);
+ if (len3)
+ memcpy(dest+len1+len2,m3.ptr,len3);
+
+ return res;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct mem_list_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeMemList ( mem_list_t *ml )
+{
+ DASSERT(ml);
+ memset(ml,0,sizeof(*ml));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetMemList ( mem_list_t *ml )
+{
+ DASSERT(ml);
+ FREE(ml->list);
+ FREE(ml->buf);
+ memset(ml,0,sizeof(*ml));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MoveMemList ( mem_list_t *dest, mem_list_t *src )
+{
+ if ( dest != src )
+ {
+ if (!dest)
+ ResetMemList(src);
+ else
+ {
+ ResetMemList(dest);
+ if (src)
+ {
+ memcpy(dest,src,sizeof(*dest));
+ memset(src,0,sizeof(*src));
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void GrowBufMemList ( mem_list_t *ml, uint need_size )
+{
+ if ( ml->buf_size < need_size )
+ {
+ PRINT("MEM-LIST: MALLOC/BUF(%u)\n",need_size);
+ char *new_buf = MALLOC(need_size);
+
+ //--- copy existing strings
+
+ char *bufptr = new_buf;
+ mem_t *dest = ml->list;
+ uint i;
+ for ( i = 0; i < ml->used; i++, dest++ )
+ if ( dest->len )
+ {
+ memcpy(bufptr,dest->ptr,dest->len);
+ dest->ptr = bufptr;
+ bufptr += dest->len;
+ *bufptr++ = 0;
+ DASSERT( bufptr <= new_buf + need_size );
+ noPRINT("OLD: %p,%u\n",dest->ptr,dest->len);
+ }
+ FREE(ml->buf);
+ ml->buf = new_buf;
+ ml->buf_used = bufptr - new_buf;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrepareMemList ( mem_list_t *ml, uint n_elem, uint buf_size )
+{
+ DASSERT(ml);
+ if ( ml->size < n_elem )
+ {
+ ml->size = n_elem;
+ PRINT("MEM-LIST: REALLOC/LIST(%u)\n",ml->size);
+ ml->list = REALLOC(ml->list,sizeof(*ml->list)*ml->size);
+ }
+
+ GrowBufMemList(ml,buf_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InsertMemListN
+(
+ mem_list_t *ml, // valid destination mem_list
+ int pos, // insert position => CheckIndex1()
+ const mem_t *src, // source list
+ uint src_len, // number of elements in source list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ DASSERT(ml);
+ DASSERT( src || !src_len );
+
+ //--- count needed size and elements
+
+ const mem_t *sptr, *src_end = src + src_len;
+
+ uint need_size = 0, add_elem = 0;
+ for ( sptr = src; sptr < src_end; sptr++ )
+ {
+ if ( sptr->len )
+ {
+ need_size += sptr->len + 1; // always terminate with NULL
+ add_elem++;
+ }
+ else if ( !src_ignore || src_ignore == 1 && sptr->ptr || src_ignore == 3 )
+ add_elem++;
+ }
+
+ if (!add_elem)
+ return;
+
+
+ //--- string pool management
+
+ if (!ml->used)
+ ml->buf_used = 0;
+
+ char *new_buf, *bufptr;
+ if ( ml->buf_used + need_size <= ml->buf_size )
+ {
+ new_buf = 0;
+ bufptr = ml->buf + ml->buf_used;
+ ml->buf_used += need_size;
+ }
+ else
+ {
+ need_size += ml->buf_used + 20;
+ need_size += need_size/10;
+
+ // don't use GrowBufMemList() here, because the old buffer is needed
+ // BUT: char old_buf = ml->buf; ml->buf = 0; GrowBufMemList(ml,need_size)
+
+ PRINT("MEM-LIST: MALLOC/BUF(%u)\n",need_size);
+ bufptr = new_buf = MALLOC(need_size);
+
+ //--- copy existing strings
+
+ mem_t *dest = ml->list;
+ uint i;
+ for ( i = 0; i < ml->used; i++, dest++ )
+ if ( dest->len )
+ {
+ memcpy(bufptr,dest->ptr,dest->len);
+ dest->ptr = bufptr;
+ bufptr += dest->len;
+ *bufptr++ = 0;
+ DASSERT( bufptr <= new_buf+need_size );
+ noPRINT("OLD: %p,%u\n",dest->ptr,dest->len);
+ }
+ }
+
+
+ //--- realloc list if necessary
+
+ const uint need_elem = ml->used + add_elem;
+ if ( need_elem > ml->size )
+ {
+ ml->size = need_elem + 5 + need_elem/10;
+ PRINT("MEM-LIST: REALLOC/LIST(%u)\n",ml->size);
+ ml->list = REALLOC(ml->list,sizeof(*ml->list)*ml->size);
+ }
+
+ pos = CheckIndex1(ml->used,pos);
+ mem_t *dest = ml->list + pos;
+ if ( pos < ml->used )
+ memmove(dest+add_elem,dest,sizeof(*dest)*(ml->used-pos));
+ ml->used += add_elem;
+
+
+ //--- iterate source list
+
+ for ( sptr = src; sptr < src_end; sptr++ )
+ {
+ if (sptr->len)
+ {
+ dest->ptr = bufptr;
+ memcpy(bufptr,sptr->ptr,sptr->len);
+ dest->len = sptr->len;
+ bufptr += sptr->len;
+ *bufptr++ = 0;
+ DASSERT( bufptr <= ( new_buf ? new_buf+need_size : ml->buf + ml->buf_size ) );
+ noPRINT("NEW: %p,%u\n",dest->ptr,dest->len);
+ dest++;
+ DASSERT( dest <= ml->list + ml->used );
+ }
+ else if ( !src_ignore || src_ignore == 1 && sptr->ptr )
+ {
+ dest->ptr = sptr->ptr ? EmptyString : NULL;
+ dest->len = 0;
+ dest++;
+ DASSERT( dest <= ml->list + ml->used );
+ }
+ else if ( src_ignore == 3 )
+ {
+ dest->ptr = EmptyString;
+ dest->len = 0;
+ dest++;
+ DASSERT( dest <= ml->list + ml->used );
+ }
+ }
+ DASSERT_MSG( dest == ml->list + pos + add_elem,
+ "DEST= %zu != %u+%u == %u",
+ dest-ml->list, pos, add_elem, pos+add_elem );
+
+
+ //--- clean
+
+ if (new_buf)
+ {
+ DASSERT(ml->buf != new_buf);
+ FREE(ml->buf);
+ ml->buf = new_buf;
+ ml->buf_size = need_size;
+ }
+ ml->buf_used = bufptr - ml->buf;
+ DASSERT( ml->buf_used <= ml->buf_size );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void CatMemListN
+(
+ mem_list_t *dest, // valid destination mem_list
+ const mem_list_t **src_list, // list with mem lists, element may be NULL
+ uint n_src_list, // number of elements in 'src'
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ DASSERT(dest);
+ DASSERT( src_list || !n_src_list );
+
+
+ //--- count needed size and elements
+
+ uint need_size = 0, need_elem = 0;
+ bool dest_is_source = false;
+
+ const mem_list_t **list_ptr, **src_list_end = src_list + n_src_list;
+ for ( list_ptr = src_list; list_ptr < src_list_end; list_ptr++ )
+ {
+ if (!*list_ptr)
+ continue;
+
+ if ( *list_ptr == dest )
+ dest_is_source = true;
+
+ const mem_t *sptr = (*list_ptr)->list, *send = sptr + (*list_ptr)->used;
+ for ( ; sptr < send; sptr++ )
+ {
+ if ( sptr->len )
+ {
+ need_size += sptr->len + 1; // always terminate with NULL
+ need_elem++;
+ }
+ else if ( !src_ignore || src_ignore == 1 && sptr->ptr || src_ignore == 3 )
+ need_elem++;
+ }
+ }
+
+ PRINT("CatMemList(N=%u) n_elem=%u, size=%u, dest_is_source=%d\n",
+ n_src_list,need_elem,need_size,dest_is_source);
+ if (!need_elem)
+ {
+ dest->used = 0;
+ return;
+ }
+
+
+ //--- setup dest buffers
+
+ char *new_buf = dest_is_source || need_size > dest->buf_size
+ ? MALLOC(need_size) : dest->buf;
+ mem_t *new_list = dest_is_source || need_elem > dest->size
+ ? MALLOC(sizeof(*new_list)*need_elem) : dest->list;
+
+
+
+ //--- assign values
+
+ char *bufptr = new_buf;
+ mem_t *listptr = new_list;
+
+ for ( list_ptr = src_list; list_ptr < src_list_end; list_ptr++ )
+ {
+ if (!*list_ptr)
+ continue;
+
+ const mem_t *sptr = (*list_ptr)->list, *send = sptr + (*list_ptr)->used;
+ for ( ; sptr < send; sptr++ )
+ {
+ if (sptr->len)
+ {
+ listptr->ptr = bufptr;
+ memcpy(bufptr,sptr->ptr,sptr->len);
+ listptr->len = sptr->len;
+ bufptr += sptr->len;
+ *bufptr++ = 0;
+ DASSERT( bufptr <= new_buf + need_size );
+ noPRINT("NEW: %p,%u\n",dest->ptr,dest->len);
+ listptr++;
+ DASSERT( listptr <= new_list + need_elem );
+ }
+ else if ( !src_ignore || src_ignore == 1 && sptr->ptr )
+ {
+ listptr->ptr = sptr->ptr ? EmptyString : NULL;
+ listptr->len = 0;
+ listptr++;
+ DASSERT( listptr <= new_list + need_elem );
+ }
+ else if ( src_ignore == 3 )
+ {
+ listptr->ptr = EmptyString;
+ listptr->len = 0;
+ listptr++;
+ DASSERT( listptr <= new_list + need_elem );
+ }
+ }
+ }
+ DASSERT( bufptr == new_buf + need_size );
+ DASSERT( listptr == new_list + need_elem );
+
+
+ //--- terminate
+
+ dest->buf_used = need_size;
+ if ( new_buf != dest->buf )
+ {
+ FREE(dest->buf);
+ dest->buf = new_buf;
+ dest->buf_size = need_size;
+ }
+
+ dest->used = need_elem;
+ if ( new_list != dest->list )
+ {
+ FREE(dest->list);
+ dest->list = new_list;
+ dest->size = need_elem;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint LeftMemList ( mem_list_t *ml, int count )
+{
+ DASSERT(ml);
+ return ml->used = CheckIndex1(ml->used,count);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint RightMemList ( mem_list_t *ml, int count )
+{
+ DASSERT(ml);
+
+ const uint begin = CheckIndex1End(ml->used,-count);
+ if (begin)
+ {
+ ml->used -= begin;
+ memmove( ml->list, ml->list + begin, ml->used * sizeof(*ml->list) );
+ }
+ return ml->used;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint MidMemList ( mem_list_t *ml, int begin, int count )
+{
+ DASSERT(ml);
+
+ begin = CheckIndex1(ml->used,begin);
+ if ( count < 0 )
+ {
+ count = -count;
+ begin -= count;
+ if ( begin < 0 )
+ {
+ count += begin;
+ begin = 0;
+ }
+ }
+
+ const uint max = ml->used - begin;
+ if ( count > max )
+ count = max;
+ memmove( ml->list, ml->list + begin, count * sizeof(*ml->list) );
+ return ml->used = count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ExtractMemList ( mem_list_t *ml, int begin, int end )
+{
+ DASSERT(ml);
+ ml->used = CheckIndex2(ml->used,&begin,&end);
+ memmove( ml->list, ml->list + begin, ml->used * sizeof(*ml->list) );
+ return ml->used;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ExtractEndMemList ( mem_list_t *ml, int begin, int end )
+{
+ DASSERT(ml);
+ ml->used = CheckIndex2End(ml->used,&begin,&end);
+ memmove( ml->list, ml->list + begin, ml->used * sizeof(*ml->list) );
+ return ml->used;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint RemoveMemList ( mem_list_t *ml, int begin, int end )
+{
+ DASSERT(ml);
+ const uint sub = CheckIndex2(ml->used,&begin,&end);
+ memmove( ml->list + begin, ml->list + end, ( ml->used - end ) * sizeof(*ml->list) );
+ return ml->used -= sub;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint RemoveEndMemList ( mem_list_t *ml, int begin, int end )
+{
+ DASSERT(ml);
+ const uint sub = CheckIndex2End(ml->used,&begin,&end);
+ memmove( ml->list + begin, ml->list + end, ( ml->used - end ) * sizeof(*ml->list) );
+ return ml->used -= sub;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MemPool_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetMemPool ( MemPool_t *mp )
+{
+ DASSERT(mp);
+ MemPoolChunk_t *ptr = mp->chunk;
+ while (ptr)
+ {
+ MemPoolChunk_t *next = ptr->next;
+ FREE(ptr);
+ ptr = next;
+ }
+ InitializeMemPool(mp,mp->chunk_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * MallocMemPool ( MemPool_t *mp, uint size )
+{
+ DASSERT(mp);
+ if (!size)
+ return (char*)EmptyString;
+
+ if ( size > mp->space )
+ {
+ uint data_size = size > mp->chunk_size ? size : mp->chunk_size;
+ if ( data_size < 0x400 )
+ data_size = 0x400;
+ MemPoolChunk_t *chunk = MALLOC(sizeof(MemPoolChunk_t)+data_size);
+ DASSERT(chunk);
+ chunk->next = mp->chunk;
+ mp->chunk = chunk;
+ mp->space = data_size;
+ }
+
+ DASSERT(mp->chunk);
+ DASSERT(mp->space >= size );
+
+ mp->space -= size;
+ return mp->chunk->data + mp->space;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * CallocMemPool ( MemPool_t *mp, uint size )
+{
+ DASSERT(mp);
+ void *res = MallocMemPool(mp,size);
+ DASSERT(res);
+ memset(res,0,size);
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * MallocMemPoolA ( MemPool_t *mp, uint size, uint align )
+{
+ DASSERT(mp);
+ char *res = MallocMemPool(mp,size);
+ DASSERT(res);
+ const uint fix = mp->space - mp->space / align * align;
+ res -= fix;
+ mp->space -= fix;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * CallocMemPoolA ( MemPool_t *mp, uint size, uint align )
+{
+ DASSERT(mp);
+ void *res = MallocMemPoolA(mp,size,align);
+ DASSERT(res);
+ memset(res,0,size);
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * MemDupMemPool ( MemPool_t *mp, cvp source, uint size )
+{
+ DASSERT(mp);
+ DASSERT(source||!size);
+
+ char *res = MallocMemPool(mp,size+1);
+ memcpy(res,source,size);
+ res[size] = 0;
+ return res;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CircBuf_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[CircBuf_t]]
+
+void InitializeCircBuf ( CircBuf_t *cb, uint size )
+{
+ DASSERT(cb);
+ memset(cb,0,sizeof(*cb));
+ cb->size = size; //data is alloced on first insertion
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetCircBuf ( CircBuf_t *cb )
+{
+ DASSERT(cb);
+ FREE(cb->buf);
+ cb->buf = 0;
+ cb->used = cb->pos = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint WriteCircBuf ( CircBuf_t *cb, cvp data, uint size )
+{
+ DASSERT(cb);
+ DASSERT(data||!size);
+
+ if ( !size || !cb->size )
+ return 0;
+
+ if (!cb->buf)
+ {
+ cb->buf = MALLOC(cb->size);
+ DASSERT(cb->buf);
+ cb->used = cb->pos = 0;
+ }
+
+ ccp d = data;
+ if ( size >= cb->size )
+ {
+ cb->used = cb->size;
+ cb->pos = 0;
+ memcpy( cb->buf, d + (size-cb->size), cb->size );
+ //fprintf(stderr,"INS*: %d+%d = %d / %d\n",cb->pos,cb->used,cb->pos+cb->used,cb->size);
+ return cb->size;
+ }
+
+ const int ins_count = size;
+ int ins_pos = cb->pos + cb->used;
+ if ( ins_pos >= cb->size )
+ ins_pos -= cb->size;
+
+ if ( ins_pos + size > cb->size )
+ {
+ const uint copy_len = cb->size - ins_pos;
+ //fprintf(stderr,"COPY %u..%u\n",ins_pos,ins_pos+copy_len);
+ memcpy( cb->buf + ins_pos, d, copy_len );
+ size -= copy_len;
+ d += copy_len;
+ ins_pos = 0;
+ }
+
+ DASSERT( size > 0 );
+ DASSERT( ins_pos <= cb->size );
+ DASSERT( ins_pos + size <= cb->size );
+ //fprintf(stderr,"COPY %u..%u\n",ins_pos,ins_pos+size);
+ memcpy( cb->buf + ins_pos, d, size );
+
+ cb->used += ins_count;
+ if ( cb->used > cb->size )
+ {
+ cb->pos += cb->used - cb->size;
+ if ( cb->pos >= cb->size )
+ cb->pos -= cb->size;
+ cb->used = cb->size;
+ }
+
+ //fprintf(stderr,"INS: %d+%d = %d / %d\n",cb->pos,cb->used,cb->pos+cb->used,cb->size);
+ return ins_count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PeakCircBuf ( CircBuf_t *cb, char *buf, uint buf_size )
+{
+ DASSERT(cb);
+ DASSERT(buf||!buf_size);
+
+ if ( !cb->size || !buf_size )
+ return 0;
+
+ if ( buf_size > cb->used )
+ buf_size = cb->used;
+
+ uint count = cb->size - cb->pos;
+ if ( count > buf_size )
+ count -= buf_size;
+
+ memcpy( buf, cb->buf + cb->pos, count );
+ if ( count < buf_size )
+ {
+ const uint copy_len = buf_size - count;
+ memcpy( buf+count, cb->buf, copy_len );
+ count += copy_len;
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ReadCircBuf ( CircBuf_t *cb, char *buf, uint buf_size )
+{
+ DASSERT(cb);
+ const uint count = PeakCircBuf(cb,buf,buf_size);
+ return DropCircBuf(cb,count);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint DropCircBuf ( CircBuf_t *cb, uint size )
+{
+ DASSERT(cb);
+
+ if ( size >= cb->used )
+ {
+ const uint res = cb->used;
+ cb->pos = cb->used = 0;
+ return res;
+ }
+
+ cb->used -= size;
+ cb->pos += size;
+ if ( cb->pos >= cb->size )
+ cb->pos -= cb->size;
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearCircBuf ( CircBuf_t *cb )
+{
+ DASSERT(cb);
+ cb->used = cb->pos = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PurgeCircBuf ( CircBuf_t *cb )
+{
+ DASSERT(cb);
+
+ if ( cb->used && cb->pos )
+ {
+ char *temp = MALLOC(cb->size);
+ PeakCircBuf(cb,temp,cb->used);
+ FREE(cb->buf);
+ cb->buf = temp;
+ }
+ cb->pos = 0;
+ return cb->used;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// enum CopyMode_t interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void * CopyData
+(
+ const void *data, // data to copy/move/link
+ uint size, // size of 'data'
+ CopyMode_t mode, // copy mode
+ bool *res_alloced // not NULL:
+ // store true, if data must be freed
+ // otherwise store false
+)
+{
+ DASSERT( data || !size );
+ if ( !data || !size )
+ {
+ if (res_alloced)
+ *res_alloced = false;
+ return (void*)EmptyString;
+ }
+
+ if (res_alloced)
+ *res_alloced = mode != CPM_LINK;
+
+ return mode == CPM_MOVE || mode == CPM_LINK
+ ? (void*)data
+ : MEMDUP(data,size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FreeData
+(
+ const void *data, // data to free
+ CopyMode_t mode // copy mode
+)
+{
+ if ( mode != CPM_LINK && data != EmptyString )
+ FREE((void*)data);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ContainerData_t, Container_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+Container_t * CreateContainer
+(
+ // returns 'c' or the alloced container
+ // 'c' is always initialized
+
+ Container_t *c, // valid container, alloc one if NULL
+ int protect, // initial value for protection
+ const void *data, // data to copy/move/link
+ uint size, // size of 'data'
+ CopyMode_t mode // copy mode on creation
+)
+{
+ if (!c)
+ c = MALLOC(sizeof(*c));
+ memset(c,0,sizeof(*c));
+ c->protect_level = protect;
+
+ ContainerData_t *cdata = CALLOC(sizeof(*cdata),1);
+ c->cdata = cdata;
+
+ cdata->data = CopyData(data,size,mode,&cdata->data_alloced);
+ cdata->size = size;
+ cdata->ref_count = 1;
+ if ( protect > 0 )
+ cdata->protect_count++;
+ return c;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+Container_t * InheritContainer
+(
+ // returns 'c' or the alloced container
+
+ Container_t *c, // valid container, alloc one if NULL
+ int protect, // initial value for protection
+ const Container_t *parent, // NULL or valid parent container
+ const void *data, // data to copy/move/link
+ uint size // size of 'data'
+)
+{
+ if (!c)
+ c = CALLOC(sizeof(*c),1);
+
+ if ( InContainerS(parent,data,size) || !InContainerS(c,data,size) )
+ {
+ UnlinkContainerData(c);
+ UseContainerData(c,protect,parent);
+ }
+ return c;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool AssignContainer
+(
+ // return TRUE on new ContainerData_t
+
+ Container_t *c, // valid container; if NULL: only FREE(data)
+ int protect, // new protection level
+ const void *data, // data to copy/move/link
+ uint size, // size of 'data'
+ CopyMode_t mode // copy mode on creation
+)
+{
+ if (c)
+ {
+ SetProtectContainer(c,protect);
+
+ if (!InContainerS(c,data,size))
+ {
+ UnlinkContainerData(c);
+ ContainerData_t *cdata = CALLOC(sizeof(*cdata),1);
+ c->cdata = cdata;
+ cdata->data = CopyData(data,size,mode,&cdata->data_alloced);
+ cdata->size = size;
+ cdata->ref_count = 1;
+ if ( protect > 0 )
+ cdata->protect_count++;
+ return true;
+ }
+ }
+
+ if ( mode == CPM_MOVE )
+ FREE((void*)data);
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetContainer
+(
+ Container_t *c // container to reset => no data
+)
+{
+ if (c)
+ {
+ UnlinkContainerData(c);
+ memset(c,0,sizeof(*c));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DeleteContainer
+(
+ Container_t *c // container to reset and to free => no data
+)
+{
+ if (c)
+ {
+ UnlinkContainerData(c);
+ FREE(c);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void UnlinkContainerData
+(
+ Container_t *c // container to reset => no data
+)
+{
+ if ( c && c->cdata )
+ {
+ ContainerData_t *cdata = c->cdata;
+ if ( c->protect_level > 0 )
+ cdata->protect_count--;
+ c->cdata = 0;
+ if ( !--cdata->ref_count )
+ {
+ if (cdata->data_alloced)
+ FREE((void*)cdata->data);
+ FREE(cdata);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ContainerData_t * LinkContainerData
+(
+ // increment 'ref_count' and return NULL or current container-data
+ // => use CatchContainerData() to complete operation
+
+ const Container_t *c // NULL or valid container
+)
+{
+ if ( !c || !c->cdata )
+ return 0;
+
+ c->cdata->ref_count++;
+ return c->cdata;
+}
+
+//-----------------------------------------------------------------------------
+
+ContainerData_t * MoveContainerData
+(
+ // return NULL or unlinked current container-data
+ // => use CatchContainerData() to complete operation
+ Container_t *c // NULL or valid container
+)
+{
+ if ( !c || !c->cdata )
+ return 0;
+
+ ContainerData_t *cdata = c->cdata;
+ cdata->ref_count++;
+ UnlinkContainerData(c);
+ return cdata;
+}
+
+//-----------------------------------------------------------------------------
+
+Container_t * CatchContainerData
+(
+ // returns 'c' or the alloced container
+ // 'c' is always initialized
+
+ Container_t *c, // valid container, alloc one if NULL
+ int protect, // initial value for protection
+ ContainerData_t *cdata // if not NULL: catch this
+)
+{
+ if (!c)
+ c = MALLOC(sizeof(*c));
+ memset(c,0,sizeof(*c));
+ c->protect_level = protect;
+
+ if (cdata)
+ {
+ c->cdata = cdata;
+ if ( protect > 0 )
+ cdata->protect_count++;
+ }
+ return c;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ContainerData_t * DisjoinContainerData
+(
+ // Disjoin data container date without freeing it. Call either
+ // JoinContainerData() or FreeContainerData() to finish operation.
+ // Reference counters are not modified.
+ // Return the data container or NULL
+
+ Container_t *c // NULL or valid container
+)
+{
+ ContainerData_t *res = 0;
+ if ( c && c->cdata )
+ {
+ const int old_protect = SetProtectContainer(c,0);
+ res = c->cdata;
+ c->cdata = 0;
+ SetProtectContainer(c,old_protect);
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void JoinContainerData
+(
+ // Join a data container, that was diosjoined by DisjoinContainerData()
+ // Reference counters are not modified.
+
+ Container_t *c, // container to reset => no data
+ ContainerData_t *cdata // NULL or container-data to join
+)
+{
+ if (c)
+ {
+ UnlinkContainerData(c);
+ DASSERT(!c->cdata);
+ if (cdata)
+ {
+ const int old_protect = SetProtectContainer(c,0);
+ c->cdata = cdata;
+ SetProtectContainer(c,old_protect);
+ }
+ }
+ else
+ FreeContainerData(cdata);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FreeContainerData
+(
+ // Decrement the reference counter of an already disjoined data container
+ // and free it if unused.
+
+ ContainerData_t *cdata // NULL or container-data to free
+)
+{
+ if ( cdata && !--cdata->ref_count )
+ {
+ if (cdata->data_alloced)
+ FREE((void*)cdata->data);
+ FREE(cdata);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool ModifyAllContainer
+(
+ // prepare modification of container-data, create a copy if necessary
+ // return true, if a new container-data is used
+
+ Container_t *c // NULL or valid container
+)
+{
+ if ( c && c->cdata && !ModificationAllowed(c) )
+ {
+ ContainerData_t *oldcd = c->cdata;
+ ContainerData_t *newcd = CALLOC(sizeof(*newcd),1);
+ newcd->data = MEMDUP(oldcd->data,oldcd->size);
+ newcd->size = oldcd->size;
+ newcd->data_alloced = true;
+ newcd->ref_count = 1;
+ if ( c->protect_level > 0 )
+ newcd->protect_count++;
+ UnlinkContainerData(c);
+ c->cdata = newcd;
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+bool ModifyContainer
+(
+ // prepare modification of container-data, create an extract if necessary
+ // return true, if a new container-data is used
+
+ Container_t *c, // NULL or valid container
+ const void *data, // data to copy/move/link
+ uint size, // size of 'data'
+ CopyMode_t mode // copy mode on creation
+)
+{
+ if ( c && c->cdata && ( !ModificationAllowed(c) || !InContainerS(c,data,size) ))
+ {
+ ContainerData_t *newcd = CALLOC(sizeof(*newcd),1);
+ newcd->data = CopyData(data,size,mode,&newcd->data_alloced);
+ newcd->size = size;
+ newcd->ref_count = 1;
+ if ( c->protect_level > 0 )
+ newcd->protect_count++;
+ UnlinkContainerData(c);
+ c->cdata = newcd;
+ return true;
+ }
+
+ if ( mode == CPM_MOVE )
+ FREE((void*)data);
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SetProtectContainer
+(
+ // returns 'c' new protection level
+ Container_t *c, // valid container, alloc one if NULL
+ int new_protect // new protection value
+)
+{
+ DASSERT(c);
+ if (c->cdata)
+ {
+ if ( new_protect > 0 )
+ {
+ if ( c->protect_level <= 0 )
+ c->cdata->protect_count++;
+ }
+ else
+ {
+ if ( c->protect_level > 0 )
+ c->cdata->protect_count--;
+ }
+ }
+ return c->protect_level = new_protect;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint DumpInfoContainer
+(
+ // return the number of printed lines
+
+ FILE *f, // valid output file
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent the output
+ ccp prefix, // not NULL: use it as prefix behind indention
+ const Container_t *c, // dump infos for this container, NULL allowed
+ uint hexdump_len // max number of bytes used for a hexdump
+)
+{
+ DASSERT(f);
+ if (!c)
+ return 0;
+
+ indent = NormalizeIndent(indent);
+ if (!colset)
+ colset = GetColorSet0();
+
+ fprintf(f,"%*s%s%s%smod=%d, PL=%d, ",
+ indent,"",
+ colset->caption, prefix ? prefix : "",
+ colset->status, ModificationAllowed(c), c->protect_level );
+ uint line_count = DumpInfoContainerData(f,0,0,0,c->cdata,hexdump_len,indent+2);
+ if (!line_count)
+ {
+ fputs("<no-data>",f);
+ line_count++;
+ }
+ fprintf(f,"%s\n",colset->reset);
+ return line_count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint DumpInfoContainerData
+(
+ // return the number of printed lines
+
+ FILE *f, // valid output file
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent of output
+ ccp prefix, // not NULL: use it as prefix behind indention
+ const
+ ContainerData_t *cdata, // dump infos for this container-data, NULL allowed
+ uint hexdump_len, // max number of bytes used for a hexdump
+ int hexdump_indent // indent of hexdump
+)
+{
+ DASSERT(f);
+ if (!cdata)
+ return 0;
+
+ indent = NormalizeIndent(indent);
+ if (!colset)
+ colset = GetColorSet0();
+
+ fprintf(f,"%*s%s%s%sPC=%d, RC=%d, alloc=%d, size=%u=0x%x, addr=%p\n",
+ indent,"",
+ colset->caption, prefix ? prefix : "", colset->status,
+ cdata->protect_count, cdata->ref_count, cdata->data_alloced,
+ cdata->size, cdata->size, cdata->data );
+
+ if ( hexdump_len > 0 && cdata->data && cdata->size )
+ {
+ if ( hexdump_len > cdata->size )
+ hexdump_len = cdata->size;
+ return 1 + HexDump16(f,hexdump_indent,0,cdata->data,hexdump_len);
+ }
+ return 1;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// split strings ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint SplitByCharMem
+(
+ // 'mem_list' will be padded with {EmptyString,0}
+
+ mem_t *mem_list, // array of mem_t
+ uint n_mem, // number of elements of 'mem_list'
+ mem_t source, // source string
+ char sep // separator character
+)
+{
+ DASSERT(mem_list);
+ DASSERT(n_mem>1);
+
+ ccp src = source.ptr;
+ ccp end = src + source.len;
+
+ mem_t *mem = mem_list;
+ mem_t *last = mem + n_mem - 1;
+
+ while ( mem < last )
+ {
+ mem->ptr = src;
+ while ( src < end && *src != sep )
+ src++;
+
+ mem->len = src - mem->ptr;
+ mem++;
+
+ if ( ++src > end )
+ break;
+ }
+
+ if ( src <= end )
+ {
+ mem->ptr = src;
+ mem->len = end - src;
+ mem++;
+ }
+ const uint result = mem - mem_list;
+
+ while ( mem <= last )
+ {
+ mem->ptr = EmptyString;
+ mem->len = 0;
+ mem++;
+ }
+
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SplitByTextMem
+(
+ // 'mem_list' will be padded with {EmptyString,0}
+
+ mem_t *mem_list, // array of mem_t
+ uint n_mem, // number of elements of 'mem_list'
+ mem_t source, // source string
+ mem_t sep // separator text
+)
+{
+ DASSERT(mem_list);
+ DASSERT(n_mem>0);
+
+ ccp src = source.ptr;
+ ccp end = src + source.len;
+
+ mem_t *mem = mem_list;
+ mem_t *last = mem + n_mem - 1;
+
+ if ( sep.len > 0 && sep.len <= source.len )
+ {
+ ccp cmp_max = end - sep.len;
+ while ( mem < last )
+ {
+ mem->ptr = src;
+ while ( src <= cmp_max && memcmp(src,sep.ptr,sep.len) )
+ src++;
+
+ if ( src > cmp_max )
+ {
+ mem->len = end - mem->ptr;
+ mem++;
+ src = end + 1;
+ break;
+ }
+
+ mem->len = src - mem->ptr;
+ mem++;
+ src += sep.len;
+ }
+ }
+
+ if ( src <= end )
+ {
+ mem->ptr = src;
+ mem->len = end - src;
+ mem++;
+ }
+ const uint result = mem - mem_list;
+
+ while ( mem <= last )
+ {
+ mem->ptr = EmptyString;
+ mem->len = 0;
+ mem++;
+ }
+
+
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SplitByCharMemList
+(
+ mem_list_t *ml, // valid destination mem_list
+ bool init_ml, // true: initialize 'ml' first
+ mem_t source, // source string
+ char sep, // separator character
+ int max_fields // >0: max number of result fields
+)
+{
+ DASSERT(ml);
+ if (init_ml)
+ InitializeMemList(ml);
+ ml->used = 0;
+
+ if ( max_fields <= 0 )
+ max_fields = INT_MAX;
+ GrowBufMemList(ml,source.len+1);
+
+ ccp src = source.ptr;
+ ccp end = src + source.len;
+
+ while ( max_fields-- > 1 )
+ {
+ mem_t mem;
+ mem.ptr = src;
+ while ( src < end && *src != sep )
+ src++;
+
+ mem.len = src - mem.ptr;
+ AppendMemListN(ml,&mem,1,0);
+ if ( ++src > end )
+ break;
+ }
+
+ if ( src <= end )
+ {
+ mem_t mem = { src, end - src };
+ AppendMemListN(ml,&mem,1,0);
+ }
+
+ return ml->used;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SplitByTextMemList
+(
+ mem_list_t *ml, // valid destination mem_list
+ bool init_ml, // true: initialize 'ml' first
+ mem_t source, // source string
+ mem_t sep, // separator text
+ int max_fields // >0: max number of result fields
+)
+{
+ DASSERT(ml);
+ if (init_ml)
+ InitializeMemList(ml);
+ ml->used = 0;
+
+ if ( max_fields <= 0 )
+ max_fields = INT_MAX;
+ GrowBufMemList(ml,source.len+1);
+
+ ccp src = source.ptr;
+ ccp end = src + source.len;
+
+ if ( sep.len > 0 && sep.len <= source.len )
+ {
+ ccp cmp_max = end - sep.len;
+ while ( max_fields-- > 1 )
+ {
+ mem_t mem;
+ mem.ptr = src;
+ while ( src <= cmp_max && memcmp(src,sep.ptr,sep.len) )
+ src++;
+
+ if ( src > cmp_max )
+ {
+ mem.len = end - mem.ptr;
+ AppendMemListN(ml,&mem,1,0);
+ src = end + 1;
+ break;
+ }
+
+ mem.len = src - mem.ptr;
+ AppendMemListN(ml,&mem,1,0);
+ src += sep.len;
+ }
+ }
+
+ if ( src <= end )
+ {
+ mem_t mem = { src, end - src };
+ AppendMemListN(ml,&mem,1,0);
+ }
+
+ return ml->used;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatchPattern() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static ccp AnalyseBrackets
+(
+ ccp pattern,
+ ccp * p_start,
+ bool * p_negate,
+ int * p_multiple
+)
+{
+ ASSERT(pattern);
+
+ bool negate = false;
+ if ( *pattern == '^' )
+ {
+ pattern++;
+ negate = true;
+ }
+ if (p_negate)
+ *p_negate = negate;
+
+ int multiple = 0;
+ if ( *pattern == '+' )
+ {
+ pattern++;
+ multiple = 1;
+ }
+ else if ( *pattern == '*' )
+ {
+ pattern++;
+ multiple = 2;
+ }
+ if (p_multiple)
+ *p_multiple = multiple;
+
+ if (p_start)
+ *p_start = pattern;
+
+ if (*pattern) // ']' allowed in first position
+ pattern++;
+ while ( *pattern && *pattern++ != ']' ) // find end
+ ;
+
+ return pattern;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool MatchBracktes
+(
+ char ch,
+ ccp pattern,
+ bool negate
+)
+{
+ if (!ch)
+ return false;
+
+ bool ok = false;
+ ccp p = pattern;
+ for (; !ok && *p && ( p == pattern || *p != ']' ); p++ )
+ {
+ if ( *p == '-' )
+ {
+ if ( ch <= *++p && ch >= p[-2] )
+ {
+ if (negate)
+ return false;
+ ok = true;
+ }
+ }
+ else
+ {
+ if ( *p == '\\' )
+ p++;
+
+ if ( *p == ch )
+ {
+ if (negate)
+ return false;
+ ok = true;
+ }
+ }
+ }
+ return ok || negate;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool MatchPatternHelper
+(
+ ccp pattern,
+ ccp text,
+ bool skip_end,
+ int alt_depth,
+ char path_sep // path separator character, standard is '/'
+)
+{
+ ASSERT(pattern);
+ ASSERT(text);
+ noTRACE(" - %d,%d |%s|%s|\n",skip_end,alt_depth,pattern,text);
+
+ char ch;
+ while ( ( ch = *pattern++ ) != 0 )
+ {
+ switch (ch)
+ {
+ case '*':
+ if ( *pattern == '*' )
+ {
+ pattern++;
+ if (*pattern)
+ while (!MatchPatternHelper(pattern,text,skip_end,alt_depth,path_sep))
+ if (!*text++)
+ return false;
+ }
+ else
+ {
+ while (!MatchPatternHelper(pattern,text,skip_end,alt_depth,path_sep))
+ if ( *text == path_sep || !*text++ )
+ return false;
+ }
+ return true;
+
+ case '#':
+ if ( *text < '0' || *text > '9' )
+ return false;
+ while ( *text >= '0' && *text <= '9' )
+ if (MatchPatternHelper(pattern,++text,skip_end,alt_depth,path_sep))
+ return true;
+ return false;
+
+ case ' ':
+ if ( *text < 1 || * text > ' ' )
+ return false;
+ text++;
+ break;
+
+ case '?':
+ if ( !*text || *text == path_sep )
+ return false;
+ text++;
+ break;
+
+ case '[':
+ {
+ ccp start;
+ bool negate;
+ int multiple;
+ TRACELINE;
+ pattern = AnalyseBrackets(pattern,&start,&negate,&multiple);
+ TRACELINE;
+
+ if ( multiple < 2 && !MatchBracktes(*text++,start,negate) )
+ return false;
+
+ if (multiple)
+ {
+ while (!MatchPatternHelper(pattern,text,skip_end,alt_depth,path_sep))
+ if (!MatchBracktes(*text++,start,negate))
+ return false;
+ return true;
+ }
+ }
+ break;
+
+ case '{':
+ for(;;)
+ {
+ if (MatchPatternHelper(pattern,text,skip_end,alt_depth+1,path_sep))
+ return true;
+ // skip until next ',' || '}'
+ int skip_depth = 1;
+ while ( skip_depth > 0 )
+ {
+ ch = *pattern++;
+ switch(ch)
+ {
+ case 0:
+ return false;
+
+ case '\\':
+ if (!*pattern)
+ return false;
+ pattern++;
+ break;
+
+ case '{':
+ skip_depth++;
+ break;
+
+ case ',':
+ if ( skip_depth == 1 )
+ skip_depth--;
+ break;
+
+ case '}':
+ if (!--skip_depth)
+ return false;
+ break;
+
+ case '[': // [2do] forgotten marker?
+ pattern = AnalyseBrackets(pattern,0,0,0);
+ break;
+ }
+ }
+ }
+
+ case ',':
+ if (alt_depth)
+ {
+ alt_depth--;
+ int skip_depth = 1;
+ while ( skip_depth > 0 )
+ {
+ ch = *pattern++;
+ switch(ch)
+ {
+ case 0:
+ return false;
+
+ case '\\':
+ if (!*pattern)
+ return false;
+ pattern++;
+ break;
+
+ case '{':
+ skip_depth++;
+ break;
+
+ case '}':
+ skip_depth--;
+ break;
+
+ case '[': // [2do] forgotten marker?
+ pattern = AnalyseBrackets(pattern,0,0,0);
+ break;
+ }
+ }
+ }
+ else if ( *text++ != ch )
+ return false;
+ break;
+
+ case '}':
+ if ( !alt_depth && *text++ != ch )
+ return false;
+ break;
+
+ case '$':
+ if ( !*pattern && !*text )
+ return true;
+ if ( *text++ != ch )
+ return false;
+ break;
+
+ case '\\':
+ ch = *pattern++;
+ // fall through
+
+ default:
+ if ( *text++ != ch )
+ return false;
+ break;
+ }
+ }
+ return skip_end || *text == 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool MatchPatternFull
+(
+ ccp pattern, // pattern text
+ ccp text // raw text
+)
+{
+ TRACE("MatchPattern(|%s|%s|)\n",pattern,text);
+ if ( !pattern || !*pattern )
+ return true;
+
+ if (!text)
+ text = "";
+
+ if ( *pattern == '^' )
+ pattern++;
+
+ return MatchPatternHelper(pattern,text,false,0,'/');
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool MatchPattern
+(
+ ccp pattern, // pattern text
+ ccp text, // raw text
+ char path_sep // path separator character, standard is '/'
+)
+{
+ TRACE("MatchPattern(|%s|%s|%c|)\n",pattern,text,path_sep);
+ if ( !pattern || !*pattern )
+ return true;
+
+ if (!text)
+ text = "";
+
+ const size_t plen = strlen(pattern);
+ ccp last = pattern + plen - 1;
+ char last_ch = *last;
+ int count = 0;
+ while ( last > pattern && *--last == '\\' )
+ count++;
+ if ( count & 1 )
+ last_ch = 0; // no special char!
+
+ if ( *pattern == path_sep || *pattern == '^' )
+ {
+ pattern++;
+ return MatchPatternHelper(pattern,text,last_ch!='$',0,path_sep);
+ }
+
+ while (*text)
+ if (MatchPatternHelper(pattern,text++,last_ch!='$',0,path_sep))
+ return true;
+
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp MatchStringField ( const StringField_t * sf, ccp key )
+{
+ DASSERT(sf);
+ DASSERT(key);
+
+ ccp *ptr = sf->field, *end;
+ for ( end = ptr + sf->used; ptr < end; ptr++ )
+ if (MatchPattern(*ptr,key,'/'))
+ return *ptr;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * MatchParamField ( const ParamField_t * pf, ccp key )
+{
+ DASSERT(pf);
+ DASSERT(key);
+
+ ParamFieldItem_t *ptr = pf->field, *end;
+ for ( end = ptr + pf->used; ptr < end; ptr++ )
+ if (MatchPattern(ptr->key,key,'/'))
+ return ptr;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * MatchRuleLine
+(
+ // returns a pointer to the first non scanned char
+
+ int *status, // not NULL: return match status here
+ // -2: no prefix found (no ruile found)
+ // -1: empty line (no ruile found)
+ // 0: rule found, but don't match
+ // 1: rule found and match
+ ccp src, // source line, scanned until CONTROL
+ char rule_prefix, // NULL or a rule-beginning-char'
+ ccp path, // path to verify
+ char path_sep // path separator character, standard is '/'
+)
+{
+ int local_status;
+ if (!status)
+ status = &local_status;
+
+ while ( *src > 0 && *src <= ' ' && *src != '\n' )
+ src++;
+
+ if (rule_prefix)
+ {
+ if ( *src != rule_prefix )
+ {
+ *status = -2;
+ return (char*)src;
+ }
+ src++;
+ while ( *src > 0 && *src <= ' ' && *src != '\n' )
+ src++;
+ }
+
+ ccp start = src;
+ while ( (uchar)*src >= ' ' )
+ src++;
+
+ char buf[1000];
+ uint len = src - start;
+ if (!len)
+ {
+ *status = -1;
+ return (char*)src;
+ }
+
+ if ( len > sizeof(buf)-1 )
+ len = sizeof(buf)-1;
+ memcpy(buf,start,len);
+ buf[len] = 0;
+
+ *status = MatchPattern(buf,path,path_sep);
+ return (char*)src;
+}
+
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan keywords ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t *GetKewordById
+(
+ const KeywordTab_t *key_tab, // NULL or pointer to command table
+ s64 id // id to search
+)
+{
+ if (key_tab)
+ {
+ const KeywordTab_t *key;
+ for ( key = key_tab; *key->name1; key++ )
+ if ( key->id == id )
+ return key;
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t *GetKewordByIdAndOpt
+(
+ const KeywordTab_t *key_tab, // NULL or pointer to command table
+ s64 id, // id to search
+ s64 opt // opt to search
+)
+{
+ if (key_tab)
+ {
+ const KeywordTab_t *key;
+ for ( key = key_tab; *key->name1; key++ )
+ if ( key->id == id && key->opt == opt )
+ return key;
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t * ScanKeyword
+(
+ int * res_abbrev, // NULL or pointer to result 'abbrev_count'
+ ccp arg, // argument to scan
+ const KeywordTab_t * key_tab // valid pointer to command table
+)
+{
+ DASSERT(arg);
+ char key_buf[KEYWORD_NAME_MAX];
+
+ char *dest = key_buf;
+ char *end = key_buf + sizeof(key_buf) - 1;
+ while ( *arg && *arg != ',' && *arg != ';' && dest < end )
+ *dest++ = toupper((int)*arg++);
+ *dest = 0;
+ const int key_len = dest - key_buf;
+
+ int favor_count = 0, abbrev_count = 0;
+ const KeywordTab_t *ct, *key_ct = 0;
+ const KeywordTab_t *favor_ct = 0, *favor_prev = 0, *abbrev_ct = 0, *abbrev_prev = 0;
+ for ( ct = key_tab; ct->name1; ct++ )
+ {
+ if ( !strcmp(ct->name1,key_buf) )
+ {
+ key_ct = ct;
+ break;
+ }
+
+ bool name2_abbrev = false;
+ if (ct->name2)
+ {
+ if (!strcmp(ct->name2,key_buf))
+ {
+ key_ct = ct;
+ break;
+ }
+ name2_abbrev = !memcmp(ct->name2,key_buf,key_len);
+ }
+
+ if ( name2_abbrev && !memcmp(ct->name1,key_buf,strlen(ct->name1)) )
+ {
+ if (!favor_ct)
+ {
+ favor_prev = favor_ct = ct;
+ favor_count++;
+ }
+ else if ( favor_prev->id != ct->id || favor_prev->opt != ct->opt )
+ {
+ favor_prev = ct;
+ favor_count++;
+ }
+ }
+
+ if ( *key_buf == '_' ) // no abbreviations for commands beginning with '_'
+ continue;
+
+ if ( name2_abbrev || !memcmp(ct->name1,key_buf,key_len) )
+ {
+ if (!abbrev_ct)
+ {
+ abbrev_prev = abbrev_ct = ct;
+ abbrev_count++;
+ }
+ else if ( abbrev_prev->id != ct->id || abbrev_prev->opt != ct->opt )
+ {
+ abbrev_prev = ct;
+ abbrev_count++;
+ }
+ }
+ }
+
+ if (key_ct)
+ abbrev_count = 0;
+ else if ( favor_count == 1 )
+ {
+ key_ct = favor_ct;
+ abbrev_count = 0;
+ }
+ else if ( abbrev_count == 1 )
+ key_ct = abbrev_ct;
+ else if (!abbrev_count)
+ abbrev_count = -1;
+
+ if (res_abbrev)
+ *res_abbrev = abbrev_count;
+
+ return key_ct;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+s64 ScanKeywordListEx
+(
+ ccp arg, // argument to scan
+ const KeywordTab_t * key_tab, // valid pointer to command table
+ KeywordCallbackFunc func, // NULL or calculation function
+ bool allow_prefix, // allow '-' | '+' | '=' as prefix
+ u32 max_number, // allow numbers < 'max_number' (0=disabled)
+ s64 result, // start value for result
+
+ uint err_mode, // bit field:
+ // 1: continue on error
+ ccp err_msg, // not NULL: print a warning message:
+ // "<ERR_MSG>: Unknown keyword: <KEY>"
+ enumError err_code, // use 'err_code' for the 'err_msg'
+ uint *err_count // not NULL: store errors here
+)
+{
+ ASSERT(arg);
+
+ char key_buf[KEYWORD_NAME_MAX];
+ char *end = key_buf + sizeof(key_buf) - 1;
+ uint err_cnt = 0;
+
+ for(;;)
+ {
+ while ( *arg > 0 && *arg <= ' ' || *arg == ',' || *arg == '.' )
+ arg++;
+
+ if ( !*arg || *arg == '#' )
+ break;
+
+ char *dest = key_buf;
+ while ( *arg > ' ' && *arg != ',' && *arg != '.' && *arg != '#'
+ && ( *arg != '+' || dest == key_buf ) && dest < end )
+ *dest++ = *arg++;
+ *dest = 0;
+ char prefix = 0;
+ int abbrev_count;
+ const KeywordTab_t * cptr = ScanKeyword(&abbrev_count,key_buf,key_tab);
+ if ( !cptr && allow_prefix && key_buf[1]
+ && ( *key_buf == '+' || *key_buf == '-'
+ || *key_buf == '/' || *key_buf == '=' ))
+ {
+ prefix = *key_buf == '/' ? '-' : *key_buf;
+ cptr = ScanKeyword(&abbrev_count,key_buf+1,key_tab);
+ }
+
+ KeywordTab_t ct_num = { 0, key_buf, 0, 0 };
+ if ( max_number && abbrev_count )
+ {
+ char * start = key_buf + (prefix!=0);
+ ulong num = str2l(start,&dest,10);
+ if ( num < max_number && dest > start && !*dest )
+ {
+ ct_num.id = num;
+ cptr = &ct_num;
+ }
+ }
+
+ noPRINT("cptr=%p [%s], opt=%llx, prefix=%02x\n",
+ cptr, cptr ? cptr->name1 : "", cptr ? cptr->opt : 0, prefix );
+ if ( !cptr || cptr->opt && prefix && prefix != '+' )
+ {
+ if (err_msg)
+ {
+ if ( abbrev_count > 1 )
+ ERROR0(err_code,"%s: Ambiguous keyword: %s\n",err_msg,key_buf);
+ else
+ ERROR0(err_code,"%s: Unknown keyword: %s\n",err_msg,key_buf);
+ }
+ err_cnt++;
+ if ( err_mode & 1 )
+ continue;
+ result = M1(result);
+ break;
+ }
+
+ if (func)
+ {
+ const s64 res = func(0,key_buf,key_tab,cptr,prefix,result);
+ if ( res == M1(res) )
+ {
+ if ( !(err_mode & 1) )
+ {
+ result = res;
+ break;
+ }
+ }
+ else
+ result = res;
+ }
+ else
+ {
+ switch (prefix)
+ {
+ case 0:
+ case '+': result = result & ~cptr->opt | cptr->id; break;
+ case '-': result &= ~cptr->id; break;
+ case '=': result = cptr->id; break;
+ }
+ }
+ }
+
+ if (err_count)
+ *err_count = err_cnt;
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanKeywordListFunc
+(
+ ccp arg, // argument to scan
+ const KeywordTab_t * key_tab, // valid pointer to command table
+ KeywordCallbackFunc func, // calculation function
+ void * param, // used define parameter for 'func'
+ bool allow_prefix // allow '-' | '+' | '=' as prefix
+)
+{
+ ASSERT(arg);
+ ASSERT(func);
+
+ char key_buf[KEYWORD_NAME_MAX];
+ char *end = key_buf + sizeof(key_buf) - 1;
+
+ for(;;)
+ {
+ while ( *arg > 0 && *arg <= ' ' || *arg == ',' || *arg == '.' )
+ arg++;
+
+ if (!*arg)
+ return ERR_OK;
+
+ char *dest = key_buf;
+ while ( *arg > ' ' && *arg != ',' && *arg != '.'
+ && ( *arg != '+' || dest == key_buf ) && dest < end )
+ *dest++ = *arg++;
+ *dest = 0;
+ char prefix = 0;
+ int abbrev_count;
+ const KeywordTab_t * cptr = ScanKeyword(&abbrev_count,key_buf,key_tab);
+ if ( !cptr && allow_prefix && key_buf[1]
+ && ( *key_buf == '+' || *key_buf == '-' || *key_buf == '/' || *key_buf == '=' ))
+ {
+ prefix = *key_buf == '/' ? '-' : *key_buf;
+ cptr = ScanKeyword(&abbrev_count,key_buf+1,key_tab);
+ }
+
+ const enumError err = func(param,key_buf,key_tab,cptr,prefix,0);
+ if (err)
+ return err;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static s64 ScanKeywordListMaskHelper
+(
+ void * param, // NULL or user defined parameter
+ ccp name, // normalized name of option
+ const KeywordTab_t * key_tab, // valid pointer to command table
+ const KeywordTab_t * key, // valid pointer to found command
+ char prefix, // 0 | '-' | '+' | '='
+ s64 result // current value of result
+)
+{
+ return key->opt
+ ? result & ~key->opt | key->id
+ : key->id;
+}
+
+//-----------------------------------------------------------------------------
+
+s64 ScanKeywordListMask
+(
+ ccp arg, // argument to scan
+ const KeywordTab_t * key_tab // valid pointer to command table
+)
+{
+ return ScanKeywordList(arg,key_tab,ScanKeywordListMaskHelper,false,0,0,0,0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CollectAmbiguousKeywords
+(
+ char *buf, // destination buffer, 200 bytes are good
+ uint buf_size, // size of buffer
+ const KeywordTab_t * key_tab, // NULL or pointer to command table
+ ccp key_arg // analyzed command
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>10);
+ DASSERT(key_tab);
+ DASSERT(key_arg);
+
+ int n = 0;
+ char *dest = buf, *buf_end = buf + buf_size - 2;
+ const int arg_len = strlen(key_arg);
+ int last_id = -1;
+
+ const KeywordTab_t *ct;
+ for ( ct = key_tab; ct->name1 && dest < buf_end; ct++ )
+ {
+ if ( ct->id != last_id )
+ {
+ ccp ok = 0;
+ if (!strncasecmp(key_arg,ct->name1,arg_len))
+ ok = ct->name1;
+ else if ( ct->name2 && !strncasecmp(key_arg,ct->name2,arg_len))
+ ok = ct->name2;
+ if (ok)
+ {
+ if (!n++)
+ {
+ *dest++ = ' ';
+ *dest++ = '[';
+ }
+ else if ( n > 5 )
+ {
+ dest = StringCopyE(dest,buf_end,",...");
+ break;
+ }
+ else
+ *dest++ = ',';
+ dest = StringCopyE(dest,buf_end,ok);
+ last_id = ct->id;
+ }
+ }
+ }
+ if ( dest > buf+1 )
+ *dest++ = ']';
+ else
+ dest = buf;
+ *dest = 0;
+ return dest - buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PrintKeywordError
+(
+ const KeywordTab_t * key_tab, // NULL or pointer to command table
+ ccp key_arg, // analyzed command
+ int key_stat, // status of ScanKeyword()
+ ccp prefix, // NULL or first prefix for messages
+ ccp object // NULL or object for error messages
+ // default= 'command'
+)
+{
+ DASSERT(key_arg);
+
+ if ( !object || !*object )
+ object = "command";
+
+ if ( key_stat <= 0 )
+ return ERROR0(ERR_SYNTAX,"%sUnknown %s: %s\n",
+ prefix ? prefix : "", object, key_arg );
+
+ char buf[1000], *dest = buf;
+ if (key_tab)
+ {
+ int n = 0;
+ ccp buf_end = buf + sizeof(buf) - 2;
+ const int arg_len = strlen(key_arg);
+ const KeywordTab_t *ct;
+ int last_id = -1;
+
+ for ( ct = key_tab; ct->name1 && dest < buf_end; ct++ )
+ {
+ if ( ct->id != last_id )
+ {
+ ccp ok = 0;
+ if (!strncasecmp(key_arg,ct->name1,arg_len))
+ ok = ct->name1;
+ else if ( ct->name2 && !strncasecmp(key_arg,ct->name2,arg_len))
+ ok = ct->name2;
+ if (ok)
+ {
+ if (!n++)
+ {
+ *dest++ = ' ';
+ *dest++ = '[';
+ }
+ else if ( n > 5 )
+ {
+ dest = StringCopyE(dest,buf_end,",...");
+ break;
+ }
+ else
+ *dest++ = ',';
+ dest = StringCopyE(dest,buf_end,ok);
+ last_id = ct->id;
+ }
+ }
+ }
+ if ( dest > buf+1 )
+ *dest++ = ']';
+ else
+ dest = buf;
+ }
+ *dest = 0;
+
+ return ERROR0(ERR_SYNTAX,"%s%c%s abbreviation is ambiguous: %s%s\n",
+ prefix ? prefix : "",
+ toupper((int)*object), object+1, key_arg, buf );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintKeywordList
+(
+ // returns a pointer to the result buffer
+
+ char *buf, // result buffer
+ // If NULL, a local circulary static buffer
+ // with max CIRC_BUF_MAX_ALLOC bytes is used
+ uint buf_size, // size of 'buf', at least 10 bytes if buf is set
+ uint *ret_length, // not NULL: store result length here
+
+ const KeywordTab_t *tab, // related keyword table
+ u64 mode, // mode to print
+ u64 default_mode, // NULL or default mode
+ u64 hide_mode // bit field to hide parameters
+)
+{
+ DASSERT( !buf || buf_size >= 10 );
+ char temp[CIRC_BUF_MAX_ALLOC];
+ if (!buf)
+ {
+ buf = temp;
+ buf_size = sizeof(temp);
+ }
+
+ char *dest = buf;
+ char *end = buf + buf_size - 1;
+
+ mode = mode | hide_mode;
+ u64 mode1 = mode;
+
+ for ( ; tab->name1 && dest < end; tab++ )
+ {
+ if ( !tab->id || tab->opt & hide_mode )
+ continue;
+
+ if ( tab->opt ? (mode & tab->opt) == tab->id : mode & tab->id )
+ {
+ if ( dest > buf )
+ *dest++ = ',';
+ dest = StringCopyE(dest,end,tab->name1);
+ mode &= ~(tab->id|tab->opt);
+ }
+ }
+
+ if ( default_mode && mode1 == (default_mode|hide_mode) )
+ dest = StringCopyE(dest,end," (default)");
+ else if (!mode1)
+ dest = StringCopyE(dest,end,"(none)");
+
+ *dest = 0;
+ const uint len = dest - buf;
+ if (ret_length)
+ *ret_length = len;
+
+ if ( buf == temp )
+ {
+ buf = GetCircBuf(len+1);
+ memcpy(buf,temp,len+1);
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanKeywordOffOn
+(
+ // returns 0 for '0|OFF'; 1 for '1|ON; -1 for empty; -2 on error
+
+ ccp arg, // argument to scan
+ uint max_num, // >0: additionally accept+return number <= max_num
+ ccp object // NULL (silent) or object for error messages
+)
+{
+ if (!arg||!*arg)
+ return -1;
+
+ static const KeywordTab_t keytab[] =
+ {
+ { 0, "OFF", "0", 0 },
+ { 1, "ON", "1", 0 },
+ {0,0,0,0}
+ };
+
+ int status;
+ const KeywordTab_t *key = ScanKeyword(&status,arg,keytab);
+ if (key)
+ return key->id;
+
+ if ( max_num > 0 )
+ {
+ char *end;
+ const uint num = str2ul(arg,&end,10);
+ if ( !*end && num <= max_num )
+ return num;
+ }
+
+ if (object)
+ PrintKeywordError(keytab,arg,status,0,object);
+
+ return -2;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// OFF/AUTO/ON/FORCE ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t KeyTab_OFF_AUTO_ON[] =
+{
+ { OFFON_OFF, "OFF", "-1", 0 },
+ { OFFON_OFF, "DISABLED", 0, 0 },
+ { OFFON_AUTO, "AUTO", "0", 0 },
+ { OFFON_ON, "ON", "1", 0 },
+ { OFFON_ON, "ENABLED", 0, 0 },
+ { OFFON_FORCE, "FORCE", "2", 0 },
+ { 0,0,0,0 }
+};
+
+//-----------------------------------------------------------------------------
+
+int ScanKeywordOffAutoOn
+(
+ // returns one of OFFON_*
+
+ ccp arg, // argument to scan
+ int on_empty, // return this value on empty
+ uint max_num, // >0: additionally accept+return number <= max_num
+ ccp object // NULL (silent) or object for error messages
+)
+{
+ if ( !arg || !*arg )
+ return on_empty;
+
+ int status;
+ const KeywordTab_t *key = ScanKeyword(&status,arg,KeyTab_OFF_AUTO_ON);
+ if (key)
+ return key->id;
+
+ if ( max_num > 0 )
+ {
+ char *end;
+ const int num = str2l(arg,&end,10);
+ if ( !*end && num >= OFFON_OFF && num <= max_num )
+ return num;
+ }
+
+ if (object)
+ PrintKeywordError(KeyTab_OFF_AUTO_ON,arg,status,0,object);
+
+ return OFFON_ERROR;
+}
+
+//-----------------------------------------------------------------------------
+
+ccp GetKeywordOffAutoOn ( OffOn_t value )
+{
+ const KeywordTab_t *key = GetKewordById(KeyTab_OFF_AUTO_ON,value);
+ return key ? key->name1 : "?";
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan command lists ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ExecCommandOfList ( CommandList_t *cli )
+{
+ DASSERT(cli);
+ if ( cli->n_bin )
+ {
+ printf("%3u. %s [n_bin=%u] = |%.*s|\n",
+ cli->cmd_count, cli->cmd, cli->n_bin, cli->param_len, cli->param );
+ uint i;
+ for ( i = 0; i < cli->n_bin; i++ )
+ {
+ printf(" > binary #%u, len=%u\n",i,cli->bin[0].len);
+ HexDump16( stdout,5,0, cli->bin[i].ptr,
+ cli->bin[i].len < 0x20 ? cli->bin[i].len : 0x20 );
+ }
+ }
+ else
+ printf("%3u. %s = |%.*s|\n",
+ cli->cmd_count, cli->cmd, cli->param_len, cli->param );
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanCommandList
+(
+ // returns -1 on 'cli->fail_count'; otherwise 'cli->cmd_count'
+
+ CommandList_t *cli, // valid command list
+ CommandListFunc func // NULL or function for each command
+)
+{
+ DASSERT(cli);
+
+ static uint log_pid = 0;
+ static uint log_seq = 0;
+ static FILE *log = 0;
+
+ if ( cli->log_fname && cli->log_level && !log_pid )
+ {
+ log_pid = getpid();
+ log = fopen(cli->log_fname,"ab");
+ if (log)
+ {
+ fcntl(fileno(log),F_SETFD,FD_CLOEXEC);
+ fprintf(log,"\n#\f\n#OPEN %s, pid=%d\n\n",
+ PrintTimeByFormat("%F %T %z",time(0)), log_pid );
+ }
+ }
+ uint log_level = log ? cli->log_level : 0;
+
+ cli->read_cmd_len = 0;
+ const char sep = cli->command_sep ? cli->command_sep : ';';
+ ccp cmd = cli->command, end = cmd + cli->command_len;
+ if ( log_level > 0 )
+ {
+ fprintf(log,"\n++ %u.%u len=%u\n",
+ log_pid, ++log_seq, cli->command_len );
+ fflush(log);
+ }
+
+ for(;;)
+ {
+ cli->n_bin = 0;
+ memset(cli->bin,0,sizeof(cli->bin));
+
+ //--- skip blanks and terminators => find begin of record
+
+ while ( cmd < end && ( (uchar)*cmd <= ' ' || *cmd == sep ))
+ cmd++;
+ cli->read_cmd_len = cmd - cli->command; // definitley scanned!
+
+ if ( cmd >= end )
+ break;
+
+ cli->record = (u8*)cmd;
+
+
+ //--- find end of command+param
+
+ ccp endcmd = memchr(cmd,sep,end-cmd);
+ if (!endcmd)
+ {
+ if (!cli->is_terminated)
+ goto abort;
+ endcmd = end;
+ }
+
+
+ //--- scan and copy command
+
+ ccp param = cmd;
+ while ( param < endcmd && ( isalnum(*param) || *param == '-' || *param == '_' ) )
+ param++;
+ cli->cmd_len = param - cmd;
+ if ( cli->cmd_len > sizeof(cli->cmd)-1 )
+ cli->cmd_len = sizeof(cli->cmd)-1;
+
+ if ( log_level > 1 )
+ {
+ fprintf(log,">%.*s\n",cli->cmd_len,cmd);
+ fflush(log);
+ }
+
+ if (cli->change_case)
+ {
+ char *dest = cli->cmd;
+ uint count = cli->cmd_len;
+ if ( cli->change_case == 1 )
+ while ( count-- > 0 )
+ *dest++ = tolower((int)*cmd++);
+ else
+ while ( count-- > 0 )
+ *dest++ = toupper((int)*cmd++);
+ *dest = 0;
+ }
+ else
+ {
+ memcpy(cli->cmd,cmd,cli->cmd_len);
+ cli->cmd[cli->cmd_len] = 0;
+ }
+
+
+ //--- scan binary
+
+ while ( cli->n_bin < COMMAND_LIST_N_BIN )
+ {
+ while ( param < endcmd && ( *param == ' ' || *param == '\t' ))
+ param++;
+
+ if ( param == endcmd || *param != '\1' )
+ break; // no binary!
+
+ //-- From now on for binary examintation, only 'end' is relevant,
+ //-- because binary data may contain the separator character.
+
+ if ( param+3 > end )
+ {
+ if ( log_level > 1 )
+ {
+ fprintf(log,"bin incomplete\n");
+ fflush(log);
+ }
+
+ if (cli->is_terminated)
+ {
+ // now we have a conflict:
+ fprintf(stderr,
+ "#WARN: Conflict in ExecCommandOfList():"
+ " Stream is terminated, but binary not: %s\n",
+ cli->user_ts ? cli->user_ts->info : "" );
+
+ if (log)
+ {
+ fprintf(log,"#WARN: Conflict: Stream is terminated, but binary not!\n");
+ fflush(log);
+ }
+ }
+ goto abort; // incomplete
+ }
+
+ const uint bin_len = be16(++param);
+ param += 2;
+ if ( log_level > 2 )
+ {
+ fprintf(log,"bin %u,%u..%u/%u%s\n",
+ bin_len,
+ (uint)(param - cli->command),
+ (uint)(param - cli->command + bin_len),
+ cli->command_len,
+ param + bin_len > end ? " incomplete" : "" );
+ fflush(log);
+ }
+ if ( param + bin_len > end )
+ goto abort; // incomplete
+
+ cli->bin[cli->n_bin].ptr = param;
+ cli->bin[cli->n_bin].len = bin_len;
+ param += bin_len;
+ cli->n_bin++;
+
+ //-- rescan 'endcmd' after binary block
+
+ endcmd = memchr(param,sep,end-param);
+ if (!endcmd)
+ {
+ if (!cli->is_terminated)
+ goto abort; // incomplete
+ endcmd = end;
+ }
+ }
+
+
+ //--- calc record length
+
+ cli->input_len = (u8*)endcmd - cli->record;
+ if ( endcmd < end )
+ cli->input_len++;
+
+
+ //--- scan param
+
+ while ( param < endcmd && (uchar)*param <= ' ' )
+ param++;
+ if ( param < endcmd && *param == '=' )
+ {
+ param++;
+ while ( param < endcmd && (uchar)*param <= ' ' )
+ param++;
+ }
+
+ ccp endparam = endcmd-1;
+ while ( endparam > param && (uchar)*endparam <= ' ' )
+ endparam--;
+ endparam++;
+
+ if ( log_level > 2 && endparam > param )
+ {
+ const uint plen = endparam - param, max = 25;
+ if ( plen <= max )
+ fprintf(log,"par %.*s\n",plen,param);
+ else
+ fprintf(log,"par %.*s…+%u\n",max,param,plen-max);
+ fflush(log);
+ }
+
+ if (cli->term_param)
+ {
+ if ( cli->term_param > 1 )
+ {
+ ((char*)endparam)[0] = sep;
+ endparam++;
+ }
+ else
+ ((char*)endparam)[0] = 0;
+ }
+
+ cli->param = param;
+ cli->param_len = endparam - param;
+ cli->record_len = endparam - (ccp)cli->record;
+ cli->read_cmd_len = endcmd - (ccp)cli->command;
+ cmd = endcmd;
+
+
+ //--- execute command
+
+ if ( log_level > 3 )
+ {
+ fprintf(log,"func\n");
+ fflush(log);
+ const int stat = func(cli);
+ fprintf(log,"s=%d\n",stat);
+ fflush(log);
+ if ( stat < 0 )
+ break;
+ }
+ else if ( func && func(cli) < 0 )
+ break;
+
+ cli->cmd_count++;
+ }
+
+ abort:;
+ if ( log_level > 1 || log && cli->read_cmd_len > cli->command_len )
+ {
+ fprintf(log,"-- %u,%u/%u%s\n",
+ cli->cmd_count,
+ cli->read_cmd_len, cli->command_len,
+ cli->read_cmd_len > cli->command_len ? " #FAIL!" : "" );
+ fflush(log);
+ }
+
+ // should never happen
+ if ( cli->read_cmd_len > cli->command_len )
+ cli->read_cmd_len = cli->command_len;
+
+ return cli->fail_count ? -1 : cli->cmd_count;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// global commands ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError Command_ARGTEST ( int argc, char ** argv )
+{
+ printf("ARGUMENT TEST: %u arguments:\n",argc);
+
+ int idx;
+ for ( idx = 0; idx < argc; idx++ )
+ printf("%4u.: |%s|\n",idx,argv[idx]);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError Command_COLORS
+(
+ int level, // only used, if mode==NULL
+ // < 0: status message (ignore mode)
+ // >= 1: include names
+ // >= 2: include alt names
+ // >= 3: include color names incl bold
+ // >= 4: include background color names
+ uint mode, // output mode => see PrintColorSetHelper()
+ uint format // output format => see PrintColorSetEx()
+)
+{
+ if (!colout)
+ SetupStdMsg();
+
+ if ( level < 0 )
+ {
+ printf("%s--color=%d [%s], colorize=%d [%s]\n"
+ "term=%s\n"
+ "stdout: tty=%d, mode=%d [%s], have-color=%d, n-colors=%u%s\n",
+ colout->status,
+ opt_colorize, GetColorModeName(opt_colorize,"?"),
+ colorize_stdout, GetColorModeName(colorize_stdout,"?"),
+ getenv("TERM"), isatty(fileno(stdout)),
+ colout->col_mode, GetColorModeName(colout->col_mode,"?"),
+ colout->colorize, colout->n_colors, colout->reset );
+ return ERR_OK;
+ }
+
+ if ( !mode && level > 0 )
+ switch (level)
+ {
+ case 1: mode = 0x08; break;
+ case 2: mode = 0x18; break;
+ case 3: mode = 0x1b; break;
+ default: mode = 0x1f; break;
+ }
+
+ if (!format)
+ {
+ const ColorMode_t col_mode = colout ? colout->col_mode : COLMD_AUTO;
+ PrintTextModes( stdout, 4, col_mode );
+ PrintColorModes( stdout, 4, col_mode, GCM_ALT );
+ #ifdef TEST
+ PrintColorModes( stdout, 4, col_mode, GCM_ALT|GCM_SHORT );
+ #endif
+ }
+ if ( mode || format )
+ PrintColorSetEx(stdout,4,colout,mode,format);
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// global commands: TESTCOLORS ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[color_info_t]]
+
+typedef struct color_info_t
+{
+ bool valid; // >0: data below is valid
+
+ u8 m_index; // index of 0..255 for "\e[*m"
+
+ s8 m_red; // -1:invalid, 0..5: red value of 'm_index'
+ s8 m_green; // -1:invalid, 0..5: green value of 'm_index'
+ s8 m_blue; // -1:invalid, 0..5: blue value of 'm_index'
+ s8 m_gray; // -1:invalid, 0..23: gray value of 'm_index'
+
+ u32 ref_color; // reference colors, based on 'm_index';
+ u32 src_color; // scanned color
+}
+color_info_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool AssignColorInfo ( color_info_t *ci, u32 rgb )
+{
+ DASSERT(ci);
+ memset(ci,0,sizeof(*ci));
+
+ ci->src_color = rgb & 0xffffff;
+ ci->m_index = ConvertColorRGBToM256(ci->src_color);
+ ci->ref_color = ConvertColorM256ToRGB(ci->m_index);
+
+ const uint r = ci->ref_color >> 16 & 0xff;
+ const uint g = ci->ref_color >> 8 & 0xff;
+ const uint b = ci->ref_color & 0xff;
+
+ ci->m_red = SingleColorToM6(r);
+ ci->m_green = SingleColorToM6(g);
+ ci->m_blue = SingleColorToM6(b);
+
+ ci->m_gray = ( r + g + b + 21 ) / 30;
+ if ( ci->m_gray > 23 )
+ ci->m_gray = 23;
+ else if ( ci->m_gray > 0 )
+ ci->m_gray--;
+
+ return ci->valid = 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool ScanColorInfo ( color_info_t *ci, ccp arg )
+{
+ DASSERT(ci);
+ memset(ci,0,sizeof(*ci));
+ if ( arg && *arg )
+ {
+ arg = SkipControls(arg);
+
+ ulong num, r, g, b;
+ if ( *arg >= 'g' && *arg <= 'z' )
+ {
+ const char mode = *arg;
+ arg = SkipControls(arg+1);
+
+ num = str2ul(arg,0,10);
+ switch (mode)
+ {
+ case 'm':
+ return AssignColorInfo(ci,ConvertColorM256ToRGB(num));
+
+ case 'g':
+ return AssignColorInfo(ci,ConvertColorM256ToRGB(232 + num % 24));
+
+ case 'c':
+ r = num / 100 % 10;
+ g = num / 10 % 10;
+ b = num % 10;
+ goto rgb5;
+ }
+ }
+ else
+ {
+ char *end;
+ num = str2ul(arg,&end,16);
+
+ ci->valid = true;
+ if ( end - arg <= 3 )
+ {
+ r = num >> 8 & 15;
+ g = num >> 4 & 15;
+ b = num & 15;
+
+ rgb5:
+ if ( r > 5 ) r = 5;
+ if ( g > 5 ) g = 5;
+ if ( b > 5 ) b = 5;
+ return AssignColorInfo(ci,ConvertColorM256ToRGB(16 + 36*r + 6*g + b));
+ }
+ return AssignColorInfo(ci,num);
+ }
+ }
+
+ return ci->valid;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void PrintColorInfo ( FILE *f, color_info_t *ci )
+{
+ DASSERT(f);
+ DASSERT(ci);
+
+ if (ci->valid)
+ fprintf(f," %06x -> %06x : %3u : %u,%u,%u : %2u"
+ " \e[48;5;16;38;5;%um test \e[0m"
+ " \e[48;5;244;38;5;%um test \e[0m"
+ " \e[48;5;231;38;5;%um test \e[0m "
+ " \e[38;5;16;48;5;%um test \e[0m"
+ " \e[38;5;244;48;5;%um test \e[0m"
+ " \e[38;5;231;48;5;%um test \e[0m\n",
+ ci->src_color, ci->ref_color,
+ ci->m_index, ci->m_red, ci->m_green, ci->m_blue, ci->m_gray,
+ ci->m_index, ci->m_index, ci->m_index,
+ ci->m_index, ci->m_index, ci->m_index );
+ else
+ fputs(" --\n",f);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError Command_TESTCOLORS ( int argc, char ** argv )
+{
+ #if HAVE_PRINT
+ GetColorByName(colout,"blue");
+ #endif
+
+ printf("TEST COLORS: %u arguments:\n",argc);
+
+ bool sep = true;
+ int idx;
+ for ( idx = 0; idx < argc; idx++ )
+ {
+ ccp minus = strchr(argv[idx],'-');
+ if ( minus || sep )
+ {
+ putchar('\n');
+ sep = false;
+ if ( strlen(argv[idx]) == 1 )
+ continue;
+ }
+
+ color_info_t ci1;
+ ScanColorInfo(&ci1,argv[idx]);
+ PrintColorInfo(stdout,&ci1);
+
+ if (!minus)
+ continue;
+
+ color_info_t ci2;
+ ScanColorInfo(&ci2,minus+1);
+ if ( ci2.ref_color == ci1.ref_color )
+ continue;
+
+ int r1 = ci1.ref_color >> 16 & 0xff;
+ int g1 = ci1.ref_color >> 8 & 0xff;
+ int b1 = ci1.ref_color & 0xff;
+ int r2 = ci2.ref_color >> 16 & 0xff;
+ int g2 = ci2.ref_color >> 8 & 0xff;
+ int b2 = ci2.ref_color & 0xff;
+
+ int n_steps = abs( r2 - r1 );
+ int diff = abs( g2 - g1 );
+ if ( n_steps < diff )
+ n_steps = diff;
+ diff = abs( b2 - b1 );
+ if ( n_steps < diff )
+ n_steps = diff;
+
+ u8 prev_m = ci1.m_index;
+ u32 prev_rgb = ci1.ref_color;
+ int i;
+ for ( i = 1; i <= n_steps; i++ )
+ {
+ uint r = ( r2 - r1 ) * i / n_steps + r1;
+ uint g = ( g2 - g1 ) * i / n_steps + g1;
+ uint b = ( b2 - b1 ) * i / n_steps + b1;
+ u8 new_m = ConvertColorRGB3ToM256(r,g,b);
+ if ( prev_m != new_m )
+ {
+ prev_m = new_m;
+ color_info_t ci3;
+ AssignColorInfo(&ci3,r<<16|g<<8|b);
+ if ( prev_rgb != ci3.ref_color )
+ {
+ prev_rgb = ci3.src_color = ci3.ref_color;
+ PrintColorInfo(stdout,&ci3);
+ }
+ }
+ }
+ sep = true;
+ }
+ putchar('\n');
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// color helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+//
+// https://en.wikipedia.org/wiki/ANSI_escape_code
+// https://jonasjacek.github.io/colors/
+//
+// 0..15: 000000 800000 008000 808000 000080 800080 008080 c0c0c0
+// 808080 ff0000 00ff00 ffff00 0000ff ff00ff 00ffff ffffff
+// => real colors varies on implementation
+//
+// 6x6x6: 0 95 135 175 215 255 == 00 5f 87 af d7 ff
+//
+// gray: 8 18 28 ... 238 == 08 12 1c 26 30 3a ... e4 ee f8
+//
+///////////////////////////////////////////////////////////////////////////////
+//
+// mkw-ana testcol 000000-ffffff
+// mkw-ana testcol 500-550 550-050 050-055 055-005 005-505 505-500
+//
+///////////////////////////////////////////////////////////////////////////////
+
+u32 ColorTab_M0_M15[16] =
+{
+ 0x000000, 0x800000, 0x008000, 0x808000,
+ 0x000080, 0x800080, 0x008080, 0xc0c0c0,
+ 0x808080, 0xff0000, 0x00ff00, 0xffff00,
+ 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff,
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+u8 ConvertColorRGB3ToM256 ( u8 r, u8 g, u8 b )
+{
+ // 0..5
+ const uint r6 = SingleColorToM6(r);
+ const uint g6 = SingleColorToM6(g);
+ const uint b6 = SingleColorToM6(b);
+
+ u8 m256 = 36*r6 + 6*g6 + b6 + 16;
+ uint delta = abs ( ( r6 ? 55 + 40 * r6 : 0 ) - r )
+ + abs ( ( g6 ? 55 + 40 * g6 : 0 ) - g )
+ + abs ( ( b6 ? 55 + 40 * b6 : 0 ) - b );
+
+ //printf("%02x>%u %02x>%u %02x>%u : m=%u delta=%d\n",r,r6,g,g6,b,b6,m256,delta);
+
+ uint m, gray, prev = 0x1000000;
+ for ( m = 232, gray = 8; m < 256; m++, gray += 10 )
+ {
+ const uint d = abs( gray - r ) + abs( gray - g ) + abs( gray - b );
+ if ( d > prev )
+ break;
+ if ( d <= delta )
+ {
+ delta = d;
+ m256 = m;
+ }
+ prev = d;
+ }
+
+ return m256;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u8 ConvertColorRGBToM256 ( u32 rgb )
+{
+ return ConvertColorRGB3ToM256( rgb >> 16, rgb >> 8, rgb );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 ConvertColorM256ToRGB ( u8 m256 )
+{
+ if ( m256 < 16 )
+ return ColorTab_M0_M15[m256];
+
+ if ( m256 < 232 )
+ {
+ m256 -= 16;
+ const uint r = m256 / 36;
+ const uint g = m256 / 6 % 6;
+ const uint b = m256 % 6;
+
+ u32 col = 0;
+ if (r) col |= 0x370000 + 0x280000 * r;
+ if (g) col |= 0x003700 + 0x002800 * g;
+ if (b) col |= 0x000037 + 0x000028 * b;
+ return col;
+ }
+
+ m256 -= 232;
+ return 0x080808 + m256 * 0x0a0a0a;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// dynamic data support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetDD ( DynData_t *dd )
+{
+ if (dd)
+ {
+ FREE(dd->data);
+ dd->data = 0;
+ dd->len = dd->size = 0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InsertDD ( DynData_t *dd, const void *data, uint data_size )
+{
+ DASSERT(dd);
+ DASSERT( !dd->data == !dd->size );
+ DASSERT( dd->len <= dd->size );
+
+ if ( !data || !data_size )
+ dd->len = 0;
+ else
+ {
+ if ( data_size > dd->size )
+ {
+ FREE(dd->data);
+ dd->data = MALLOC(data_size);
+ dd->size = data_size;
+ }
+
+ memcpy(dd->data,data,data_size);
+ dd->len = data_size;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetDDL ( DynDataList_t *ddl )
+{
+ if (ddl)
+ {
+ DynData_t *dd = ddl->list;
+ DynData_t *end = dd + ddl->used;
+ for ( ; dd < end; dd++ )
+ FREE(dd->data);
+
+ FREE(ddl->list);
+ ddl->list = 0;
+ ddl->used = ddl->size = 0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+DynData_t *InsertDDL
+(
+ DynDataList_t *ddl, // valid dynamic data list
+ uint index, // element index to change
+ const void *data, // NULL or data to assign
+ uint data_size // size of data
+)
+{
+ DASSERT(ddl);
+ DASSERT( !ddl->list == !ddl->size );
+ DASSERT( ddl->used <= ddl->size );
+
+ if ( index >= ddl->size )
+ {
+ const uint new_size = 11 * index / 10 + 10;
+ ddl->list = REALLOC( ddl->list, sizeof(*ddl->list) * new_size );
+ memset( ddl->list + ddl->size, 0, (new_size - ddl->size) * sizeof(*ddl->list) );
+ ddl->size = new_size;
+ }
+
+ if ( ddl->used <= index )
+ {
+ uint idx;
+ for ( idx = ddl->used; idx < index; idx++ )
+ ddl->list[idx].len = 0;
+ ddl->used = index+1;
+ }
+
+ DynData_t *dd = ddl->list + index;
+ InsertDD(dd,data,data_size);
+ return dd;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+DynData_t *GetDDL
+(
+ DynDataList_t *ddl, // valid dynamic data list
+ uint index, // element index to get
+ uint create // create an empty element if not exist
+)
+{
+ DASSERT(ddl);
+ DASSERT( !ddl->list == !ddl->size );
+ DASSERT( ddl->used <= ddl->size );
+
+ return index < ddl->used
+ ? ddl->list + ddl->used
+ : create
+ ? InsertDDL(ddl, index >= create ? index : create-1, 0,0)
+ : 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// StringField_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetStringField ( StringField_t * sf )
+{
+ DASSERT(sf);
+ if (sf)
+ {
+ if ( sf->used > 0 )
+ {
+ DASSERT(sf->field);
+ ccp *ptr = sf->field, *end;
+ for ( end = ptr + sf->used; ptr < end; ptr++ )
+ FreeString(*ptr);
+ }
+ FREE(sf->field);
+
+ int (*func_cmp)( ccp s1, ccp s2 ) = sf->func_cmp;
+ InitializeStringField(sf);
+ sf->func_cmp = func_cmp;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MoveStringField ( StringField_t * dest, StringField_t * src )
+{
+ DASSERT(src);
+ DASSERT(dest);
+ if ( src != dest )
+ {
+ ResetStringField(dest);
+ dest->field = src->field;
+ dest->used = src->used;
+ dest->size = src->size;
+ dest->func_cmp = src->func_cmp;
+
+ InitializeStringField(src);
+ src->func_cmp = dest->func_cmp;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindStringFieldIndex ( const StringField_t * sf, ccp key, int not_found_value )
+{
+ bool found;
+ const int idx = FindStringFieldHelper(sf,&found,key);
+ return found ? idx : not_found_value;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp FindStringField ( const StringField_t * sf, ccp key )
+{
+ bool found;
+ const int idx = FindStringFieldHelper(sf,&found,key);
+ return found ? sf->field[idx] : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp * InsertStringFieldHelper ( StringField_t * sf, int idx )
+{
+ DASSERT(sf);
+ DASSERT( sf->used <= sf->size );
+ noPRINT("+SF: %u/%u/%u\n",idx,sf->used,sf->size);
+ if ( sf->used == sf->size )
+ {
+ sf->size += 0x100;
+ sf->field = REALLOC(sf->field,sf->size*sizeof(*sf->field));
+ }
+ DASSERT( idx <= sf->used );
+ ccp * dest = sf->field + idx;
+ memmove(dest+1,dest,(sf->used-idx)*sizeof(*dest));
+ sf->used++;
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool InsertStringField ( StringField_t * sf, ccp key, bool move_key )
+{
+ if (!key)
+ return 0;
+
+ bool found;
+ int idx = FindStringFieldHelper(sf,&found,key);
+ if (found)
+ {
+ if (move_key)
+ FreeString(key);
+ }
+ else
+ {
+ ccp * dest = InsertStringFieldHelper(sf,idx);
+ *dest = move_key ? key : STRDUP(key);
+ }
+
+ return !found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool RemoveStringField ( StringField_t * sf, ccp key )
+{
+ bool found;
+ uint idx = FindStringFieldHelper(sf,&found,key);
+ if (found)
+ {
+ sf->used--;
+ ASSERT( idx <= sf->used );
+ ccp * dest = sf->field + idx;
+ FreeString(*dest);
+ memmove(dest,dest+1,(sf->used-idx)*sizeof(*dest));
+ }
+ return found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint RemoveStringFieldByIndex ( StringField_t * sf, int index, int n )
+{
+ DASSERT(sf);
+
+ if ( index < 0 )
+ index += sf->used;
+ if ( n < 0 )
+ {
+ index += n;
+ n -= n;
+ }
+
+ if ( index < 0 )
+ index = 0;
+ int last = index + n;
+ if ( last > sf->used )
+ last = sf->used;
+ if ( index >= last )
+ return 0;
+
+ ccp * src = sf->field + index;
+ ccp * end = sf->field + last;
+ while ( src < end )
+ FreeString(*src++);
+
+ memmove( sf->field + index, src, ( sf->used - last) * sizeof(*sf->field) );
+ const uint rm = last - index;
+ sf->used -= rm;
+ return rm;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AppendStringField ( StringField_t * sf, ccp key, bool move_key )
+{
+ DASSERT(sf);
+ if (key)
+ {
+ DASSERT( sf->used <= sf->size );
+ noPRINT(">SF: %u/%u\n",sf->used,sf->size);
+ if ( sf->used == sf->size )
+ {
+ sf->size += 0x100;
+ sf->field = REALLOC(sf->field,sf->size*sizeof(*sf->field));
+ }
+ TRACE("AppendStringField(%s,%d) %d/%d\n",key,move_key,sf->used,sf->size);
+ ccp *dest = sf->field + sf->used++;
+ *dest = move_key ? key : STRDUP(key);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SortStringField ( StringField_t * sf )
+{
+ DASSERT(sf);
+ if ( sf->used > 1 )
+ {
+ StringField_t temp;
+ InitializeStringField(&temp);
+ temp.func_cmp = sf->func_cmp;
+ uint i;
+ for ( i = 0; i < sf->used; i++ )
+ InsertStringField(&temp,sf->field[i],true);
+ sf->used = 0;
+ MoveStringField(sf,&temp);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint FindStringFieldHelper ( const StringField_t * sf, bool * p_found, ccp key )
+{
+ DASSERT(sf);
+
+ int (*cmp)( ccp s1, ccp s2 ) = sf->func_cmp ? sf->func_cmp : strcmp;
+
+ int beg = 0;
+ if ( sf && key )
+ {
+ int end = sf->used - 1;
+ while ( beg <= end )
+ {
+ const uint idx = (beg+end)/2;
+ const int stat = cmp(key,sf->field[idx]);
+ if ( stat < 0 )
+ end = idx - 1;
+ else if ( stat > 0 )
+ beg = idx + 1;
+ else
+ {
+ noTRACE("FindStringFieldHelper(%s) FOUND=%d/%d/%d\n",
+ key, idx, sf->used, sf->size );
+ if (p_found)
+ *p_found = true;
+ return idx;
+ }
+ }
+ }
+
+ noTRACE("FindStringFieldHelper(%s) failed=%d/%d/%d\n",
+ key, beg, sf->used, sf->size );
+
+ if (p_found)
+ *p_found = false;
+ return beg;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadStringField
+(
+ StringField_t * sf, // string field
+ bool init_sf, // true: initialize 'sf' first
+ bool sort, // true: sort 'sf'
+ ccp filename, // filename of source file
+ bool silent // true: don't print open/read errors
+)
+{
+ ASSERT(sf);
+ DASSERT(filename);
+ DASSERT(*filename);
+
+ TRACE("LoadStringField(%p,%d,%d,%s,%d)\n",sf,init_sf,sort,filename,silent);
+
+ if (init_sf)
+ InitializeStringField(sf);
+
+ FILE * f = fopen(filename,"rb");
+ if (!f)
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",filename);
+ return ERR_CANT_OPEN;
+ }
+
+ char iobuf[10000];
+ while (fgets(iobuf,sizeof(iobuf)-1,f))
+ {
+ char *ptr = iobuf;
+ while (*ptr)
+ ptr++;
+ if ( ptr > iobuf && ptr[-1] == '\n' )
+ {
+ ptr--;
+ if ( ptr > iobuf && ptr[-1] == '\r' )
+ ptr--;
+ }
+
+ if ( ptr > iobuf )
+ {
+ *ptr++ = 0;
+ const size_t len = ptr-iobuf;
+ ptr = MALLOC(len);
+ memcpy(ptr,iobuf,len);
+ if (sort)
+ InsertStringField(sf,ptr,true);
+ else
+ AppendStringField(sf,ptr,true);
+ }
+ }
+
+ fclose(f);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SaveStringField
+(
+ StringField_t * sf, // valid string field
+ ccp filename, // filename of dest file
+ bool rm_if_empty // true: rm dest file if 'sf' is empty
+)
+{
+ ASSERT(sf);
+ ASSERT(filename);
+ ASSERT(*filename);
+
+ TRACE("SaveStringField(%p,%s,%d)\n",sf,filename,rm_if_empty);
+
+ if ( !sf->used && rm_if_empty )
+ {
+ unlink(filename);
+ return ERR_OK;
+ }
+
+ FILE * f = fopen(filename,"wb");
+ if (!f)
+ return ERROR1(ERR_CANT_CREATE,"Can't create file: %s\n",filename);
+
+ enumError err = WriteStringField(f,filename,sf,0,0);
+ fclose(f);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError WriteStringField
+(
+ FILE * f, // open destination file
+ ccp filename, // NULL or filename (needed on write error)
+ StringField_t * sf, // valid string field
+ ccp prefix, // not NULL: insert prefix before each line
+ ccp eol // end of line text (if NULL: use LF)
+)
+{
+ if (!prefix)
+ prefix = "";
+ if (!eol)
+ eol = "\n";
+
+ ccp *ptr = sf->field, *end;
+ for ( end = ptr + sf->used; ptr < end; ptr++ )
+ if ( fprintf(f,"%s%s%s",prefix,*ptr,eol) < 0 )
+ return ERROR1(ERR_WRITE_FAILED,
+ "Error while writing string list: %s\n",
+ filename ? filename : "?" );
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateStringField
+(
+ FILE *f, // output file
+ ccp name_prefix, // NULL or prefix of name, up to 40 chars
+ uint tab_pos, // tab pos of '='
+ const StringField_t *sf, // valid string field
+ EncodeMode_t emode // encoding mode
+)
+{
+ DASSERT(f);
+ DASSERT(sf);
+
+ if (!name_prefix)
+ name_prefix = EmptyString;
+
+ int base = 8*tab_pos + 7 - strlen(name_prefix);
+ fprintf(f,"%sn%.*s= %u\n", name_prefix, (base-1)/8, Tabs20, sf->used );
+
+ char buf[5000], name[10];
+
+ uint i;
+ for ( i = 0; i < sf->used; i++ )
+ {
+ const uint len = snprintf(name,sizeof(name),"%u",i);
+ mem_t mem = EncodeByModeMem(buf,sizeof(buf),sf->field[i],-1,emode);
+ fprintf(f, "%s%s%.*s= %s\n",
+ name_prefix, name, (base-len)/8, Tabs20, mem.ptr );
+ if ( mem.ptr != buf )
+ FreeMem(&mem);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateStringField
+(
+ RestoreState_t *rs, // info data, may be modified
+ ccp name_prefix, // NULL or prefix of name, up to 40 chars
+ StringField_t *sf, // string field
+ bool init_sf, // true: initialize 'sf', otherwise reset
+ EncodeMode_t emode // encoding mode
+)
+{
+ DASSERT(rs);
+ DASSERT(sf);
+
+ if (init_sf)
+ InitializeStringField(sf);
+ else
+ ResetStringField(sf);
+
+ if (!name_prefix)
+ name_prefix = EmptyString;
+
+ char name[50];
+ snprintf(name,sizeof(name),"%sn",name_prefix);
+ int n = GetParamFieldINT(rs,name,0);
+ PRINT("RestoreStateStringField(), N=%d\n",n);
+ if ( n > 0 )
+ {
+ sf->size = n;
+ sf->field = MALLOC(sf->size*sizeof(*sf->field));
+
+ uint i;
+ for ( i = 0; i < n; i++ )
+ {
+ snprintf(name,sizeof(name),"%s%u",name_prefix,i);
+ ParamFieldItem_t *pfi = GetParamField(rs,name);
+ if (pfi)
+ {
+ mem_t mem = DecodeByModeMem(0,0,pfi->data,-1,emode);
+ AppendStringField(sf,mem.ptr,true);
+ }
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ParamField_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeParamField ( ParamField_t * pf )
+{
+ DASSERT(pf);
+ memset(pf,0,sizeof(*pf));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetParamField ( ParamField_t * pf )
+{
+ DASSERT(pf);
+ if (pf)
+ {
+ if ( pf->used > 0 )
+ {
+ DASSERT(pf->field);
+ ParamFieldItem_t *ptr = pf->field, *end;
+ for ( end = ptr + pf->used; ptr < end; ptr++ )
+ {
+ FreeString(ptr->key);
+ if (pf->free_data)
+ FREE(ptr->data);
+ }
+ }
+ FREE(pf->field);
+
+ const bool free_data = pf->free_data;
+ int (*func_cmp)( ccp s1, ccp s2 ) = pf->func_cmp;
+ InitializeParamField(pf);
+ pf->free_data = free_data;
+ pf->func_cmp = func_cmp;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MoveParamField ( ParamField_t * dest, ParamField_t * src )
+{
+ DASSERT(src);
+ DASSERT(dest);
+ if ( src != dest )
+ {
+ ResetParamField(dest);
+ dest->field = src->field;
+ dest->used = src->used;
+ dest->size = src->size;
+ dest->free_data = src->free_data;
+ InitializeParamField(src);
+ src->free_data = dest->free_data;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindParamFieldIndex ( const ParamField_t * pf, ccp key, int not_found_value )
+{
+ bool found;
+ const int idx = FindParamFieldHelper(pf,&found,key);
+ return found ? idx : not_found_value;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * FindParamField ( const ParamField_t * pf, ccp key )
+{
+ bool found;
+ const int idx = FindParamFieldHelper(pf,&found,key);
+ return found ? pf->field + idx : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * FindParamFieldByNum ( const ParamField_t * pf, uint num )
+{
+ DASSERT(pf);
+ noTRACE("FindParamFieldByNum(%x)\n",num);
+
+ ParamFieldItem_t *ptr = pf->field, *end;
+ for ( end = ptr + pf->used; ptr < end; ptr++ )
+ if ( ptr->num == num )
+ return ptr;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static ParamFieldItem_t * InsertParamFieldHelper ( ParamField_t * pf, int idx )
+{
+ DASSERT(pf);
+ DASSERT( pf->used <= pf->size );
+ noPRINT("+SF: %u/%u/%u\n",idx,pf->used,pf->size);
+ if ( pf->used == pf->size )
+ {
+ pf->size += 0x100;
+ pf->field = REALLOC(pf->field,pf->size*sizeof(*pf->field));
+ }
+ DASSERT( idx <= pf->used );
+ ParamFieldItem_t * dest = pf->field + idx;
+ memmove(dest+1,dest,(pf->used-idx)*sizeof(*dest));
+ pf->used++;
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * FindInsertParamField
+ ( ParamField_t * pf, ccp key, bool move_key, bool *old_found )
+{
+ if (!key)
+ return 0;
+
+ noTRACE("InsertParamField(%s,%x)\n",key,num);
+
+ bool found;
+ int idx = FindParamFieldHelper(pf,&found,key);
+ ParamFieldItem_t *item;
+ if (found)
+ {
+ if (move_key)
+ FreeString(key);
+ item = pf->field + idx;
+ }
+ else
+ {
+ item = InsertParamFieldHelper(pf,idx);
+ item->key = move_key ? key : STRDUP(key);
+ item->num = 0;
+ item->data = 0;
+ }
+
+ if (old_found)
+ *old_found = found;
+ return item;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * CountParamField ( ParamField_t * pf, ccp key, bool move_key )
+{
+ ParamFieldItem_t *item = FindInsertParamField(pf,key,move_key,0);
+ if (item)
+ item->num++;
+ return item;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool InsertParamField
+ ( ParamField_t * pf, ccp key, bool move_key, uint num, cvp data )
+{
+ if (!key)
+ return 0;
+
+ noTRACE("InsertParamField(%s,%x)\n",key,num);
+
+ bool found;
+ int idx = FindParamFieldHelper(pf,&found,key);
+ if (found)
+ {
+ if (move_key)
+ FreeString(key);
+ if (pf->free_data)
+ FREE((void*)data);
+ }
+ else
+ {
+ ParamFieldItem_t * dest = InsertParamFieldHelper(pf,idx);
+ dest->key = move_key ? key : STRDUP(key);
+ dest->num = num;
+ dest->data = (void*)data;
+ }
+
+ return !found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * InsertParamFieldEx
+ ( ParamField_t * pf, ccp key, bool move_key, uint num, bool *p_found )
+{
+ if (!key)
+ return 0;
+
+ noTRACE("InsertParamField(%s,%x)\n",key,num);
+
+ bool found;
+ int idx = FindParamFieldHelper(pf,&found,key);
+ if (p_found)
+ *p_found = found;
+
+ if (found)
+ {
+ if (move_key)
+ FreeString(key);
+ return pf->field + idx;
+ }
+
+ ParamFieldItem_t * dest = InsertParamFieldHelper(pf,idx);
+ dest->key = move_key ? key : STRDUP(key);
+ dest->num = num;
+ dest->data = 0;
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ReplaceParamField
+ ( ParamField_t * pf, ccp key, bool move_key, uint num, cvp data )
+{
+ if (!key)
+ return 0;
+
+ noTRACE("InsertParamField(%s,%x)\n",key,num);
+
+ bool found;
+ int idx = FindParamFieldHelper(pf,&found,key);
+ if (found)
+ {
+ if (move_key)
+ FreeString(key);
+
+ ParamFieldItem_t * dest = pf->field + idx;
+ dest->num = num;
+ FREE(dest->data);
+ dest->data = (void*)data;
+ }
+ else
+ {
+ ParamFieldItem_t * dest = InsertParamFieldHelper(pf,idx);
+ dest->key = move_key ? key : STRDUP(key);
+ dest->num = num;
+ dest->data = (void*)data;
+ }
+
+ return !found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool RemoveParamField ( ParamField_t * pf, ccp key )
+{
+ bool found;
+ uint idx = FindParamFieldHelper(pf,&found,key);
+ if (found)
+ {
+ pf->used--;
+ ASSERT( idx <= pf->used );
+ ParamFieldItem_t * dest = pf->field + idx;
+ FreeString(dest->key);
+ if (pf->free_data)
+ FREE(dest->data);
+ memmove(dest,dest+1,(pf->used-idx)*sizeof(*dest));
+ }
+ return found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * AppendParamField
+ ( ParamField_t * pf, ccp key, bool move_key, uint num, cvp data )
+{
+ DASSERT(pf);
+ if (key)
+ {
+ DASSERT( pf->used <= pf->size );
+ noPRINT(">SF: %u/%u\n",pf->used,pf->size);
+ if ( pf->used == pf->size )
+ {
+ pf->size += 0x100;
+ pf->field = REALLOC(pf->field,pf->size*sizeof(*pf->field));
+ }
+ TRACE("AppendParamField(%s,%d) %d/%d\n",key,move_key,pf->used,pf->size);
+ ParamFieldItem_t * dest = pf->field + pf->used++;
+ dest->key = move_key ? key : STRDUP(key);
+ dest->num = num;
+ dest->data = (void*)data;
+ return dest;
+ }
+
+ if (pf->free_data)
+ FREE((void*)data);
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint FindParamFieldHelper ( const ParamField_t * pf, bool * p_found, ccp key )
+{
+ DASSERT(pf);
+
+ int (*cmp)( ccp s1, ccp s2 ) = pf->func_cmp ? pf->func_cmp : strcmp;
+
+ int beg = 0;
+ if ( pf && key )
+ {
+ int end = pf->used - 1;
+ while ( beg <= end )
+ {
+ uint idx = (beg+end)/2;
+ int stat = cmp(key,pf->field[idx].key);
+ if ( stat < 0 )
+ end = idx - 1 ;
+ else if ( stat > 0 )
+ beg = idx + 1;
+ else
+ {
+ noTRACE("FindParamFieldHelper(%s) FOUND=%d/%d/%d\n",
+ key, idx, pf->used, pf->size );
+ if (p_found)
+ *p_found = true;
+ return idx;
+ }
+ }
+ }
+
+ noTRACE("FindParamFieldHelper(%s) failed=%d/%d/%d\n",
+ key, beg, pf->used, pf->size );
+
+ if (p_found)
+ *p_found = false;
+ return beg;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Memory Maps ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeMemMap ( MemMap_t * mm )
+{
+ DASSERT(mm);
+ memset(mm,0,sizeof(*mm));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetMemMap ( MemMap_t * mm )
+{
+ DASSERT(mm);
+
+ uint i;
+ if (mm->field)
+ {
+ for ( i = 0; i < mm->used; i++ )
+ FREE(mm->field[i]);
+ FREE(mm->field);
+ }
+ memset(mm,0,sizeof(*mm));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CopyMemMap
+(
+ MemMap_t * mm1, // merged mem map, not NULL, cleared
+ const MemMap_t * mm2, // NULL or second mem map
+ bool use_tie // TRUE: use InsertMemMapTie()
+)
+{
+ DASSERT(mm1);
+ ResetMemMap(mm1);
+ MergeMemMap(mm1,mm2,use_tie);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MergeMemMap
+(
+ MemMap_t * mm1, // merged mem map, not NULL, not cleared
+ const MemMap_t * mm2, // NULL or second mem map
+ bool use_tie // TRUE: use InsertMemMapTie()
+)
+{
+ DASSERT(mm1);
+ if (mm2)
+ {
+ uint i;
+ for ( i = 0; i < mm2->used; i++ )
+ {
+ const MemMapItem_t *src = mm2->field[i];
+ if (src)
+ {
+ MemMapItem_t *it = use_tie
+ ? InsertMemMapTie(mm1,src->off,src->size)
+ : InsertMemMap(mm1,src->off,src->size);
+ if (it)
+ StringCopyS(it->info,sizeof(it->info),src->info);
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MemMapItem_t * FindMemMap ( MemMap_t * mm, off_t off, off_t size )
+{
+ DASSERT(mm);
+
+ off_t off_end = off + size;
+ int beg = 0;
+ int end = mm->used - 1;
+ while ( beg <= end )
+ {
+ uint idx = (beg+end)/2;
+ MemMapItem_t * mi = mm->field[idx];
+ if ( off_end <= mi->off )
+ end = idx - 1 ;
+ else if ( off >= mi->off + mi->size )
+ beg = idx + 1;
+ else
+ return mi;
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint InsertMemMapIndex
+(
+ // returns the index of the new item
+
+ MemMap_t * mm, // mem map pointer
+ off_t off, // offset of area
+ off_t size // size of area
+)
+{
+ DASSERT(mm);
+ uint idx = FindMemMapHelper(mm,off,size);
+
+ DASSERT( mm->used <= mm->size );
+ if ( mm->used == mm->size )
+ {
+ mm->size += 64;
+ mm->field = REALLOC(mm->field,mm->size*sizeof(*mm->field));
+ }
+
+ DASSERT( idx <= mm->used );
+ MemMapItem_t ** dest = mm->field + idx;
+ memmove(dest+1,dest,(mm->used-idx)*sizeof(*dest));
+ mm->used++;
+
+ MemMapItem_t * mi = MALLOC(sizeof(MemMapItem_t));
+ mi->off = off;
+ mi->size = size;
+ mi->overlap = 0;
+ *dest = mi;
+ return idx;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MemMapItem_t * InsertMemMap
+(
+ // returns a pointer to a new item (never NULL)
+
+ MemMap_t * mm, // mem map pointer
+ off_t off, // offset of area
+ off_t size // size of area
+)
+{
+ const uint idx = InsertMemMapIndex(mm,off,size);
+ // a C sequence point is important here
+ return mm->field[idx];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool TieMemMap
+(
+ // returns true if element are tied togehther
+
+ MemMap_t * mm, // mem map pointer
+ uint idx, // tie element 'idx' and 'idx+1'
+ bool force // always tie and not only if overlapped
+)
+{
+ DASSERT(mm);
+ DASSERT( idx+1 < mm->used );
+
+ MemMapItem_t * i1 = mm->field[idx];
+ MemMapItem_t * i2 = mm->field[idx+1];
+ if ( force || i1->off + i1->size >= i2->off )
+ {
+ const off_t new_size = i2->off + i2->size - i1->off;
+ if ( i1->size < new_size )
+ i1->size = new_size;
+ FREE(i2);
+ idx++;
+ mm->used--;
+ memmove( mm->field + idx,
+ mm->field + idx + 1,
+ ( mm->used - idx ) * sizeof(MemMapItem_t*) );
+
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MemMapItem_t * InsertMemMapTie
+(
+ // returns a pointer to a new or existing item (never NULL)
+
+ MemMap_t * mm, // mem map pointer
+ off_t off, // offset of area
+ off_t size // size of area
+)
+{
+ uint idx = InsertMemMapIndex(mm,off,size);
+
+ if ( idx > 0 && TieMemMap(mm,idx-1,false) )
+ idx--;
+
+ while ( idx + 1 < mm->used && TieMemMap(mm,idx,false) )
+ ;
+
+ return mm->field[idx];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InsertMemMapWrapper
+(
+ void * param, // user defined parameter
+ u64 offset, // offset of object
+ u64 size, // size of object
+ ccp info // info about object
+)
+{
+ noTRACE("InsertMemMapWrapper(%p,%llx,%llx,%s)\n",param,offset,size,info);
+ DASSERT(param);
+ MemMapItem_t * mi = InsertMemMap(param,offset,size);
+ StringCopyS(mi->info,sizeof(mi->info),info);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint FindMemMapHelper ( MemMap_t * mm, off_t off, off_t size )
+{
+ DASSERT(mm);
+
+ int beg = 0;
+ int end = mm->used - 1;
+ while ( beg <= end )
+ {
+ uint idx = (beg+end)/2;
+ MemMapItem_t * mi = mm->field[idx];
+ if ( off < mi->off )
+ end = idx - 1 ;
+ else if ( off > mi->off )
+ beg = idx + 1;
+ else if ( size < mi->size )
+ end = idx - 1 ;
+ else if ( size > mi->size )
+ beg = idx + 1;
+ else
+ {
+ TRACE("FindMemMapHelper(%llx,%llx) FOUND=%d/%d/%d\n",
+ (u64)off, (u64)size, idx, mm->used, mm->size );
+ return idx;
+ }
+ }
+
+ TRACE("FindStringFieldHelper(%llx,%llx) failed=%d/%d/%d\n",
+ (u64)off, (u64)size, beg, mm->used, mm->size );
+ return beg;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CalcOverlapMemMap ( MemMap_t * mm )
+{
+ DASSERT(mm);
+
+ uint i, count = 0;
+ MemMapItem_t * prev = 0;
+ for ( i = 0; i < mm->used; i++ )
+ {
+ MemMapItem_t * ptr = mm->field[i];
+ ptr->overlap = 0;
+ if ( prev && ptr->off < prev->off + prev->size )
+ {
+ ptr ->overlap |= 1;
+ prev->overlap |= 2;
+ count++;
+ }
+ prev = ptr;
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 FindFreeSpaceMemMap
+(
+ // return address or NULL on not-found
+
+ const MemMap_t * mm,
+ u64 addr_beg, // first possible address
+ u64 addr_end, // last possible address
+ u64 size, // minimal size
+ u64 align, // aligning
+ u64 *space // not NULL: store available space here
+)
+{
+ DASSERT(mm);
+
+ if (!size)
+ goto term;
+
+ if (!align)
+ align = 1;
+ addr_beg = ALIGN64(addr_beg,align);
+
+ uint i;
+ for ( i = 0; i < mm->used; i++ )
+ {
+ MemMapItem_t *mi = mm->field[i];
+ const u64 end = ALIGN64( mi->off + mi->size, align );
+ if ( mi->off > addr_beg )
+ {
+ u64 found_space = ALIGNOFF64(mi->off,align) - addr_beg;
+ if ( size <= found_space )
+ {
+ if (space)
+ *space = found_space;
+ return addr_beg;
+ }
+ }
+ if ( addr_beg < end )
+ addr_beg = end;
+ }
+
+ u64 found_space = ALIGNOFF64(addr_end,align) - addr_beg;
+ if ( size <= found_space )
+ {
+ if (space)
+ *space = found_space;
+ return addr_beg;
+ }
+
+ term:
+ if (space)
+ *space = 0;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintMemMap ( MemMap_t * mm, FILE * f, int indent, ccp info_head )
+{
+ DASSERT(mm);
+ if ( !f || !mm->used )
+ return;
+
+ CalcOverlapMemMap(mm);
+ indent = NormalizeIndent(indent);
+
+ static char ovl[][3] = { " ", "!.", ".!", "!!" };
+
+ if (!info_head)
+ info_head = "info";
+ int i, max_ilen = strlen(info_head);
+ for ( i = 0; i < mm->used; i++ )
+ {
+ MemMapItem_t * ptr = mm->field[i];
+ ptr->info[sizeof(ptr->info)-1] = 0;
+ const int ilen = strlen(ptr->info);
+ if ( max_ilen < ilen )
+ max_ilen = ilen;
+ }
+
+ fprintf(f,"%*s unused : off(beg) .. off(end) : size : %s\n%*s%.*s\n",
+ indent, "", info_head,
+ indent, "", max_ilen+52, Minus300 );
+
+ off_t max_end = mm->begin;
+ for ( i = 0; i < mm->used; i++ )
+ {
+ MemMapItem_t * ptr = mm->field[i];
+ if ( !i && max_end > ptr->off )
+ max_end = ptr->off;
+ const off_t end = ptr->off + ptr->size;
+ if ( ptr->off > max_end )
+ fprintf(f,"%*s%s%10llx :%10llx ..%10llx :%10llx : %s\n",
+ indent, "", ovl[ptr->overlap&3], (u64)( ptr->off - max_end ),
+ (u64)ptr->off, (u64)end, (u64)ptr->size, ptr->info );
+ else
+ fprintf(f,"%*s%s :%10llx ..%10llx :%10llx : %s\n",
+ indent, "", ovl[ptr->overlap&3],
+ (u64)ptr->off, (u64)end, (u64)ptr->size, ptr->info );
+ if ( max_end < end )
+ max_end = end;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Memory Map: Scan addresses ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanAddress
+(
+ // returns first not scanned char of 'arg'
+
+ ScanAddr_t * result, // result, initialized by ScanAddress()
+ ccp arg, // format: ADDR | ADDR:END | ADDR#SIZE
+ uint default_base // base for numbers without '0x' prefix
+ // 0: C like with octal support
+ // 10: standard value for decimal numbers
+ // 16: standard value for hex numbers
+)
+{
+ DASSERT(result);
+ memset(result,0,sizeof(*result));
+ if (!arg)
+ return 0;
+
+ ccp end = ScanUINT(&result->addr,arg,default_base);
+ if ( end == arg )
+ return (char*)arg;
+ arg = end;
+ while ( *arg == ' ' || *arg == '\t' )
+ arg++;
+ result->size = 1;
+ result->stat = 1;
+
+ if ( *arg == ':' )
+ {
+ uint addr2;
+ end = ScanUINT(&addr2,++arg,default_base);
+ if ( end == arg )
+ return(char*) arg-1;
+
+ result->size = addr2 > result->addr ? addr2 - result->addr : 0;
+ result->stat = 2;
+ }
+ else if ( *arg == '#' )
+ {
+ end = ScanUINT(&result->size,++arg,default_base);
+ result->stat = 2;
+ }
+
+ return (char*)end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint InsertAddressMemMap
+(
+ // returns the number of added addresses
+
+ MemMap_t * mm, // mem map pointer
+ bool use_tie, // TRUE: use InsertMemMapTie()
+ ccp arg, // comma separated list of addresses
+ // formats: ADDR | ADDR:END | ADDR#SIZE
+ uint default_base // base for numbers without '0x' prefix
+ // 0: C like with octal support
+ // 10: standard value for decimal numbers
+ // 16: standard value for hex numbers
+)
+{
+ DASSERT(mm);
+ if (!arg)
+ return 0;
+
+ uint count = 0;
+ while (*arg)
+ {
+ ScanAddr_t addr;
+ arg = ScanAddress(&addr,arg,default_base);
+ if (addr.stat)
+ {
+ if (use_tie)
+ InsertMemMapTie(mm,addr.addr,addr.size);
+ else
+ InsertMemMap(mm,addr.addr,addr.size);
+ count++;
+ }
+
+ while ( *arg && *arg != ' ' && *arg != '\t' && *arg != ',' )
+ arg++;
+ while ( *arg == ' ' || *arg == '\t' || *arg == ',' )
+ arg++;
+ }
+
+ return count;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// DataBuf_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeDataBuf
+ ( DataBuf_t *db, uint buf_size, DataBufFill_t func, void *user_data )
+{
+ DASSERT(db);
+ memset(db,0,sizeof(*db));
+
+ if (buf_size)
+ {
+ db->size = buf_size;
+ db->buf = MALLOC(buf_size);
+ db->buf_end = db->buf + buf_size;
+ db->data = db->data_end = db->buf;
+ }
+
+ db->fill_func = func;
+ db->user_data = user_data;
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetDataBuf ( DataBuf_t *db )
+{
+ if (db)
+ {
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ FREE(db->buf);
+ memset(db,0,sizeof(*db));
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResizeDataBuf ( DataBuf_t *db, uint new_buf_size )
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ DASSERT(new_buf_size>0);
+
+ if (!db->buf)
+ return;
+
+ PackDataBuf(db);
+ db->buf = REALLOC(db->buf,new_buf_size);
+ db->buf_end = db->buf + new_buf_size;
+ db->size = new_buf_size;
+ if ( db->used > new_buf_size )
+ db->used = new_buf_size;
+ db->data = db->buf;
+ db->data_end = db->data + db->used;
+
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearDataBuf ( DataBuf_t *db, uint new_buf_size_if_not_null )
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ db->offset = 0;
+ db->data = db->data_end = db->buf;
+ db->used = 0;
+ db->offset = 0;
+ if (new_buf_size_if_not_null)
+ ResizeDataBuf(db,new_buf_size_if_not_null);
+
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void EmptyDataBuf ( DataBuf_t *db )
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ db->data = db->data_end = db->buf;
+ db->used = 0;
+
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PackDataBuf ( DataBuf_t *db )
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ if ( !db->used || !db->buf )
+ {
+ db->data = db->data_end = db->buf;
+ }
+ else if ( db->data > db->buf )
+ {
+ if ( db->data < db->data_end )
+ {
+ // easy case: FREE1 DATA FREE2
+
+ memmove(db->buf,db->data,db->used);
+ }
+ else
+ {
+ // case: DATA2 FREE DATA1
+
+ uint d1len = db->buf_end - db->data;
+ uint d2len = db->data_end - db->buf;
+ DASSERT( d1len + d2len == db->used );
+
+ #ifdef TEST0
+ u8 temp[10];
+ #else
+ u8 temp[0x8000];
+ #endif
+ if ( d2len <= sizeof(temp) && d2len < d1len )
+ {
+ memcpy(temp,db->buf,d2len);
+ memmove(db->buf,db->data,d1len);
+ memcpy(db->buf+d1len,temp,d2len);
+ }
+ else if ( d1len <= sizeof(temp) )
+ {
+ memcpy(temp,db->data,d1len);
+ memmove(db->buf+d1len,db->buf,d2len);
+ memcpy(db->buf,temp,d1len);
+ }
+ else
+ {
+ noPRINT("PACK %u+%u/%u, temp=%zu\n",d2len,d1len,db->size,sizeof(temp));
+ // use a dynamic buffer => [2do]] optimize later
+ u8 *temp = MEMDUP(db->buf,d2len);
+ memmove(db->buf,db->data,d1len);
+ memcpy(db->buf+d1len,temp,d2len);
+ FREE(temp);
+ }
+ }
+ db->data = db->buf;
+ db->data_end = db->data + db->used;
+ }
+
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// debugging
+
+bool IsValidDataBuf ( DataBuf_t *db )
+{
+ if ( !db || db->used > db->size )
+ return false;
+
+ if (db->buf)
+ {
+ if ( db->buf_end != db->buf + db->size
+ || db->data < db->buf || db->data >= db->buf_end )
+ return false;
+
+ u8 *end = db->data + db->used;
+ if ( end > db->buf_end )
+ end -= db->size;
+ return end == db->data_end;
+ }
+
+ return !db->size && !db->buf_end && !db->data && !db->data_end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool DumpDataBuf ( FILE *f, uint indent, DataBuf_t *db, bool if_invalid )
+{
+ DASSERT(f);
+ DASSERT(db);
+ if ( if_invalid && IsValidDataBuf(db) )
+ return false;
+
+ fflush(stdout);
+ fflush(stderr);
+ indent = NormalizeIndent(indent);
+ if (!db)
+ {
+ fprintf(f,"%*sdata_buf_t: NULL\n",indent,"");
+ return true;
+ }
+
+ if (!db->buf)
+ {
+ fprintf(f,"%*sdata_buf_t buf: Invalid buf\n",indent,"");
+ return true;
+ }
+
+ fprintf(f,"%*sdata_buf_t %p .. %d, data %d .. %d, used %u/%u, off=%llu\n",
+ indent,"", db->buf, (int)(db->buf_end - db->buf),
+ (int)(db->data - db->buf), (int)(db->data_end - db->buf),
+ db->used, db->size, db->offset );
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FillPatternDataBuf ( DataBuf_t *db )
+{
+ // fill the buffer with a test pattern:
+ // '-' for unused bytes
+ // ">0123456789<" in a longer or shorter form for used bytes
+
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ memset(db->buf,'-',db->size);
+
+ if ( db->used <= 1 )
+ {
+ if ( db->used == 1 )
+ *db->data = '+';
+ return;
+ }
+
+ u8 *ptr = db->data;
+ u8 *end = ptr + db->used - 1;
+ if ( end >= db->buf_end )
+ end -= db->size;
+ *ptr++ = '>';
+ *end = '<';
+ uint i = 0, len = end - ptr;
+ if ( len > db->size )
+ len += db->size;
+ for(;;)
+ {
+ if ( ptr == db->buf_end )
+ ptr = db->buf;
+ if ( ptr == end )
+ break;
+ *ptr++ = 'a' + i/len;
+ i += 26;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint InsertDataBuf
+(
+ // Insert at top of buffer.
+ // Return the number of inserted bytes
+
+ DataBuf_t *db, // valid pointer to data buffer
+ const void *data, // data to insert
+ uint data_size, // size of 'data'
+ bool all_or_none // true: insert complete data or nothing
+)
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ uint space = db->size - db->used;
+ if ( data_size > space )
+ {
+ if (all_or_none)
+ return 0;
+ data_size = space;
+ }
+
+ if (!db->used)
+ db->data = db->data_end = db->buf + data_size;
+ db->used += data_size;
+
+ uint copy_len = db->data - db->buf;
+ if ( copy_len > data_size )
+ copy_len = data_size;
+ memcpy(db->data-copy_len,data,copy_len);
+ db->data -= copy_len;
+ if ( data_size > copy_len )
+ {
+ const uint copy_len2 = data_size - copy_len;
+ memcpy(db->buf_end-copy_len2,(u8*)data+copy_len,copy_len2);
+ db->data = db->buf_end - copy_len2;
+ }
+
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ return data_size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint AppendDataBuf
+(
+ // Append data at end of buffer
+ // Return the number of inserted bytes
+
+ DataBuf_t *db, // valid pointer to data buffer
+ const void *data, // data to insert
+ uint data_size, // size of 'data'
+ bool all_or_none // true: insert complete data or nothing
+)
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ uint space = db->size - db->used;
+ noPRINT("APPEND %u/%u [%u/%u]\n",data_size,space,db->used,db->size);
+ if ( data_size > space )
+ {
+ if (all_or_none)
+ return 0;
+ data_size = space;
+ }
+
+ if (!db->used)
+ db->data = db->data_end = db->buf;
+ db->used += data_size;
+
+ uint copy_len = db->buf_end - db->data_end;
+ if ( copy_len > data_size )
+ copy_len = data_size;
+ memcpy(db->data_end,data,copy_len);
+ db->data_end += copy_len;
+ if ( data_size > copy_len )
+ {
+ const uint copy_len2 = data_size - copy_len;
+ memcpy(db->buf,(u8*)data+copy_len,copy_len2);
+ db->data_end = db->buf + copy_len2;
+ }
+
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ return data_size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void * GetPtrDataBuf
+(
+ // Return a pointer to the data and make sure,
+ // that the data is not wrapped. Return NULL if failed.
+
+ DataBuf_t *db, // valid pointer to data buffer
+ uint data_size, // number of needed bytes
+ uint align, // >1: Data alignment is also needed.
+ // Only 2,4,8,16,... are allowed, but not verified.
+ // Begin of buffer is always well aligned.
+ bool drop // true: On success, drop data from buffer
+ // In real, the data is available and only
+ // the pointers and counters are updated.
+)
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ if (!data_size)
+ return db->data;
+ if ( data_size > db->used )
+ {
+ if ( data_size <= db->size && db->fill_func )
+ db->fill_func(db,data_size-db->used,0);
+ if ( data_size > db->used )
+ return 0;
+ }
+
+ if ( db->data + data_size > db->buf_end )
+ PackDataBuf(db);
+ else if ( align > 1 )
+ {
+ const uint offset = db->data - db->buf;
+ if ( offset & (align-1) )
+ PackDataBuf(db);
+ }
+
+ u8 *res = db->data;
+ if (drop)
+ {
+ db->data += data_size;
+ db->used -= data_size;
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ }
+
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CopyDataBuf
+(
+ // Copy data from buffer, but don't remove it
+ // Return the number of copied bytes
+
+ DataBuf_t *db, // valid pointer to data buffer
+ void *dest, // destination buffer wit at least 'max_size' space
+ uint min_read, // return with 0 read bytes if less bytes available
+ // if 'min_read' > 'size', NULL is returned
+ uint max_read // try to read upto 'max_read' bytes
+)
+{
+ DASSERT(db);
+ DASSERT(dest);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ if ( min_read > db->used )
+ {
+ if ( min_read <= db->size && db->fill_func )
+ db->fill_func(db,min_read-db->used,0);
+ if ( min_read > db->used )
+ return 0;
+ }
+
+ if ( max_read > db->used )
+ max_read = db->used;
+ uint copy_len1 = db->buf_end - db->data;
+ if ( copy_len1 >= max_read )
+ memcpy(dest,db->data,max_read);
+ else
+ {
+ memcpy(dest,db->data,copy_len1);
+ memcpy((u8*)dest+copy_len1,db->buf,max_read-copy_len1);
+ }
+
+ return max_read;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetDataBuf
+(
+ // Copy data from buffer and remove it
+ // Return the number of copied bytes
+
+ DataBuf_t *db, // valid pointer to data buffer
+ void *dest, // destination buffer wit at least 'max_size' space
+ uint min_read, // return with 0 read bytes if less bytes available
+ uint max_read // try to read upto 'max_read' bytes
+)
+{
+ DASSERT(db);
+ DASSERT(dest);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ if ( min_read > db->used )
+ {
+ if ( min_read <= db->size && db->fill_func )
+ db->fill_func(db,min_read-db->used,0);
+ if ( min_read > db->used )
+ return 0;
+ }
+
+ if ( max_read > db->used )
+ max_read = db->used;
+ uint copy_len1 = db->buf_end - db->data;
+ if ( copy_len1 >= max_read )
+ memcpy(dest,db->data,max_read);
+ else
+ {
+ memcpy(dest,db->data,copy_len1);
+ memcpy((u8*)dest+copy_len1,db->buf,max_read-copy_len1);
+ }
+
+ db->used -= max_read;
+ if (db->used)
+ {
+ db->data += max_read;
+ if ( db->data >= db->buf_end )
+ db->data -= db->size;
+ }
+ else
+ db->data = db->data_end = db->buf;
+
+ db->offset += max_read;
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ return max_read;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SkipDataBuf
+(
+ // Skip 'skip_size' from input stream. Read more data if needed.
+ // Return the number of really skiped bytes
+ // If <skip_size is returned, no more data is available.
+
+ DataBuf_t *db, // valid pointer to data buffer
+ uint skip_size // number of bytes to drop
+)
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ uint skipped = 0;
+ for(;;)
+ {
+ uint stat = DropDataBuf(db,skip_size);
+ skipped += stat;
+ skip_size -= stat;
+ if (!skip_size)
+ break;
+
+ if (db->fill_func)
+ {
+ const uint max_fill = skip_size < db->size ? skip_size : db->size;
+ db->fill_func(db,max_fill,0);
+ }
+
+ if (!db->used)
+ break;
+ }
+
+ db->offset += skipped;
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ return skipped;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint DropDataBuf
+(
+ // Remove upto 'drop_size' bytes from buffer.
+ // Return the number of dropped bytes.
+
+ DataBuf_t *db, // valid pointer to data buffer
+ uint drop_size // number of bytes to drop
+)
+{
+ DASSERT(db);
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+
+ if ( drop_size >= db->used )
+ {
+ drop_size = db->used;
+ db->used = 0;
+ db->data = db->data_end = db->buf;
+ }
+ else
+ {
+ db->data += drop_size;
+ if ( db->data >= db->buf_end )
+ db->data -= db->size;
+ db->used -= drop_size;
+ }
+ db->offset += drop_size;
+
+ DASSERT(!DumpDataBuf(stderr,0,db,true));
+ return drop_size;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ResizeElement_t, ResizeHelper_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeResize
+(
+ ResizeHelper_t *r, // data structure
+ uint s_size, // size of source
+ uint d_size // size of dest
+)
+{
+ DASSERT(r);
+ memset(r,0,sizeof(*r));
+
+ r->src_size = s_size;
+ r->dest_size= d_size;
+ if ( !s_size || !d_size )
+ return;
+
+ const uint divisor = gcd(s_size,d_size);
+ s_size /= divisor;
+ d_size /= divisor;
+
+ r->sum_factor = s_size;
+ r->half_factor = r->sum_factor/2;
+
+ r->idx_inc = s_size / d_size;
+ r->frac_inc = s_size % d_size;
+ r->frac_next = d_size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool CalcResize ( ResizeHelper_t *r )
+{
+ DASSERT(r);
+
+ ResizeElement_t *re = r->elem;
+ re[0].n_elem = re[1].n_elem = re[2].n_elem = 0;
+ DASSERT(!re[3].n_elem);
+
+ if ( r->src_idx >= r->src_size || r->dest_idx >= r->dest_size )
+ return false;
+
+ uint sum = r->sum_factor;
+ if (r->src_frac)
+ {
+ uint max = r->frac_next - r->src_frac;
+ if ( max > sum )
+ max = sum;
+ re->n_elem = 1;
+ re->factor = max;
+ re++;
+ sum -= max;
+ }
+
+ if ( sum >= r->frac_next )
+ {
+ const uint n = sum / r->frac_next;
+ re->n_elem = n;
+ re->factor = r->frac_next;
+ re++;
+ sum -= n * r->frac_next;
+ }
+
+ if ( sum > 0 )
+ {
+ re->n_elem = 1;
+ re->factor = sum;
+ re++;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool FirstResize ( ResizeHelper_t *r )
+{
+ DASSERT(r);
+
+ r->dest_idx = r->src_idx = r->src_frac = 0;
+ memset(r->elem,0,sizeof(r->elem));
+ return CalcResize(r);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool NextResize ( ResizeHelper_t *r )
+{
+ DASSERT(r);
+
+ r->dest_idx++;
+ r->src_idx += r->idx_inc;
+ r->src_frac += r->frac_inc;
+ if ( r->src_frac >= r->frac_next )
+ {
+ r->src_frac -= r->frac_next;
+ r->src_idx++;
+ }
+
+ return CalcResize(r);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// GrowBuffer_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static const uint grow_buffer_size_factor = 0x100;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeGrowBuffer ( GrowBuffer_t *gb, uint max_buf_size )
+{
+ DASSERT(gb);
+ memset(gb,0,sizeof(*gb));
+ gb->grow_size = grow_buffer_size_factor;
+ gb->max_size = ( max_buf_size + grow_buffer_size_factor - 1 )
+ / grow_buffer_size_factor * grow_buffer_size_factor;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetGrowBuffer ( GrowBuffer_t *gb )
+{
+ DASSERT(gb);
+ FREE(gb->buf);
+ const uint grow_size = gb->grow_size;
+ InitializeGrowBuffer(gb,gb->max_size);
+ gb->grow_size = grow_size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetSpaceGrowBuffer
+(
+ // returns the maximal possible space
+ const GrowBuffer_t *gb // valid grow-buffer object
+)
+{
+ DASSERT(gb);
+ const uint max = gb->max_size > gb->size ? gb->max_size : gb->size;
+ return max - gb->used;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrepareGrowBuffer
+(
+ // returns the number of available space, smaller than or equal 'size'
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ uint size, // needed size
+ bool force // always alloc enough space and return 'size'
+)
+{
+ #if HAVE_DASSERT
+ DASSERT(gb);
+ if (gb->buf)
+ {
+ DASSERT( gb->used <= gb->size );
+ DASSERT( gb->ptr >= gb->buf );
+ DASSERT( gb->ptr + gb->used <= gb->buf + gb->size );
+ }
+ else
+ {
+ DASSERT(!gb->ptr);
+ DASSERT(!gb->size);
+ DASSERT(!gb->used);
+ }
+ #endif
+
+ if (!gb->buf)
+ {
+ //--- initial setup of buffer
+
+ if ( gb->grow_size < grow_buffer_size_factor )
+ gb->grow_size = grow_buffer_size_factor;
+ else
+ gb->grow_size = gb->grow_size
+ / grow_buffer_size_factor * grow_buffer_size_factor;
+ gb->size = ( size / gb->grow_size + 1 ) * gb->grow_size;
+
+ if ( gb->max_size < grow_buffer_size_factor )
+ gb->max_size = grow_buffer_size_factor;
+ else
+ gb->max_size = gb->max_size
+ / grow_buffer_size_factor * grow_buffer_size_factor;
+ if ( gb->size > gb->max_size )
+ gb->size = gb->max_size;
+
+ if ( gb->size < size && force )
+ gb->size = ( size + gb->grow_size )
+ / gb->grow_size * gb->grow_size - 1;
+
+ gb->buf = gb->ptr = MALLOC(gb->size+1); // 1 extra byte for NULL-TERM
+ gb->used = 0;
+ }
+ else
+ {
+ if (!gb->grow_size)
+ gb->grow_size = grow_buffer_size_factor;
+ if (!gb->used)
+ gb->ptr = gb->buf;
+ }
+ DASSERT(gb->grow_size);
+
+ uint avail = gb->size - gb->used;
+ if ( size > avail && ( force || gb->size < gb->max_size ) )
+ {
+ //--- grow buffer
+
+ gb->size = ( size + gb->used + gb->grow_size )
+ / gb->grow_size * gb->grow_size - 1;
+ DASSERT( gb->used <= gb->size );
+ u8 *newbuf = MALLOC(gb->size+1); // 1 extra byte for NULL-TERM
+ memcpy(newbuf,gb->ptr,gb->used);
+ FREE(gb->buf);
+ gb->buf = gb->ptr = newbuf;
+ }
+
+ uint space = ( gb->buf + gb->size ) - ( gb->ptr + gb->used );
+ if ( space < size && gb->buf != gb->ptr )
+ {
+ memmove(gb->buf,gb->ptr,gb->used);
+ gb->ptr = gb->buf;
+ space = gb->size - gb->used;
+ }
+
+ DASSERT( gb->used <= gb->size );
+ DASSERT( gb->ptr >= gb->buf );
+ DASSERT( gb->ptr + gb->used <= gb->buf + gb->size );
+
+ return size < space ? size : space;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint InsertGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ const void *data, // data to insert
+ uint size // size of 'data'
+)
+{
+ DASSERT(gb);
+ size = PrepareGrowBuffer(gb,size,false);
+ DASSERT( gb->ptr + gb->used + size <= gb->buf + gb->size );
+ memcpy( gb->ptr + gb->used, data, size );
+ gb->used += size;
+ gb->ptr[gb->used] = 0;
+ if ( gb->max_used < gb->used )
+ gb->max_used = gb->used;
+
+ DASSERT( gb->used <= gb->size );
+ DASSERT( gb->ptr >= gb->buf );
+ DASSERT( gb->ptr + gb->used <= gb->buf + gb->size );
+
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char *tiparm ( ccp str, ... );
+
+uint InsertCapNGrowBuffer
+(
+ // insert 'cap_1' or 'cap_n' depending of 'n' and the existence of 'cap_*'
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ int n, // repeat number; if <1: insert nothing
+ mem_t cap_1, // default for N=1
+ mem_t cap_n // default for N>1
+)
+{
+ DASSERT(gb);
+
+ if ( n <= 0 )
+ return 0;
+
+ if ( n == 1 && cap_1.len )
+ return InsertMemGrowBuffer(gb,cap_1);
+
+ #if DCLIB_TERMINAL
+ if (cap_n.len)
+ {
+ ccp str = tiparm(cap_n.ptr,n);
+ if (str)
+ return InsertGrowBuffer(gb,str,strlen(str));
+ }
+ #endif
+
+ if (!cap_1.len)
+ return 0;
+
+ const uint total = cap_1.len * n;
+ PrepareGrowBuffer(gb,total,true);
+ u8 *ptr = gb->ptr + gb->used;
+ while ( n-- > 0 )
+ {
+ memcpy(ptr,cap_1.ptr,cap_1.len);
+ ptr += cap_1.len;
+ }
+ gb->used += total;
+ gb->ptr[gb->used] = 0;
+ if ( gb->max_used < gb->used )
+ gb->max_used = gb->used;
+ return total;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint WriteCapN
+(
+ // print 'cap_1' or 'cap_n' depending of 'n' and the existence of 'cap_*'
+ // returns the number of printed bytes
+
+ FILE *f, // valid FILE
+ int n, // repeat number; if <1: insert nothing
+ mem_t cap_1, // default for N=1
+ mem_t cap_n // default for N>1
+)
+{
+ DASSERT(f);
+
+ if ( n <= 0 )
+ return 0;
+
+ if ( n == 1 && cap_1.len )
+ return fwrite(cap_1.ptr,cap_1.len,1,f);
+
+ #if DCLIB_TERMINAL
+ if (cap_n.len)
+ {
+ ccp str = tiparm(cap_n.ptr,n);
+ if (str)
+ return fwrite(str,strlen(str),1,f);
+ }
+ #endif
+
+ if (!cap_1.len)
+ return 0;
+
+ const uint total = cap_1.len * n;
+ while ( n-- > 0 )
+ fwrite(cap_1.ptr,cap_1.len,1,f);
+ return total;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint InsertCatchedGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ SavedStdFiles_t *ssf // saved output and catched data
+)
+{
+ DASSERT(gb);
+ DASSERT(ssf);
+
+ TermCatchStdFiles(ssf);
+ uint stat = 0;
+ if (ssf->data)
+ {
+ PrepareGrowBuffer(gb,ssf->size,true);
+ stat = InsertGrowBuffer(gb,ssf->data,ssf->size);
+ XFREE(ssf->data);
+ ssf->data = 0;
+ ssf->size = 0;
+ }
+ return stat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintArgGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ ccp format, // format string for vfprintf()
+ va_list arg // parameters for 'format'
+)
+{
+ DASSERT(gb);
+ DASSERT(format);
+
+ char buf[10000];
+
+ va_list arg2;
+ va_copy(arg2,arg);
+ int stat = vsnprintf(buf,sizeof(buf),format,arg2);
+ va_end(arg2);
+
+ if ( stat < sizeof(buf) )
+ return InsertGrowBuffer(gb,buf,stat);
+
+
+ //--- buffer too small, use dynamic memory
+
+ noPRINT("PrintArgGrowBuffer() -> MALLOC(%u)\n",stat+1);
+
+ char *dest = MALLOC(stat+1);
+ stat = vsnprintf(dest,stat+1,format,arg);
+
+ const uint res = InsertGrowBuffer(gb,dest,stat);
+ FREE(dest);
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vsnprintf(format,...)'
+)
+{
+ DASSERT(gb);
+ DASSERT(format);
+
+ va_list arg;
+ va_start(arg,format);
+ enumError err = PrintArgGrowBuffer(gb,format,arg);
+ va_end(arg);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ConvertToCrLfGrowBuffer
+(
+ // returns the number of inserted CR bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ uint begin // index to begin with conversion
+)
+{
+ DASSERT(gb);
+ uint count = 0;
+
+ if ( begin < gb->used )
+ {
+ //--- count LF without CR
+
+ u8 *ptr = gb->ptr + begin;
+ u8 *end = gb->ptr + gb->used;
+ for ( ; ptr < end; ptr++ )
+ {
+ if ( *ptr == '\r' )
+ ptr++;
+ else if ( *ptr == '\n' )
+ count++;
+ }
+
+ if ( count > 0 )
+ {
+ PrepareGrowBuffer(gb,count,true);
+ u8 *src = gb->ptr + gb->used;
+ u8 *dest = src + count;
+ u8 *beg = gb->ptr + begin;
+ while ( src > beg )
+ {
+ const char ch = *--src;
+ *--dest = ch;
+ if ( ch == '\n' && src[-1] != '\r' )
+ *--dest = '\r';
+ }
+ DASSERT( src == dest );
+ gb->used += count;
+ gb->ptr[gb->used] = 0;
+ if ( gb->max_used < gb->used )
+ gb->max_used = gb->used;
+ }
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ClearGrowBuffer ( GrowBuffer_t *gb )
+{
+ DASSERT(gb);
+ gb->ptr = gb->buf;
+ if (gb->ptr)
+ *gb->ptr = 0;
+ gb->used = 0;
+ return gb->size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PurgeGrowBuffer ( GrowBuffer_t *gb )
+{
+ if (!gb->used)
+ return ClearGrowBuffer(gb);
+
+ if ( gb->ptr > gb->buf )
+ {
+ memmove(gb->buf,gb->ptr,gb->used);
+ gb->ptr = gb->buf;
+ }
+ return gb->size - gb->used;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint DropGrowBuffer
+(
+ // returns the number of dropped bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ uint size // number of bytes to drop
+)
+{
+ #if HAVE_DASSERT
+ DASSERT(gb);
+ if (gb->buf)
+ {
+ DASSERT_MSG( gb->used < gb->size, "%d/%d\n",gb->used,gb->size);
+ DASSERT( gb->ptr >= gb->buf );
+ DASSERT( gb->ptr + gb->used <= gb->buf + gb->size );
+ }
+ else
+ {
+ DASSERT(!gb->ptr);
+ DASSERT(!gb->size);
+ DASSERT(!gb->used);
+ }
+ #endif
+
+ if (!gb->buf)
+ return 0;
+
+ if ( size >= gb->used )
+ {
+ size = gb->used;
+ gb->used = 0;
+ gb->ptr = gb->buf;
+ *gb->ptr = 0;
+ return size;
+ }
+
+ gb->ptr += size;
+ gb->used -= size;
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void LogGrowBuffer
+(
+ FILE *f, // output file
+ int indent, // indention
+ const GrowBuffer_t *gb, // valid grow-buffer
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vfprintf(format,...)'
+)
+{
+ DASSERT(f);
+ DASSERT(gb);
+ indent = NormalizeIndent(indent);
+
+ fprintf(f,"%*sTB: size=%s/%s/%s idx=%s grow=%s/%s disabled=%d",
+ indent,"",
+ PrintSize1024(0,0,gb->used,DC_SFORM_TINY|DC_SFORM_ALIGN),
+ PrintSize1024(0,0,gb->max_used,DC_SFORM_TINY|DC_SFORM_ALIGN),
+ PrintSize1024(0,0,gb->size,DC_SFORM_TINY|DC_SFORM_ALIGN),
+ PrintNumberU6(0,0,gb->ptr-gb->buf,true),
+ PrintSize1024(0,0,gb->grow_size,DC_SFORM_TINY|DC_SFORM_ALIGN),
+ PrintSize1024(0,0,gb->max_size,DC_SFORM_TINY|DC_SFORM_ALIGN),
+ gb->disabled );
+
+ if (format)
+ {
+ fputs(" : ",f);
+ va_list arg;
+ va_start(arg,format);
+ vfprintf(f,format,arg);
+ va_end(arg);
+ }
+
+ fputc('\n',f);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+#ifndef __APPLE__
+
+FILE * OpenFileGrowBuffer
+(
+ // returns a FILE opened by fmemopen() => call CloseFileGrowBuffer()
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ uint size // needed size
+)
+{
+ DASSERT(gb);
+ uint space = PurgeGrowBuffer(gb);
+ if ( space < size )
+ space = PrepareGrowBuffer(gb,size,true);
+ return fmemopen( gb->ptr + gb->used, space, "w" );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CloseFileGrowBuffer
+(
+ // returns the number of wirtten bytes, or -1 on error
+ GrowBuffer_t *gb, // valid grow-buffer object
+ FILE *f // FILE returned by OpenFileGrowBuffer()
+)
+{
+ DASSERT(gb);
+ if (!f)
+ return -1;
+
+ const int stat = ftell(f);
+ if ( stat >= 0 && gb->ptr + gb->used + stat <= gb->buf + gb->size )
+ gb->used += stat;
+
+ fclose(f);
+ return stat;
+}
+
+#endif // !__APPLE__
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define GROW_BUFFER_CHUNK_SIZE 3*1024 // multiple of 3 because of BASE64 (3->4)
+
+void SaveCurrentStateGrowBuffer
+(
+ FILE *f, // output file
+ ccp name_prefix, // NULL or prefix of name
+ uint tab_pos, // tab pos of '='
+ const GrowBuffer_t *gb // valid grow-buffer
+)
+{
+ DASSERT(f);
+ DASSERT(gb);
+
+ if (!name_prefix)
+ name_prefix = EmptyString;
+
+ const int base = 8*tab_pos + 7 - strlen(name_prefix);
+
+ fprintf(f,
+ "%s" "disabled%.*s= %u\n"
+ "%s" "used%.*s= %u\n"
+ "%s" "size%.*s= %u\n"
+ "%s" "grow-size%.*s= %u\n"
+ "%s" "max-size%.*s= %u\n"
+ ,name_prefix, (base-8)/8, Tabs20, gb->disabled
+ ,name_prefix, (base-4)/8, Tabs20, gb->used
+ ,name_prefix, (base-4)/8, Tabs20, gb->size
+ ,name_prefix, (base-9)/8, Tabs20, gb->grow_size
+ ,name_prefix, (base-8)/8, Tabs20, gb->max_size
+ );
+
+ char buf[4*GROW_BUFFER_CHUNK_SIZE/3+10], name[20];
+ uint start,i;
+
+ for ( start = i = 0; start < gb->used; i++ )
+ {
+ uint size = gb->used - start;
+ if ( size > GROW_BUFFER_CHUNK_SIZE )
+ size = GROW_BUFFER_CHUNK_SIZE;
+
+ const uint len = snprintf(name,sizeof(name),"data-%u",i);
+ EncodeBase64(buf,sizeof(buf),(ccp)gb->ptr+start,size,0,true,0,0);
+ fprintf(f, "%s%s%.*s= %s\n",
+ name_prefix, name, (base-len)/8, Tabs20, buf );
+
+ start += size;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateGrowBuffer
+(
+ GrowBuffer_t *gb, // valid grow-buffer
+ ccp name_prefix, // NULL or prefix of name
+ RestoreState_t *rs // info data, can be modified (cleaned after call)
+)
+{
+ DASSERT(gb);
+ DASSERT(rs);
+
+ ResetGrowBuffer(gb);
+
+ if (!name_prefix)
+ name_prefix = EmptyString;
+
+ char name[200]; // name buf
+ snprintf(name,sizeof(name),"%sused",name_prefix);
+ int size = GetParamFieldUINT(rs,name,0);
+ snprintf(name,sizeof(name),"%sdisabled",name_prefix);
+ gb->disabled = GetParamFieldINT(rs,name,gb->disabled) > 0;
+
+ if ( size > 0 )
+ {
+ char buf[2*GROW_BUFFER_CHUNK_SIZE];
+
+ PrepareGrowBuffer(gb,size,true);
+ uint index = 0;
+ while ( size > 0 )
+ {
+ snprintf(name,sizeof(name),"%sdata-%u",name_prefix,index++);
+ int res = GetParamFieldBUF(buf,sizeof(buf),rs,name,ENCODE_BASE64URL,0);
+ if ( res < 0 )
+ break;
+
+ InsertGrowBuffer(gb,buf,res);
+ size -= res;
+ }
+
+ #ifdef TEST0
+ if ( gb->used > 10000 )
+ {
+ fprintf(stderr,"--- ResetGrowBuffer() size = %d\n",gb->used);
+ fwrite(gb->ptr,1,gb->used,stderr);
+ fprintf(stderr,"--- ResetGrowBuffer() size = %d\n",gb->used);
+ }
+ #endif
+ }
+
+
+ //--- known as 'unused'
+
+ if ( rs->log_mode & RSL_UNUSED_NAMES )
+ {
+ static ccp name_tab[] =
+ {
+ "size",
+ "grow-size",
+ "max-size",
+ 0
+ };
+
+ ccp *np;
+ for ( np = name_tab; *np; np++ )
+ {
+ StringCat2S(name,sizeof(name),name_prefix,*np);
+ GetParamField(rs,name);
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// misc ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int opt_new = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CreateUniqueId ( int range )
+{
+ static uint unique_id = 1;
+ const uint ret = unique_id;
+ if ( range > 0 )
+ unique_id += range;
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Sha1Hex2Bin ( sha1_hash_t bin, ccp src, ccp end )
+{
+ DASSERT(bin);
+ DASSERT(src);
+
+ uint i;
+ for ( i = 0; i < 20; i++ )
+ bin[i] = ScanDigits(&src,end,16,2,0);
+}
+
+//-----------------------------------------------------------------------------
+
+void Sha1Bin2Hex ( sha1_hex_t hex, cvp bin )
+{
+ DASSERT(hex);
+ DASSERT(bin);
+
+ snprintf(hex,sizeof(sha1_hex_t),"%08x%08x%08x%08x%08x",
+ be32(bin), be32(bin+4), be32(bin+8), be32(bin+12), be32(bin+16) );
+}
+
+//-----------------------------------------------------------------------------
+
+ccp GetSha1Hex ( cvp bin )
+{
+ DASSERT(bin);
+
+ char *hex = GetCircBuf(sizeof(sha1_hex_t));
+ Sha1Bin2Hex(hex,bin);
+ return hex;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-basics.h b/project/dclib/dclib-basics.h
new file mode 100644
index 0000000..7c6e7a2
--- /dev/null
+++ b/project/dclib/dclib-basics.h
@@ -0,0 +1,5252 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_BASICS_H
+#define DCLIB_BASICS_H 1
+
+#define _GNU_SOURCE 1
+
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+#ifndef WIN_DCLIB
+ #include <stdint.h>
+#endif
+
+#include "dclib-types.h"
+#include "dclib-debug.h"
+#include "dclib-color.h"
+#include "dclib-numeric.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define SIZEOFMEMBER(type,a) sizeof(((type*)0)->a)
+
+#define SIZEOFRANGE(type,a,b) \
+ ( offsetof(type,b) + sizeof(((type*)0)->b) - offsetof(type,a) )
+
+#define PTR_DISTANCE(a,b) ( (u8*)(a) - (u8*)(b) )
+#define PTR_DISTANCE_INT(a,b) ( (int)( (u8*)(a) - (u8*)(b) ))
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// bits ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern const uchar TableBitCount[0x100];
+extern const char TableLowest0Bit[0x100];
+extern const char TableLowest1Bit[0x100];
+extern const char TableHighest0Bit[0x100];
+extern const char TableHighest1Bit[0x100];
+
+uint Count1Bits ( cvp addr, uint size );
+static inline uint Count0Bits ( cvp addr, uint size )
+ { return CHAR_BIT * size - Count1Bits(addr,size); }
+
+#define Count1BitsObj(obj) Count1Bits(&(obj),sizeof(obj))
+#define Count0BitsObj(obj) Count1Bits(&(obj),sizeof(obj))
+
+static inline uint Count1Bits8 ( u8 data )
+{
+ return TableBitCount[data];
+}
+
+static inline uint Count1Bits16 ( u16 data )
+{
+ const u8 * d = (u8*)&data;
+ return TableBitCount[d[0]]
+ + TableBitCount[d[1]];
+}
+
+static inline uint Count1Bits32 ( u32 data )
+{
+ // __builtin_popcount(unsigned int) is slower than table based summary
+
+ const u8 * d = (u8*)&data;
+ return TableBitCount[d[0]]
+ + TableBitCount[d[1]]
+ + TableBitCount[d[2]]
+ + TableBitCount[d[3]];
+}
+
+static inline uint Count1Bits64 ( u64 data )
+{
+ #if __GNUC__ > 3
+ return __builtin_popcountll(data);
+ #else
+ const u8 * d = (u8*)&data;
+ return TableBitCount[d[0]]
+ + TableBitCount[d[1]]
+ + TableBitCount[d[2]]
+ + TableBitCount[d[3]]
+ + TableBitCount[d[4]]
+ + TableBitCount[d[5]]
+ + TableBitCount[d[6]]
+ + TableBitCount[d[7]];
+ #endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindLowest0BitBE ( cvp addr, uint size );
+int FindLowest1BitBE ( cvp addr, uint size );
+int FindHighest0BitBE ( cvp addr, uint size );
+int FindHighest1BitBE ( cvp addr, uint size );
+
+int FindLowest0BitLE ( cvp addr, uint size );
+int FindLowest1BitLE ( cvp addr, uint size );
+int FindHighest0BitLE ( cvp addr, uint size );
+int FindHighest1BitLE ( cvp addr, uint size );
+
+#if __BYTE_ORDER == 1234
+ #define FindLowest0Bit FindLowest0BitLE
+ #define FindLowest1Bit FindLowest1BitLE
+ #define FindHighest0Bit FindHighest0BitLE
+ #define FindHighest1Bit FindHighest1BitLE
+#else
+ #define FindLowest0Bit FindLowest0BitBE
+ #define FindLowest1Bit FindLowest1BitBE
+ #define FindHighest0Bit FindHighest0BitBE
+ #define FindHighest1Bit FindHighest1BitBE
+#endif
+
+u32 GetAlign32 ( u32 data );
+u64 GetAlign64 ( u64 data );
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline void SetBit ( void *addr, uint bit_num )
+ { ((u8*)addr)[ bit_num >> 3 ] |= 1 << ( bit_num & 7 ); }
+
+static inline void ClearBit ( void *addr, uint bit_num )
+ { ((u8*)addr)[ bit_num >> 3 ] &= ~(1 << ( bit_num & 7 )); }
+
+static inline void InvertBit ( void *addr, uint bit_num )
+ { ((u8*)addr)[ bit_num >> 3 ] ^= 1 << ( bit_num & 7 ); }
+
+static inline bool TestBit ( const void *addr, uint bit_num )
+ { return ( ((u8*)addr)[ bit_num >> 3 ] & 1 << ( bit_num & 7 ) ) != 0; }
+
+// change a single bit and return the previous state
+bool TestSetBit ( void *addr, uint bit_num );
+bool TestClearBit ( void *addr, uint bit_num );
+bool TestInvertBit ( void *addr, uint bit_num );
+
+// change bits inclusive 'beg' to exclusive 'end' if beg_bitnum<end_bitnum
+void SetBits ( void *addr, uint beg_bitnum, uint end_bitnum );
+void ClearBits ( void *addr, uint beg_bitnum, uint end_bitnum );
+void InvertBits ( void *addr, uint beg_bitnum, uint end_bitnum );
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int PrintBitfieldEx
+(
+ // returns the number of printed characters, or -1 if buffer is to small
+
+ char *buf, // buffer
+ uint buf_size, // size of 'buf'
+ cvp bit_field, // address of bit field
+ uint min_idx, // minimal index (inclusive) to check
+ uint max_idx, // maximal index (inclusive) to check
+ bool use_ranges, // true: create ranges 'a:b'
+ int mult, // each printed index is first multiplyed by 'mult'
+ int add // ... and then added to 'add'.
+);
+
+static inline int PrintBitfield
+(
+ // returns the number of printed characters, or -1 if buffer is to small
+
+ char *buf, // buffer
+ uint buf_size, // size of 'buf'
+ cvp bit_field, // address of bit field
+ uint bit_field_size, // size of 'bit_field' in bytes
+ bool use_ranges // true: create ranges 'a:b'
+)
+{
+ return PrintBitfieldEx ( buf, buf_size, bit_field,
+ 0, 8*bit_field_size-1, use_ranges, 1, 0 );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// low level endian conversions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+//----------------------------------------------------
+// swapping bytes = inverse byte order
+// void dswap16 ( void * data_ptr );
+// void dswap24 ( void * data_ptr );
+// void dswap32 ( void * data_ptr );
+// void dswap48 ( void * data_ptr );
+// void dswap64 ( void * data_ptr );
+//----------------------------------------------------
+
+static inline void dswap16 ( void * data_ptr )
+{
+ u8 *d = data_ptr;
+ u8 temp = d[0];
+ d[0] = d[1];
+ d[1] = temp;
+}
+
+static inline void dswap24 ( void * data_ptr )
+{
+ u8 *d = data_ptr;
+ const u8 temp = d[0];
+ d[0] = d[2];
+ d[2] = temp;
+}
+
+static inline void dswap32 ( void * data_ptr )
+{
+ u8 temp, *d = data_ptr;
+ temp = d[0]; d[0] = d[3]; d[3] = temp;
+ temp = d[1]; d[1] = d[2]; d[2] = temp;
+}
+
+static inline void dswap48 ( void * data_ptr )
+{
+ u8 temp, *d = data_ptr;
+ temp = d[0]; d[0] = d[5]; d[5] = temp;
+ temp = d[1]; d[1] = d[4]; d[4] = temp;
+ temp = d[2]; d[2] = d[3]; d[3] = temp;
+}
+
+static inline void dswap64 ( void * data_ptr )
+{
+ u8 temp, *d = data_ptr;
+ temp = d[0]; d[0] = d[7]; d[7] = temp;
+ temp = d[1]; d[1] = d[6]; d[6] = temp;
+ temp = d[2]; d[2] = d[5]; d[5] = temp;
+ temp = d[3]; d[3] = d[4]; d[4] = temp;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+//----------------------------------------------------
+// read data and swap bytes (inverse byte order)
+// u16 swap16 ( const void * data_ptr );
+// u32 swap24 ( const void * data_ptr );
+// u32 swap32 ( const void * data_ptr );
+// u64 swap48 ( const void * data_ptr );
+// u64 swap64 ( const void * data_ptr );
+// float swapf4 ( const void * data_ptr );
+// double swapf8 ( const void * data_ptr );
+//----------------------------------------------------
+
+static inline u16 swap16 ( const void * data_ptr )
+{
+ u8 r[2];
+ const u8 *d = data_ptr;
+ r[1] = *d++;
+ r[0] = *d;
+ return *(u16*)r;
+}
+
+static inline u32 swap24 ( const void * data_ptr )
+{
+ u8 r[4];
+ const u8 *d = data_ptr;
+ r[3] = 0;
+ r[2] = *d++;
+ r[1] = *d++;
+ r[0] = *d;
+ return *(u32*)r;
+}
+
+static inline u32 swap32 ( const void * data_ptr )
+{
+ u8 r[4];
+ const u8 *d = data_ptr;
+ r[3] = *d++;
+ r[2] = *d++;
+ r[1] = *d++;
+ r[0] = *d;
+ return *(u32*)r;
+}
+
+static inline u64 swap48 ( const void * data_ptr )
+{
+ u8 r[8];
+ const u8 *d = data_ptr;
+ r[7] = 0;
+ r[6] = 0;
+ r[5] = *d++;
+ r[4] = *d++;
+ r[3] = *d++;
+ r[2] = *d++;
+ r[1] = *d++;
+ r[0] = *d;
+ return *(u64*)r;
+}
+
+static inline u64 swap64 ( const void * data_ptr )
+{
+ u8 r[8];
+ const u8 *d = data_ptr;
+ r[7] = *d++;
+ r[6] = *d++;
+ r[5] = *d++;
+ r[4] = *d++;
+ r[3] = *d++;
+ r[2] = *d++;
+ r[1] = *d++;
+ r[0] = *d;
+ return *(u64*)r;
+}
+
+static inline float swapf4 ( const void * data_ptr )
+{
+ // assume: local system supports IEEE 754
+ union { float f; u32 i; } u;
+ u.i = swap32(data_ptr);
+ return u.f;
+}
+
+static inline double swapf8 ( const void * data_ptr )
+{
+ // assume: local system supports IEEE 754
+ union { double f; u64 i; } u;
+ u.i = swap64(data_ptr);
+ return u.f;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+//----------------------------------------------------
+// convert big endian data to a number in host format
+// u16 be16 ( const void * be_data_ptr );
+// u32 be24 ( const void * be_data_ptr );
+// u32 be32 ( const void * be_data_ptr );
+// u64 be40 ( const void * be_data_ptr );
+// u64 be48 ( const void * be_data_ptr );
+// u64 be56 ( const void * be_data_ptr );
+// u64 be64 ( const void * be_data_ptr );
+// float bef4 ( const void * be_data_ptr );
+// double bef8 ( const void * be_data_ptr );
+//----------------------------------------------------
+
+static inline u16 be16 ( const void * be_data_ptr )
+{
+ const u8 * d = be_data_ptr;
+ return d[0] << 8 | d[1];
+}
+
+static inline u32 be24 ( const void * be_data_ptr )
+{
+ const u8 * d = be_data_ptr;
+ return ( d[0] << 8 | d[1] ) << 8 | d[2];
+}
+
+static inline u32 be32 ( const void * be_data_ptr )
+{
+ const u8 * d = be_data_ptr;
+ return (( d[0] << 8 | d[1] ) << 8 | d[2] ) << 8 | d[3];
+}
+
+static inline u64 be40 ( const void * be_data_ptr )
+{
+ const u8 * d = be_data_ptr;
+ return (u64)be32(d) << 8 | ((u8*)d)[4];
+}
+
+static inline u64 be48 ( const void * be_data_ptr )
+{
+ const u8 * d = be_data_ptr;
+ return (u64)be32(d) << 16 | be16(d+4);
+}
+
+static inline u64 be56 ( const void * be_data_ptr )
+{
+ const u8 * d = be_data_ptr;
+ return (u64)be32(d) << 24 | be24(d+4);
+}
+
+static inline u64 be64 ( const void * be_data_ptr )
+{
+ const u8 * d = be_data_ptr;
+ return (u64)be32(d) << 32 | be32(d+4);
+}
+
+static inline float bef4 ( const void * be_data_ptr )
+{
+ // assume: local system supports IEEE 754
+ union { float f; u32 i; } u;
+ u.i = be32(be_data_ptr);
+ return u.f;
+}
+
+static inline double bef8 ( const void * be_data_ptr )
+{
+ // assume: local system supports IEEE 754
+ union { double f; u64 i; } u;
+ u.i = be64(be_data_ptr);
+ return u.f;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+//-------------------------------------------------------
+// convert little endian data to a number in host format
+// u16 le16 ( const void * le_data_ptr );
+// u32 le24 ( const void * le_data_ptr );
+// u32 le32 ( const void * le_data_ptr );
+// u64 le40 ( const void * le_data_ptr );
+// u64 le48 ( const void * le_data_ptr );
+// u64 le56 ( const void * le_data_ptr );
+// u64 le64 ( const void * le_data_ptr );
+// float lef4 ( const void * le_data_ptr );
+// double lef8 ( const void * le_data_ptr );
+//-------------------------------------------------------
+
+static inline u16 le16 ( const void * le_data_ptr )
+{
+ const u8 * d = le_data_ptr;
+ return d[1] << 8 | d[0];
+}
+
+static inline u32 le24 ( const void * le_data_ptr )
+{
+ const u8 * d = le_data_ptr;
+ return ( d[2] << 8 | d[1] ) << 8 | d[0];
+}
+
+static inline u32 le32 ( const void * le_data_ptr )
+{
+ const u8 * d = le_data_ptr;
+ return (( d[3] << 8 | d[2] ) << 8 | d[1] ) << 8 | d[0];
+}
+
+static inline u64 le40 ( const void * le_data_ptr )
+{
+ const u8 * d = le_data_ptr;
+ return (u64)le32(d+1) << 8 | ((u8*)d)[0];
+}
+
+static inline u64 le48 ( const void * le_data_ptr )
+{
+ const u8 * d = le_data_ptr;
+ return (u64)le16(d+4) << 32 | le32(d);
+}
+
+static inline u64 le56 ( const void * le_data_ptr )
+{
+ const u8 * d = le_data_ptr;
+ return (u64)le24(d+4) << 32 | le32(d);
+}
+
+static inline u64 le64 ( const void * le_data_ptr )
+{
+ const u8 * d = le_data_ptr;
+ return (u64)le32(d+4) << 32 | le32(d);
+}
+
+static inline float lef4 ( const void * le_data_ptr )
+{
+ // assume: local system supports IEEE 754
+
+ union { float f; u32 i; } u;
+ u.i = le32(le_data_ptr);
+ return u.f;
+}
+
+static inline double lef8 ( const void * le_data_ptr )
+{
+ // assume: local system supports IEEE 754
+
+ union { double f; u64 i; } u;
+ u.i = le64(le_data_ptr);
+ return u.f;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+//-------------------------------------------
+// convert u64 from/to network byte order
+// be64_t hton64 ( u64 data );
+// u64 ntoh64 ( be64_t data );
+//-------------------------------------------
+
+static inline be64_t hton64 ( u64 data )
+{
+ be64_t result;
+ ((u32*)&result)[0] = htonl( (u32)(data >> 32) );
+ ((u32*)&result)[1] = htonl( (u32)data );
+ return result;
+}
+
+static inline u64 ntoh64 ( be64_t data )
+{
+ return (u64)ntohl(((u32*)&data)[0]) << 32 | ntohl(((u32*)&data)[1]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// convert a number in host format into big endian data
+void write_be16 ( void * be_data_ptr, u16 data );
+void write_be24 ( void * be_data_ptr, u32 data );
+void write_be32 ( void * be_data_ptr, u32 data );
+void write_be40 ( void * be_data_ptr, u64 data );
+void write_be48 ( void * be_data_ptr, u64 data );
+void write_be56 ( void * be_data_ptr, u64 data );
+void write_be64 ( void * be_data_ptr, u64 data );
+void write_bef4 ( void * be_data_ptr, float data );
+void write_bef8 ( void * be_data_ptr, double data );
+
+// convert a number in host format into little endian data
+void write_le16 ( void * le_data_ptr, u16 data );
+void write_le24 ( void * le_data_ptr, u32 data );
+void write_le32 ( void * le_data_ptr, u32 data );
+void write_le40 ( void * le_data_ptr, u64 data );
+void write_le48 ( void * le_data_ptr, u64 data );
+void write_le56 ( void * le_data_ptr, u64 data );
+void write_le64 ( void * le_data_ptr, u64 data );
+void write_lef4 ( void * le_data_ptr, float data );
+void write_lef8 ( void * le_data_ptr, double data );
+
+// convert lists
+void be16n ( u16 * dest, const u16 * src, int n );
+void be32n ( u32 * dest, const u32 * src, int n );
+void bef4n ( float32 * dest, const float32 * src, int n );
+void le16n ( u16 * dest, const u16 * src, int n );
+void lef4n ( float32 * dest, const float32 * src, int n );
+
+void write_be16n ( u16 * dest, const u16 * src, int n );
+void write_be32n ( u32 * dest, const u32 * src, int n );
+void write_bef4n ( float32 * dest, const float32 * src, int n );
+void write_le16n ( u16 * dest, const u16 * src, int n );
+void write_lef4n ( float32 * dest, const float32 * src, int n );
+
+//-----------------------------------------------------------------------------
+// [[endian_func_t]]
+
+typedef struct endian_func_t
+{
+ //--- info section
+
+ u8 bom[2]; // btye order mark: BE=0xfe,0xff, LE=0xff,0xfe
+ bool is_be; // true: is big endian
+ bool is_le; // true: is little endian
+ dcEndian_t endian; // DC_BIG_ENDIAN or DC_LITTLE_ENDIAN
+
+ //--- read functions
+
+ u16 (*rd16) ( const void * data_ptr );
+ u32 (*rd24) ( const void * data_ptr );
+ u32 (*rd32) ( const void * data_ptr );
+ u64 (*rd40) ( const void * data_ptr );
+ u64 (*rd48) ( const void * data_ptr );
+ u64 (*rd56) ( const void * data_ptr );
+ u64 (*rd64) ( const void * data_ptr );
+ float (*rdf4) ( const void * data_ptr );
+ double (*rdf8) ( const void * data_ptr );
+
+ //--- write functions
+
+ void (*wr16) ( void * data_ptr, u16 data );
+ void (*wr24) ( void * data_ptr, u32 data );
+ void (*wr32) ( void * data_ptr, u32 data );
+ void (*wr40) ( void * data_ptr, u64 data );
+ void (*wr48) ( void * data_ptr, u64 data );
+ void (*wr56) ( void * data_ptr, u64 data );
+ void (*wr64) ( void * data_ptr, u64 data );
+ void (*wrf4) ( void * data_ptr, float data );
+ void (*wrf8) ( void * data_ptr, double data );
+
+} endian_func_t;
+
+extern const endian_func_t be_func;
+extern const endian_func_t le_func;
+
+const endian_func_t * GetEndianFunc ( const void * byte_order_mark );
+uint GetTextBOMLen ( const void * data, uint data_size );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct mem_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[mem_t]]
+
+typedef struct mem_t
+{
+ ccp ptr; // NULL or a pointer to the data
+ int len; // length in bytes of the data
+}
+mem_t;
+
+extern mem_t EmptyMem; // mem_t(EmptyString,0)
+extern mem_t NullMem; // mem_t(0,0)
+
+//-----------------------------------------------------------------------------
+
+static inline mem_t MemByChar ( char ch )
+{
+ mem_t mem = { &ch, 1 };
+ return mem;
+}
+
+static inline mem_t MemByString0 ( ccp str )
+{
+ if (!str)
+ return NullMem;
+
+ mem_t mem = { str, strlen(str) };
+ return mem;
+}
+
+static inline mem_t MemByString ( ccp str )
+{
+ mem_t mem = { str, strlen(str) };
+ return mem;
+}
+
+static inline mem_t MemByStringS ( ccp str, int len )
+{
+ mem_t mem = { str, len < 0 ? strlen(str) : len };
+ return mem;
+}
+
+static inline mem_t MemByStringE ( ccp str, ccp end )
+{
+ mem_t mem = { str, end ? end-str : strlen(str) };
+ return mem;
+}
+
+static inline mem_t MemByS ( ccp str, int len )
+{
+ mem_t mem = { str, len };
+ return mem;
+}
+
+static inline mem_t MemByE ( ccp str, ccp end )
+{
+ mem_t mem = { str, end-str };
+ return mem;
+}
+
+//-----------------------------------------------------------------------------
+
+// Index helpers
+// For the 'End' variants, index 0 is end_of_source
+
+int CheckIndex1 ( int max, int index );
+int CheckIndex1End ( int max, int index );
+int CheckIndex2 ( int max, int * p_begin, int * p_end );
+int CheckIndex2End ( int max, int * p_begin, int * p_end );
+
+//-----------------------------------------------------------------------------
+
+// All functions are robust.
+// A negative index is relative to the end of source.
+// For RightMem() and ExtractEndMem(), index 0 means: end of source.
+// count<0 means: copy left from 'begin' (only MidMem()) or skip bytes.
+
+mem_t MidMem ( const mem_t src, int begin, int count );
+
+static inline mem_t LeftMem ( const mem_t src, int count )
+{
+ mem_t res;
+ res.ptr = src.ptr;
+ res.len = CheckIndex1(src.len,count);
+ return res;
+}
+
+static inline mem_t RightMem ( const mem_t src, int count )
+{
+ mem_t res;
+ res.len = src.len - CheckIndex1End(src.len,-count);
+ res.ptr = src.ptr + src.len - res.len;
+ return res;
+}
+
+static inline mem_t ExtractMem ( const mem_t src, int begin, int end )
+{
+ mem_t res;
+ res.len = CheckIndex2(src.len,&begin,&end);
+ res.ptr = src.ptr + begin;
+ return res;
+}
+
+static inline mem_t ExtractEndMem ( const mem_t src, int begin, int end )
+{
+ mem_t res;
+ res.len = CheckIndex2End(src.len,&begin,&end);
+ res.ptr = src.ptr + begin;
+ return res;
+}
+
+mem_t BeforeMem ( const mem_t src, ccp ref );
+mem_t BehindMem ( const mem_t src, ccp ref );
+
+//-----------------------------------------------------------------------------
+
+// Alloc dest buffer and terminate with 0.
+// If m*.len < 0, then use strlen().
+
+mem_t MemCat2A ( const mem_t m1, const mem_t m2 );
+mem_t MemCat3A ( const mem_t m1, const mem_t m2, const mem_t m3 );
+
+//-----------------------------------------------------------------------------
+
+int CmpMem ( const mem_t s1, const mem_t s2 );
+int StrCmpMem ( const mem_t mem, ccp str );
+bool LeftStrCmpMemEQ ( const mem_t mem, ccp str );
+
+// free a string if str is not NULL|EmptyString|MinusString
+void FreeString ( ccp str );
+
+static inline void FreeMem ( mem_t *mem )
+{
+ DASSERT(mem);
+ FreeString(mem->ptr);
+ mem->ptr = 0;
+ mem->len = 0;
+}
+
+static inline void AssignDupMem ( mem_t *dest, mem_t src )
+{
+ DASSERT(dest);
+ FreeString(dest->ptr);
+ dest->ptr = MEMDUP(src.ptr,src.len);
+ dest->len = src.len;
+}
+
+static inline mem_t DupMem ( const mem_t src )
+{
+ mem_t res = { MEMDUP(src.ptr,src.len), src.len };
+ return res;
+}
+
+static inline char * DupStrMem ( const mem_t src )
+{
+ return MEMDUP(src.ptr,src.len);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct mem_list_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[mem_list_t]]
+
+typedef struct mem_list_t
+{
+ mem_t *list; // List of mem_t objects.
+ // Elements are always terminated with NULL.
+ // Content is stored in 'buf' or points to EmptyString.
+
+ uint used; // Number of active 'list' elements
+ uint size; // Number of alloced 'list' elements
+
+ char *buf; // Alloced temp buffer
+ uint buf_used; // Usage of 'buf'
+ uint buf_size; // Size of 'buf'
+}
+mem_list_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeMemList ( mem_list_t *ml );
+void ResetMemList ( mem_list_t *ml );
+void MoveMemList ( mem_list_t *dest, mem_list_t *src );
+
+void GrowBufMemList ( mem_list_t *ml, uint need_size );
+void PrepareMemList ( mem_list_t *ml, uint n_elem, uint buf_size );
+
+//-----------------------------------------------------------------------------
+
+void InsertMemListN
+(
+ mem_list_t *ml, // valid destination mem_list
+ int pos, // insert position => CheckIndex1()
+ const mem_t *src, // source list
+ uint src_len, // number of elements in source list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+);
+
+//-----------------------------------------------------------------------------
+
+static inline void InsertMemList2
+(
+ mem_list_t *ml, // valid destination mem_list
+ int pos, // insert position => CheckIndex1()
+ const mem_list_t *src, // source mem_list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ DASSERT(src);
+ InsertMemListN(ml,pos,src->list,src->used,src_ignore);
+}
+
+//-----------------------------------------------------------------------------
+
+static inline void AppendMemListN
+(
+ mem_list_t *ml, // valid destination mem_list
+ const mem_t *src, // source list
+ uint src_len, // number of elements in source list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ DASSERT(ml);
+ InsertMemListN(ml,ml->used,src,src_len,src_ignore);
+}
+
+//-----------------------------------------------------------------------------
+
+static inline void AppendMemList2
+(
+ mem_list_t *ml, // valid destination mem_list
+ const mem_list_t *src, // source mem_list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ DASSERT(ml);
+ DASSERT(src);
+ InsertMemListN(ml,ml->used,src->list,src->used,src_ignore);
+}
+
+//-----------------------------------------------------------------------------
+
+static inline void AssignMemListN
+(
+ mem_list_t *ml, // valid destination mem_list
+ const mem_t *src, // source list
+ uint src_len, // number of elements in source list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ DASSERT(ml);
+ ml->used = 0;
+ InsertMemListN(ml,0,src,src_len,src_ignore);
+}
+
+//-----------------------------------------------------------------------------
+
+static inline void AssignMemList2
+(
+ mem_list_t *ml, // valid destination mem_list
+ const mem_list_t *src, // source mem_list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ DASSERT(ml);
+ DASSERT(src);
+ ml->used = 0;
+ InsertMemListN(ml,0,src->list,src->used,src_ignore);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CatMemListN
+(
+ mem_list_t *dest, // valid destination mem_list
+ const mem_list_t **src_list, // list with mem lists, element may be NULL
+ uint n_src_list, // number of elements in 'src'
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+);
+
+//-----------------------------------------------------------------------------
+
+static inline void CatMemList2
+(
+ mem_list_t *dest, // valid destination mem_list
+ const mem_list_t *src1, // source mem_list
+ const mem_list_t *src2, // source mem_list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ const mem_list_t *ml[2] = { src1, src2 };
+ CatMemListN(dest,ml,2,src_ignore);
+}
+
+//-----------------------------------------------------------------------------
+
+static inline void CatMemList3
+(
+ mem_list_t *dest, // valid destination mem_list
+ const mem_list_t *src1, // source mem_list
+ const mem_list_t *src2, // source mem_list
+ const mem_list_t *src3, // source mem_list
+ uint src_ignore // 0: insert all
+ // 1: ignore NULL
+ // 2: ignore NULL and empty
+ // 3: replace NULL by EmptyString
+)
+{
+ const mem_list_t *ml[3] = { src1, src2, src3 };
+ CatMemListN(dest,ml,3,src_ignore);
+}
+
+//-----------------------------------------------------------------------------
+
+// All functions are robust and modify the current list.
+// A negative index is relative to the end of source.
+// For RightMemList() and *EndMemList(), index 0 means: end of source.
+// count<0 means: copy left from 'begin' (only MidMemList()) or skip bytes.
+// All functions return the number of elements aufer operation.
+
+uint LeftMemList ( mem_list_t *ml, int count );
+uint RightMemList ( mem_list_t *ml, int count );
+uint MidMemList ( mem_list_t *ml, int begin, int count );
+uint ExtractMemList ( mem_list_t *ml, int begin, int end );
+uint ExtractEndMemList ( mem_list_t *ml, int begin, int end );
+uint RemoveMemList ( mem_list_t *ml, int begin, int end );
+uint RemoveEndMemList ( mem_list_t *ml, int begin, int end );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MemPool_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[MemPoolChunk_t]]
+
+typedef struct MemPoolChunk_t
+{
+ struct MemPoolChunk_t *next;
+ char data[];
+}
+MemPoolChunk_t;
+
+//-----------------------------------------------------------------------------
+// [[MemPool_t]]
+
+typedef struct MemPool_t
+{
+ MemPoolChunk_t *chunk; // pointer to first (=current) chunk
+ uint space; // space left in current chunk
+ uint chunk_size; // wanted chunk size
+}
+MemPool_t;
+
+//-----------------------------------------------------------------------------
+
+static inline void InitializeMemPool ( MemPool_t *mp, uint chunk_size )
+ { DASSERT(mp); memset(mp,0,sizeof(*mp)); mp->chunk_size = chunk_size; }
+
+void ResetMemPool ( MemPool_t *mp );
+void * MallocMemPool ( MemPool_t *mp, uint size );
+void * CallocMemPool ( MemPool_t *mp, uint size );
+void * MallocMemPoolA ( MemPool_t *mp, uint size, uint align );
+void * CallocMemPoolA ( MemPool_t *mp, uint size, uint align );
+void * MemDupMemPool ( MemPool_t *mp, cvp source, uint size );
+
+static inline void * StrDupMemPool ( MemPool_t *mp, ccp source )
+ { DASSERT(mp); return source ? MemDupMemPool(mp,source,strlen(source)) : 0; }
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CircBuf_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[CircBuf_t]]
+
+typedef struct CircBuf_t
+{
+ char *buf; // NULL or alloced buffer
+ uint pos; // position of first valid byte
+ uint used; // used bytes in buffer
+ uint size; // size of 'buf'
+}
+CircBuf_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeCircBuf ( CircBuf_t *cb, uint size );
+void ResetCircBuf ( CircBuf_t *cb );
+uint WriteCircBuf ( CircBuf_t *cb, cvp data, uint size );
+uint PeakCircBuf ( CircBuf_t *cb, char *buf, uint buf_size );
+uint ReadCircBuf ( CircBuf_t *cb, char *buf, uint buf_size );
+uint DropCircBuf ( CircBuf_t *cb, uint size );
+void ClearCircBuf ( CircBuf_t *cb );
+uint PurgeCircBuf ( CircBuf_t *cb );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CopyMode_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum CopyMode_t
+{
+ CPM_COPY, // make a copy of the data, alloced
+ CPM_MOVE, // move the alloced data
+ CPM_LINK, // link the data
+
+} CopyMode_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * CopyData
+(
+ const void *data, // data to copy/move/link
+ uint size, // size of 'data'
+ CopyMode_t mode, // copy mode
+ bool *res_alloced // not NULL:
+ // store true, if data must be freed
+ // otherwise store false
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FreeData
+(
+ const void *data, // data to free
+ CopyMode_t mode // copy mode
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// split strings ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint SplitByCharMem
+(
+ // 'mem_list' will be padded with {EmptyString,0}
+
+ mem_t *mem_list, // array of mem_t
+ uint n_mem, // number of elements of 'mem_list'
+ mem_t source, // source string
+ char sep // separator character
+);
+
+//-----------------------------------------------------------------------------
+
+uint SplitByTextMem
+(
+ // 'mem_list' will be padded with {EmptyString,0}
+
+ mem_t *mem_list, // array of mem_t
+ uint n_mem, // number of elements of 'mem_list'
+ mem_t source, // source string
+ mem_t sep // separator text
+);
+
+//-----------------------------------------------------------------------------
+
+uint SplitByCharMemList
+(
+ mem_list_t *ml, // valid destination mem_list
+ bool init_ml, // true: initialize 'ml' first
+ mem_t source, // source string
+ char sep, // separator character
+ int max_fields // >0: max number of result fields
+);
+
+//-----------------------------------------------------------------------------
+
+uint SplitByTextMemList
+(
+ mem_list_t *ml, // valid destination mem_list
+ bool init_ml, // true: initialize 'ml' first
+ mem_t source, // source string
+ mem_t sep, // separator text
+ int max_fields // >0: max number of result fields
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// alloc info ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern bool alloc_info_calced;
+extern uint alloc_info_overhead;
+extern uint alloc_info_block_size;
+extern uint alloc_info_add1;
+extern uint alloc_info_add2;
+extern uint alloc_info_mask;
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SetupAllocInfo(void);
+
+static inline uint GetGoodAllocSize ( uint need )
+{
+ return ( need + alloc_info_add1 & alloc_info_mask )
+ + alloc_info_add2;
+}
+
+static inline uint GetGoodAllocSize2 ( uint n_elem, uint itemsize )
+{
+ return (( n_elem*itemsize + alloc_info_add1 & alloc_info_mask )
+ + alloc_info_add2 ) / itemsize;
+}
+
+static inline uint GetGoodAllocSizeA ( uint need, uint align )
+{
+ need = ( need + align - 1) / align * align;
+ return (( need + alloc_info_add1 & alloc_info_mask )
+ + alloc_info_add2 ) / align * align;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// some basic string functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern const char EmptyQuote[]; // »""« Ignored by FreeString()
+extern const char EmptyString[4]; // »« + 4*NULL, Ignored by FreeString()
+extern const char MinusString[4]; // »-« + 3*NULL, Ignored by FreeString()
+
+extern const char LoDigits[0x41]; // 0-9 a-z A-Z .+ NULL
+extern const char HiDigits[0x41]; // 0-9 A-Z a-z .+ NULL
+
+extern const char Tabs20[21]; // 20 * TAB + NULL
+extern const char LF20[21]; // 20 * LF + NULL
+extern const char Space200[201]; // 200 * ' ' + NULL
+extern const char Minus300[301]; // 300 * '-' + NULL
+extern const char Underline200[201]; // 200 * '_' + NULL
+extern const char Hash200[201]; // 200 * '#' + NULL
+extern const char Slash200[201]; // 200 * '/' + NULL
+
+// UTF-8 => 3 bytes per char
+extern const char ThinLine300_3[901]; // 300 * '─' (U+2500) + NULL
+
+//-----------------------------------------------------------------------------
+
+void SetupMultiProcessing(void);
+void PrintSettingsDCLIB ( FILE *f, int indent );
+void SetupProgname ( int argc, char ** argv,
+ ccp tool_name, ccp tool_vers, ccp tool_title );
+
+// return NULL or 'progpath', calc GetProgramPath() once if needed
+ccp ProgramPath(void);
+
+// return NULL or 'progdir', calc ProgramPath() once if needed
+ccp ProgramDirectory(void);
+
+#ifdef __CYGWIN__
+ ccp ProgramPathNoExt(void);
+#else
+ static inline ccp ProgramPathNoExt(void) { return ProgramPath(); }
+#endif
+
+int GetProgramPath
+(
+ // returns strlen(buf) on success, -1 on failure
+
+ char *buf, // buffer for the result; empty string on failure.
+ uint buf_size, // size of buffer
+ bool check_proc, // true: read files of /proc/... to find path
+ ccp argv0 // not NULL: analyse this argument (argv[0])
+);
+
+//-----
+
+// StringCopy(), StringCopyE(), StringCat*()
+// RESULT: end of copied string pointing to a NULL character.
+// 'src*' may be a NULL pointer.
+
+char * StringCopyS ( char * buf, size_t bufsize, ccp src );
+char * StringCopySM ( char * buf, size_t bufsize, ccp src, size_t max_copy );
+char * StringCat2S ( char * buf, size_t bufsize, ccp src1, ccp src2 );
+char * StringCat3S ( char * buf, size_t bufsize, ccp src1, ccp src2, ccp src3 );
+
+char * StringCopyE ( char * buf, ccp buf_end, ccp src );
+char * StringCopyEM ( char * buf, ccp buf_end, ccp src, size_t max_copy );
+char * StringCat2E ( char * buf, ccp buf_end, ccp src1, ccp src2 );
+char * StringCat3E ( char * buf, ccp buf_end, ccp src1, ccp src2, ccp src3 );
+
+// alloc space and return
+char * StringCat2A ( ccp src1, ccp src2 );
+char * StringCat3A ( ccp src1, ccp src2, ccp src3 );
+
+static inline char * StringCopySMem ( char * buf, size_t bufsize, mem_t mem )
+ { return StringCopySM(buf,bufsize,mem.ptr,mem.len); }
+static inline char * StringCopyEMem ( char * buf, ccp buf_end, mem_t mem )
+ { return StringCopyEM(buf,buf_end,mem.ptr,mem.len); }
+
+char * StringLowerS ( char * buf, size_t bufsize, ccp src );
+char * StringLowerE ( char * buf, ccp buf_end, ccp src );
+char * StringUpperS ( char * buf, size_t bufsize, ccp src );
+char * StringUpperE ( char * buf, ccp buf_end, ccp src );
+
+// special handling for unsigned decimals
+int StrNumCmp ( ccp a, ccp b );
+
+// count the number of equal bytes and return a value of 0 to SIZE
+uint CountEqual ( cvp m1, cvp m2, uint size );
+
+// Concatenate path + path, return pointer to buf
+// Return pointer to buf or path1|path2|buf or alloced string
+char *PathCatBufPP ( char *buf, size_t bufsize, ccp path1, ccp path2 );
+char *PathCatBufPPE ( char *buf, size_t bufsize, ccp path1, ccp path2, ccp ext );
+ccp PathCatPP ( char *buf, size_t bufsize, ccp path1, ccp path2 );
+//ccp PathCatPPE ( char *buf, size_t bufsize, ccp path1, ccp path2, ccp ext );
+ccp PathAllocPP ( ccp path1, ccp path2 );
+ccp PathAllocPPE ( ccp path1, ccp path2, ccp ext );
+
+// inline wrapper
+static inline ccp PathCatPPE ( char *buf, size_t bufsize, ccp path1, ccp path2, ccp ext )
+ { return PathCatBufPPE(buf,bufsize,path1,path2,ext); }
+
+// Same as PathCatPP*(), but use 'base' as prefix for relative pathes.
+// If 'base' is NULL (but not empty), use getcwd() instead.
+// PathCatBufBP() and PathCatBP() are special: path can be part of buf
+char *PathCatBufBP ( char *buf, size_t bufsize, ccp base, ccp path );
+char *PathCatBufBPP ( char *buf, size_t bufsize, ccp base, ccp path1, ccp path2 );
+char *PathCatBufBPPE ( char *buf, size_t bufsize, ccp base, ccp path1, ccp path2, ccp ext );
+ccp PathCatBP ( char *buf, size_t bufsize, ccp base, ccp path );
+ccp PathCatBPP ( char *buf, size_t bufsize, ccp base, ccp path1, ccp path2 );
+ccp PathCatBPPE ( char *buf, size_t bufsize, ccp base, ccp path1, ccp path2, ccp ext );
+ccp PathAllocBP ( ccp base, ccp path );
+ccp PathAllocBPP ( ccp base, ccp path1, ccp path2 );
+ccp PathAllocBPPE ( ccp base, ccp path1, ccp path2, ccp ext );
+
+char * PathCombine ( char *temp_buf, uint buf_size, ccp path, ccp base_path );
+char * PathCombineFast ( char *dest_buf, uint buf_size, ccp path, ccp base_path );
+
+// Remove last extension and add a new one.
+// If ext==NULL or emtpy: remove only
+// 'ext' may start with '.'. If not, a additional '.' is included
+// If 'path' is NULL or path==buf: Inplace job using 'buf' as source.
+char * NewFileExtE ( char * buf, ccp buf_end, ccp path, ccp ext );
+char * NewFileExtS ( char * buf, size_t bufsize, ccp path, ccp ext );
+
+// eliminate leading './' before comparing
+int StrPathCmp ( ccp path1, ccp path2 );
+
+int PathCmp
+(
+ ccp path1, // NULL or first path
+ ccp path2, // NULL or second path
+ uint mode // bit field:
+ // 1: skip (multiple) leading './'
+ // 2: compare case insensitive
+ // 4: sort with respect for unsigned decimal numbers
+);
+
+int ReplaceToBuf
+(
+ // returns -1 on error (buf to small) or the length of the resulting string.
+ // In the later case, a 0 byte is appended, but not counting for the length.
+
+ char *buf, // pointer to buf
+ uint buf_size, // size of 'buf'
+
+ ccp src1, // NULL or source, maybe part of 'buf'
+ int src1_len, // length of 'src1', if -1: determine by strlen()
+ int pos, // robust replace position, <0: relative to end
+ int rm_len, // robust num of chars to remove, <0: rm before 'pos'
+
+ ccp src2, // NULL or source to insert, must not be part of 'buf'
+ int src2_len // lengh of 'src2', if -1: determine by strlen()
+);
+
+int NormalizeIndent ( int indent );
+
+//-----------------------------------------------------
+
+// skips all character 1..SPACE; 'src' can be NULL
+char * SkipControls ( ccp src );
+
+// skips all character 0..SPACE; if end==NULL: Use SkipControls(); 'src' can be NULL
+char * SkipControlsE ( ccp src, ccp end );
+
+// same as above, but skip also 'ch1'; 'src' can be NULL
+char * SkipControls1 ( ccp src, char ch1 );
+char * SkipControlsE1 ( ccp src, ccp end, char ch1 );
+
+// same as above, but skip also 'ch1' and 'ch2'; 'src' can be NULL
+char * SkipControls2 ( ccp src, char ch1, char ch2 );
+char * SkipControlsE2 ( ccp src, ccp end, char ch1, char ch2 );
+
+//-----------------------------------------------------
+
+// return the number of chars until first EOL ( \0 | \r | \n );
+// pointer are NULL save
+uint LineLen ( ccp ptr );
+uint LineLenE ( ccp ptr, ccp end );
+
+//-----------------------------------------------------
+
+uint TrimBlanks
+(
+ char *str, // pointer to string, modified
+ ccp end // NULL or end of string
+);
+
+char * ScanName
+(
+ // return a pointer to the first unused character
+ char *buf, // destination buffer, terminated with NULL
+ uint buf_size, // size of 'buf', must be >0
+ ccp str, // source string
+ ccp end, // NULL or end of 'str'
+ bool allow_signs // true: allow '-' and '+'
+);
+
+//-----------------------------------------------------
+// Format of version number: AABBCCDD = A.BB | A.BB.CC
+// If D != 0x00 && D != 0xff => append: 'beta' D
+//-----------------------------------------------------
+
+char * PrintVersion
+(
+ char * buf, // result buffer
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 version // version number to print
+);
+
+char * PrintID
+(
+ const void * id, // ID to convert in printable format
+ size_t id_len, // len of 'id'
+ void * buf // Pointer to buffer, size >= id_len + 1
+ // If NULL, a local circulary static buffer is used
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// circ buf ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define CIRC_BUF_MAX_ALLOC 0x0400 // request limit
+#define CIRC_BUF_SIZE 0x4000 // internal buffer size
+
+char * GetCircBuf
+(
+ // never returns NULL, but always ALIGN(4)
+
+ u32 buf_size // wanted buffer size, caller must add 1 for NULL-term
+ // if buf_size > CIRC_BUF_MAX_ALLOC:
+ // ==> ERROR0(ERR_OUT_OF_MEMORY)
+);
+
+char * CopyCircBuf
+(
+ // never returns NULL, but always ALIGN(4)
+
+ cvp data, // source to copy
+ u32 data_size // see GetCircBuf()
+);
+
+char * CopyCircBuf0
+(
+ // never returns NULL, but always ALIGN(4)
+ // an additional char is alloced and set to NULL
+
+ cvp data, // source to copy
+ u32 data_size // see GetCircBuf()
+);
+
+char * PrintCircBuf
+(
+ ccp format, // format string for vsnprintf()
+ ... // arguments for 'format'
+)
+__attribute__ ((__format__(__printf__,1,2)));
+
+void ReleaseCircBuf
+(
+ ccp end_buf, // pointer to end of previous alloced buffer
+ uint release_size // number of bytes to give back from end
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct IntMode_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[IntMode_t]]
+
+typedef enum IntMode_t
+{
+ IMD_UNSET = 0, // default, always 0
+
+ //--- big endian modes
+
+ IMD_BE0, // default big endian
+ IMD_BE1,
+ IMD_BE2,
+ IMD_BE3,
+ IMD_BE4,
+ IMD_BE5,
+ IMD_BE6,
+ IMD_BE7,
+ IMD_BE8,
+
+ //--- little endian modes
+
+ IMD_LE0, // default little endian
+ IMD_LE1,
+ IMD_LE2,
+ IMD_LE3,
+ IMD_LE4,
+ IMD_LE5,
+ IMD_LE6,
+ IMD_LE7,
+ IMD_LE8,
+
+ //--- local endian modes
+
+ #ifdef LITTLE_ENDIAN
+
+ IMD_0 = IMD_LE0,
+ IMD_1 = IMD_LE1,
+ IMD_2 = IMD_LE2,
+ IMD_3 = IMD_LE3,
+ IMD_4 = IMD_LE4,
+ IMD_5 = IMD_LE5,
+ IMD_6 = IMD_LE6,
+ IMD_7 = IMD_LE7,
+ IMD_8 = IMD_LE8,
+
+ #else
+
+ IMD_0 = IMD_BE0,
+ IMD_1 = IMD_BE1,
+ IMD_2 = IMD_BE2,
+ IMD_3 = IMD_BE3,
+ IMD_4 = IMD_BE4,
+ IMD_5 = IMD_BE5,
+ IMD_6 = IMD_BE6,
+ IMD_7 = IMD_BE7,
+ IMD_8 = IMD_BE8,
+
+ #endif
+}
+__attribute__ ((packed)) IntMode_t;
+
+//-----------------------------------------------------------------------------
+
+ccp GetIntModeName ( IntMode_t mode );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct FastBuf_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[FastBuf_t]]
+
+typedef struct FastBuf_t
+{
+ char *buf; // pointer to buffer
+ char *ptr; // pointer to first unused char (always space for 0-term)
+ char *end; // pointer to end of buffer := buf+size-1
+
+ uint fast_buf_size; // size of 'fast_buf+space'
+ char fast_buf[4]; // first use buffer with minimal space
+ char space[]; // additional space
+}
+FastBuf_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+//------------------------------------------------------------------
+//
+// 4 examples for initialization:
+//
+// FastBuf_t fb1;
+// InitializeFastBufAlloc(&fb1,1000);
+// printf("fb1: %s: %s\n", GetFastBufStatus(&fb1), fb1.buf );
+//
+// FastBuf_t fb2;
+// InitializeFastBuf(&fb2,sizeof(fb2));
+// printf("fb2: %s: %s\n", GetFastBufStatus(&fb2), fb2.buf );
+//
+// struct { FastBuf_t b; char space[2000]; } fb3;
+// InitializeFastBuf(&fb3,sizeof(fb3));
+// printf("fb3: %s: %s\n", GetFastBufStatus(&fb.b), fb3.b.buf );
+//
+// char fb4x[2000];
+// FastBuf_t *fb4 = InitializeFastBuf(fb4x,sizeof(fb4x));
+// printf("fb4: %s: %s\n", GetFastBufStatus(fb4), fb4->buf );
+//
+//------------------------------------------------------------------
+
+FastBuf_t * InitializeFastBuf ( cvp mem, uint size );
+FastBuf_t * InitializeFastBufAlloc ( FastBuf_t *fb, uint size );
+
+void ResetFastBuf ( FastBuf_t *fb );
+static inline void ClearFastBuf ( FastBuf_t *fb )
+ { DASSERT(fb); fb->ptr = fb->buf; }
+
+ccp GetFastBufStatus ( const FastBuf_t *fb );
+int ReserveSpaceFastBuf ( FastBuf_t *fb, uint size );
+char * GetSpaceFastBuf ( FastBuf_t *fb, uint size );
+
+uint InsertFastBuf ( FastBuf_t *fb, int index, cvp source, int size );
+char * AppendFastBuf ( FastBuf_t *fb, cvp source, int size );
+char * WriteFastBuf ( FastBuf_t *fb, uint offset, cvp source, int size );
+uint AssignFastBuf ( FastBuf_t *fb, cvp source, int size );
+uint DropFastBuf ( FastBuf_t *fb, int index, int size );
+
+void CopyFastBuf ( FastBuf_t *dest, const FastBuf_t *src );
+void MoveFastBuf ( FastBuf_t *dest, FastBuf_t *src );
+
+static inline void AppendMemFastBuf ( FastBuf_t *fb, const mem_t mem )
+ { AppendFastBuf(fb,mem.ptr,mem.len); }
+
+static inline void AppendCharFastBuf ( FastBuf_t *fb, char ch )
+{
+ DASSERT(fb);
+ GetSpaceFastBuf(fb,1)[0] = ch;
+}
+
+void AppendUTF8CharFastBuf ( FastBuf_t *fb, u32 code );
+void AppendBE16FastBuf ( FastBuf_t *fb, u16 num );
+void AppendBE32FastBuf ( FastBuf_t *fb, u32 num );
+void AppendInt64FastBuf ( FastBuf_t *fb, u64 val, IntMode_t mode );
+
+static inline void AppendU16FastBuf ( FastBuf_t *fb, u16 num )
+ { AppendFastBuf(fb,&num,sizeof(num)); }
+static inline void AppendU32FastBuf ( FastBuf_t *fb, u32 num )
+ { AppendFastBuf(fb,&num,sizeof(num)); }
+static inline void AppendU64FastBuf ( FastBuf_t *fb, u64 num )
+ { AppendFastBuf(fb,&num,sizeof(num)); }
+
+static inline int GetFastBufLen ( const FastBuf_t *fb )
+ { DASSERT(fb); return fb->ptr - fb->buf; }
+
+//-----------------------------------------------------------------------------
+// The resulting strings of GetFastBufString() and GetFastBufMem0() are always
+// terminated by an additional NULL byte.
+// The resulting strings of GetFastBufMem() is not terminated by a NULL byte.
+//-----------------------------------------------------------------------------
+
+static inline char * GetFastBufString ( const FastBuf_t *fb )
+{
+ DASSERT(fb);
+ *fb->ptr = 0;
+ return fb->buf;
+}
+
+static inline mem_t GetFastBufMem0 ( const FastBuf_t *fb )
+{
+ DASSERT(fb);
+ *fb->ptr = 0;
+ mem_t mem = { fb->buf, fb->ptr - fb->buf };
+ return mem;
+}
+
+static inline mem_t GetFastBufMem ( const FastBuf_t *fb )
+{
+ DASSERT(fb);
+ mem_t mem = { fb->buf, fb->ptr - fb->buf };
+ return mem;
+}
+
+//-----------------------------------------------------------------------------
+// The allocated result is always terminated by an additional NULL byte.
+// FastBuf_t itself is reset, similar to ResetFastBuf()
+
+char * MoveFromFastBufString ( FastBuf_t *fb );
+mem_t MoveFromFastBufMem ( FastBuf_t *fb );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatchPattern() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define PATTERN_WILDCARDS "*# ?[{"
+
+bool MatchPatternFull
+(
+ ccp pattern, // pattern text
+ ccp text // raw text
+);
+
+bool MatchPattern
+(
+ ccp pattern, // pattern text
+ ccp text, // raw text
+ char path_sep // path separator character, standard is '/'
+);
+
+char * MatchRuleLine
+(
+ // returns a pointer to the first non scanned char
+
+ int *status, // not NULL: return match status here
+ // -2: no prefix found (no ruile found)
+ // -1: empty line (no ruile found)
+ // 0: rule found, but don't match
+ // 1: rule found and match
+ ccp src, // source line, scanned untilCONTROL
+ char rule_prefix, // NULL or a rule-beginning-char'
+ ccp path, // path to verify
+ char path_sep // path separator character, standard is '/'
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline int GetHexDigit ( char ch )
+{
+ return ch >= '0' && ch <= '9' ? ch-'0'
+ : ch >= 'a' && ch <= 'f' ? ch-'a'+10
+ : ch >= 'A' && ch <= 'F' ? ch-'A'+10
+ : -1;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// SplitArg_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[SplitArg_t]]
+
+typedef struct SplitArg_t
+{
+ uint argc; // number of arguments in 'argv'
+ char **argv; // array with 'argc' arguments + a NULL term, alloced
+ uint argv_size; // number of alloced pointers for 'argv' without NULL term
+
+ char *temp; // NULL or temporary buffer
+ uint temp_size; // size of 'temp'
+}
+SplitArg_t;
+
+static inline void InitializeSplitArg ( SplitArg_t *sa )
+ { DASSERT(sa); memset(sa,0,sizeof(*sa)); }
+
+void ResetSplitArg ( SplitArg_t *sa );
+
+int ScanSplitArg
+(
+ // returns arg->argc;
+
+ SplitArg_t *arg, // pointer to object
+ bool init_arg, // true: call InitializeSplitArg()
+ ccp src, // NULL or source string
+ ccp src_end, // end of 'src'; if NULL: calculated by strlen(src)
+ char *work_buf // pointer to a buffer for strings, may be 'src'
+ // size must be: length of 'src' + 1
+ // if NULL, a temporary buffer is alloced.
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ContainerData_t, Container_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+//-----------------------------------------------------------------------------
+// OVERVIEW
+//-----------------------------------------------------------------------------
+// Container_t * CreateContainer ( Container_t, protect, data,size, CopyMode_t )
+// Container_t * InheritContainer ( Container_t cur, protect, Container_t parent, data,size )
+// bool AssignContainer ( Container_t, protect, data,size, CopyMode_t )
+// void ResetContainer ( Container_t )
+// void DeleteContainer ( Container_t )
+// void UnlinkContainerData ( Container_t )
+// ContainerData_t * LinkContainerData ( Container_t )
+// ContainerData_t * MoveContainerData ( Container_t )
+// Container_t * CatchContainerData ( Container_t, int protect, ContainerData_t )
+// Container_t * UseContainerData ( Container_t, int protect, Container_t )
+//------------
+// ContainerData_t DisjoinContainerData ( Container_t )
+// void JoinContainerData ( Container_t, ContainerData_t )
+// void FreeContainerData ( ContainerData_t )
+//------------
+// static inline bool ModificationAllowed ( const Container_t )
+// void ModifyAllContainer ( Container_t )
+// void ModifyContainer ( Container_t, data, size, CopyMode_t )
+// int SetProtectContainer ( Container_t, int new_protect )
+// static inline int AddProtectContainer ( Container_t, int add_protect )
+// static inline bool IsValidContainer ( const Container_t * c )
+// static inline bool InContainerP ( const Container_t, cvp ptr )
+// static inline bool InContainerS ( const Container_t, cvp ptr, uint size )
+// static inline bool InContainerE ( const Container_t, cvp ptr, cvp end )
+// uint DumpInfoContainer ( f,colset,indent,prefix, Container_t, hexdump_len )
+// uint DumpInfoContainerData ( f,col,indent,prefix, CData_t, hex_len, hex_indent )
+//-----------------------------------------------------------------------------
+
+///////////////////////////////////////////////////////////////////////////////
+// [[ContainerData_t]]
+
+typedef struct ContainerData_t
+{
+ u8 *data; // data
+ uint size; // size of 'data'
+ int ref_count; // reference counter
+ int protect_count; // >0: don't modify data
+ bool data_alloced; // true: free data
+
+} ContainerData_t;
+
+//-----------------------------------------------------------------------------
+// [[Container_t]]
+
+typedef struct Container_t
+{
+ ContainerData_t *cdata; // NULL or pointer to ContainerData
+ int protect_level; // >0: don't modify data
+
+} Container_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+Container_t * CreateContainer
+(
+ // returns 'c' or the alloced container
+ // 'c' is always initialized
+
+ Container_t *c, // valid container, alloc one if NULL
+ int protect, // initial value for protection
+ const void *data, // data to copy/move/link
+ uint size, // size of 'data'
+ CopyMode_t mode // copy mode on creation
+);
+
+//-----------------------------------------------------------------------------
+
+Container_t * InheritContainer
+(
+ // returns 'c' or the alloced container
+
+ Container_t *c, // valid container, alloc one if NULL
+ int protect, // initial value for protection
+ const Container_t *parent, // NULL or valid parent container
+ const void *data, // data to copy/move/link
+ uint size // size of 'data'
+);
+
+//-----------------------------------------------------------------------------
+
+bool AssignContainer
+(
+ // return TRUE on new ContainerData_t
+
+ Container_t *c, // valid container; if NULL: only FREE(data)
+ int protect, // new protection level
+ const void *data, // data to copy/move/link
+ uint size, // size of 'data'
+ CopyMode_t mode // copy mode on creation
+);
+
+//-----------------------------------------------------------------------------
+
+void ResetContainer
+(
+ Container_t *c // container to reset => no data
+);
+
+//-----------------------------------------------------------------------------
+
+void DeleteContainer
+(
+ Container_t *c // container to reset and to free => no data
+);
+
+//-----------------------------------------------------------------------------
+
+void UnlinkContainerData
+(
+ Container_t *c // container to reset => no data
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+ContainerData_t * LinkContainerData
+(
+ // increment 'ref_count' and return NULL or current ContainerData
+ // => use CatchContainerData() to complete operation
+
+ const Container_t *c // NULL or valid container
+);
+
+//-----------------------------------------------------------------------------
+
+ContainerData_t * MoveContainerData
+(
+ // return NULL or unlinked current ContainerData
+ // => use CatchContainerData() to complete operation
+ Container_t *c // NULL or valid container
+);
+
+//-----------------------------------------------------------------------------
+
+Container_t * CatchContainerData
+(
+ // returns 'c' or the alloced container
+ // 'c' is always initialized
+
+ Container_t *c, // valid container, alloc one if NULL
+ int protect, // initial value for protection
+ ContainerData_t *cdata // if not NULL: catch this
+);
+
+//-----------------------------------------------------------------------------
+
+static inline Container_t * UseContainerData
+(
+ // returns 'c' or the alloced container
+
+ Container_t *c, // valid container, alloc one if NULL
+ int protect, // initial value for protection
+ const Container_t *src // if not NULL: catch this
+)
+{
+ return CatchContainerData(c,protect,LinkContainerData(src));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ContainerData_t * DisjoinContainerData
+(
+ // Disjoin data container date without freeing it. Call either
+ // JoinContainerData() or FreeContainerData() to finish operation.
+ // Reference counters are not modified.
+ // Return the data container or NULL
+
+ Container_t *c // NULL or valid container
+);
+
+//-----------------------------------------------------------------------------
+
+void JoinContainerData
+(
+ // Join a data container, that was diojoined by DisjoinContainerData()
+ // Reference counters are not modified.
+
+ Container_t *c, // if NULL: FreeContainerData()
+ ContainerData_t *cdata // NULL or container-data to join
+);
+
+//-----------------------------------------------------------------------------
+
+void FreeContainerData
+(
+ // Decrement the reference counter of an already disjoined data container
+ // and free it if unused.
+
+ ContainerData_t *cdata // NULL or container-data to free
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline bool ModificationAllowed ( const Container_t *c )
+ { return c && c->cdata
+ && ( c->cdata->ref_count <= 1
+ || c->cdata->protect_count <= ( c->protect_level > 0 ) );
+ }
+
+//-----------------------------------------------------------------------------
+
+bool ModifyAllContainer
+(
+ // prepare modification of container-data, create a copy if necessary
+ // return true, if a new container-data is used
+
+ Container_t *c // NULL or valid container
+);
+
+//-----------------------------------------------------------------------------
+
+bool ModifyContainer
+(
+ // prepare modification of container-data, create an extract if necessary
+ // return true, if a new container-data is used
+
+ Container_t *c, // NULL or valid container
+ const void *data, // data to copy/move/link
+ uint size, // size of 'data'
+ CopyMode_t mode // copy mode on creation
+);
+
+//-----------------------------------------------------------------------------
+
+int SetProtectContainer
+(
+ // returns 'c' new protection level
+ Container_t *c, // valid container, alloc one if NULL
+ int new_protect // new protection value
+);
+
+//-----------------------------------------------------------------------------
+
+static inline int AddProtectContainer
+(
+ // returns 'c' new protection level
+ Container_t *c, // valid container, alloc one if NULL
+ int add_protect // new protection value
+)
+{
+ DASSERT(c);
+ return SetProtectContainer(c,c->protect_level+add_protect);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline bool IsValidContainer ( const Container_t * c )
+ { return c && c->cdata && c->cdata->data; }
+
+//-----------------------------------------------------------------------------
+
+static inline bool InContainerP ( const Container_t * c, cvp ptr )
+ { return c && c->cdata && ptr
+ && (u8*)ptr >= c->cdata->data
+ && (u8*)ptr <= c->cdata->data + c->cdata->size; }
+
+//-----------------------------------------------------------------------------
+
+static inline bool InContainerS ( const Container_t * c, cvp ptr, uint size )
+ { return c && c->cdata && ptr
+ && (u8*)ptr >= c->cdata->data
+ && (u8*)ptr + size <= c->cdata->data + c->cdata->size; }
+
+//-----------------------------------------------------------------------------
+
+static inline bool InContainerE ( const Container_t * c, cvp ptr, cvp end )
+ { return c && c->cdata
+ && ptr && (u8*)ptr >= c->cdata->data
+ && end && (u8*)end <= c->cdata->data + c->cdata->size; }
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct ColorSet_t;
+uint DumpInfoContainer
+(
+ // return the number of printed lines
+
+ FILE *f, // valid output file
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent the output
+ ccp prefix, // not NULL: use it as prefix behind indention
+ const Container_t *c, // dump infos for this container, NULL allowed
+ uint hexdump_len // max number of bytes used for a hexdump
+);
+
+//-----------------------------------------------------------------------------
+
+uint DumpInfoContainerData
+(
+ // return the number of printed lines
+
+ FILE *f, // valid output file
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent of output
+ ccp prefix, // not NULL: use it as prefix behind indention
+ const
+ ContainerData_t *cdata, // dump infos for this container-data, NULL allowed
+ uint hexdump_len, // max number of bytes used for a hexdump
+ int hexdump_indent // indent of hexdump
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print lines with autobreak ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void PutLines
+(
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ int fw, // field width of output
+ int first_line, // length without prefix of already printed first line
+ ccp prefix, // NULL or prefix for each line
+ ccp text, // text to print
+ ccp eol // End Of Line test. If NULL -> LF
+);
+
+void PrintArgLines
+(
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ int fw, // field width of output
+ int first_line, // length without prefix of already printed first line
+ ccp prefix, // NULL or prefix for each line
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'format'
+);
+
+void PrintLines
+(
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ int fw, // field width of output
+ int first_line, // length without prefix of already printed first line
+ ccp prefix, // NULL or prefix for each line
+ ccp format, // format string for vsnprintf()
+ ... // arguments for 'vsnprintf(format,...)'
+)
+__attribute__ ((__format__(__printf__,6,7)));
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+//
+// Rules for P*ColoredLines():
+//
+// |+ : Define a TAB pos (not is defined at start)
+// |- : Remove a TAB pos
+// |[pos,...] : Comma separated list: Clear all tabs and define new TABs
+// POS : Define TAB add POS
+// POS*DELTA : Define TAB add POS and then at every DELTA
+// \t : Jump to TAB stop
+//
+// | or |> : Define an indention for continuation lines.
+// Use the single char only, if a letter, digit, underline, SPACE,
+// CTRL or '{' follows. All ather chars are reserved for extensions.
+// \r : Force a new continuation line.
+// \n : Terminate current line.
+// \r\n : Same as '\n'.
+//
+// {name} : Change active color to NAME
+// {name|text} : Use color NAME only for TEXT, then restore active color.
+// {} : Reset and deactivate all colors.
+//
+// || : Print Single '|'
+// {{ : Print Single '{'
+//
+///////////////////////////////////////////////////////////////////////////////
+
+uint PutColoredLines
+(
+ // returns the number of written lines
+
+ FILE * f, // valid output stream
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent of output
+ int fw, // field width; indent+prefix+eol don't count
+ ccp prefix, // NULL or prefix for each line
+ ccp eol, // End Of Line test. If NULL -> LF
+ ccp text // text to print
+);
+
+uint PrintArgColoredLines
+(
+ // returns the number of written lines
+
+ FILE * f, // valid output stream
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent of output
+ int fw, // field width; indent+prefix+eol don't count
+ ccp prefix, // NULL or prefix for each line
+ ccp eol, // End Of Line test. If NULL -> LF
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'format'
+);
+
+uint PrintColoredLines
+(
+ // returns the number of written lines
+
+ FILE * f, // valid output stream
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent of output
+ int fw, // field width; indent+prefix+eol don't count
+ ccp prefix, // NULL or prefix for each line
+ ccp eol, // End Of Line test. If NULL -> LF
+ ccp format, // format string for vsnprintf()
+ ... // arguments for 'vsnprintf(format,...)'
+)
+__attribute__ ((__format__(__printf__,7,8)));
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// encoding/decoding ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern const u32 TableCRC32[0x100];
+u32 CalcCRC32 ( u32 crc, cvp buf, uint size );
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern const u16 TableCP1252_80[0x20];
+
+///////////////////////////////////////////////////////////////////////////////
+// [[CharMode_t]]
+
+typedef enum CharMode_t // select encodig/decoding method
+{
+ CHMD_UTF8 = 0x01, // UTF8 support enabled (compatible with legacy bool)
+ CHMD_ESC = 0x02, // escape ESC by \e
+
+ CHMD__ALL = CHMD_UTF8 | CHMD_ESC,
+}
+__attribute__ ((packed)) CharMode_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[EncodeMode_t]]
+
+typedef enum EncodeMode_t // select encodig/decoding method
+{
+ ENCODE_OFF, // no encoding (always 0)
+ ENCODE_STRING, // ScanEscapedString(ANSI), byte mode
+ ENCODE_UTF8, // ScanEscapedString(UTF8), force UTF8 on decoding
+ ENCODE_BASE64, // Base64
+ ENCODE_BASE64URL, // Base64.URL (=) / decoder detects Standard + URL + STAR
+ ENCODE_BASE64STAR, // Base64.URL (*) / decoder detects Standard + URL + STAR
+ ENCODE_BASE64XML, // Base64 with XML name tokens / decoder detects Standard + XML (name+id)
+ ENCODE_JSON, // JSON string encoding
+
+ ENCODE__N // number of encoding modes
+}
+EncodeMode_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[DecodeType_t]]
+
+enum DecodeType_t // for decoding tables
+{
+ DECODE_NULL = -1,
+ DECODE_CONTROL = -2,
+ DECODE_LINE = -3,
+ DECODE_SPACE = -4,
+ DECODE_SEPARATE = -5,
+ DECODE_FILLER = -6,
+ DECODE_OTHER = -7
+};
+
+extern const char TableNumbers[256];
+
+extern char TableDecode64[256]; // Standard coding
+extern char TableDecode64url[256]; // Standard + URL + STAR coding
+extern char TableDecode64xml[256]; // Standard + XML (name tokens + identifiers)
+
+extern const char TableEncode64[64+1]; // last char is filler
+extern const char TableEncode64url[64+1]; // URL coding, used by Nintendo
+extern const char TableEncode64star[64+1]; // Like URL, with '*' instead of '=' as filler
+extern const char TableEncode64xml[64+1]; // XML name tokens
+
+// for tests: encode the 48 bytes to get the full BASE64 alphabet
+extern const u8 TableAlphabet64[48];
+
+// Default tables for DecodeBase64() and EncodeBase64(), if no table is defined.
+// They are initialized with TableDecode64[] and TableEncode64[].
+extern ccp TableDecode64default, TableEncode64default;
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintEscapedString
+(
+ // returns 'buf'
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 10
+ ccp source, // NULL string to print
+ int len, // length of string. if -1, str is null terminated
+ CharMode_t char_mode, // modes, bit field (CHMD_*)
+ char quote, // NULL or quotation char, that must be quoted
+ uint *dest_len // not NULL: Store length of result here
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ScanEscapedString
+(
+ // returns the number of valid bytes in 'buf' (NULL term not counted)
+
+ char *buf, // valid destination buffer, maybe source
+ uint buf_size, // size of 'buf'
+ ccp source, // string to scan
+ int len, // length of string. if -1, str is null terminated
+ bool utf8, // true: source and output is UTF-8
+ int quote, // 0:none, -1:auto, >0: quotation char
+ uint *scanned_len // not NULL: Store number of scanned 'source' bytes here
+);
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u64 DecodeU64
+(
+ ccp source, // string to decode
+ int len, // length of string. if -1, str is null terminated
+ uint base, // number of possible digits (2..36):
+ uint *scanned_len // not NULL: Store number of scanned 'str' bytes here
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp EncodeU64
+(
+ char * buf, // result buffer (size depends on base)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 num, // number to encode
+ uint base // number of possible digits (2..64):
+);
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint DecodeBase64
+(
+ // returns the number of valid bytes in 'buf'
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 3
+ ccp source, // NULL or string to decode
+ int len, // length of string. if -1, str is null terminated
+ const char decode64[256], // decoding table; if NULL: use TableDecode64default
+ bool allow_white_spaces, // true: skip white spaces
+ uint *scanned_len // not NULL: Store number of scanned 'str' bytes here
+);
+
+//-----------------------------------------------------------------------------
+
+mem_t DecodeBase64Circ
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ ccp source, // NULL or string to decode
+ int len, // length of string. if -1, str is null terminated
+ const char decode64[256] // decoding table; if NULL: use TableDecode64default
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint EncodeBase64
+(
+ // returns the number of scanned bytes of 'source'
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 4
+ const void *source, // NULL or data to encode
+ int source_len, // length of 'source'; if <0: use strlen(source)
+ const char encode64[64+1], // encoding table; if NULL: use TableEncode64default
+ bool use_filler, // use filler for aligned output
+ ccp next_line, // not NULL: use this string as new line sep
+ uint next_line_trigger // >0: use 'next_line' every # input bytes
+);
+
+//-----------------------------------------------------------------------------
+
+mem_t EncodeBase64Circ
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ const void *source, // NULL or data to encode
+ int source_len, // length of 'source'; if <0: use strlen(source)
+ const char encode64[64+1] // encoding table; if NULL: use TableEncode64default
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline uint EncodeBase64Len ( uint src_len )
+ { return 4 * (src_len+2) / 3; }
+
+static inline uint EncodeBase64FillLen ( uint src_len )
+ { return (src_len+2) / 3 * 4; }
+
+static inline uint DecodeBase64Len ( uint src_len )
+ { return 3 * src_len / 4; }
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static inline uint DecodeJSON
+(
+ // returns the number of valid bytes in 'buf'
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 3
+ ccp source, // NULL or string to decode
+ int source_len, // length of 'source'. If -1, str is NULL terminated
+ uint *scanned_len // not NULL: Store number of scanned 'str' bytes here
+)
+{
+ return ScanEscapedString(buf,buf_size,source,source_len,true,0,scanned_len);
+}
+
+//-----------------------------------------------------------------------------
+
+mem_t DecodeJSONCirc
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ ccp source, // NULL or string to decode
+ int source_len // length of 'source'. If -1, str is NULL terminated
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint EncodeJSON
+(
+ // returns the number of valid bytes in 'buf'. Result is NULL-terminated.
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >2 and 2 bytes longer than needed
+ const void *source, // NULL or data to encode
+ int source_len // length of 'source'; if <0: use strlen(source)
+);
+
+//-----------------------------------------------------------------------------
+
+mem_t EncodeJSONCirc
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ const void *source, // NULL or data to encode
+ int source_len // length of 'source'; if <0: use strlen(source)
+);
+
+//-----------------------------------------------------------------------------
+
+mem_t QuoteJSONCirc
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ const void *source, // NULL or data to encode
+ int source_len, // length of 'source'; if <0: use strlen(source)
+ int null_if // return 'null' without quotes ...
+ // <=0: never
+ // >=1: if source == NULL
+ // >=2: if source_len == 0
+);
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint DecodeByMode
+(
+ // returns the number of valid bytes in 'buf'. Result is NULL-terminated.
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 3
+ ccp source, // string to decode
+ int slen, // length of string. if -1, str is null terminated
+ EncodeMode_t emode // encoding mode
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint EncodeByMode
+(
+ // returns the number of valid bytes in 'buf'. Result is NULL-terminated.
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 4
+ ccp source, // string to encode
+ int slen, // length of string. if -1, str is null terminated
+ EncodeMode_t emode // encoding mode
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t DecodeByModeMem
+(
+ // Returns the decoded 'source'. Result is NULL-terminated.
+ // It points either to 'buf' or is alloced (on buf==NULL or to less space)
+ // If alloced (mem.ptr!=buf) => call FreeMem(&mem)
+
+ char *buf, // NULL or destination buffer
+ uint buf_size, // size of 'buf'
+ ccp source, // string to decode
+ int slen, // length of string. if -1, str is null terminated
+ EncodeMode_t emode // encoding mode
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t EncodeByModeMem
+(
+ // Returns the encoded 'source'. Result is NULL-terminated.
+ // It points either to 'buf' or is alloced (on buf==NULL or to less space)
+ // If alloced (mem.ptr!=buf) => call FreeMem(&mem)
+
+ char *buf, // NULL or destination buffer
+ uint buf_size, // size of 'buf'
+ ccp source, // string to encode
+ int slen, // length of string. if -1, str is null terminated
+ EncodeMode_t emode // encoding mode
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define SJIS_TAB_MAPPING_BEG 0xeea0 // first code used for table mapping
+#define SJIS_TAB_MAPPING_END 0xeecf // last code used for table mapping
+#define SJIS_INVALID_CODE 0xffff // invalid code
+#define SJIS_CACHE_SIZE 0xffff // number of elements in cache
+
+static inline bool IsValidShiftJIS ( uint code )
+ { return code < SJIS_TAB_MAPPING_BEG
+ || code > SJIS_TAB_MAPPING_END && code < SJIS_INVALID_CODE; }
+
+int ScanShiftJISChar ( cucp * str );
+int ScanShiftJISCharE ( cucp * str, cvp end );
+
+void SetupGetShiftJISCache(void);
+int GetShiftJISChar ( u32 code );
+
+ccp GetShiftJISStatistics(void);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan keywords ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define KEYWORD_NAME_MAX 100
+
+typedef s64 (*KeywordCallbackFunc)
+(
+ void *param, // NULL or user defined parameter
+ ccp name, // normalized name of option
+ const KeywordTab_t *key_tab, // valid pointer to command table
+ const KeywordTab_t *key, // valid pointer to found command
+ char prefix, // 0 | '-' | '+' | '='
+ s64 result // current value of result
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t *GetKewordById
+(
+ const KeywordTab_t *key_tab, // NULL or pointer to command table
+ s64 id // id to search
+);
+
+//-----------------------------------------------------------------------------
+
+const KeywordTab_t *GetKewordByIdAndOpt
+(
+ const KeywordTab_t *key_tab, // NULL or pointer to command table
+ s64 id, // id to search
+ s64 opt // opt to search
+);
+
+//-----------------------------------------------------------------------------
+
+static inline ccp GetKewordNameById
+(
+ const KeywordTab_t *key_tab, // NULL or pointer to command table
+ s64 id, // id to search
+ ccp res_not_found // return this if not found
+)
+{
+ const KeywordTab_t *key = GetKewordById(key_tab,id);
+ return key ? key->name1 : res_not_found;
+}
+
+//-----------------------------------------------------------------------------
+
+static inline ccp GetKewordNameByIdAndOpt
+(
+ const KeywordTab_t *key_tab, // NULL or pointer to command table
+ s64 id, // id to search
+ s64 opt, // opt to search
+ ccp res_not_found // return this if not found
+)
+{
+ const KeywordTab_t *key = GetKewordByIdAndOpt(key_tab,id,opt);
+ return key ? key->name1 : res_not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t * ScanKeyword
+(
+ int *res_abbrev, // NULL or pointer to result 'abbrev_count'
+ ccp arg, // argument to scan
+ const KeywordTab_t *key_tab // valid pointer to command table
+);
+
+//-----------------------------------------------------------------------------
+
+s64 ScanKeywordListEx
+(
+ ccp arg, // argument to scan
+ const KeywordTab_t * key_tab, // valid pointer to command table
+ KeywordCallbackFunc func, // NULL or calculation function
+ bool allow_prefix, // allow '-' | '+' | '=' as prefix
+ u32 max_number, // allow numbers < 'max_number' (0=disabled)
+ s64 result, // start value for result
+
+ uint err_mode, // bit field:
+ // 1: continue on error
+ ccp err_msg, // not NULL: print a warning message:
+ // "<ERR_MSG>: Unknown keyword: <KEY>"
+ enumError err_code, // use 'err_code' for the 'err_msg'
+ uint *err_count // not NULL: store errors here
+);
+
+static inline s64 ScanKeywordList
+(
+ ccp arg, // argument to scan
+ const KeywordTab_t * key_tab, // valid pointer to command table
+ KeywordCallbackFunc func, // NULL or calculation function
+ bool allow_prefix, // allow '-' | '+' | '=' as prefix
+ u32 max_number, // allow numbers < 'max_number' (0=disabled)
+ s64 result, // start value for result
+
+ ccp err_msg, // not NULL: print a warning message:
+ // "<ERR_MSG>: Unknown keyword: <KEY>"
+ enumError err_code // use 'err_code' for the 'err_msg'
+)
+{
+ return ScanKeywordListEx( arg, key_tab, func, allow_prefix,
+ max_number, result, 0, err_msg, err_code, 0 );
+}
+
+//-----------------------------------------------------------------------------
+
+enumError ScanKeywordListFunc
+(
+ ccp arg, // argument to scan
+ const KeywordTab_t *key_tab, // valid pointer to command table
+ KeywordCallbackFunc func, // calculation function
+ void *param, // used define parameter for 'func'
+ bool allow_prefix // allow '-' | '+' | '=' as prefix
+);
+
+//-----------------------------------------------------------------------------
+
+s64 ScanKeywordListMask
+(
+ ccp arg, // argument to scan
+ const KeywordTab_t * key_tab // valid pointer to command table
+);
+
+//-----------------------------------------------------------------------------
+
+enumError PrintKeywordError
+(
+ const KeywordTab_t * key_tab, // NULL or pointer to command table
+ ccp key_arg, // analyzed command
+ int key_stat, // status of ScanKeyword()
+ ccp prefix, // NULL or prefix for messages
+ ccp object // NULL or object for error messages
+ // default= 'command'
+);
+
+uint CollectAmbiguousKeywords
+(
+ char *buf, // destination buffer, 200 bytes are good
+ uint buf_size, // size of buffer
+ const KeywordTab_t * key_tab, // NULL or pointer to command table
+ ccp key_arg // analyzed command
+);
+
+//-----------------------------------------------------------------------------
+
+char * PrintKeywordList
+(
+ // returns a pointer to the result buffer
+
+ char *buf, // result buffer
+ // If NULL, a local circulary static buffer
+ // with max CIRC_BUF_MAX_ALLOC bytes is used
+ uint buf_size, // size of 'buf', at least 10 bytes if buf is set
+ uint *ret_length, // not NULL: store result length here
+
+ const KeywordTab_t *tab, // related keyword table
+ u64 mode, // mode to print
+ u64 default_mode, // NULL or default mode
+ u64 hide_mode // bit field to hide parameters
+);
+
+//-----------------------------------------------------------------------------
+
+int ScanKeywordOffOn
+(
+ // returns 0 for '0|OFF'; 1 for '1|ON; -1 for empty; -2 on error
+
+ ccp arg, // argument to scan
+ uint max_num, // >0: additionally accept+return number <= max_num
+ ccp object // NULL (silent) or object for error messages
+);
+
+//-----------------------------------------------------------------------------
+
+enumError Command_ARGTEST ( int argc, char ** argv );
+enumError Command_TESTCOLORS ( int argc, char ** argv );
+
+enumError Command_COLORS
+(
+ int level, // only used, if mode==NULL
+ // < 0: status message (ignore mode)
+ // >= 1: include names
+ // >= 2: include alt names
+ // >= 3: include color names incl bold
+ // >= 4: include background color names
+ uint mode, // output mode => see PrintColorSetHelper()
+ uint format // output format => see PrintColorSetEx()
+);
+
+
+///////////////////////////////////////////////////////////////////////////////
+/////////////// OFF/AUTO/ON/FORCE ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[OffOn_t]]
+
+typedef enum OffOn_t
+{
+ OFFON_ERROR = -99,
+ OFFON_OFF = -1,
+ OFFON_AUTO = 0,
+ OFFON_ON = 1,
+ OFFON_FORCE = 2,
+}
+OffOn_t;
+
+extern const KeywordTab_t KeyTab_OFF_AUTO_ON[];
+
+int ScanKeywordOffAutoOn
+(
+ // returns one of OFFON_*
+
+ ccp arg, // argument to scan
+ int on_empty, // return this value on empty
+ uint max_num, // >0: additionally accept+return number <= max_num
+ ccp object // NULL (silent) or object for error messages
+);
+
+ccp GetKeywordOffAutoOn ( OffOn_t value );
+
+// //
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan command lists ///////////////
+///////////////////////////////////////////////////////////////////////////////
+//
+// Scan a semicolon separated command list with binary support of format:
+// COMMAND := NAME [BINLIST] CONTROL* ['='] CONTROL* [PARAM] CONTROL* ';'
+// BLANK := TAB(9) or SPACE(32)
+// CONTROL := NUL..SPACE (0..32)
+// NAME := 1..99 alphanumeric chars incl. minus and underline
+// BINLIST := BINARY [BINLIST]
+// BINARY := BLANK* '\1' be16:SIZE DATA
+// PARAM := all except ';'
+//
+// CONTROLS are possible before and behind the '=' and at the end of param.
+// Only BLANK* are allowed between command NAME and BINARY block.
+// BINARY data starts with \1 and is stored as '->bin' and '->bin_len' and
+// inludes SIZE and DATA. The BINARY.SIZE includes itself and is a 16-bit
+// big-endian number. In '->bin[]', member 'be16:SIZE' is excluded,
+// but '->bin[].ptr - 2' points to be16:SIZE.
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[CommandList_t]] docu -> see above
+
+struct TCPStream_t;
+#define COMMAND_LIST_N_BIN 5
+
+typedef struct CommandList_t
+{
+ //--- input params
+
+ ccp command; // command text
+ uint command_len; // total length of 'command'
+ char command_sep; // not NULL: alternative command separator
+ u8 change_case; // >0: convert command to lower(1) or upper(2) case
+ bool term_param; // 1: write a NULL at end-of-param => modify 'command'
+ // 2: write a ';' at end-of-param => modify 'command'
+ // in this case, ';' is counted to 'record_len'
+ bool is_terminated; // true: last command is terminated
+ // even without ';'
+
+ //--- user parameters, pass-through
+
+ struct TCPStream_t
+ *user_ts; // NULL or a stream for replies
+ void *user_ptr; // any pointer
+ int user_int; // any number or id
+
+ ccp log_fname; // not NULL: open log file
+ uint log_level; // 0:off, 1:stat+err, 2:+cmd, 3:+bin+param, 4:+func
+
+ //--- analysed data, only valid at callback
+
+ char cmd[100]; // scanned command name
+ uint cmd_len; // length of scanned command name
+
+ uint n_bin; // number of binary blocks
+ mem_t bin[COMMAND_LIST_N_BIN];
+ // binary data detected, if bin[].ptr not null
+
+ ccp param; // pointer to text parameter, not NULL terminated
+ uint param_len; // length of text parameter
+ const u8 *record; // complete record from NAME to END_RECORD_CHAR
+ uint record_len; // length of trimmed record
+ uint input_len; // length of complete record at input time
+
+ //--- statistics
+
+ uint read_cmd_len; // number of used bytes of 'command'
+ uint fail_count; // number of failed commands
+ uint cmd_count; // number of commands
+}
+CommandList_t;
+
+// return -1 to abort scanning
+typedef int (*CommandListFunc) ( CommandList_t *cli );
+
+//-----------------------------------------------------------------------------
+
+static inline void InitializeCommandList ( CommandList_t *cli )
+ { DASSERT(cli); memset(cli,0,sizeof(*cli)); }
+
+static inline void ClearCommandList ( CommandList_t *cli )
+ { DASSERT(cli); }
+
+static inline void ResetCommandList ( CommandList_t *cli )
+ { DASSERT(cli); memset(cli,0,sizeof(*cli)); }
+
+// a test function for ScanCommandList()
+int ExecCommandOfList ( CommandList_t *cli );
+
+int ScanCommandList
+(
+ // scan a semicolon separated command list. See above for format
+ // returns -1 on 'cli->fail_count'; otherwise 'cli->cmd_count'
+
+ CommandList_t *cli, // valid command list
+ CommandListFunc func // NULL or function for each command
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// numeric functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern int urandom_available; // <0:not, =0:not tested, >0:ok
+extern bool use_urandom_for_myrandom;
+
+uint ReadFromUrandom ( void *dest, uint size );
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 MyRandom ( u32 max );
+u64 MySeed ( u64 base );
+u64 MySeedByTime(void);
+
+void MyRandomFill ( void * buf, size_t size );
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CreateUUID ( uuid_buf_t dest );
+uint CreateTextUUID ( char *buf, uint bufsize );
+uint PrintUUID ( char *buf, uint bufsize, uuid_buf_t uuid );
+char * ScanUUID ( uuid_buf_t uuid, ccp source );
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint gcd ( uint n1, uint n2 ); // greatest common divisor, german: ggt
+u32 gcd32 ( u32 n1, u32 n2 );
+u64 gcd64 ( u64 n1, u64 n2 );
+
+uint lcm ( uint n1, uint n2 ); // lowest common multiple, german: kgv
+u32 lcm32 ( u32 n1, u32 n2 );
+u64 lcm64 ( u64 n1, u64 n2 );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan number ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enum DigitTable_t
+{
+ NUMBER_NULL = -1, // NULL
+ NUMBER_CONTROL = -2, // < 0x20, but not SPACE or LINE
+ NUMBER_LINE = -3, // LF, CR
+ NUMBER_SPACE = -4, // SPACE, TAB, VT
+ NUMBER_TIE = -5, // ':' '-' '.'
+ NUMBER_SEPARATE = -6, // ',' ';'
+ NUMBER_OTHER = -7 // all other
+};
+
+extern const char DigitTable[256];
+
+//-----------------------------------------------------------------------------
+
+u64 ScanDigits
+(
+ // same as ScanNumber(), but other interface
+ // returns the scanned number
+
+ ccp *source, // pointer to source string, modified
+ ccp end_source, // NULL or end of 'source'
+ uint intbase, // integer base, 2..36
+ int maxchar, // max number of digits to read
+ uint *count // not NULL: store number of scanned digits here
+);
+
+//-----------------------------------------------------------------------------
+
+char * ScanNumber
+(
+ // same as ScanDigits(), but other interface
+ // returns a pointer to the first not used character
+
+ uint *dest_num, // store result here, never NULL
+ ccp src, // pointer to source string, modified
+ ccp src_end, // NULL or end of 'src'
+ uint intbase, // integer base, 2..36
+ int maxchar // max number of digits to read
+);
+
+//-----------------------------------------------------------------------------
+
+char * ScanEscape
+(
+ // returns a pointer to the first not used character
+
+ uint *dest_code, // store result here, never NULL
+ ccp src, // pointer to source string (behind escape char)
+ ccp src_end // NULL or end of 'src'
+);
+
+//-----------------------------------------------------------------------------
+
+uint ScanHexString
+(
+ // return the number of written bytes
+
+ void *buf, // write scanned data here
+ uint buf_size, // size of buf
+ ccp *source, // pointer to source string, modified
+ ccp end_source, // NULL or end of 'source'
+ bool allow_tie // allow chars ' .:-' and TAB,VT as byte separator
+);
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanS32
+(
+ // return 'source' on error
+
+ s32 *res_num, // not NULL: store result (only on success)
+ ccp source, // NULL or source text
+ uint default_base // base for numbers without '0x' prefix
+ // 0: C like with octal support
+ // 10: standard value for decimal numbers
+ // 16: standard value for hex numbers
+);
+
+static inline char * ScanU32 ( u32 *res_num, ccp source, uint default_base )
+ { return ScanS32((s32*)res_num,source,default_base); }
+
+//-----------------------------------------------------------------------------
+
+char * ScanS64
+(
+ // return 'source' on error
+
+ s64 *res_num, // not NULL: store result (only on success)
+ ccp source, // NULL or source text
+ uint default_base // base for numbers without '0x' prefix
+ // 0: C like with octal support
+ // 10: standard value for decimal numbers
+ // 16: standard value for hex numbers
+);
+
+static inline char * ScanU64 ( u64 *res_num, ccp source, uint default_base )
+ { return ScanS64((s64*)res_num,source,default_base); }
+
+//-----------------------------------------------------------------------------
+
+#if __WORDSIZE >= 64
+
+ static inline char * ScanINT ( int *res_num, ccp source, uint default_base )
+ { return ScanS32((s32*)res_num,source,default_base); }
+ static inline char * ScanUINT ( uint *res_num, ccp source, uint default_base )
+ { return ScanS32((s32*)res_num,source,default_base); }
+
+#else
+
+ static inline char * ScanINT ( int *res_num, ccp source, uint default_base )
+ { return ScanS64((s64*)res_num,source,default_base); }
+ static inline char * ScanUINT ( uint *res_num, ccp source, uint default_base )
+ { return ScanS64((s64*)res_num,source,default_base); }
+
+#endif
+
+//-----------------------------------------------------------------------------
+// strto*() replacements: Use Scan*() with better base support, NULL allowed as source
+
+long int str2l ( const char *nptr, char **endptr, int base );
+long long int str2ll ( const char *nptr, char **endptr, int base );
+unsigned long int str2ul ( const char *nptr, char **endptr, int base );
+unsigned long long int str2ull ( const char *nptr, char **endptr, int base );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print numbers and size ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[sizeform_mode_t]]
+
+typedef enum sizeform_mode_t
+{
+ DC_SFORM_ALIGN = 0x01, // aligned output
+ DC_SFORM_NARROW = 0x02, // suppress space between number and unit
+ DC_SFORM_UNIT1 = 0x04, // limit unit to 1 character (space for bytes)
+ DC_SFORM_INC = 0x08, // increment to next factor if no fraction is lost
+ DC_SFORM_PLUS = 0x10, // on signed output: print PLUS sign for values >0
+ DC_SFORM_DASH = 0x20, // on NULL: print only a dash (minus sign)
+
+ DC_SFORM__MASK = 0x3f, // mask of above
+ DC_SFORM__AUTO = 0x40, // hint for some functions to decide by themself
+
+ DC_SFORM_TINY = DC_SFORM_NARROW | DC_SFORM_UNIT1,
+}
+sizeform_mode_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+float RoundF2bytes ( float f );
+float RoundF3bytes ( float f );
+double RoundD6bytes ( double d );
+double RoundD7bytes ( double d );
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberU4 ( char *buf, size_t buf_size, u64 num, sizeform_mode_t mode );
+char * PrintNumberU5 ( char *buf, size_t buf_size, u64 num, sizeform_mode_t mode );
+char * PrintNumberU6 ( char *buf, size_t buf_size, u64 num, sizeform_mode_t mode );
+char * PrintNumberU7 ( char *buf, size_t buf_size, u64 num, sizeform_mode_t mode );
+
+char * PrintNumberS5 ( char *buf, size_t buf_size, s64 num, sizeform_mode_t mode );
+char * PrintNumberS6 ( char *buf, size_t buf_size, s64 num, sizeform_mode_t mode );
+char * PrintNumberS7 ( char *buf, size_t buf_size, s64 num, sizeform_mode_t mode );
+
+char * PrintNumberD5 ( char *buf, size_t buf_size, double num, sizeform_mode_t mode );
+char * PrintNumberD6 ( char *buf, size_t buf_size, double num, sizeform_mode_t mode );
+char * PrintNumberD7 ( char *buf, size_t buf_size, double num, sizeform_mode_t mode );
+
+// Parameters:
+// char *buf, // result buffer
+// // NULL: use a local circulary static buffer
+// size_t buf_size, // size of 'buf', ignored if buf==NULL
+// * num, // number to print
+// sizeform_mode_t mode // any of DC_SFORM_ALIGN, DC_SFORM_DASH[not *D*()]
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[size_mode_t]]
+
+typedef enum size_mode_t
+{
+ //----- modes => used as index for dc_size_tab_1000[] & dc_size_tab_1024[]
+
+ DC_SIZE_DEFAULT = 0, // special default value, fall back to AUTO
+ DC_SIZE_AUTO, // select unit automatically
+
+ DC_SIZE_BYTES, // force output in bytes
+ DC_SIZE_K, // force output in KB or KiB (kilo,kibi)
+ DC_SIZE_M, // force output in MB or MiB (mega,mebi)
+ DC_SIZE_G, // force output in GB or GiB (giga,gibi)
+ DC_SIZE_T, // force output in TB or TiB (tera,tebi)
+ DC_SIZE_P, // force output in PB or PiB (peta,pebi)
+ DC_SIZE_E, // force output in EB or EiB (exa, exbi)
+ // zetta/zebi & yotta/yobi not supported because >2^64
+
+ DC_SIZE_N_MODES, // number of modes
+
+ //----- flags
+
+ DC_SIZE_F_1000 = 0x010, // force output in SI units (kB=1000, MB=1000000,...)
+ DC_SIZE_F_1024 = 0x020, // force output in IEC units (KiB=1024, MiB=1024*1024,...)
+ DC_SIZE_F_AUTO_UNIT = 0x040, // suppress output of unit for non AUTO mode
+ DC_SIZE_F_NO_UNIT = 0x080, // suppress allways output of unit
+
+ //----- masks
+
+ DC_SIZE_M_MODE = 0x00f, // mask for modes
+ DC_SIZE_M_BASE = 0x030, // mask for base
+ DC_SIZE_M_ALL = 0x0ff, // all relevant bits
+}
+size_mode_t;
+
+//-----------------------------------------------------------------------------
+
+extern ccp dc_size_tab_1000[DC_SIZE_N_MODES+1];
+extern ccp dc_size_tab_1024[DC_SIZE_N_MODES+1];
+
+//-----------------------------------------------------------------------------
+
+ccp GetSizeUnit // get a unit for column headers
+(
+ size_mode_t mode, // print mode
+ ccp if_invalid // output for invalid modes
+);
+
+//-----------------------------------------------------------------------------
+
+int GetSizeFW // get a good value field width
+(
+ size_mode_t mode, // print mode
+ int min_fw // minimum fw => return max(calc_fw,min_fw);
+ // this value is also returned for invalid modes
+);
+
+//-----------------------------------------------------------------------------
+
+char * PrintSize
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 size, // size to print
+ sizeform_mode_t sform_mode, // output format, bit field
+ size_mode_t mode // print mode
+);
+
+//-----------------------------------------------------------------------------
+
+char * PrintSize1000
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 size, // size to print
+ sizeform_mode_t sform_mode // output format, bit field
+);
+
+//-----------------------------------------------------------------------------
+
+char * PrintSize1024
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 size, // size to print
+ sizeform_mode_t sform_mode // output format, bit field
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan size ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern u64 (*ScanSizeFactorHook)
+(
+ char ch_factor, // char to analyze
+ int force_base // if 1000|1024: force multiple of this
+);
+
+u64 ScanSizeFactor
+(
+ char ch_factor, // char to analyze
+ int force_base // if 1000|1024: force multiple of this
+);
+
+char * ScanSizeTerm
+(
+ double *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ int force_base // if 1000|1024: force multiple of this
+);
+
+char * ScanSize
+(
+ double *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base // if 1000|1024: force multiple of this
+);
+
+char * ScanSizeU32
+(
+ u32 *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base // if 1000|1024: force multiple of this
+);
+
+char * ScanSizeU64
+(
+ u64 *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base // if 1000|1024: force multiple of this
+);
+
+//-----------------------------------------------------------------------------
+// strto*() like wrappers
+
+u32 GetSizeU32 ( ccp src, char **end, int force_base );
+u64 GetSizeU64 ( ccp src, char **end, int force_base );
+double GetSizeD ( ccp src, char **end, int force_base );
+
+//-----------------------------------------------------------------------------
+
+char * ScanSizeRange
+(
+ int *stat, // if not NULL: store result
+ // 0:none, 1:single, 2:range
+ double *num1, // not NULL: store 'from' result
+ double *num2, // not NULL: store 'to' result
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ double max_value // >0: max value for open ranges
+);
+
+char * ScanSizeRangeU32
+(
+ int *stat, // if not NULL: store result
+ // 0:none, 1:single, 2:range
+ u32 *num1, // not NULL: store 'from' result
+ u32 *num2, // not NULL: store 'to' result
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ u32 max_value // >0: max value for open ranges
+);
+
+char * ScanSizeRangeU64
+(
+ int *stat, // if not NULL: store result
+ // 0:none, 1:single, 2:range
+ u64 *num1, // not NULL: store 'from' result
+ u64 *num2, // not NULL: store 'to' result
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ u64 max_value // >0: max value for open ranges
+);
+
+//-----------------------------------------------------------------------------
+
+char * ScanSizeRangeList
+(
+ uint *n_range, // not NULL: store number of scanned ranges
+ double *num, // array with '2*max_range' elements
+ // unused elements are filled with 0.0
+ uint max_range, // max number of allowed ranges
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ double max_value // >0: max value for open ranges
+);
+
+char * ScanSizeRangeListU32
+(
+ uint *n_range, // not NULL: store number of scanned ranges
+ u32 *num, // array with '2*max_range' elements
+ // unused elements are filled with 0
+ uint max_range, // max number of allowed ranges
+
+ ccp source, // source text
+ u32 default_factor, // use this factor if number hasn't one
+ u32 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ u32 max_value // >0: max value for open ranges
+);
+
+char * ScanSizeRangeListU64
+(
+ uint *n_range, // not NULL: store number of scanned ranges
+ u64 *num, // array with '2*max_range' elements
+ // unused elements are filled with 0
+ uint max_range, // max number of allowed ranges
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ u64 max_value // >0: max value for open ranges
+);
+
+//-----------------------------------------------------------------------------
+
+enumError ScanSizeOpt
+(
+ double *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+
+ ccp opt_name, // NULL or name of option for error messages
+ u64 min, // >0: minimum allowed value
+ u64 max, // >0: maximum allowed value
+ bool print_err // true: print error messages
+);
+
+enumError ScanSizeOptU64
+(
+ u64 *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ int force_base, // if 1000|1024: force multiple of this
+
+ ccp opt_name, // NULL or name of option for error messages
+ u64 min, // >0: minimum allowed value
+ u64 max, // >0: maximum allowed value
+ u32 multiple, // >0: result must be multiple
+ u32 pow2, // >0: result must power of '1<<pow2'
+ bool print_err // true: print error messages
+);
+
+enumError ScanSizeOptU32
+(
+ u32 *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ int force_base, // if 1000|1024: force multiple of this
+
+ ccp opt_name, // NULL or name of option for error messages
+ u64 min, // >0: minimum allowed value
+ u64 max, // >0: maximum allowed value
+ u32 multiple, // >0: result must be multiple
+ u32 pow2, // >0: result must power of '1<<pow2'
+ bool print_err // true: print error messages
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Scan Duration ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[ScanDuration_t]]
+
+typedef enum ScanDuration_t
+{
+ SDUMD_SKIP_BLANKS = 0x01, // skip leading blanks
+ SDUMD_ALLOW_COLON = 0x02, // allow 'H:M' and 'H:M:S'
+ SDUMD_ALLOW_DIV = 0x04, // allow fraction 'n/d' as number
+
+ SDUMD_ALLOW_LIST = 0x10, // allow lists like '9h12m'
+ SDUMD_ALLOW_SPACE_SEP = 0x20, // lists: allow spaces as list separator
+ SDUMD_ALLOW_PLUS_SEP = 0x40, // lists: allow '+' as list separator
+ SDUMD_ALLOW_MINUS_SEP = 0x80, // lists: allow '-' as list separator
+
+ SDUMD_M_ALL = 0xf7,
+}
+ScanDuration_t;
+
+//-----------------------------------------------------------------------------
+
+char * ScanDuration
+(
+ double *num, // not NULL: store result
+ ccp source, // source text
+ double default_factor, // default factor if no SI unit found
+ ScanDuration_t mode
+);
+
+double GetDurationFactor ( char ch );
+
+char * ScanSIFactor
+(
+ // returns end of scanned string
+
+ double *num, // not NULL: store result
+ ccp source, // source text
+ double default_factor // return this if no factor found
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// time & timer ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+//--- conversion factors
+
+#define SEC_PER_MIN 60
+#define SEC_PER_HOUR 3600
+#define SEC_PER_DAY 86400
+#define SEC_PER_WEEK 604800
+
+#define MSEC_PER_SEC 1000ull
+#define MSEC_PER_MIN (60*MSEC_PER_SEC)
+#define MSEC_PER_HOUR (3600*MSEC_PER_SEC)
+#define MSEC_PER_DAY (SEC_PER_DAY*MSEC_PER_SEC)
+#define MSEC_PER_WEEK (SEC_PER_WEEK*MSEC_PER_SEC)
+
+#define USEC_PER_MSEC 1000ull
+#define USEC_PER_SEC 1000000ull
+#define USEC_PER_MIN (60*USEC_PER_SEC)
+#define USEC_PER_HOUR (3600*USEC_PER_SEC)
+#define USEC_PER_DAY (SEC_PER_DAY*USEC_PER_SEC)
+#define USEC_PER_WEEK (SEC_PER_WEEK*USEC_PER_SEC)
+
+//--- time types
+
+typedef uint u_sec_t; // unsigned type to store time as seconds
+typedef int s_sec_t; // signed type to store time as seconds
+typedef u64 u_msec_t; // unsigned type to store time as milliseconds
+typedef s64 s_msec_t; // signed type to store time as milliseconds
+typedef u64 u_usec_t; // unsigned type to store time as microseconds
+typedef s64 s_usec_t; // signed type to store time as microseconds
+
+// [[DayTime_t]]
+typedef struct DayTime_t
+{
+ time_t time; // seconds since epoch, like time()
+ int day; // days since epoch
+ int hour; // hour of day
+ int min; // minute of hour
+ int sec; // second of minute
+ int usec; // microsecond of second
+}
+DayTime_t;
+
+//--- only valid after call to SetupTimezone()
+
+extern s64 timezone_adjust_sec;
+extern s64 timezone_adjust_usec;
+extern int timezone_adjust_isdst;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupTimezone ( bool force );
+
+struct timeval GetTimeOfDay ( bool localtime );
+DayTime_t GetDayTime ( bool localtime );
+u_sec_t GetTimeSec ( bool localtime );
+u_msec_t GetTimeMSec ( bool localtime );
+u_usec_t GetTimeUSec ( bool localtime );
+
+u_msec_t GetTimerMSec(void);
+u_usec_t GetTimerUSec(void);
+
+static inline u64 double2msec ( double d ) { return d>0.0 ? (u64)trunc( 1e3*d+0.5 ) : 0; }
+static inline u64 double2usec ( double d ) { return d>0.0 ? (u64)trunc( 1e6*d+0.5 ) : 0; }
+
+static inline double msec2double ( u64 num ) { return num * 1e-3; }
+static inline double usec2double ( u64 num ) { return num * 1e-6; }
+
+//--- another epoch: Monday, 2001-01-01
+
+#define EPOCH_2001_SEC 0x3a4fc880
+#define EPOCH_2001_MSEC (EPOCH_2001_SEC*MSEC_PER_SEC)
+#define EPOCH_2001_USEC (EPOCH_2001_SEC*USEC_PER_SEC)
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimeByFormat
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ time_t time // seconds since epoch -> time()
+);
+
+ccp PrintTimeByFormatUTC
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ time_t time // seconds since epoch -> time()
+);
+
+//-----------------------------------------------------------------------------
+
+ccp PrintUsecByFormat
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ time_t time, // seconds since epoch -> time()
+ uint usec // micro second of second
+);
+
+ccp PrintUsecByFormatUTC
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ time_t time, // seconds since epoch -> time()
+ uint usec // micro second of second
+);
+
+//-----------------------------------------------------------------------------
+
+
+ccp PrintTimevalByFormat
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ const struct timeval *tv // time to print, if NULL use gettimeofday()
+);
+
+
+ccp PrintTimevalByFormatUTC
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ const struct timeval *tv // time to print, if NULL use gettimeofday()
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimeSec
+(
+ char * buf, // result buffer (>19 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_sec_t sec // seconds (=time) to print
+);
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimeMSec
+(
+ char * buf, // result buffer (>23 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_msec_t msec, // milliseconds to print
+ uint fraction // number of digits (0-3) to print as fraction
+);
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimeUSec
+(
+ char * buf, // result buffer (>26 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_usec_t usec, // microseconds to print
+ uint fraction // number of digits (0-6) to print as fraction
+);
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimerMSec
+(
+ char * buf, // result buffer (>12 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 msec, // milliseconds to print
+ uint fraction // number of digits (0-3) to print as fraction
+);
+
+ccp PrintTimerUSec
+(
+ char * buf, // result buffer (>16 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s_usec_t usec, // microseconds to print
+ uint fraction // number of digits (0-3) to print as fraction
+);
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimer3 // helper function
+(
+ char * buf, // result buffer (>3 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 sec, // seconds to print
+ int usec, // 0...999999: usec fraction, otherwise suppress ms/us output
+ bool aligned // true: print aligned 3 character output
+);
+
+static inline ccp PrintTimerSec3
+(
+ char * buf, // result buffer (>3 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_sec_t sec, // seconds to print
+ bool aligned // true: print aligned 3 character output
+)
+{
+ return PrintTimer3(buf,buf_size,sec,-1,aligned);
+}
+
+static inline ccp PrintTimerMSec3
+(
+ char * buf, // result buffer (>3 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_msec_t msec, // microseconds to print
+ bool aligned // true: print aligned 3 character output
+)
+{
+ return PrintTimer3(buf,buf_size,msec/1000,msec%1000*1000,aligned);
+}
+
+static inline ccp PrintTimerUSec3
+(
+ char * buf, // result buffer (>3 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_usec_t usec, // microseconds to print
+ bool aligned // true: print aligned 3 character output
+)
+{
+ return PrintTimer3(buf,buf_size,usec/1000000,usec%1000000,aligned);
+}
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimer4 // helper function
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 sec, // seconds to print
+ int usec, // 0...999999: usec fraction, otherwise suppress ms/us output
+ bool aligned // true: print aligned 4 character output
+);
+
+static inline ccp PrintTimerSec4
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_sec_t sec, // seconds to print
+ bool aligned // true: print aligned 4 character output
+)
+{
+ return PrintTimer4(buf,buf_size,sec,-1,aligned);
+}
+
+static inline ccp PrintTimerMSec4
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_msec_t msec, // microseconds to print
+ bool aligned // true: print aligned 4 character output
+)
+{
+ return PrintTimer4(buf,buf_size,msec/1000,msec%1000*1000,aligned);
+}
+
+static inline ccp PrintTimerUSec4
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_usec_t usec, // microseconds to print
+ bool aligned // true: print aligned 4 character output
+)
+{
+ return PrintTimer4(buf,buf_size,usec/1000000,usec%1000000,aligned);
+}
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimer6 // helper function
+(
+ char * buf, // result buffer (>6 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 sec, // seconds to print
+ int usec, // 0...999999: usec fraction,
+ // otherwise suppress ms/us output
+ bool aligned // true: print aligned 6 character output
+);
+
+static inline ccp PrintTimerSec6
+(
+ char * buf, // result buffer (>6 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_sec_t sec, // seconds to print
+ bool aligned // true: print aligned 6 character output
+)
+{
+ return PrintTimer6(buf,buf_size,sec,-1,aligned);
+}
+
+static inline ccp PrintTimerMSec6
+(
+ char * buf, // result buffer (>6 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_msec_t msec, // microseconds to print
+ bool aligned // true: print aligned 6 character output
+)
+{
+ return PrintTimer6(buf,buf_size,msec/1000,msec%1000*1000,aligned);
+}
+
+static inline ccp PrintTimerUSec6
+(
+ char * buf, // result buffer (>6 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_usec_t usec, // microseconds to print
+ bool aligned // true: print aligned 6 character output
+)
+{
+ return PrintTimer6(buf,buf_size,usec/1000000,usec%1000000,aligned);
+}
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimerUSec4s
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s_usec_t usec, // microseconds to print
+ sizeform_mode_t mode // support of DC_SFORM_ALIGN, DC_SFORM_PLUS
+);
+
+static inline ccp PrintTimerMSec4s
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s_msec_t msec, // milliseconds to print
+ sizeform_mode_t mode // support of DC_SFORM_ALIGN, DC_SFORM_PLUS
+)
+{
+ return PrintTimerUSec4s(buf,buf_size,1000*msec,mode);
+}
+
+static inline ccp PrintTimerSec4s
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ int sec, // seconds to print
+ sizeform_mode_t mode // support of DC_SFORM_ALIGN, DC_SFORM_PLUS
+)
+{
+ return PrintTimerUSec4s(buf,buf_size,1000000ll*sec,mode);
+}
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimerUSec7s
+(
+ char * buf, // result buffer (>7 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s_usec_t usec, // microseconds to print
+ sizeform_mode_t mode // support of DC_SFORM_ALIGN, DC_SFORM_PLUS
+);
+
+static inline ccp PrintTimerMSec7s
+(
+ char * buf, // result buffer (>7 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s_msec_t msec, // milliseconds to print
+ sizeform_mode_t mode // support of DC_SFORM_ALIGN, DC_SFORM_PLUS
+)
+{
+ return PrintTimerUSec7s(buf,buf_size,1000*msec,mode);
+}
+
+static inline ccp PrintTimerSec7s
+(
+ char * buf, // result buffer (>7 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ int sec, // seconds to print
+ sizeform_mode_t mode // support of DC_SFORM_ALIGN, DC_SFORM_PLUS
+)
+{
+ return PrintTimerUSec7s(buf,buf_size,1000000ll*sec,mode);
+}
+
+//-----------------------------------------------------------------------------
+
+typedef struct xtime_t
+{
+ time_t time;
+ suseconds_t usec;
+ u64 total_usec;
+ u64 rel_usec;
+
+} xtime_t;
+
+u64 GetXTime ( xtime_t * xtime );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan date & time ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// ScanInterval*() is old ==> try ScanDuration()
+
+char * ScanInterval
+(
+ time_t *res_time, // not NULL: store seconds
+ u32 *res_usec, // not NULL: store micro seconds of second
+ ccp source // source text
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanInterval64
+(
+ u64 *res_usec, // not NULL: store total micro seconds
+ ccp source // source text
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanDateTime
+(
+ time_t *res_time, // not NULL: store seconds
+ u32 *res_usec, // not NULL: store micro seconds of second
+ ccp source, // source text
+ bool allow_delta // true: allow +|- interval
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanDateTime64
+(
+ u64 *res_usec, // not NULL: store total micro seconds
+ ccp source, // source text
+ bool allow_delta // true: allow +|- interval
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Since 2001 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// Values for day and week are exact.
+// Values for month, quarter and year are based on 365.2425 days/year.
+
+//-----------------------------------------------------------------------
+// name full/13 short/11 alt/10 tiny/8
+//-----------------------------------------------------------------------
+// hour YYYY-MM-DD HH YY-MM-DD HH DD. HH:xx MM-DD HH
+// day YYYY-MM-DD << << YY-MM-DD
+// week YYYYwWW << << <<
+// month YYYY-MM << << <<
+// quarter YYYYqQ << << <<
+// year YYYY << << <<
+//-----------------------------------------------------------------------
+
+enum
+{
+ S2001_FW_FULL = 13, ///< Standard-Text mit vollem Jahr (maximal 13 Zeichen).
+ S2001_FW_SHORT = 11, ///< Verkürzte Stundenangabe (maximal 11 Zeichen).
+ S2001_FW_ALT = 10, ///< Alternative Stundenangabe mit »:xx« (maximal 10 Zeichen).
+ S2001_FW_TINY = 8, ///< Verkürzte Stunden- und Wochenangabe (maximal 8 Zeichen).
+
+ S2001_DUR_HOUR = 3600, ///< Dauer einer Stunde in Sekunden.
+ S2001_DUR_DAY = 86400, ///< Dauer eines Tages in Sekunden.
+ S2001_DUR_WEEK = 604800, ///< Dauer einer Woche in Sekunden.
+ S2001_DUR_MONTH = 2629746, ///< Dauer eines Monats in Sekunden (365.2425/12 Tage).
+ S2001_DUR_QUARTER = 7889238, ///< Dauer eines Quartals in Sekunden (365.2425/4 Tage).
+ S2001_DUR_YEAR = 31556952, ///< Dauer eines Jahres in Sekunden (365.2425 Tage).
+};
+
+///////////////////////////////////////////////////////////////////////////
+// hour support
+
+static inline int time2hour ( s64 time )
+ { return time / 3600 - 271752; }
+
+static inline double time2hourF ( s64 time )
+ { return time / 3600.0 - 271752; }
+
+static inline s64 hour2time ( int hour )
+ { return ( hour + 271752 ) * 3600; }
+
+static inline int hour2duration ( int hour )
+ { return S2001_DUR_HOUR; }
+
+ccp hour2text
+(
+ int hour, // hour to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use '-'
+ ccp sep_dt // NULL or separator of between date and time
+ // if NULL: use ' '
+);
+
+///////////////////////////////////////////////////////////////////////////
+// day support
+
+static inline int time2day ( s64 time )
+ { return time / 86400 - 11323; }
+
+static inline double time2dayF ( s64 time )
+ { return time / 86400.0 - 11323; }
+
+static inline s64 day2time ( int day )
+ { return ( day + 11323 ) * 86400; }
+
+static inline int day2duration ( int day )
+ { return S2001_DUR_DAY; }
+
+ccp day2text
+(
+ int day, // day to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use '-'
+ ccp sep_dt // ignored
+);
+
+///////////////////////////////////////////////////////////////////////////
+// week support
+
+static inline int time2week ( s64 time )
+ { return ( time - 978307200 ) / 604800; }
+
+static inline double time2weekF ( s64 time )
+ { return ( time - 978307200 ) / 604800.0; }
+
+static inline s64 week2time ( int week )
+ { return week * 604800 + 978307200; }
+
+static inline int week2duration ( int week )
+ { return S2001_DUR_WEEK; }
+
+ccp week2text
+(
+ int week, // week to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use 'w'
+ ccp sep_dt // ignored
+);
+
+///////////////////////////////////////////////////////////////////////////
+// month support
+
+int time2month ( time_t tim );
+time_t month2time ( int month );
+
+static inline int month2duration ( int month )
+ { return month2time(month+1) - month2time(month); }
+
+ccp month2text
+(
+ int month, // month to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use '-'
+ ccp sep_dt // ignored
+);
+
+///////////////////////////////////////////////////////////////////////////
+// quarter support
+
+int time2quarter ( time_t tim );
+time_t quarter2time ( int quarter );
+
+static inline int quarter2duration ( int quarter )
+ { return quarter2time(quarter+1) - quarter2time(quarter); }
+
+ccp quarter2text
+(
+ int quarter, // quarter to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use 'q'
+ ccp sep_dt // ignored
+);
+
+///////////////////////////////////////////////////////////////////////////
+// year support
+
+int time2year ( time_t tim );
+time_t year2time ( int year );
+
+static inline int year2duration ( int year )
+ { return year2time(year+1) - year2time(year); }
+
+static inline ccp year2text
+(
+ int year, // year to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // ignored
+ ccp sep_dt // ignored
+)
+{
+ return PrintCircBuf("%04d",year);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// sockaddr support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[sockaddr_t]] [[sockaddr_in_t]] [[sockaddr_un_t]] [[sockaddr_info_t]]
+
+typedef struct sockaddr sockaddr_t;
+typedef struct sockaddr_in sockaddr_in_t;
+typedef struct sockaddr_un sockaddr_un_t;
+
+typedef struct sockaddr_info_t
+{
+ sockaddr_t *sa; // pointer to sockaddr or sockaddr_*
+ uint size; // size of '*sa'
+}
+sockaddr_info_t;
+
+//-----------------------------------------------------------------------------
+
+// [[doxygen]]
+static inline sockaddr_info_t CreateSockaddrInfo ( cvp sa, uint size )
+{
+ sockaddr_info_t i =
+ {
+ .sa = (sockaddr_t*)sa,
+ .size = size
+ };
+ return i;
+}
+
+//-----------------------------------------------------------------------------
+
+// [[doxygen]]
+static inline void SetupSockaddrInfo ( sockaddr_info_t *sai, cvp sa, uint size )
+{
+ DASSERT(sai);
+ sai->sa = (sockaddr_t*)sa;
+ sai->size = size ? size : sizeof(sockaddr_in_t);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// PrintIP4*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define FW_IPV4_A 12 // 255.16777215
+#define FW_IPV4_B 13 // 255.255.65535
+#define FW_IPV4_C 15 // 255.255.255.255
+
+#define FW_IPV4_PORT 5 // 65535
+
+#define FW_IPV4_A_PORT (FW_IPV4_A+1+FW_IPV4_PORT)
+#define FW_IPV4_B_PORT (FW_IPV4_B+1+FW_IPV4_PORT)
+#define FW_IPV4_C_PORT (FW_IPV4_C+1+FW_IPV4_PORT)
+
+//-----------------------------------------------------------------------------
+
+char * PrintIP4A
+(
+ // print in A-notation
+
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+);
+
+char * PrintIP4B
+(
+ // print in A-notation
+
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+);
+
+//-----------------------------------------------------------------------------
+// print in C-notation (usual notation)
+
+char * PrintIP4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+);
+
+char * PrintLeftIP4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+);
+
+char * PrintRightIP4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port, // 0..0xffff: add ':port'
+ uint port_mode // 0: 'ip:port', 1: 'ip :port', 2: 'ip : port'
+);
+
+char * PrintAlignedIP4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+);
+
+//-----------------------------------------------------------------------------
+struct sockaddr_in;
+
+static inline char * PrintIP4sa
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+
+ const struct sockaddr_in *sa // socket address
+)
+{
+ DASSERT(sa);
+ return PrintIP4(buf,buf_size,
+ ntohl(sa->sin_addr.s_addr), ntohs(sa->sin_port) );
+}
+
+static inline char * PrintRightIP4sa
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+
+ struct sockaddr_in *sa // socket address
+)
+{
+ DASSERT(sa);
+ return PrintRightIP4(buf,buf_size,
+ ntohl(sa->sin_addr.s_addr), ntohs(sa->sin_port), 0 );
+}
+
+static inline char * PrintAlignedIP4sa
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+
+ struct sockaddr_in *sa // socket address
+)
+{
+ DASSERT(sa);
+ return PrintAlignedIP4(buf,buf_size,
+ ntohl(sa->sin_addr.s_addr), ntohs(sa->sin_port) );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// dynamic data support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[DynData_t]]
+
+typedef struct DynData_t
+{
+ u8 *data; // pointer to data, alloced
+ uint len; // length of used data
+ uint size; // size of allocated data
+}
+DynData_t;
+
+//-----------------------------------------------------------------------------
+
+void ResetDD ( DynData_t *dd );
+void InsertDD ( DynData_t *dd, const void *data, uint data_size );
+
+///////////////////////////////////////////////////////////////////////////////
+// [[DynDataList_t]]
+
+typedef struct DynDataList_t
+{
+ DynData_t *list; // pointer to the data list
+ uint used; // number of used data elements
+ uint size; // number of allocated pointer in 'list'
+}
+DynDataList_t;
+
+//-----------------------------------------------------------------------------
+
+void ResetDDL ( DynDataList_t *ddl );
+
+DynData_t *InsertDDL
+(
+ DynDataList_t *ddl, // valid dynamic data list
+ uint index, // element index to change
+ const void *data, // NULL or data to assign
+ uint data_size // size of data
+);
+
+DynData_t *GetDDL
+(
+ DynDataList_t *ddl, // valid dynamic data list
+ uint index, // element index to get
+ uint create // >0: create an empty element if not exist
+ // >index: create at least # elements
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct StringField_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[StringField_t]]
+
+typedef struct StringField_t
+{
+ ccp * field; // pointer to the string field
+ uint used; // number of used titles in the title field
+ uint size; // number of allocated pointer in 'field'
+ int (*func_cmp)( ccp s1, ccp s2 ); // compare function, default is strcmp()
+
+} StringField_t;
+
+//-----------------------------------------------------------------------------
+
+static inline void InitializeStringField ( StringField_t * sf )
+ { DASSERT(sf); memset(sf,0,sizeof(*sf)); }
+
+void ResetStringField ( StringField_t * sf );
+void MoveStringField ( StringField_t * dest, StringField_t * src );
+
+int FindStringFieldIndex ( const StringField_t * sf, ccp key, int not_found_value );
+ccp FindStringField ( const StringField_t * sf, ccp key );
+
+// return: true if item inserted/deleted
+bool InsertStringField ( StringField_t * sf, ccp key, bool move_key );
+bool RemoveStringField ( StringField_t * sf, ccp key );
+uint RemoveStringFieldByIndex ( StringField_t * sf, int index, int n );
+
+// helper function
+ccp * InsertStringFieldHelper ( StringField_t * sf, int idx );
+
+// append at the end an do not sort
+void AppendStringField ( StringField_t * sf, ccp key, bool move_key );
+
+// re-sort field using sf->func_cmp()
+void SortStringField ( StringField_t * sf );
+
+// return the index of the (next) item
+uint FindStringFieldHelper ( const StringField_t * sf, bool * found, ccp key );
+
+// find first pattern that matches
+ccp MatchStringField ( const StringField_t * sf, ccp key );
+
+//-----------------------------------------------------------------------------
+// file support
+
+enumError LoadStringField
+(
+ StringField_t * sf, // string field
+ bool init_sf, // true: initialize 'sf' first
+ bool sort, // true: sort 'sf'
+ ccp filename, // filename of source file
+ bool silent // true: don't print open/read errors
+);
+
+enumError SaveStringField
+(
+ StringField_t * sf, // valid string field
+ ccp filename, // filename of dest file
+ bool rm_if_empty // true: rm dest file if 'sf' is empty
+);
+
+enumError WriteStringField
+(
+ FILE * f, // open destination file
+ ccp filename, // NULL or filename (needed on write error)
+ StringField_t * sf, // valid string field
+ ccp prefix, // not NULL: insert prefix before each line
+ ccp eol // end of line text (if NULL: use LF)
+);
+
+//-----------------------------------------------------------------------------
+// save-state support
+
+void SaveCurrentStateStringField
+(
+ FILE *f, // output file
+ ccp name_prefix, // NULL or prefix of name, up to 40 chars
+ uint tab_pos, // tab pos of '='
+ const StringField_t *sf, // valid string field
+ EncodeMode_t emode // encoding mode
+);
+
+struct RestoreState_t;
+void RestoreStateStringField
+(
+ struct RestoreState_t *rs, // info data, may be modified
+ ccp name_prefix, // NULL or prefix of name, up to 40 chars
+ StringField_t *sf, // string field
+ bool init_sf, // true: initialize 'sf', otherwise reset
+ EncodeMode_t emode // encoding mode
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct ParamField_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[ParamFieldItem_t]]
+
+typedef struct ParamFieldItem_t
+{
+ ccp key; // string as main key
+ uint num; // user defined numeric parameter
+ void *data; // user defined pointer
+
+} ParamFieldItem_t;
+
+//-----------------------------------------------------------------------------
+// [[ParamField_t]]
+
+typedef struct ParamField_t
+{
+ ParamFieldItem_t * field; // pointer to the string field
+ uint used; // number of used titles in the title field
+ uint size; // number of allocated pointer in 'field'
+ bool free_data; // true: unused data will be free'd
+ // initialized with 'false'
+ int (*func_cmp)( ccp s1, ccp s2 ); // compare function, default is strcmp()
+} ParamField_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeParamField ( ParamField_t * pf );
+void ResetParamField ( ParamField_t * pf );
+void MoveParamField ( ParamField_t * dest, ParamField_t * src );
+
+int FindParamFieldIndex ( const ParamField_t * pf, ccp key, int not_found_value );
+ParamFieldItem_t * FindParamField ( const ParamField_t * pf, ccp key );
+ParamFieldItem_t * FindParamFieldByNum ( const ParamField_t * pf, uint num );
+ParamFieldItem_t * FindInsertParamField
+ ( ParamField_t * pf, ccp key, bool move_key, bool *old_found );
+ParamFieldItem_t * CountParamField ( ParamField_t * pf, ccp key, bool move_key );
+
+// return: true if item inserted/deleted
+bool InsertParamField ( ParamField_t * pf, ccp key, bool move_key, uint num, cvp data );
+bool ReplaceParamField( ParamField_t * pf, ccp key, bool move_key, uint num, cvp data );
+bool RemoveParamField ( ParamField_t * pf, ccp key );
+
+ParamFieldItem_t * InsertParamFieldEx
+ ( ParamField_t * pf, ccp key, bool move_key, uint num, bool *found );
+
+// append at the end an do not sort
+ParamFieldItem_t * AppendParamField
+ ( ParamField_t * pf, ccp key, bool move_key, uint num, cvp data );
+
+// return the index of the (next) item
+uint FindParamFieldHelper ( const ParamField_t * pf, bool * found, ccp key );
+
+// find first pattern that matches
+ParamFieldItem_t * MatchParamField ( const ParamField_t * pf, ccp key );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Memory Map ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[MemMapItem_t]]
+
+typedef struct MemMapItem_t
+{
+ u64 off; // offset
+ u64 size; // size
+ u8 overlap; // system info: item overlaps other items
+ u8 index; // user defined index
+ char info[62]; // user defined info text
+
+} MemMapItem_t;
+
+//-----------------------------------------------------------------------------
+// [[MemMap_t]]
+
+typedef struct MemMap_t
+{
+ MemMapItem_t ** field; // pointer to the item field
+ uint used; // number of used titles in the item field
+ uint size; // number of allocated pointer in 'field'
+ u64 begin; // first address
+
+} MemMap_t;
+
+//-----------------------------------------------------------------------------
+// Memory maps allow duplicate entries.
+// The off+size pair is used as key.
+// The entries are sorted by off and size (small values first).
+//-----------------------------------------------------------------------------
+
+void InitializeMemMap ( MemMap_t * mm );
+void ResetMemMap ( MemMap_t * mm );
+
+void CopyMemMap
+(
+ MemMap_t * mm1, // merged mem map, not NULL, cleared
+ const MemMap_t * mm2, // NULL or second mem map
+ bool use_tie // TRUE: use InsertMemMapTie()
+);
+
+void MergeMemMap
+(
+ MemMap_t * mm1, // merged mem map, not NULL, not cleared
+ const MemMap_t * mm2, // NULL or second mem map
+ bool use_tie // TRUE: use InsertMemMapTie()
+);
+
+MemMapItem_t * FindMemMap
+(
+ // returns NULL or the pointer to the one! matched key (={off,size})
+
+ MemMap_t * mm,
+ off_t off,
+ off_t size
+);
+
+uint InsertMemMapIndex
+(
+ // returns the index of the new item
+
+ MemMap_t * mm, // mem map pointer
+ off_t off, // offset of area
+ off_t size // size of area
+);
+
+MemMapItem_t * InsertMemMap
+(
+ // returns a pointer to a new item (never NULL)
+
+ MemMap_t * mm, // mem map pointer
+ off_t off, // offset of area
+ off_t size // size of area
+);
+
+MemMapItem_t * InsertMemMapTie
+(
+ // returns a pointer to a new or existing item (never NULL)
+
+ MemMap_t * mm, // mem map pointer
+ off_t off, // offset of area
+ off_t size // size of area
+);
+
+void InsertMemMapWrapper
+(
+ void * param, // user defined parameter
+ u64 offset, // offset of object
+ u64 size, // size of object
+ ccp info // info about object
+);
+
+// Remove all entires with key. Return number of delete entries
+//uint RemoveMemMap ( MemMap_t * mm, off_t off, off_t size );
+
+// Return the index of the found or next item
+uint FindMemMapHelper ( MemMap_t * mm, off_t off, off_t size );
+
+// Calculate overlaps. Return number of items with overlap.
+uint CalcOverlapMemMap ( MemMap_t * mm );
+
+u64 FindFreeSpaceMemMap
+(
+ // return address or NULL on not-found
+
+ const MemMap_t * mm,
+ u64 addr_beg, // first possible address
+ u64 addr_end, // last possible address
+ u64 size, // minimal size
+ u64 align, // aligning
+ u64 *space // not NULL: store available space here
+);
+
+// Print out memory map
+void PrintMemMap ( MemMap_t * mm, FILE * f, int indent, ccp info_head );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Memory Map: Scan addresses ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[ScanAddr_t]]
+
+typedef struct ScanAddr_t
+{
+ uint stat; // 0:invalid, 1:single address, 2:range
+ uint addr; // address
+ uint size; // size inf bytes
+
+} ScanAddr_t;
+
+//-----------------------------------------------------------------------------
+
+char * ScanAddress
+(
+ // returns first not scanned char of 'arg'
+
+ ScanAddr_t * result, // result, initialized by ScanAddress()
+ ccp arg, // format: ADDR | ADDR:END | ADDR#SIZE
+ uint default_base // base for numbers without '0x' prefix
+ // 0: C like with octal support
+ // 10: standard value for decimal numbers
+ // 16: standard value for hex numbers
+);
+
+//-----------------------------------------------------------------------------
+
+uint InsertAddressMemMap
+(
+ // returns the number of added addresses
+
+ MemMap_t * mm, // mem map pointer
+ bool use_tie, // TRUE: use InsertMemMapTie()
+ ccp arg, // comma separated list of addresses
+ // formats: ADDR | ADDR:END | ADDR#SIZE
+ uint default_base // base for numbers without '0x' prefix
+ // 0: C like with octal support
+ // 10: standard value for decimal numbers
+ // 16: standard value for hex numbers
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// DataBuf_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+struct DataBuf_t;
+
+typedef enumError (*DataBufFill_t)
+(
+ // function to fill the buffer
+
+ struct DataBuf_t* db, // valid pointer to related DataBuf_t to fill
+ uint need, // number of bytes needed => wait for more data
+ int mode // user mode, null on auto fill
+);
+
+//-----------------------------------------------------------------------------
+// [[DataBuf_t]]
+
+typedef struct DataBuf_t
+{
+ uint size; // size of buffer
+ uint used; // number of used bytes
+ u64 offset; // current file offset, related to 'data'
+
+ u8 *buf; // pointer to buffer, cyclic usage
+ u8 *buf_end; // pointer to buffer end := buf + size
+
+ u8 *data; // pointer to data
+ u8 *data_end; // pointer to end of data
+
+ //--- user data
+
+ DataBufFill_t fill_func; // function to fill buffer
+ void *user_data; // NULL or pointer to user data for 'fill_func'
+}
+DataBuf_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// management
+
+void InitializeDataBuf
+ ( DataBuf_t *db, uint buf_size, DataBufFill_t func, void *user_data );
+void ResetDataBuf ( DataBuf_t *db );
+void ResizeDataBuf ( DataBuf_t *db, uint new_buf_size );
+void ClearDataBuf ( DataBuf_t *db, uint new_buf_size_if_not_null );
+void EmptyDataBuf ( DataBuf_t *db );
+void PackDataBuf ( DataBuf_t *db );
+
+///////////////////////////////////////////////////////////////////////////////
+// debugging
+
+bool IsValidDataBuf ( DataBuf_t *db );
+bool DumpDataBuf ( FILE *f, uint indent, DataBuf_t *db, bool if_invalid );
+
+// fill the buffer with a test pattern:
+// '-' for unused bytes
+// ">0123456789<" in a longer or shorter form for used bytes
+void FillPatternDataBuf ( DataBuf_t *db );
+
+///////////////////////////////////////////////////////////////////////////////
+// insert and remove data
+
+static inline enumError FillDataBuf( DataBuf_t *db, uint need, int mode )
+{
+ return db && db->fill_func ? db->fill_func(db,need,mode) : ERR_WARNING;
+}
+
+uint InsertDataBuf
+(
+ // Insert at top of buffer.
+ // Return the number of inserted bytes
+
+ DataBuf_t *db, // valid pointer to data buffer
+ const void *data, // data to insert
+ uint data_size, // size of 'data'
+ bool all_or_none // true: insert complete data or nothing
+);
+
+uint AppendDataBuf
+(
+ // Append data at end of buffer
+ // Return the number of inserted bytes
+
+ DataBuf_t *db, // valid pointer to data buffer
+ const void *data, // data to insert
+ uint data_size, // size of 'data'
+ bool all_or_none // true: insert complete data or nothing
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * GetPtrDataBuf
+(
+ // Return a pointer to the data and make sure,
+ // that the data is not wrapped. Return NULL if failed.
+
+ DataBuf_t *db, // valid pointer to data buffer
+ uint data_size, // number of needed bytes
+ uint align, // >1: Data alignment is also needed.
+ // Only 2,4,8,16,... are allowed, but not verified.
+ // Begin of buffer is always well aligned.
+ bool drop // true: On success, drop data from buffer
+ // In real, the data is available and only
+ // the pointers and counters are updated.
+);
+
+uint CopyDataBuf
+(
+ // Copy data from buffer, but don't remove it
+ // Return the number of copied bytes
+
+ DataBuf_t *db, // valid pointer to data buffer
+ void *dest, // destination buffer wit at least 'max_size' space
+ uint min_read, // return with 0 read bytes if less bytes available
+ // if 'min_read' > 'size', NULL is returned
+ uint max_read // try to read upto 'max_read' bytes
+);
+
+uint GetDataBuf
+(
+ // Copy data from buffer and remove it
+ // Return the number of copied bytes
+
+ DataBuf_t *db, // valid pointer to data buffer
+ void *dest, // destination buffer wit at least 'max_size' space
+ uint min_read, // return with 0 read bytes if less bytes available
+ uint max_read // try to read upto 'max_read' bytes
+);
+
+uint SkipDataBuf
+(
+ // Skip 'skip_size' from input stream. Read more data if needed.
+ // Return the number of really skiped bytes
+ // If <skip_size is returned, no more data is available.
+
+ DataBuf_t *db, // valid pointer to data buffer
+ uint skip_size // number of bytes to drop
+);
+
+uint DropDataBuf
+(
+ // Remove upto 'drop_size' bytes from buffer.
+ // Return the number of dropped bytes.
+
+ DataBuf_t *db, // valid pointer to data buffer
+ uint drop_size // number of bytes to drop
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ResizeElement_t, ResizeHelper_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct ResizeElement_t
+{
+ uint n_elem;
+ uint factor;
+}
+ResizeElement_t;
+
+//-----------------------------------------------------------------------------
+
+typedef struct ResizeHelper_t
+{
+ //--- basic data
+
+ uint src_size; // size of source
+ uint dest_size; // size of dest
+
+
+ //--- calculated data
+
+ uint sum_factor; // sum of all factors
+ uint half_factor; // half of 'sum_factor2'
+
+ uint idx_inc; // increment of 'src_idx'
+ uint frac_inc; // increment of 'src_frac'
+ uint frac_next; // if 'frac>=frac_next': increment 'src_idx'
+
+
+ //--- iteration data
+
+ uint dest_idx; // dest index: (0..dest_size(
+ uint src_idx; // src index: (0..src_size(
+ uint src_frac; // (0..frac_next(
+
+ ResizeElement_t // list terminates with n_elem=0
+ elem[4]; // ==> max 3 elements are used
+
+}
+ResizeHelper_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeResize
+(
+ ResizeHelper_t *r, // data structure
+ uint s_size, // size of source
+ uint d_size // size of dest
+);
+
+bool FirstResize ( ResizeHelper_t *r );
+bool NextResize ( ResizeHelper_t *r );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// restore configuration ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[RestoreStateLog_t]]
+
+typedef enum RestoreStateLog_t
+{
+ RSL_WARNINGS = 0x01, // general warnings
+ RSL_MISSED_NAMES = 0x02, // hints about missed names
+ RSL_UNUSED_NAMES = 0x04, // hints about unused names
+
+ RSL_ALL = 0x07 // all flags
+}
+RestoreStateLog_t;
+
+//-----------------------------------------------------------------------------
+// [[RestoreState_t]]
+
+typedef struct RestoreState_t
+{
+ ccp sect; // section name
+ ccp path; // section sub path
+ int index; // index or -1
+ ParamField_t param; // name=value parameters
+ cvp user_param; // pointer provided as parameter by user
+ cvp user_info; // pointer to deliver insfos between stages
+
+ RestoreStateLog_t log_mode; // log mode
+ FILE *log_file; // log file
+
+}
+RestoreState_t;
+
+//-----------------------------------------------------------------------------
+
+typedef void (*RestoreStateFunc)
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+);
+
+//-----------------------------------------------------------------------------
+
+typedef struct RestoreStateTab_t
+{
+ ccp name; // name of section
+ RestoreStateFunc func; // function to call
+ cvp user_table; // any user provided pointer
+}
+RestoreStateTab_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RestoreState
+(
+ const RestoreStateTab_t
+ *table, // section table, terminated by name==0
+ void *data, // file data, modified, terminated by NULL or LF
+ uint size, // size of file data
+ RestoreStateLog_t log_mode, // print warnings (bit field)
+ FILE *log_file, // error file; if NULL use stderr
+ cvp user_param // pointer provided by user
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+// print into cyclic buffer
+ccp PrintRestoreStateSection ( const RestoreState_t *rs );
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * GetParamField
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name // name of member
+);
+
+//-----------------------------------------------------------------------------
+
+int GetParamFieldINT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ int not_found // return this value if not found
+);
+
+//-----------------------------------------------------------------------------
+
+uint GetParamFieldUINT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ uint not_found // return this value if not found
+);
+
+//-----------------------------------------------------------------------------
+
+u64 GetParamFieldS64
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ s64 not_found // return this value if not found
+);
+
+//-----------------------------------------------------------------------------
+
+u64 GetParamFieldU64
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ u64 not_found // return this value if not found
+);
+
+//-----------------------------------------------------------------------------
+
+float GetParamFieldFLOAT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ float not_found // return this value if not found
+);
+
+//-----------------------------------------------------------------------------
+
+double GetParamFieldDBL
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ double not_found // return this value if not found
+);
+
+//-----------------------------------------------------------------------------
+
+long double GetParamFieldLDBL
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ long double not_found // return this value if not found
+);
+
+//-----------------------------------------------------------------------------
+
+int GetParamFieldBUF
+(
+ // returns >=0: length of read data; -1:nothing done (not_found==NULL)
+
+ char *buf, // buffer to store result
+ uint buf_size, // size of buffer
+
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ EncodeMode_t decode, // decoding mode, fall back to OFF
+ // supported: STRING, UTF8, BASE64, BASE64X
+ ccp not_found // not NULL: store this value if not found
+);
+
+//-----------------------------------------------------------------------------
+
+mem_t GetParamFieldMEM
+(
+ // Returns the decoded 'source'. Result is NULL-terminated.
+ // It points either to 'buf' or is alloced (on buf==NULL or to less space)
+ // If alloced (mem.ptr!=buf) => call FreeMem(&mem)
+
+ char *buf, // buffer to store result
+ uint buf_size, // size of buffer
+
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ EncodeMode_t decode, // decoding mode, fall back to OFF
+ // supported: STRING, UTF8, BASE64, BASE64X
+ mem_t not_found // not NULL: return this value
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// save + restore config by table ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[SaveRestoreType_t]]
+
+typedef enum SaveRestoreType_t
+{
+ SRT__TERM, // list terminator
+
+ SRT_BOOL, // type bool
+ SRT_UINT, // unsigned int of any size (%llu)
+ SRT_HEX, // unsigned int of any size as hex (%#llx)
+ SRT_INT, // signed int of any size (%lld)
+ SRT_FLOAT, // float (%.8g) or double (%.16g) or long double (%.20g)
+ SRT_XFLOAT, // float or double or long double as hex-float (%a/%La)
+
+ SRT_STRING_SIZE, // string with max size, var is char[]
+ SRT_STRING_ALLOC, // alloced string, var is ccp or char*
+ SRT_MEM, // alloced string, var is mem_t
+
+ SRT__IS_LIST, //----- from here: print lists; also used as separator
+
+ SRT_STRING_FIELD, // var is StringField_t
+ SRT_PARAM_FIELD, // var is ParamField_t
+}
+SaveRestoreType_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[SaveRestoreTab_t]]
+
+typedef struct SaveRestoreTab_t
+{
+ uint offset; // offset of variable
+ uint size; // sizeof( var or string )
+ ccp name; // name in configuration file
+ u16 n_elem; // >0: is array with N elements
+ u8 type; // SaveRestoreType_t
+ u8 emode; // EncodeMode_t
+}
+__attribute__ ((packed)) SaveRestoreTab_t;
+
+//-----------------------------------------------------------------------------
+
+// At the very beginning of the table, define 'SRT_NAME' by 2 lines:
+// #undef SRT_NAME
+// #define SRT_NAME name_of_the_struct
+
+#define DEF_SRT_VAR(v,ne,n,t,e) {offsetof(SRT_NAME,v),sizeof(((SRT_NAME*)0)->v),n,ne,t,e}
+
+#define DEF_SRT_BOOL(v,n) DEF_SRT_VAR(v,1,n,SRT_BOOL,0)
+#define DEF_SRT_UINT(v,n) DEF_SRT_VAR(v,1,n,SRT_UINT,0)
+#define DEF_SRT_HEX(v,n) DEF_SRT_VAR(v,1,n,SRT_HEX,0)
+#define DEF_SRT_INT(v,n) DEF_SRT_VAR(v,1,n,SRT_INT,0)
+#define DEF_SRT_FLOAT(v,n) DEF_SRT_VAR(v,1,n,SRT_FLOAT,0)
+#define DEF_SRT_XFLOAT(v,n) DEF_SRT_VAR(v,1,n,SRT_XFLOAT,0)
+
+#define DEF_SRT_STR_SIZE(v,n,e) DEF_SRT_VAR(v,1,n,SRT_STRING_SIZE,e)
+#define DEF_SRT_STR_ALLOC(v,n,e) DEF_SRT_VAR(v,1,n,SRT_STRING_ALLOC,e)
+#define DEF_SRT_MEM(v,n,e) DEF_SRT_VAR(v,1,n,SRT_MEM,e)
+#define DEF_SRT_STRING_FIELD(v,n,e) DEF_SRT_VAR(v,1,n,SRT_STRING_FIELD,e)
+#define DEF_SRT_PARAM_FIELD(v,n,e) DEF_SRT_VAR(v,1,n,SRT_PARAM_FIELD,e)
+
+#define DEF_SRT_BOOL_N(v,ne,n) DEF_SRT_VAR(v,ne,n,SRT_BOOL,0)
+#define DEF_SRT_UINT_N(v,ne,n) DEF_SRT_VAR(v,ne,n,SRT_UINT,0)
+#define DEF_SRT_HEX_N(v,ne,n) DEF_SRT_VAR(v,ne,n,SRT_HEX,0)
+#define DEF_SRT_INT_N(v,ne,n) DEF_SRT_VAR(v,ne,n,SRT_INT,0)
+#define DEF_SRT_FLOAT_N(v,ne,n) DEF_SRT_VAR(v,ne,n,SRT_FLOAT,0)
+#define DEF_SRT_XFLOAT_N(v,ne,n) DEF_SRT_VAR(v,ne,n,SRT_XFLOAT,0)
+
+#define DEF_SRT_ARRAY(v,n,t,e) \
+ {offsetof(SRT_NAME,v), sizeof(((SRT_NAME*)0)->v[0]), n, \
+ sizeof(((SRT_NAME*)0)->v)/sizeof(*((SRT_NAME*)0)->v), t, e }
+
+#define DEF_SRT_BOOL_A(v,n) DEF_SRT_ARRAY(v,n,SRT_BOOL,0)
+#define DEF_SRT_UINT_A(v,n) DEF_SRT_ARRAY(v,n,SRT_UINT,0)
+#define DEF_SRT_HEX_A(v,n) DEF_SRT_ARRAY(v,n,SRT_HEX,0)
+#define DEF_SRT_INT_A(v,n) DEF_SRT_ARRAY(v,n,SRT_INT,0)
+#define DEF_SRT_FLOAT_A(v,n) DEF_SRT_ARRAY(v,n,SRT_FLOAT,0)
+#define DEF_SRT_XFLOAT_A(v,n) DEF_SRT_ARRAY(v,n,SRT_XFLOAT,0)
+
+#define DEF_SRT_SEPARATOR() {0,0,0,0,SRT__IS_LIST,0}
+#define DEF_SRT_COMMENT(c) {0,0,c,0,SRT__IS_LIST,0}
+#define DEF_SRT_TERM() {0,0,0,0,SRT__TERM,0}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpStateTable
+(
+ FILE *f, // valid output file
+ int indent,
+ const SaveRestoreTab_t
+ *srt // list of variables
+);
+
+void SaveCurrentStateByTable
+(
+ FILE *f, // valid output file
+ cvp data0, // valid pointer to source struct
+ const SaveRestoreTab_t
+ *srt, // list of variables
+ ccp prefix, // NULL or prefix for names
+ uint fw_name // field width of name, 0=AUTO
+ // tabs are used for multiple of 8 and for AUTO
+);
+
+void RestoreStateByTable
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ void *data0, // valid pointer to destination struct
+ const SaveRestoreTab_t
+ *srt, // list of variables
+ ccp prefix // NULL or prefix for names
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// GrowBuffer_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[GrowBuffer_t]]
+
+typedef struct GrowBuffer_t
+{
+ // allways: gb->ptr[gb->used] == 0
+
+ u8 *buf; // NULL or data buffer
+ uint size; // size of 'buf'
+ uint grow_size; // >0: auto grow buffer by multiple of this
+ uint max_size; // >0: max size for auto grow
+
+ u8 *ptr; // pointer to first valid byte
+ uint used; // number of valid bytes behind 'ptr'
+ uint max_used; // max 'used' value
+
+ uint disabled; // disable rading from TCP socket
+}
+GrowBuffer_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void LogGrowBuffer
+(
+ FILE *f, // output file
+ int indent, // indention
+ const GrowBuffer_t *gb, // valid grow-buffer
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vfprintf(format,...)'
+)
+__attribute__ ((__format__(__printf__,4,5)));
+
+//-----------------------------------------------------------------------------
+
+void InitializeGrowBuffer ( GrowBuffer_t *gb, uint max_buf_size );
+void ResetGrowBuffer ( GrowBuffer_t *gb );
+uint ClearGrowBuffer ( GrowBuffer_t *gb ); // returns available space
+uint PurgeGrowBuffer ( GrowBuffer_t *gb ); // returns available space
+
+//-----------------------------------------------------------------------------
+
+uint GetSpaceGrowBuffer
+(
+ // returns the maximum possible space
+ const GrowBuffer_t *gb // valid grow-buffer object
+);
+
+//-----------------------------------------------------------------------------
+
+uint PrepareGrowBuffer
+(
+ // returns the number of available space, smaller than or equal 'size'
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ uint size, // needed size
+ bool force // always alloc enough space and return 'size'
+);
+
+uint InsertGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ const void *data, // data to insert
+ uint size // size of 'data'
+);
+
+static inline uint InsertCharGrowBuffer
+(
+ // returns the number of inserted bytes (0 on fail, 1 else)
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ char ch // any character including NULL
+)
+{
+ return InsertGrowBuffer(gb,&ch,1);
+}
+
+static inline uint InsertMemGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ mem_t mem // any data
+)
+{
+ return InsertGrowBuffer(gb,mem.ptr,mem.len);
+}
+
+uint InsertCapNGrowBuffer
+(
+ // insert 'cap_1' or 'cap_n' depending of 'n' and the existence of 'cap_*'
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ int n, // repeat number; if <1: insert nothing
+ mem_t cap_1, // default for N=1
+ mem_t cap_n // default for N>1
+);
+
+uint WriteCapN
+(
+ // print 'cap_1' or 'cap_n' depending of 'n' and the existence of 'cap_*'
+ // returns the number of printed bytes
+
+ FILE *f, // valid FILE
+ int n, // repeat number; if <1: insert nothing
+ mem_t cap_1, // default for N=1
+ mem_t cap_n // default for N>1
+);
+
+uint InsertCatchedGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ SavedStdFiles_t *ssf // saved output and catched data
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintArgGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ ccp format, // format string for vfprintf()
+ va_list arg // arguments for 'vsnprintf(format,...)'
+);
+
+uint PrintGrowBuffer
+(
+ // returns the number of inserted bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vsnprintf(format,...)'
+)
+__attribute__ ((__format__(__printf__,2,3)));
+
+//-----------------------------------------------------------------------------
+
+uint ConvertToCrLfGrowBuffer
+(
+ // returns the number of inserted CR bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ uint begin // index to begin with conversion
+);
+
+//-----------------------------------------------------------------------------
+
+uint DropGrowBuffer
+(
+ // returns the number of dropped bytes
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ uint size // number of bytes to drop
+);
+
+//-----------------------------------------------------------------------------
+#ifndef __APPLE__
+
+FILE * OpenFileGrowBuffer
+(
+ // returns a FILE opened by fmemopen() => call CloseFileGrowBuffer()
+
+ GrowBuffer_t *gb, // valid grow-buffer object
+ uint size // needed size
+);
+
+int CloseFileGrowBuffer
+(
+ // returns the number of wirtten bytes, or -1 on error
+ GrowBuffer_t *gb, // valid grow-buffer object
+ FILE *f // FILE returned by OpenFileGrowBuffer()
+);
+
+#endif // !__APPLE__
+//-----------------------------------------------------------------------------
+
+void SaveCurrentStateGrowBuffer
+(
+ FILE *f, // output file
+ ccp name_prefix, // NULL or prefix of name
+ uint tab_pos, // tab pos of '='
+ const GrowBuffer_t *gb // valid grow-buffer
+);
+
+void RestoreStateGrowBuffer
+(
+ GrowBuffer_t *gb, // valid grow-buffer
+ ccp name_prefix, // NULL or prefix of name
+ RestoreState_t *rs // info data, can be modified (cleaned after call)
+);
+
+//-----------------------------------------------------------------------------
+
+int WriteDirectGrowBuffer
+(
+ // write() data direct without blocking and without calling any notifier.
+ // If output buf is not empty or send failed, append the data to 'gb'.
+ // Returns the number of bytes added to the grow buffer or -1 on error.
+ // The data is send+stored completely (returns 'size') or not at all
+ // (returns '0'). -1 is returned if the socket is invalid.
+
+ GrowBuffer_t *gb, // grow buffer to cache data
+ int fd, // destination file
+ bool flush_output, // true: try to flush 'gb' before
+ cvp data, // data to send
+ uint size, // size of 'data', If NULL && flush: flush only
+ uint *send_count // not NULL: add num of sent bytes
+);
+
+//-----------------------------------------------------------------------------
+
+int SendDirectGrowBuffer
+(
+ // send() data direct without blocking and without calling any notifier.
+ // If output buf is not empty or send failed, append the data to 'gb'.
+ // Returns the number of bytes added to the grow buffer or -1 on error.
+ // The data is send+stored completely (returns 'size') or not at all
+ // (returns '0'). -1 is returned if the socket is invalid.
+
+ GrowBuffer_t *gb, // grow buffer to cache data
+ int sock, // destination socket
+ bool flush_output, // true: try to flush 'gb' before
+ cvp data, // data to send
+ uint size, // size of 'data', If NULL && flush: flush only
+ uint *send_count // not NULL: add num of sent bytes
+);
+
+//-----------------------------------------------------------------------------
+
+static inline int FlushGrowBuffer
+(
+ // Try to flush the bufer using SendDirectGrowBuffer() withour new data.
+
+ GrowBuffer_t *gb, // grow buffer to cache data
+ int sock, // destination socket
+ uint *send_count // not NULL: add num of sent bytes
+)
+{
+ return SendDirectGrowBuffer(gb,sock,true,0,0,send_count);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// misc ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define CONVERT_TO_STRING1(a) #a
+#define CONVERT_TO_STRING(a) CONVERT_TO_STRING1(a)
+
+extern int opt_new; // default 0
+
+float double2float ( double d ); // reduce precision
+
+uint CreateUniqueId ( int range );
+
+void Sha1Hex2Bin ( sha1_hash_t bin, ccp src, ccp end );
+void Sha1Bin2Hex ( sha1_hex_t hex, cvp bin );
+ccp GetSha1Hex ( cvp bin );
+
+///////////////////////////////////////////////////////////////////////////////
+
+static uint snprintfS ( char *buf, size_t size, ccp format, ... )
+ __attribute__ ((__format__(__printf__,3,4)));
+
+static inline uint snprintfS ( char *buf, size_t size, ccp format, ... )
+{
+ va_list arg;
+ va_start(arg,format);
+ const int res = vsnprintf(buf,size,format,arg);
+ va_end(arg);
+
+ return res < 0 ? 0 : res < size ? res : size - 1;
+}
+
+static char * snprintfE ( char *buf, char *end, ccp format, ... )
+ __attribute__ ((__format__(__printf__,3,4)));
+
+static inline char * snprintfE ( char *buf, char *end, ccp format, ... )
+{
+ const int size = end - buf;
+ DASSERT( size > 0 );
+
+ va_list arg;
+ va_start(arg,format);
+ const int res = vsnprintf(buf,size,format,arg);
+ va_end(arg);
+
+ return buf + ( res < 0 ? 0 : res < size ? res : size - 1 );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_BASICS_H
diff --git a/project/dclib/dclib-color.c b/project/dclib/dclib-color.c
new file mode 100644
index 0000000..341c915
--- /dev/null
+++ b/project/dclib/dclib-color.c
@@ -0,0 +1,4351 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+//#include <ncurses/curses.h>
+//#include <term.h>
+
+#include "dclib-color.h"
+#include "dclib-basics.h"
+#include "dclib-utf8.h"
+
+#ifdef DCLIB_TERMINAL
+ #include "dclib-terminal.h"
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// color modes ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t color_mode_keywords[] =
+{
+ { COLMD_OFF, "OFF", "NONE", 0 },
+ { COLMD_OFF, "NO-COLORS", "NOCOLORS", 0 },
+ { COLMD_OFF, "0-COLORS", "0COLORS", 0 },
+ { COLMD_AUTO, "AUTO", 0, 0 },
+ { COLMD_ON, "ON", 0, 0 },
+ { COLMD_8_COLORS, "8-COLORS", "8COLORS", 0 },
+ { COLMD_256_COLORS, "256-COLORS", "256COLORS", 0 },
+ {0,0,0,0}
+};
+
+ColorMode_t opt_colorize = COLMD_AUTO;
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanOptColorize ( ColorMode_t *opt, ccp arg, ccp err_prefix )
+{
+ if (!opt)
+ opt = &opt_colorize;
+
+ if (!arg)
+ {
+ if ( *opt < COLMD_ON )
+ *opt = COLMD_ON;
+ return 0;
+ }
+
+ int abbrev;
+ const KeywordTab_t *key = ScanKeyword(&abbrev,arg,color_mode_keywords);
+ if (!key)
+ return PrintKeywordError( color_mode_keywords, arg, abbrev,
+ err_prefix ? err_prefix : "Option --color: ",
+ "keyword");
+
+ *opt = key->id;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetColorModeName ( ColorMode_t col_mode, ccp res_not_found )
+{
+ return GetKewordNameById(color_mode_keywords,col_mode,res_not_found);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ColorMode_t NormalizeColorMode ( ColorMode_t col_mode, int n_colors )
+{
+ // returns COLMD_OFF or COLMD_8_COLORS or COLMD_256_COLORS
+
+ switch (col_mode)
+ {
+ case COLMD_OFF:
+ case COLMD_8_COLORS:
+ case COLMD_256_COLORS:
+ return col_mode;
+
+ case COLMD_ON:
+ return n_colors < 256 ? COLMD_8_COLORS : COLMD_256_COLORS;
+
+ default:
+ //case COLMD_AUTO:
+ return n_colors < 8 ? COLMD_OFF
+ : n_colors < 256 ? COLMD_8_COLORS : COLMD_256_COLORS;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ColorMode_t NormalizeColorModeByTermName
+(
+ ColorMode_t col_mode, // predefind mode
+ ccp term_name // find "256color" in terminal name.
+ // if NULL: use getenv("TERM")
+ // assume 8 or 256 colors!
+)
+{
+ if (!term_name)
+ term_name = getenv("TERM");
+ const int n_colors = term_name && strstr(term_name,"256color") ? 256 : 8;
+ return NormalizeColorMode(col_mode,n_colors);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ColorMode_t GetNextColorMode ( ColorMode_t col_mode )
+{
+ switch(col_mode)
+ {
+ case COLMD_8_COLORS: return COLMD_256_COLORS;
+ case COLMD_256_COLORS: return COLMD_OFF;
+ default: return COLMD_8_COLORS;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ColorMode_t GetPrevColorMode ( ColorMode_t col_mode )
+{
+ switch(col_mode)
+ {
+ case COLMD_8_COLORS: return COLMD_OFF;
+ case COLMD_256_COLORS: return COLMD_8_COLORS;
+ default: return COLMD_256_COLORS;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print lines with autobreak ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void PutLines
+(
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ int fw, // field width of output
+ int first_line, // length without prefix of already printed first line
+ ccp prefix, // NULL or prefix for each line
+ ccp text, // text to print
+ ccp eol // End Of Line test. If NULL -> LF
+)
+{
+ DASSERT(f);
+ DASSERT( indent >= 0 );
+
+ if (!prefix)
+ prefix = "";
+ if (!eol)
+ eol = "\n";
+ TRACE("PutLines(,%d,%d,%d,%.10s,%.20s)\n",indent,fw,first_line,prefix,text);
+ fw -= strlen(prefix);
+ if ( fw < 10 )
+ fw = 10;
+
+ ccp prefix1 = "";
+ int indent1, fw1;
+ if ( indent > first_line )
+ {
+ indent1 = indent - first_line;
+ fw1 = fw - indent;
+ }
+ else
+ {
+ indent1 = 0;
+ fw1 = fw - first_line;
+ }
+
+ fw -= indent;
+ if ( fw < 20 )
+ fw = 20;
+
+ if ( fw1 < 20 )
+ {
+ fputs(eol,f);
+ indent1 = indent;
+ fw1 = fw;
+ prefix1 = prefix;
+ }
+
+ while ( *text )
+ {
+ // skip blank and control
+ if ( *text == '\n' )
+ {
+ // don't skip spaces behind a LF ==> needed for tables
+ while ( *text > 0 && *text < ' ' )
+ text++;
+ }
+ else
+ {
+ // but ignore spaces on an automatic line break
+ while ( *text > 0 && *text <= ' ' )
+ text++;
+ }
+
+ // setup
+ ccp start = text, last_blank = text;
+ ccp max = text + fw1;
+
+ while ( text < max && *text && *text != '\n' )
+ {
+ if ( *text > 0 && *text <= ' ' )
+ last_blank = text;
+ text++;
+ }
+
+ // set back to last blank
+ if ( last_blank > start && (u8)*text > ' ' )
+ text = last_blank;
+
+ // print out
+ if ( *text || text > start )
+ fprintf(f,"%s%*s%.*s%s",
+ prefix1, indent1, "",
+ (int)(text-start), start, eol );
+
+ // use standard values for next lines
+ indent1 = indent;
+ fw1 = fw;
+ prefix1 = prefix;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintArgLines
+(
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ int fw, // field width of output
+ int first_line, // length without prefix of already printed first line
+ ccp prefix, // NULL or prefix for each line
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'format'
+)
+{
+ DASSERT(f);
+ DASSERT(format);
+
+ char buf[10000];
+
+ va_list arg2;
+ va_copy(arg2,arg);
+ int stat = vsnprintf(buf,sizeof(buf),format,arg2);
+ va_end(arg2);
+
+ if ( stat < sizeof(buf) )
+ {
+ PutLines(f,indent,fw,first_line,prefix,buf,0);
+ return;
+ }
+
+
+ //--- buffer too small, use dynamic memory
+
+ noPRINT("PrintArgGrowBuffer() -> MALLOC(%u)\n",stat+1);
+
+ char *temp = MALLOC(stat+1);
+ vsnprintf(temp,stat+1,format,arg);
+ va_end(arg);
+ PutLines(f,indent,fw,first_line,prefix,temp,0);
+ FREE(temp);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintLines
+(
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ int fw, // field width of output
+ int first_line, // length without prefix of already printed first line
+ ccp prefix, // NULL or prefix for each line
+ ccp format, // format string for vsnprintf()
+ ... // arguments for 'vsnprintf(format,...)'
+)
+{
+ DASSERT(f);
+ DASSERT(format);
+
+ va_list arg;
+ va_start(arg,format);
+ PrintArgLines(f,indent,fw,first_line,prefix,format,arg);
+ va_end(arg);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print colored lines with autobreak ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint PutColoredLines
+(
+ // returns the number of written lines
+
+ FILE * f, // valid output stream
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent of output
+ int fw, // field width; indent+prefix+eol don't count
+ ccp prefix, // NULL or prefix for each line
+ ccp eol, // End Of Line test. If NULL -> LF
+ ccp text // text to print
+)
+{
+ DASSERT(f);
+ DASSERT(text);
+
+
+ //--- normalize params
+
+ indent = NormalizeIndent(indent);
+
+ if ( fw < 1 || fw > 1000 )
+ fw = 1000;
+ else if ( fw < 20 )
+ fw = 20;
+
+ if (!eol)
+ eol = "\n";
+
+ if (!prefix)
+ prefix = "";
+ indent += strlen(prefix);
+
+ if (!colset)
+ colset = GetFileColorSet(f);
+
+
+ //--- setup tabs
+
+ u8 is_tab[200];
+ memset(is_tab,0,sizeof(is_tab));
+
+
+ //--- prepare output buffer
+
+ char buf[10000];
+ char *buf_end = buf + 3*sizeof(buf)/4;
+ int cont_indent = -1;
+ uint n_lines = 0, n_space = 0;
+ uint column = 0;
+ //ccp col_begin = "";
+
+ enum { GB_NONE, GB_SIGN, GB_COMMA, GB_SPACE } gb_mode = GB_NONE;
+
+ struct gb_save_t
+ {
+ ccp text; // text position before code assignment
+ char *dest; // destination
+ ccp col_active; // NULL or active color by last "{name}"
+ ccp col_embed; // NULL or active color "{name|text}"
+ };
+ struct gb_save_t cur = { text, buf, 0, 0 };
+ struct gb_save_t save = cur; // only valid if gb_mode != GB_NONE
+
+
+ //--- main loop
+
+ for(;;)
+ {
+ #ifdef TEST
+ if ( n_lines > 1000 )
+ return n_lines;
+ #endif
+
+ cur.text = text;
+ u32 code = ScanUTF8AnsiChar(&text);
+ switch(code)
+ {
+ case 0:
+ if (!column)
+ return n_lines;
+ // fall through
+
+ case '\r':
+ if ( *text == '\n' )
+ code = *text++;
+ // fall through
+
+ case '\n':
+ cur.text = text;
+ new_line:
+ *cur.dest = 0;
+ fprintf(f,"%-*s%s%s%s", indent,prefix, buf,
+ cur.col_active || cur.col_embed ? colset->reset : "", eol );
+ cur.dest = buf;
+ text = cur.text;
+ n_lines++;
+ column = 0;
+ n_space = 0;
+ gb_mode = GB_NONE;
+
+ if ( code == '\n' )
+ cont_indent = -1;
+ else if (!code)
+ return n_lines;
+ else if ( cont_indent > 0 )
+ n_space = cont_indent;
+
+ if ( cur.col_embed )
+ cur.dest = StringCopyE(cur.dest,buf+sizeof(buf),cur.col_embed);
+ else if ( cur.col_active )
+ cur.dest = StringCopyE(cur.dest,buf+sizeof(buf),cur.col_active);
+ break;
+
+ case '{':
+ if ( *text == '{' )
+ {
+ text++;
+ goto is_char;
+ }
+ else if ( *text == '}' )
+ {
+ cur.dest = StringCopyE(cur.dest,buf+sizeof(buf),colset->reset);
+ cur.col_active = cur.col_embed = 0;
+ text++;
+ }
+ else
+ {
+ ccp name = text;
+ while ( *text && *text != '|' && *text != '}' )
+ text++;
+ if (!*text)
+ break;
+
+ ccp col = "";
+ char namebuf[20];
+ const uint nlen = text - name;
+ if ( nlen < sizeof(namebuf) )
+ {
+ memcpy(namebuf,name,nlen);
+ namebuf[nlen] = 0;
+ col = GetColorByName(colset,namebuf);
+ }
+
+ if (*col)
+ {
+ cur.dest = StringCopyE(cur.dest,buf+sizeof(buf),col);
+ if ( *text == '}' )
+ cur.col_active = col;
+ else
+ cur.col_embed = col;
+ }
+ else if ( *text == '|' )
+ cur.col_embed = ""; // to detect the closing '}'
+ text++;
+ }
+ break;
+
+ case '}':
+ if (!cur.col_embed)
+ goto is_char;
+ cur.col_embed = 0;
+ cur.dest = StringCopyE(cur.dest,buf+sizeof(buf),
+ cur.col_active ? cur.col_active : colset->reset );
+ break;
+
+ case '\t':
+ case ' ':
+ if ( !n_space++ && column )
+ {
+ gb_mode = column < fw/2 ? GB_COMMA : GB_SPACE;
+ save = cur;
+ }
+ if ( code == '\t' )
+ {
+ uint pos;
+ for ( pos = column + n_space; pos < sizeof(is_tab); pos++ )
+ if (is_tab[pos])
+ {
+ n_space = pos - column;
+ break;
+ }
+ }
+ break;
+
+ case '|':
+ if ( *text == '|' )
+ {
+ text++;
+ goto is_char;
+ }
+ else if ( *text == '+' )
+ {
+ text++;
+ uint pos = column + n_space;
+ if ( pos < sizeof(is_tab) )
+ is_tab[pos] = 1;
+ }
+ else if ( *text == '-' )
+ {
+ uint pos = column + n_space;
+ if ( pos < sizeof(is_tab) )
+ is_tab[pos] = 0;
+ }
+ else if ( *text == '[' )
+ {
+ text++;
+ memset(is_tab,0,sizeof(is_tab));
+ for(;;)
+ {
+ while ( *text == ',' )
+ text++;
+
+ char *end;
+ uint num = str2ul(text,&end,10);
+ if ( text == end )
+ break;
+ text = end;
+ if ( num < sizeof(is_tab) )
+ is_tab[num] = 1;
+
+ if ( *text == '*' )
+ {
+ text++;
+ uint delta = str2ul(text,&end,10);
+ if ( text == end )
+ break;
+ if ( delta > 0 )
+ for ( ; num < sizeof(is_tab); num += delta )
+ is_tab[num] = 1;
+ }
+ }
+ if ( *text == ']' )
+ text++;
+ //HexDump16(stderr,0,0,is_tab,sizeof(is_tab));
+ }
+ else
+ {
+ if ( *text == '>' )
+ text++;
+ cont_indent = column + n_space;
+ if ( cont_indent > fw/2 )
+ cont_indent = fw/2;
+ }
+ break;
+
+ case '+':
+ case '-':
+ case '=':
+ case '/':
+ if ( gb_mode <= GB_SIGN )
+ {
+ gb_mode = GB_SIGN;
+ save = cur;
+ }
+ // fall through
+
+ default:
+ is_char:
+ column += n_space;
+ if ( column >= fw )
+ {
+ // auto break line
+ if ( gb_mode > GB_NONE )
+ cur = save;
+ while ( *cur.text == ' ' )
+ cur.text++;
+ goto new_line;
+ }
+
+ while ( n_space > 0 )
+ *cur.dest++ = ' ', n_space--;
+ cur.dest = PrintUTF8Char(cur.dest,code);
+ if ( cur.dest > buf_end )
+ goto new_line; // should never happen!
+ column++;
+
+ if ( gb_mode <= GB_COMMA && ( code == ',' || code == ';' ))
+ {
+ gb_mode = GB_COMMA;
+ save = cur;
+ save.text++;
+ }
+ break;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintArgColoredLines
+(
+ // returns the number of written lines
+
+ FILE * f, // valid output stream
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent of output
+ int fw, // field width; indent+prefix+eol don't count
+ ccp prefix, // NULL or prefix for each line
+ ccp eol, // End Of Line test. If NULL -> LF
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'format'
+)
+{
+ DASSERT(f);
+ DASSERT(format);
+
+ char buf[10000];
+
+ va_list arg2;
+ va_copy(arg2,arg);
+ int stat = vsnprintf(buf,sizeof(buf),format,arg2);
+ va_end(arg2);
+
+ if ( stat < sizeof(buf) )
+ return PutColoredLines(f,colset,indent,fw,prefix,eol,buf);
+
+
+ //--- buffer too small, use dynamic memory
+
+ noPRINT("PrintArgGrowBuffer() -> MALLOC(%u)\n",stat+1);
+
+ char *temp = MALLOC(stat+1);
+ stat = vsnprintf(temp,stat+1,format,arg);
+ const uint res = PutColoredLines(f,colset,indent,fw,prefix,eol,temp);
+ FREE(temp);
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintColoredLines
+(
+ // returns the number of written lines
+
+ FILE * f, // valid output stream
+ const ColorSet_t *colset, // NULL or color set
+ int indent, // indent of output
+ int fw, // field width; indent+prefix+eol don't count
+ ccp prefix, // NULL or prefix for each line
+ ccp eol, // End Of Line test. If NULL -> LF
+ ccp format, // format string for vsnprintf()
+ ... // arguments for 'vsnprintf(format,...)'
+)
+{
+ DASSERT(f);
+ DASSERT(format);
+
+ va_list arg;
+ va_start(arg,format);
+ const uint res
+ = PrintArgColoredLines(f,colset,indent,fw,prefix,eol,format,arg);
+ va_end(arg);
+ return res;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// terminal ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// Sequence counter for output to stdout (and stderr).
+// Functions shall increment it on output, so that other functions
+// know about a destroyed screen.
+
+uint stdout_seq_count = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint opt_width = 0;
+uint opt_height = 0;
+
+int ScanOptWidth ( ccp arg )
+{
+ return ERR_OK != ScanSizeOptU32(
+ &opt_width, // u32 * num
+ arg, // ccp source
+ 1, // default_factor1
+ 0, // int force_base
+ "width", // ccp opt_name
+ 40, // u64 min
+ 10000, // u64 max
+ 1, // u32 multiple
+ 0, // u32 pow2
+ true // bool print_err
+ );
+}
+
+int ScanOptHeight ( ccp arg )
+{
+ return ERR_OK != ScanSizeOptU32(
+ &opt_height, // u32 * num
+ arg, // ccp source
+ 1, // default_factor1
+ 0, // int force_base
+ "height", // ccp opt_name
+ 40, // u64 min
+ 10000, // u64 max
+ 1, // u32 multiple
+ 0, // u32 pow2
+ true // bool print_err
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+term_size_t GetTermSize ( int default_width, int default_height )
+{
+ TRACE("GetTermWidth(%d,%d) opt=%d,%d\n",
+ default_width, default_height, opt_width, opt_height );
+
+ term_size_t ts;
+ if ( opt_width > 0 && opt_height > 0 )
+ {
+ ts.width = opt_width;
+ ts.height = opt_height;
+ }
+ else
+ {
+ ts = GetTermSizeFD(STDOUT_FILENO,default_width,default_height);
+ if ( opt_width > 0 )
+ ts.width = opt_width;
+ if ( opt_height > 0 )
+ ts.height = opt_height;
+ }
+
+ return ts;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+term_size_t GetTermSizeFD ( int fd, int default_width, int default_height )
+{
+ PRINT("GetTermSizeFD(%d,%d,%d)\n",fd,default_width,default_height);
+
+ #ifdef TIOCGSIZE
+ TRACE(" - have TIOCGSIZE\n");
+ #endif
+
+ #ifdef TIOCGWINSZ
+ TRACE(" - have TIOCGWINSZ\n");
+ #endif
+
+ #ifndef WIN_SZS_LIB
+ if (isatty(fd))
+ {
+ TRACE(" - isatty\n");
+
+ #ifdef TIOCGSIZE
+ {
+ struct ttysize ts;
+ if ( !ioctl(fd,TIOCGSIZE,&ts))
+ {
+ PRINT(" - TIOCGSIZE = %d*%d\n",ts.ts_cols,ts.ts_lines);
+ if ( ts.ts_cols > 0 && ts.ts_lines > 0 )
+ {
+ term_size_t tes = {ts.ts_cols,ts.ts_lines};
+ return tes;
+ }
+ }
+ }
+ #endif
+
+ #ifdef TIOCGWINSZ
+ {
+ struct winsize ws;
+ if ( !ioctl(fd,TIOCGWINSZ,&ws))
+ {
+ PRINT(" - TIOCGWINSZ = %d*%d\n",ws.ws_col,ws.ws_row);
+ if ( ws.ws_col > 0 && ws.ws_row > 0 )
+ {
+ term_size_t ts = {ws.ws_col,ws.ws_row};
+ return ts;
+ }
+ }
+ }
+ #endif
+ }
+ #endif
+
+ term_size_t ts = {default_width,default_height};
+ return ts;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int GetTermWidth ( int default_value, int min_value )
+{
+ TRACE("GetTermWidth(%d,%d) opt=%d\n",default_value,min_value,opt_width);
+
+ int term_width = opt_width > 0
+ ? opt_width
+ : GetTermWidthFD(STDOUT_FILENO,-1,min_value);
+
+ return term_width > 0 ? term_width : default_value;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetTermWidthFD ( int fd, int default_value, int min_value )
+{
+ TRACE("GetTermWidthFD(%d,%d,%d)\n",fd,default_value,min_value);
+
+ term_size_t ts = GetTermSizeFD(fd,default_value,25);
+ return ts.width > min_value ? ts.width : min_value;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int GetTermHeight ( int default_value, int min_value )
+{
+ TRACE("GetTermHeight(%d,%d) opt=%d\n",default_value,min_value,opt_height);
+
+ int term_height = opt_height > 0
+ ? opt_height
+ : GetTermHeightFD(STDOUT_FILENO,-1,min_value);
+
+ return term_height > 0 ? term_height : default_value;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetTermHeightFD ( int fd, int default_value, int min_value )
+{
+ TRACE("GetTermHeightFD(%d,%d,%d)\n",fd,default_value,min_value);
+
+ term_size_t ts = GetTermSizeFD(fd,default_value,25);
+ return ts.height > min_value ? ts.height : min_value;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+term_size_t auto_term_size = {80,25};
+uint auto_term_resized = 0; // incremented for each resize, init by 0
+uint auto_term_size_dirty = 0; // >0: SIGWINCH received, auto_term_size is dirty
+
+//-----------------------------------------------------------------------------
+
+bool GetAutoTermSize()
+{
+ // return true if changed
+
+ auto_term_size_dirty = 0;
+ int fd = isatty(1) ? 1 : isatty(0) ? 0 : -1;
+ if ( fd >= 0 )
+ {
+ term_size_t size
+ = GetTermSizeFD(fd,auto_term_size.width,auto_term_size.height);
+ if (memcmp(&auto_term_size,&size,sizeof(size)))
+ {
+ auto_term_size = size;
+ if (!++auto_term_resized)
+ auto_term_resized++;
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+static void Signal_SIGWINCH ( int signal )
+{
+ if (!++auto_term_size_dirty)
+ auto_term_size_dirty++;
+}
+
+void EnableAutoTermSize()
+{
+ signal(SIGWINCH,Signal_SIGWINCH);
+ GetAutoTermSize();
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp TermCursorUp = TERM_CURSOR_UP; // move cursor up
+ccp TermCursorDown = TERM_CURSOR_DOWN; // move cursor down
+ccp TermCursorRight = TERM_CURSOR_RIGHT; // move cursor right
+ccp TermCursorLeft = TERM_CURSOR_LEFT; // move cursor left
+
+ccp TermCursorUpN = TERM_CURSOR_UP_N; // move cursor N* up (format 1 '%d')
+ccp TermCursorDownN = TERM_CURSOR_DOWN_N; // move cursor N* down (format 1 '%d')
+ccp TermCursorRightN = TERM_CURSOR_RIGHT_N; // move cursor N* right (format 1 '%d')
+ccp TermCursorLeftN = TERM_CURSOR_LEFT_N; // move cursor N* left (format 1 '%d')
+
+ccp TermClearEOL = TERM_CLEAR_EOL; // clear from cursor to end of line
+ccp TermClearBOL = TERM_CLEAR_BOL; // clear from beginning of line to cursor
+ccp TermClearLine = TERM_CLEAR_LINE; // clear entire line
+
+ccp TermClearEOD = TERM_CLEAR_EOD; // clear from cursor to end of display
+ccp TermClearBOD = TERM_CLEAR_BOD; // clear from beginning of display to cursor
+ccp TermClearDisplay = TERM_CLEAR_DISPLAY; // clear Entire display
+
+ccp TermSetWrap = TERM_SET_WRAP; // enabled line wrapping
+ccp TermResetWrap = TERM_RESET_WRAP; // disable line wrapping
+
+ccp TermTextModeBeg = TERM_TEXT_MODE_BEG; // begin of text mode sequence
+ccp TermTextModeEnd = TERM_TEXT_MODE_END; // end of text mode sequence
+ccp TermTextModeReset = TERM_TEXT_MODE_RESET; // reset text mode
+
+static uint grayscale_tab[4] = {0,8,7,15};
+
+#if SUPPORT_36_COLORS
+
+ const char ColorIndexName[TCI__N][16] =
+ {
+ "black",
+ "dark_gray_1",
+ "dark_gray_2",
+ "dark_gray_3",
+ "light_gray_1",
+ "light_gray_2",
+ "light_gray_3",
+ "white",
+
+ "red",
+ "red_orange",
+ "orange",
+ "orange_yellow",
+ "yellow",
+ "yellow_green",
+ "green",
+ "green_cyan",
+ "cyan",
+ "cyan_blue",
+ "blue",
+ "blue_magenta",
+ "magenta",
+ "magenta_red",
+
+ "b_red",
+ "b_red_orange",
+ "b_orange",
+ "b_orange_yellow",
+ "b_yellow",
+ "b_yellow_green",
+ "b_green",
+ "b_green_cyan",
+ "b_cyan",
+ "b_cyan_blue",
+ "b_blue",
+ "b_blue_magenta",
+ "b_magenta",
+ "b_magenta_red",
+ };
+#else // !SUPPORT_36_COLORS
+
+ const char ColorIndexName[TCI__N][11] =
+ {
+ "black",
+ "dark gray",
+ "light gray",
+ "white",
+
+ "red",
+ "orange",
+ "yellow",
+ "green",
+ "cyan",
+ "blue",
+ "magenta",
+
+ "b_red",
+ "b_orange",
+ "b_yellow",
+ "b_green",
+ "b_cyan",
+ "b_blue",
+ "b_magenta",
+ };
+#endif // !SUPPORT_36_COLORS
+
+const int Color18Index[18+1] = // 18 elements + -1 as terminator
+{
+ TCI_BLACK, // gray scale
+ TCI_DARKGRAY,
+ TCI_LIGHTGRAY,
+ TCI_WHITE,
+
+ TCI_RED, // normal colors in rainbow order
+ TCI_ORANGE,
+ TCI_YELLOW,
+ TCI_GREEN,
+ TCI_CYAN,
+ TCI_BLUE,
+ TCI_MAGENTA,
+
+ TCI_B_RED, // bold colors in rainbow order
+ TCI_B_ORANGE,
+ TCI_B_YELLOW,
+ TCI_B_GREEN,
+ TCI_B_CYAN,
+ TCI_B_BLUE,
+ TCI_B_MAGENTA,
+
+ -1
+};
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static char * GetColorCircBuf ( uint len )
+{
+ static char circ_buf[1024];
+ static char *circ_ptr = circ_buf;
+ DASSERT( circ_ptr >= circ_buf && circ_ptr <= circ_buf + sizeof(circ_buf) );
+
+ if ( len > sizeof(circ_buf)/8 )
+ return GetCircBuf(len);
+
+ if ( circ_buf + sizeof(circ_buf) - circ_ptr < len )
+ circ_ptr = circ_buf;
+
+ char *buf = circ_ptr;
+ circ_ptr += len;
+ DASSERT( circ_ptr >= circ_buf && circ_ptr <= circ_buf + sizeof(circ_buf) );
+
+ return buf;
+}
+
+//-----------------------------------------------------------------------------
+
+ccp GetTextMode
+(
+ // returns a pointer to an internal circulary buffer (not GetCircBuf())
+ ColorMode_t col_mode, // if < COLMD_ON: return an empty string
+ TermTextMode_t mode // mode for text generation
+)
+{
+ if ( col_mode < COLMD_ON )
+ return EmptyString;
+
+ if ( mode == TTM_RESET )
+ return TermTextModeReset;
+
+ uint len = strlen(TermTextModeBeg) + strlen(TermTextModeEnd) + 2;
+
+ if ( mode & TTM_F_COLOR )
+ len += 3;
+ else if ( mode & TTM_F_GRAYSCALE )
+ len += 8;
+
+ if ( mode & TTM_F_BG_COLOR )
+ len += 3;
+ else if ( mode & TTM_F_BG_GRAYSCALE )
+ len += 8;
+
+ if ( mode & TTM_RESET )
+ len += 2;
+ if ( mode & (TTM_BOLD|TTM_NO_BOLD) )
+ len += 3;
+ if ( mode & (TTM_UL|TTM_NO_UL) )
+ len += 3;
+
+ //--- use a private circulary buffer (long term valid)
+
+ char *buf = GetColorCircBuf(len);
+
+ //--- compose string
+
+ uint sep = 0;
+ char *end = buf + len;
+ char *dest = StringCopyS(buf,len,TermTextModeBeg);
+
+ if ( mode & TTM_RESET )
+ {
+ *dest++ = '0';
+ sep++;
+ }
+
+ if ( mode & TTM_BOLD )
+ {
+ if (sep++)
+ *dest++ = ';';
+ *dest++ = '1';
+ }
+ else if ( mode & TTM_NO_BOLD )
+ {
+ if (sep++)
+ *dest++ = ';';
+ *dest++ = '2';
+ *dest++ = '2';
+ }
+
+ if ( mode & TTM_UL )
+ {
+ if (sep++)
+ *dest++ = ';';
+ *dest++ = '4';
+ }
+ else if ( mode & TTM_NO_UL )
+ {
+ if (sep++)
+ *dest++ = ';';
+ *dest++ = '2';
+ *dest++ = '4';
+ }
+
+ if ( mode & TTM_F_COLOR )
+ {
+ if (sep++)
+ *dest++ = ';';
+ dest = snprintfE(dest,end,"%u",(mode&TTM_M_COLOR)+30);
+ }
+ else if ( mode & TTM_F_GRAYSCALE )
+ {
+ if (sep++)
+ *dest++ = ';';
+ dest = snprintfE(dest,end,"38;5;%u",
+ grayscale_tab[ mode & TTM_M_GRAYSCALE ]);
+ }
+
+ if ( mode & TTM_F_BG_COLOR )
+ {
+ if (sep++)
+ *dest++ = ';';
+ dest = snprintfE(dest,end,"%u",((mode&TTM_M_BG_COLOR)>>TTM_SHIFT_BG)+40);
+ }
+ else if ( mode & TTM_F_BG_GRAYSCALE )
+ {
+ if (sep++)
+ *dest++ = ';';
+ dest = snprintfE(dest,end,"48;5;%u",
+ grayscale_tab[ (mode & TTM_M_BG_GRAYSCALE) >> TTM_SHIFT_BG ]);
+ }
+
+ StringCopyE(dest,end,TermTextModeEnd);
+ return buf;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#if SUPPORT_36_COLORS
+
+ ccp GetColorMode // 36 colors version
+ (
+ // returns a pointer to an internal circulary buffer (not GetCircBuf())
+
+ ColorMode_t col_mode, // if < COLMD_ON: return an empty string
+ TermColorIndex_t font_index, // index of font color
+ TermColorIndex_t bg_index, // index of background color
+ GetColorOption_t option // execution options
+ )
+ {
+ col_mode = NormalizeColorMode(col_mode,8);
+ if ( col_mode < COLMD_ON )
+ return EmptyString;
+
+
+ //--- recommended combinations
+
+ if ( option & GCM_ALT
+ && (uint)font_index < TCI__IGNORE
+ && (uint)bg_index < TCI__IGNORE )
+ {
+ static const u8 tab[TCI__N_BG][TCI__N_FONT] =
+ {
+ //--- TCI_GRAY0 = TCI_BLACK
+ {
+ TCI_GRAY2, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY2, // TCI_GRAY1
+ TCI_GRAY3, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GRAY1
+ {
+ TCI_GRAY3, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY3, // TCI_GRAY1
+ TCI_GRAY4, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY4, // TCI_GRAY3
+ TCI_GRAY5, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GRAY2 = TCI_DARKGRAY
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY0, // TCI_GRAY1
+ TCI_GRAY0, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY4, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GRAY3
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY0, // TCI_GRAY1
+ TCI_GRAY1, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY1, // TCI_GRAY3
+ TCI_GRAY5, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GRAY4 = TCI_LIGHTGRAY
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY1, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY2, // TCI_GRAY3
+ TCI_GRAY2, // TCI_GRAY4
+ TCI_GRAY6, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GRAY5 = TCI_LIGHTGRAY
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY2, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY3, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY7, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GRAY6
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY2, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY3, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY4, // TCI_GRAY6
+ TCI_GRAY4, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_ORANGE_YELLOW, // TCI_YELLOW
+ TCI_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_GREEN, // TCI_GREEN
+ TCI_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GRAY7 = TCI_WHITE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY4, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY5, // TCI_GRAY6
+ TCI_GRAY5, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_ORANGE_YELLOW, // TCI_YELLOW
+ TCI_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_GREEN, // TCI_GREEN
+ TCI_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_RED
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY1, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY4, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_ORANGE, // TCI_RED
+ TCI_B_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_RED_ORANGE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY2, // TCI_GRAY3
+ TCI_GRAY5, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED, // TCI_RED_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_ORANGE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED, // TCI_RED_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_ORANGE_YELLOW
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY6, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_YELLOW
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY2, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY3, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_YELLOW_GREEN
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY6, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GREEN
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_GREEN_CYAN
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY6, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_CYAN
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY6, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_CYAN_BLUE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY1, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY2, // TCI_GRAY3
+ TCI_GRAY2, // TCI_GRAY4
+ TCI_GRAY6, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_BLUE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY0, // TCI_GRAY1
+ TCI_GRAY3, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY4, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_BLUE_MAGENTA
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY0, // TCI_GRAY1
+ TCI_GRAY3, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY4, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_MAGENTA
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY1, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY4, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED_ORANGE, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_MAGENTA_RED
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY1, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY4, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_RED_ORANGE, // TCI_RED
+ TCI_B_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_B_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_RED
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY1, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY4, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_B_ORANGE, // TCI_RED
+ TCI_B_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ TCI_B_MAGENTA, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_RED_ORANGE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY2, // TCI_GRAY3
+ TCI_GRAY5, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED, // TCI_RED_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_ORANGE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY3, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_ORANGE_YELLOW
+ {
+
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY4, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_YELLOW, // TCI_YELLOW
+ TCI_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_GREEN, // TCI_GREEN
+ TCI_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_YELLOW
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY4, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY5, // TCI_GRAY6
+ TCI_GRAY5, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_YELLOW, // TCI_YELLOW
+ TCI_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_GREEN, // TCI_GREEN
+ TCI_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_YELLOW_GREEN
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY4, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY5, // TCI_GRAY6
+ TCI_GRAY5, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_YELLOW, // TCI_YELLOW
+ TCI_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_GREEN, // TCI_GREEN
+ TCI_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_GREEN
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY4, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY5, // TCI_GRAY6
+ TCI_GRAY5, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_YELLOW, // TCI_YELLOW
+ TCI_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_GREEN, // TCI_GREEN
+ TCI_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_GREEN_CYAN
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY4, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY5, // TCI_GRAY6
+ TCI_GRAY5, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_YELLOW, // TCI_YELLOW
+ TCI_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_GREEN, // TCI_GREEN
+ TCI_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_CYAN
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY4, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY5, // TCI_GRAY6
+ TCI_GRAY5, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_YELLOW, // TCI_YELLOW
+ TCI_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_GREEN, // TCI_GREEN
+ TCI_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_CYAN_BLUE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_BLUE
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_BLUE_MAGENTA
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_MAGENTA
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED_ORANGE, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+
+ //--- TCI_B_MAGENTA_RED
+ {
+ TCI_GRAY0, // TCI_GRAY0 = TCI_BLACK
+ TCI_GRAY1, // TCI_GRAY1
+ TCI_GRAY2, // TCI_GRAY2 = TCI_DARKGRAY
+ TCI_GRAY3, // TCI_GRAY3
+ TCI_GRAY4, // TCI_GRAY4
+ TCI_GRAY5, // TCI_GRAY5 = TCI_LIGHTGRAY
+ TCI_GRAY6, // TCI_GRAY6
+ TCI_GRAY7, // TCI_GRAY7 = TCI_WHITE
+
+ TCI_RED, // TCI_RED
+ TCI_RED, // TCI_RED_ORANGE
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_ORANGE_YELLOW, // TCI_ORANGE_YELLOW
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_YELLOW_GREEN, // TCI_YELLOW_GREEN
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_GREEN_CYAN, // TCI_GREEN_CYAN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN_BLUE, // TCI_CYAN_BLUE
+ TCI_BLUE, // TCI_BLUE
+ TCI_BLUE_MAGENTA, // TCI_BLUE_MAGENTA
+ TCI_MAGENTA, // TCI_MAGENTA
+ TCI_MAGENTA_RED, // TCI_MAGENTA_RED
+ },
+ };
+
+ const uint font = font_index < TCI_B_RED ? font_index
+ : font_index - (TCI_B_RED-TCI_RED);
+ font_index = tab[bg_index][font];
+ }
+
+
+ //--- color index
+
+ static const u8 color_index_8[] =
+ {
+ 0, // TCI_GRAY0 = TCI_BLACK
+ 0, // TCI_GRAY1
+ 8, // TCI_GRAY2 = TCI_DARKGRAY
+ 8, // TCI_GRAY3
+ 7, // TCI_GRAY4
+ 7, // TCI_GRAY5 = TCI_LIGHTGRAY
+ 15, // TCI_GRAY6
+ 15, // TCI_GRAY7 = TCI_WHITE
+
+ 1, // TCI_RED
+ 1, // TCI_RED_ORANGE
+ 1, // TCI_ORANGE
+ 1, // TCI_ORANGE_YELLOW
+ 3, // TCI_YELLOW
+ 3, // TCI_YELLOW_GREEN
+ 2, // TCI_GREEN
+ 2, // TCI_GREEN_CYAN
+ 6, // TCI_CYAN
+ 6, // TCI_CYAN_BLUE
+ 4, // TCI_BLUE
+ 4, // TCI_BLUE_MAGENTA
+ 5, // TCI_MAGENTA
+ 5, // TCI_MAGENTA_RED
+
+ 9, // TCI_B_RED
+ 9, // TCI_B_RED_ORANGE
+ 9, // TCI_B_ORANGE
+ 9, // TCI_B_ORANGE_YELLOW
+ 11, // TCI_B_YELLOW
+ 11, // TCI_B_YELLOW_GREEN
+ 10, // TCI_B_GREEN
+ 10, // TCI_B_GREEN_CYAN
+ 14, // TCI_B_CYAN
+ 14, // TCI_B_CYAN_BLUE
+ 12, // TCI_B_BLUE
+ 12, // TCI_B_BLUE_MAGENTA
+ 13, // TCI_B_MAGENTA
+ 13, // TCI_B_MAGENTA_RED
+ };
+
+ static const u8 color_index_256[] =
+ {
+ 232, // TCI_GRAY0 = TCI_BLACK
+ 236, // TCI_GRAY1
+ 240, // TCI_GRAY2 = TCI_DARKGRAY
+ 243, // TCI_GRAY3
+ 246, // TCI_GRAY4
+ 249, // TCI_GRAY5 = TCI_LIGHTGRAY
+ 252, // TCI_GRAY6
+ 255, // TCI_GRAY7 = TCI_WHITE
+
+ 160, // TCI_RED
+ 166, // TCI_RED_ORANGE
+ 172, // TCI_ORANGE
+ 136, // TCI_ORANGE_YELLOW
+ 142, // TCI_YELLOW
+ 106, // TCI_YELLOW_GREEN
+ 34, // TCI_GREEN
+ 36, // TCI_GREEN_CYAN
+ 37, // TCI_CYAN
+ 32, // TCI_CYAN_BLUE
+ 21, // TCI_BLUE
+ 92, // TCI_BLUE_MAGENTA
+ 164, // TCI_MAGENTA
+ 162, // TCI_MAGENTA_RED
+
+ 196, // TCI_B_RED
+ 202, // TCI_B_RED_ORANGE
+ 208, // TCI_B_ORANGE
+ 214, // TCI_B_ORANGE_YELLOW
+ 226, // TCI_B_YELLOW
+ 154, // TCI_B_YELLOW_GREEN
+ 46, // TCI_B_GREEN
+ 49, // TCI_B_GREEN_CYAN
+ 51, // TCI_B_CYAN
+ 75, // TCI_B_CYAN_BLUE
+ 105, // TCI_B_BLUE
+ 177, // TCI_B_BLUE_MAGENTA
+ 207, // TCI_B_MAGENTA
+ 204, // TCI_B_MAGENTA_RED
+ };
+
+ const u8 *color_index;
+ uint x_bg;
+ if ( col_mode == COLMD_256_COLORS )
+ {
+ color_index = color_index_256;
+ x_bg = 8;
+ }
+ else
+ {
+ color_index = color_index_8;
+ x_bg = 16;
+ option |= GCM_SHORT;
+ }
+
+
+ //--- output
+
+ char buf[100], *end = buf + sizeof(buf);
+ char *dest = StringCopyE(buf,end,TermTextModeBeg);
+
+ const uint bg = (uint)bg_index < TCI__IGNORE ? color_index[bg_index] : ~0;
+
+ ccp sep = "";
+ if ( (uint)font_index < TCI__IGNORE )
+ {
+ uint idx = color_index[font_index];
+ if ( idx < 16 && idx == (bg&7) && option & GCM_ALT )
+ idx = idx ^ 8;
+
+ if ( !(option & GCM_SHORT) || idx >= 16 )
+ dest = snprintfE(dest,end,"38;5;%u",idx);
+ else if ( idx < 8 )
+ dest = snprintfE(dest,end,"0;%u",30+idx);
+ else
+ dest = snprintfE(dest,end,"1;%u",22+idx);
+ sep = ";";
+ }
+
+ if ( (uint)bg_index < TCI__IGNORE )
+ {
+ if ( !(option & GCM_SHORT) || bg >= x_bg )
+ dest = snprintfE(dest,end,"%s48;5;%u",sep,bg);
+ else
+ dest = snprintfE(dest,end,"%s%u",sep,40+(bg&7));
+ }
+
+ dest = StringCopyE(dest,end,TermTextModeEnd);
+ const int len = dest + 1 - buf;
+ char *res = GetColorCircBuf(len);
+ memcpy(res,buf,len);
+ return res;
+ }
+
+#else // !SUPPORT_36_COLORS
+
+ ccp GetColorMode // 18 colors version
+ (
+ // returns a pointer to an internal circulary buffer (not GetCircBuf())
+
+ ColorMode_t col_mode, // if < COLMD_ON: return an empty string
+ TermColorIndex_t font_index, // index of font color
+ TermColorIndex_t bg_index, // index of background color
+ GetColorOption_t option // execution options
+ )
+ {
+ col_mode = NormalizeColorMode(col_mode,8);
+ if ( col_mode < COLMD_ON )
+ return EmptyString;
+
+
+ //--- recommended combinations
+
+ if ( option & GCM_ALT
+ && (uint)font_index < TCI__IGNORE
+ && (uint)bg_index < TCI__IGNORE )
+ {
+
+ static const u8 tab[TCI__N_BG][TCI__N_FONT] =
+ {
+ //--- TCI_BLACK
+ {
+ TCI_DARKGRAY, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_LIGHTGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_B_RED, // TCI_RED
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_DARKGRAY
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_LIGHTGRAY, // TCI_DARKGRAY
+ TCI_LIGHTGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_B_RED, // TCI_RED
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_LIGHTGRAY
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_DARKGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_GREEN, // TCI_GREEN
+ TCI_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_WHITE
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_LIGHTGRAY, // TCI_LIGHTGRAY
+ TCI_LIGHTGRAY, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_ORANGE, // TCI_YELLOW
+ TCI_GREEN, // TCI_GREEN
+ TCI_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_RED
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_LIGHTGRAY, // TCI_DARKGRAY
+ TCI_LIGHTGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_B_RED, // TCI_RED
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_B_BLUE, // TCI_BLUE
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_ORANGE
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_WHITE, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_YELLOW
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_WHITE, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_GREEN
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_LIGHTGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_CYAN
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_LIGHTGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_BLUE
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_LIGHTGRAY, // TCI_DARKGRAY
+ TCI_LIGHTGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN, // TCI_BLUE
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_MAGENTA
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_LIGHTGRAY, // TCI_DARKGRAY
+ TCI_LIGHTGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN, // TCI_BLUE
+ TCI_B_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_B_RED
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_WHITE, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_B_ORANGE
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_WHITE, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_B_YELLOW
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_DARKGRAY, // TCI_LIGHTGRAY
+ TCI_DARKGRAY, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_YELLOW, // TCI_YELLOW
+ TCI_GREEN, // TCI_GREEN
+ TCI_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_B_GREEN
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_DARKGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_GREEN, // TCI_GREEN
+ TCI_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_B_CYAN
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_DARKGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_ORANGE, // TCI_ORANGE
+ TCI_YELLOW, // TCI_YELLOW
+ TCI_GREEN, // TCI_GREEN
+ TCI_CYAN, // TCI_CYAN
+ TCI_BLUE, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_B_BLUE
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_DARKGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_B_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+
+ //--- TCI_B_MAGENTA
+ {
+ TCI_BLACK, // TCI_BLACK
+ TCI_DARKGRAY, // TCI_DARKGRAY
+ TCI_DARKGRAY, // TCI_LIGHTGRAY
+ TCI_WHITE, // TCI_WHITE
+ TCI_RED, // TCI_RED
+ TCI_B_ORANGE, // TCI_ORANGE
+ TCI_B_YELLOW, // TCI_YELLOW
+ TCI_GREEN, // TCI_GREEN
+ TCI_B_CYAN, // TCI_CYAN
+ TCI_CYAN, // TCI_BLUE
+ TCI_MAGENTA, // TCI_MAGENTA
+ },
+ };
+
+ const uint font = font_index < TCI_B_RED ? font_index
+ : font_index - (TCI_B_RED-TCI_RED);
+ font_index = tab[bg_index][font];
+ }
+
+
+ //--- color index
+
+ static const u8 color_index_8[] =
+ {
+ 0, // TCI_BLACK
+ 8, // TCI_DARKGRAY
+ 7, // TCI_LIGHTGRAY
+ 15, // TCI_WHITE
+
+ 1, // TCI_RED
+ 1, // TCI_ORANGE
+ 3, // TCI_YELLOW
+ 2, // TCI_GREEN
+ 6, // TCI_CYAN
+ 4, // TCI_BLUE
+ 5, // TCI_MAGENTA
+
+ 9, // TCI_B_RED
+ 9, // TCI_B_ORANGE
+ 11, // TCI_B_YELLOW
+ 10, // TCI_B_GREEN
+ 14, // TCI_B_CYAN
+ 12, // TCI_B_BLUE
+ 13, // TCI_B_MAGENTA
+ };
+
+ static const u8 color_index_256[] =
+ {
+ 0, // TCI_BLACK
+ 8, // TCI_DARKGRAY
+ 7, // TCI_LIGHTGRAY
+ 15, // TCI_WHITE
+
+ 160, // TCI_RED
+ 172, // TCI_ORANGE
+ 142, // TCI_YELLOW
+ 34, // TCI_GREEN
+ 37, // TCI_CYAN
+ 21, // TCI_BLUE
+ 164, // TCI_MAGENTA
+
+ 196, // TCI_B_RED
+ 208, // TCI_B_ORANGE
+ 226, // TCI_B_YELLOW
+ 46, // TCI_B_GREEN
+ 51, // TCI_B_CYAN
+ 105, // TCI_B_BLUE
+ 207, // TCI_B_MAGENTA
+ };
+
+ const u8 *color_index;
+ uint x_bg;
+ if ( col_mode == COLMD_256_COLORS )
+ {
+ color_index = color_index_256;
+ x_bg = 8;
+ }
+ else
+ {
+ color_index = color_index_8;
+ x_bg = 16;
+ option |= GCM_SHORT;
+ }
+
+
+ //--- output
+
+ char buf[100], *end = buf + sizeof(buf);
+ char *dest = StringCopyE(buf,end,TermTextModeBeg);
+
+ ccp sep = "";
+ if ( (uint)font_index < TCI__IGNORE )
+ {
+ const uint idx = color_index[font_index];
+ if ( !(option & GCM_SHORT) || idx >= 16 )
+ dest = snprintfE(dest,end,"38;5;%u",idx);
+ else if ( idx < 8 )
+ dest = snprintfE(dest,end,"0;%u",30+idx);
+ else
+ dest = snprintfE(dest,end,"1;%u",22+idx);
+ sep = ";";
+ }
+
+ if ( (uint)bg_index < TCI__IGNORE )
+ {
+ const uint idx = color_index[bg_index];
+ if ( !(option & GCM_SHORT) || idx >= x_bg )
+ dest = snprintfE(dest,end,"%s48;5;%u",sep,idx);
+ else
+ dest = snprintfE(dest,end,"%s%u",sep,40+(idx&7));
+ }
+
+ dest = StringCopyE(dest,end,TermTextModeEnd);
+ const int len = dest + 1 - buf;
+ char *res = GetColorCircBuf(len);
+ memcpy(res,buf,len);
+ return res;
+ }
+
+#endif // !SUPPORT_36_COLORS
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// colors ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const ColorSet_t * GetColorSet0()
+{
+ static ColorSet_t col = {0};
+ if (!col.col_mode)
+ {
+ col.col_mode = COLMD_OFF;
+ col.colorize = false;
+ col.n_colors = 0;
+
+ col.space = EmptyString;
+ col.tab = EmptyString;
+ col.lf = EmptyString;
+
+ ccp *ptr = &col.reset;
+ ccp *end = (ccp*)((ccp)&col + sizeof(col));
+ while ( ptr < end )
+ *ptr++ = EmptyString;
+ }
+ return &col;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const ColorSet_t * GetColorSet8()
+{
+ static ColorSet_t col = {0};
+ if (!col.col_mode)
+ {
+ col.col_mode = COLMD_8_COLORS;
+ col.colorize = true;
+ col.n_colors = 8;
+
+ col.space = Space200;
+ col.tab = Tabs20;
+ col.lf = LF20;
+
+ col.reset = TermTextModeReset;
+ col.setup = STRDUP(GetTextMode(1,TTM_COL_SETUP));
+ col.run = STRDUP(GetTextMode(1,TTM_COL_RUN));
+ col.abort = STRDUP(GetTextMode(1,TTM_COL_ABORT));
+ col.finish = STRDUP(GetTextMode(1,TTM_COL_FINISH));
+ col.script = STRDUP(GetTextMode(1,TTM_COL_SCRIPT));
+ col.open = STRDUP(GetTextMode(1,TTM_COL_OPEN));
+ col.close = STRDUP(GetTextMode(1,TTM_COL_CLOSE));
+ col.file = STRDUP(GetTextMode(1,TTM_COL_FILE));
+ col.job = STRDUP(GetTextMode(1,TTM_COL_JOB));
+ col.info = STRDUP(GetTextMode(1,TTM_COL_INFO));
+ col.hint = STRDUP(GetTextMode(1,TTM_COL_HINT));
+ col.warn = STRDUP(GetTextMode(1,TTM_COL_WARN));
+ col.debug = STRDUP(GetTextMode(1,TTM_COL_DEBUG));
+ col.log = STRDUP(GetTextMode(1,TTM_COL_LOG));
+ col.name = STRDUP(GetTextMode(1,TTM_COL_NAME));
+ col.value = STRDUP(GetTextMode(1,TTM_COL_VALUE));
+ col.success = STRDUP(GetTextMode(1,TTM_COL_SUCCESS));
+ col.error = STRDUP(GetTextMode(1,TTM_COL_ERROR));
+ col.fail = STRDUP(GetTextMode(1,TTM_COL_FAIL));
+ col.fail2 = STRDUP(GetTextMode(1,TTM_COL_FAIL2));
+ col.fatal = STRDUP(GetTextMode(1,TTM_COL_FATAL));
+ col.mark = STRDUP(GetTextMode(1,TTM_COL_MARK));
+ col.bad = STRDUP(GetTextMode(1,TTM_COL_BAD));
+ col.select = STRDUP(GetTextMode(1,TTM_COL_SELECT));
+ col.differ = STRDUP(GetTextMode(1,TTM_COL_DIFFER));
+ col.stat_line = STRDUP(GetTextMode(1,TTM_COL_STAT_LINE));
+ col.warn_line = STRDUP(GetTextMode(1,TTM_COL_WARN_LINE));
+ col.proc_line = STRDUP(GetTextMode(1,TTM_COL_PROC_LINE));
+ col.cite = STRDUP(GetTextMode(1,TTM_COL_CITE));
+ col.status = STRDUP(GetTextMode(1,TTM_COL_STATUS));
+ col.highlight = STRDUP(GetTextMode(1,TTM_COL_HIGHLIGHT));
+ col.hide = STRDUP(GetTextMode(1,TTM_COL_HIDE));
+ col.heading = STRDUP(GetTextMode(1,TTM_COL_HEADING));
+ col.caption = STRDUP(GetTextMode(1,TTM_COL_CAPTION));
+ col.section = STRDUP(GetTextMode(1,TTM_COL_SECTION));
+ col.syntax = STRDUP(GetTextMode(1,TTM_COL_SYNTAX));
+ col.cmd = STRDUP(GetTextMode(1,TTM_COL_CMD));
+ col.option = STRDUP(GetTextMode(1,TTM_COL_OPTION));
+ col.param = STRDUP(GetTextMode(1,TTM_COL_PARAM));
+ col.on = STRDUP(GetTextMode(1,TTM_COL_ON));
+ col.off = STRDUP(GetTextMode(1,TTM_COL_OFF));
+
+ col.black = STRDUP(GetTextMode(1,TTM_NO_BOLD|TTM_BLACK));
+ col.b_black = STRDUP(GetTextMode(1,TTM_BOLD|TTM_BLACK));
+ col.white = STRDUP(GetTextMode(1,TTM_NO_BOLD|TTM_WHITE));
+ col.b_white = STRDUP(GetTextMode(1,TTM_BOLD|TTM_WHITE));
+
+ col.red = STRDUP(GetTextMode(1,TTM_NO_BOLD|TTM_RED));
+ col.red_orange = col.red;
+ col.orange = col.red;
+ col.yellow = STRDUP(GetTextMode(1,TTM_NO_BOLD|TTM_YELLOW));
+ col.orange_yellow = col.yellow;
+ col.yellow_green = col.yellow;
+ col.green = STRDUP(GetTextMode(1,TTM_NO_BOLD|TTM_GREEN));
+ col.green_cyan = col.green;
+ col.cyan = STRDUP(GetTextMode(1,TTM_NO_BOLD|TTM_CYAN));
+ col.cyan_blue = col.cyan;
+ col.blue = STRDUP(GetTextMode(1,TTM_NO_BOLD|TTM_BLUE));
+ col.blue_magenta = col.blue;
+ col.magenta = STRDUP(GetTextMode(1,TTM_NO_BOLD|TTM_MAGENTA));
+ col.magenta_red = col.magenta;
+
+ col.b_red = STRDUP(GetTextMode(1,TTM_BOLD|TTM_RED));
+ col.b_red_orange = col.b_red;
+ col.b_orange = col.b_red;
+ col.b_yellow = STRDUP(GetTextMode(1,TTM_BOLD|TTM_YELLOW));
+ col.b_orange_yellow = col.b_yellow;
+ col.b_yellow_green = col.b_yellow;
+ col.b_green = STRDUP(GetTextMode(1,TTM_BOLD|TTM_GREEN));
+ col.b_green_cyan = col.b_green;
+ col.b_cyan = STRDUP(GetTextMode(1,TTM_BOLD|TTM_CYAN));
+ col.b_cyan_blue = col.b_cyan;
+ col.b_blue = STRDUP(GetTextMode(1,TTM_BOLD|TTM_BLUE));
+ col.b_blue_magenta = col.b_blue;
+ col.b_magenta = STRDUP(GetTextMode(1,TTM_BOLD|TTM_MAGENTA));
+ col.b_magenta_red = col.b_magenta;
+
+ TermColorIndex_t font, bg;
+ for ( font = 0; font < TCI__N_FONT; font++ )
+ for ( bg = 0; bg < TCI__N_BG; bg++ )
+ col.matrix[font][bg]
+ = STRDUP(GetColorMode(COLMD_8_COLORS,font,bg,GCM_ALT|GCM_SHORT));
+ }
+
+ return &col;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const ColorSet_t * GetColorSet256()
+{
+ static ColorSet_t col = {0};
+ if (!col.col_mode)
+ {
+ //--- default settings (copy of 8-COLORS)
+
+ memcpy(&col,GetColorSet8(),sizeof(col));
+ col.col_mode = COLMD_256_COLORS;
+ col.n_colors = 256;
+
+
+ //--- special settings
+
+ col.setup = "\e[38;5;86;40m";
+ col.run = "\e[38;5;229;40m";
+ col.abort = "\e[38;5;198;40m";
+ col.finish = "\e[38;5;154;40m";
+ col.script = "\e[0;30;48;5;219m";
+ col.open = "\e[38;5;225;40m";
+ col.close = "\e[38;5;219;40m";
+ col.heading = "\e[38;5;111;40m";
+ col.info = "\e[38;5;49;40m";
+ col.hint = "\e[38;5;220;40m";
+ col.warn = "\e[38;5;208;40m";
+ col.debug = "\e[38;5;210;40m";
+ col.error = "\e[38;5;255;48;5;130m";
+ col.fail = "\e[38;5;251;48;5;88m";
+ col.fail2 = "\e[38;5;255;48;5;88m";
+ col.fatal = "\e[38;5;255;48;5;129m";
+ col.differ = "\e[38;5;226;48;5;19m";
+ col.stat_line = "\e[38;5;231;48;5;19m";
+ col.warn_line = "\e[38;5;215;48;5;19m";
+ col.hide = "\e[38;5;245;40m";
+
+
+ //--- special settings: color names
+
+ // dark:
+ // ./mkw-ana testcol 400-420 420-330 330-030 030-033 033-005 005-404 404-400
+ // ./mkw-ana testcol 400 410 420 320 330 230 030 032 033 024 005 204 404 402 400
+
+ col.red = "\e[38;5;160m";
+ col.red_orange = "\e[38;5;166m";
+ col.orange = "\e[38;5;172m";
+ col.orange_yellow = "\e[38;5;136m";
+ col.yellow = "\e[38;5;142m";
+ col.yellow_green = "\e[38;5;106m";
+ col.green = "\e[38;5;34m";
+ col.green_cyan = "\e[38;5;36m";
+ col.cyan = "\e[38;5;37m";
+ col.cyan_blue = "\e[38;5;32m";
+ col.blue = "\e[38;5;21m";
+ col.blue_magenta = "\e[38;5;92m";
+ col.magenta = "\e[38;5;164m";
+ col.magenta_red = "\e[38;5;162m";
+
+ // bold:
+ // ./mkw-ana testcol 500-520 520-550 550-050 050-055 055-225 225-515 515-500
+ // ./mkw-ana testcol 500 510 520 530 550 350 050 053 055 135 225 425 515 512 500
+
+ col.b_red = "\e[38;5;196m";
+ col.b_red_orange = "\e[38;5;202m";
+ col.b_orange = "\e[38;5;208m";
+ col.b_orange_yellow = "\e[38;5;214m";
+ col.b_yellow = "\e[38;5;226m";
+ col.b_yellow_green = "\e[38;5;154m";
+ col.b_green = "\e[38;5;46m";
+ col.b_green_cyan = "\e[38;5;49m";
+ col.b_cyan = "\e[38;5;51m";
+ col.b_cyan_blue = "\e[38;5;75m";
+ col.b_blue = "\e[38;5;105m";
+ col.b_blue_magenta = "\e[38;5;177m";
+ col.b_magenta = "\e[38;5;207m";
+ col.b_magenta_red = "\e[38;5;204m";
+
+ TermColorIndex_t font, bg;
+ for ( font = 0; font < TCI__N_FONT; font++ )
+ for ( bg = 0; bg < TCI__N_BG; bg++ )
+ col.matrix[font][bg]
+ = STRDUP(GetColorMode(COLMD_256_COLORS,font,bg,GCM_ALT|GCM_SHORT));
+ }
+
+ return &col;
+
+//---------- old definitions ----------
+//
+// col.differ = STRDUP(GetColorMode(COLMD_256_COLORS,
+// TCI_YELLOW, TCI_BLUE, GCM_ALT|GCM_SHORT ));
+// col.stat_line = STRDUP(GetColorMode(COLMD_256_COLORS,
+// TCI_WHITE, TCI_BLUE, GCM_ALT|GCM_SHORT ));
+// col.warn_line = STRDUP(GetColorMode(COLMD_256_COLORS,
+// TCI_ORANGE, TCI_BLUE, GCM_ALT|GCM_SHORT ));
+//
+// col.orange = STRDUP(GetColorMode(COLMD_256_COLORS,
+// TCI_ORANGE, TCI__IGNORE, GCM_ALT|GCM_SHORT ));
+// col.b_orange = STRDUP(GetColorMode(COLMD_256_COLORS,
+// TCI_B_ORANGE, TCI__IGNORE, GCM_ALT|GCM_SHORT ));
+//
+//-------------------------------------
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int setupterm(char *term, int fildes, int *errret);
+int tigetnum(char *capname);
+
+const ColorSet_t * GetColorSetAuto ( bool force_on )
+{
+ static ColorMode_t auto_mode = COLMD_AUTO;
+ if ( opt_colorize != COLMD_AUTO )
+ {
+ if ( opt_colorize == COLMD_ON )
+ force_on = true;
+ else
+ auto_mode = opt_colorize;
+ }
+
+ if ( auto_mode == COLMD_AUTO )
+ {
+ int error;
+ setupterm(getenv("TERM"),1,&error);
+ const int ncol = tigetnum("colors");
+
+ auto_mode = ncol >= 256 ? COLMD_256_COLORS
+ : ncol >= 8 ? COLMD_8_COLORS
+ : COLMD_OFF;
+ if ( auto_mode == COLMD_OFF && !strcmp(getenv("TERM"),"cygwin") )
+ auto_mode = COLMD_8_COLORS;
+
+ #ifdef TEST
+ fprintf(stderr,">>> GetColorSetAuto(%d) => \"%s\" n=%d => %d [%s]\n",
+ force_on, getenv("TERM"), ncol,
+ auto_mode, GetColorModeName(auto_mode,0) );
+ #endif
+ }
+
+ switch(auto_mode)
+ {
+ case COLMD_8_COLORS:
+ return GetColorSet8();
+
+ case COLMD_256_COLORS:
+ return GetColorSet256();
+
+ default:
+ return force_on ? GetColorSet8() : GetColorSet0();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const ColorSet_t * GetColorSet ( ColorMode_t col_mode )
+{
+ switch(col_mode)
+ {
+ case COLMD_AUTO:
+ return GetColorSetAuto(false);
+
+ case COLMD_ON:
+ return GetColorSetAuto(true);
+
+ case COLMD_8_COLORS:
+ return GetColorSet8();
+
+ case COLMD_256_COLORS:
+ return GetColorSet256();
+
+ default:
+ return GetColorSet0();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const ColorSet_t * GetFileColorSet ( FILE *f )
+{
+ return GetColorSet(GetFileColorized(f));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SetupColorSet ( ColorSet_t *cs, FILE *f )
+{
+ DASSERT(cs);
+ memcpy(cs,GetColorSet(GetFileColorized(f)),sizeof(*cs));
+ return cs->colorize;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetColorByName ( const ColorSet_t *colset, ccp name )
+{
+ struct cdef_t
+ {
+ u16 delta;
+ char name[16];
+ };
+
+ #undef DEF1
+ #undef DEF2
+ #define DEF1(n) { offsetof(ColorSet_t,n), #n },
+ #define DEF2(t,n) { offsetof(ColorSet_t,n), #t },
+
+ static const struct cdef_t tab[] =
+ {
+ DEF1(abort)
+ DEF1(b_black)
+ DEF1(b_blue)
+ DEF1(b_blue_magenta)
+ DEF1(b_cyan)
+ DEF1(b_cyan_blue)
+ DEF1(b_green)
+ DEF1(b_green_cyan)
+ DEF1(b_magenta)
+ DEF1(b_magenta_red)
+ DEF1(b_orange)
+ DEF1(b_orange_yellow)
+ DEF1(b_red)
+ DEF1(b_red_orange)
+ DEF1(b_white)
+ DEF1(b_yellow)
+ DEF1(b_yellow_green)
+ DEF1(bad)
+ DEF1(black)
+ DEF1(blue)
+ DEF1(blue_magenta)
+ DEF1(caption)
+ DEF1(cite)
+ DEF1(close)
+ DEF1(cmd)
+ DEF1(cyan)
+ DEF1(cyan_blue)
+ DEF1(debug)
+ DEF1(differ)
+ DEF1(error)
+ DEF1(fail)
+ DEF1(fail2)
+ DEF1(fatal)
+ DEF1(file)
+ DEF1(finish)
+ DEF1(green)
+ DEF1(green_cyan)
+ DEF2(head,heading)
+ DEF1(heading)
+ DEF1(hide)
+ DEF1(highlight)
+ DEF1(hint)
+ DEF2(hl,highlight)
+ DEF1(info)
+ DEF1(job)
+ DEF1(log)
+ DEF1(magenta)
+ DEF1(magenta_red)
+ DEF1(mark)
+ DEF1(name)
+ DEF1(off)
+ DEF1(on)
+ DEF1(open)
+ DEF2(opt,option)
+ DEF1(option)
+ DEF1(orange)
+ DEF1(orange_yellow)
+ DEF2(par,param)
+ DEF1(param)
+ DEF1(proc_line)
+ DEF1(red)
+ DEF1(red_orange)
+ DEF1(reset)
+ DEF1(run)
+ DEF1(script)
+ DEF1(section)
+ DEF1(select)
+ DEF1(setup)
+ DEF1(stat_line)
+ DEF1(status)
+ DEF1(success)
+ DEF1(syntax)
+ DEF2(val,value)
+ DEF1(value)
+ DEF1(warn)
+ DEF1(warn_line)
+ DEF1(white)
+ DEF1(yellow)
+ DEF1(yellow_green)
+ };
+
+ #if HAVE_PRINT
+ static bool done = false;
+ if (!done)
+ {
+ done = true;
+ uint i, max_len = 0, err_count = 0;
+ const struct cdef_t *cd = tab;
+ for ( i = 0; i < sizeof(tab)/sizeof(*tab); i++, cd++ )
+ {
+ if ( max_len < strlen(cd->name) )
+ max_len = strlen(cd->name);
+
+ if ( i > 0 && strcmp(cd[-1].name,cd->name) >= 0 )
+ {
+ err_count++;
+ PRINT("WRONG COL ORDER: %s > %s\n", cd[-1].name, cd->name);
+ }
+ }
+
+ DASSERT_MSG( max_len < sizeof(cd->name),
+ "%s(): max name len = %d, possible = %zd\n",
+ __FUNCTION__, max_len, sizeof(cd->name)-1 );
+ DASSERT_MSG( !err_count,
+ "%s(): color order is %d* wrong!\n",
+ __FUNCTION__, err_count );
+ }
+ #endif
+
+ //--- search the name
+
+ if ( colset && name )
+ {
+ int beg = 0;
+ int end = sizeof(tab)/sizeof(*tab) - 1;
+ while ( beg <= end )
+ {
+ const uint idx = (beg+end)/2;
+ const int stat = strcmp(name,tab[idx].name);
+ if ( stat < 0 )
+ end = idx - 1 ;
+ else if ( stat > 0 )
+ beg = idx + 1;
+ else
+ return *(ccp*)((u8*)colset+tab[idx].delta);
+ }
+ }
+ return EmptyString;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintColorSetHelper
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ const ColorSet_t *cs, // valid color set; if NULL: use std colors
+ PrintColorFunc func, // output function, never NULL
+ uint mode // output mode (bit field, NULL=default):
+ // 1: print normal colors (e.g. RED)
+ // 2: print bold colors (e.g. B_RED)
+ // 4: print background (e.g. BLUE_RED)
+ // 8: print color names (e.g. HIGHLIGHT)
+ // 16: include alternative names too (e.g. HL)
+)
+{
+ DASSERT(f);
+ DASSERT(func);
+
+ if (!cs)
+ cs = GetColorSet(COLMD_ON);
+ indent = NormalizeIndent(indent) + 10; // +10 is max name width
+ if (!mode)
+ mode = 15;
+
+ #undef JOB1
+ #undef JOB2
+ #define JOB1(m,n) func(f,indent,cs,m,#n,cs->n)
+ #define JOB2(m,t,n) func(f,indent,cs,m,#t,cs->n)
+
+ if ( mode & 1 )
+ {
+ func(f,indent,cs,1,0,0);
+ JOB1(1,black);
+ JOB1(1,red);
+ JOB1(1,red_orange);
+ JOB1(1,orange);
+ JOB1(1,orange_yellow);
+ JOB1(1,yellow);
+ JOB1(1,yellow_green);
+ JOB1(1,green);
+ JOB1(1,green_cyan);
+ JOB1(1,cyan);
+ JOB1(1,cyan_blue);
+ JOB1(1,blue);
+ JOB1(1,blue_magenta);
+ JOB1(1,magenta);
+ JOB1(1,magenta_red);
+ JOB1(1,white);
+ }
+
+ if ( mode & 2 )
+ {
+ func(f,indent,cs,2,0,0);
+ JOB1(2,b_black);
+ JOB1(2,b_red);
+ JOB1(2,b_red_orange);
+ JOB1(2,b_orange);
+ JOB1(2,b_orange_yellow);
+ JOB1(2,b_yellow);
+ JOB1(2,b_yellow_green);
+ JOB1(2,b_green);
+ JOB1(2,b_green_cyan);
+ JOB1(2,b_cyan);
+ JOB1(2,b_cyan_blue);
+ JOB1(2,b_blue);
+ JOB1(2,b_blue_magenta);
+ JOB1(2,b_magenta);
+ JOB1(2,b_magenta_red);
+ JOB1(2,b_white);
+ }
+
+ if ( mode & 8 )
+ {
+ const bool alt = ( mode & 16 ) != 0;
+ func(f,indent,cs,8,0,0);
+
+ JOB1(4,setup);
+ JOB1(4,run);
+ JOB1(4,abort);
+ JOB1(4,finish);
+ JOB1(4,script);
+ JOB1(4,open);
+ JOB1(4,close);
+ JOB1(4,file);
+ JOB1(4,job);
+
+ JOB1(4,info);
+ JOB1(4,hint);
+ JOB1(4,warn);
+ JOB1(4,debug);
+ JOB1(4,log);
+
+ JOB1(4,name);
+ JOB1(4,value); if (alt) JOB2(12,val,value);
+ JOB1(4,success);
+ JOB1(4,error);
+ JOB1(4,fail);
+ JOB1(4,fail2);
+ JOB1(4,fatal);
+ JOB1(4,mark);
+ JOB1(4,bad);
+
+ JOB1(4,select);
+ JOB1(4,differ);
+ JOB1(4,stat_line);
+ JOB1(4,warn_line);
+ JOB1(4,proc_line);
+ JOB1(4,cite);
+ JOB1(4,status);
+ JOB1(4,highlight); if (alt) JOB2(12,hl,highlight);
+ JOB1(4,hide);
+
+ JOB1(4,heading); if (alt) JOB2(12,head,heading);
+ JOB1(4,caption);
+ JOB1(4,section);
+ JOB1(4,syntax);
+ JOB1(4,cmd);
+ JOB1(4,option); if (alt) JOB2(12,opt,option);
+ JOB1(4,param); if (alt) JOB2(12,par,param);
+
+ JOB1(4,off);
+ JOB1(4,on);
+ }
+
+ #undef JOB1
+ #undef JOB2
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void PrintColorSetCOL
+(
+ // On start of group: col_name := NULL, col_string := NULL
+
+ FILE *f, // valid output file
+ int indent, // normalized indention of output
+ const ColorSet_t *cs, // valid color set, never NULL
+ uint mode, // output mode of PrintColorSetHelper()
+ ccp col_name, // name of color
+ ccp col_string // escape string for the color
+)
+{
+ if (!col_name)
+ fputc('\n',f);
+ else
+ {
+ DASSERT(col_string);
+ #ifdef TEST
+ fprintf(f,"%*s %s %s %s | %s %s %s\n", \
+ indent, col_name, col_string, col_name, cs->reset,
+ GetColorByName(cs,col_name), col_name, cs->reset );
+ #else
+ fprintf(f,"%*s %s %s %s\n",
+ indent, col_name, col_string, col_name, cs->reset );
+ #endif
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static void PrintColorSetSH
+(
+ // On start of group: col_name := NULL, col_string := NULL
+
+ FILE *f, // valid output file
+ int indent, // normalized indention of output
+ const ColorSet_t *cs, // valid color set, never NULL
+ uint mode, // output mode of PrintColorSetHelper()
+ ccp col_name, // name of color
+ ccp col_string // escape string for the color
+)
+{
+ if (col_name)
+ {
+ DASSERT(col_string);
+
+ char name[50], buf[50];
+ char *dest = name;
+ while ( *col_name && dest < name + sizeof(name) - 1 )
+ *dest++ = toupper((int)*col_name++);
+ *dest = 0;
+
+ PrintEscapedString(buf,sizeof(buf),col_string,-1,CHMD_ESC,0,0);
+ if (memcmp(buf,"\\x1B",4))
+ fprintf(f,"COL_%s=$'%s'\n",name,buf);
+ else
+ fprintf(f,"COL_%s='\\033%s'\n",name,buf+4);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static void PrintColorSetPHP
+(
+ // On start of group: col_name := NULL, col_string := NULL
+
+ FILE *f, // valid output file
+ int indent, // normalized indention of output
+ const ColorSet_t *cs, // valid color set, never NULL
+ uint mode, // output mode of PrintColorSetHelper()
+ ccp col_name, // name of color
+ ccp col_string // escape string for the color
+)
+{
+ if (col_name)
+ {
+ DASSERT(col_string);
+
+ char buf[50];
+ PrintEscapedString(buf,sizeof(buf),col_string,-1,CHMD_ESC,0,0);
+ if (memcmp(buf,"\\x1B",4))
+ fprintf(f,"$COL->%-15s = \"%s\";\n",col_name,buf);
+ else
+ fprintf(f,"$COL->%-15s = \"\\033%s\";\n",col_name,buf+4);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void PrintColorSet
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ const ColorSet_t *cs // valid color set; if NULL: use std colors
+)
+{
+ DASSERT(f);
+ PrintColorSetHelper(f,indent,cs,PrintColorSetCOL,4);
+ fputc('\n',f);
+}
+
+//-----------------------------------------------------------------------------
+
+void PrintColorSetEx
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ const ColorSet_t *cs, // valid color set; if NULL: use std colors
+ uint mode, // output mode => see PrintColorSetHelper()
+ uint format // output format
+ // 0: colored list
+ // 1: shell definitions
+ // 2: php class
+)
+{
+ DASSERT(f);
+ char buf[50];
+
+ switch(format)
+ {
+ case 1:
+ if (!cs)
+ cs = GetColorSet(COLMD_ON);
+ PrintColorSetHelper(f,indent,cs,PrintColorSetSH,mode);
+ PrintEscapedString(buf,sizeof(buf),cs->reset,-1,CHMD_ESC,0,0);
+ if (memcmp(buf,"\\x1B",4))
+ fprintf(f,"COL0=$'%s'\n",buf);
+ else
+ fprintf(f,"COL0=$'\\033%s'\n",buf+4);
+ break;
+
+ case 2:
+ if (!cs)
+ cs = GetColorSet(COLMD_ON);
+ PrintColorSetHelper(f,indent,cs,PrintColorSetPHP,mode);
+ break;
+
+ default:
+ PrintColorSetHelper(f,indent,cs,PrintColorSetCOL,mode);
+ fputc('\n',f);
+ break;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ViewColor*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupColorView ( ColorView_t *cv )
+{
+ DASSERT(cv);
+
+ if (!cv->f)
+ cv->f = stdout;
+ cv->indent = NormalizeIndent(cv->indent);
+
+#ifdef DCLIB_TERMINAL
+ cv->col_mode = cv->colset
+ ? cv->colset->col_mode
+ : NormalizeColorMode(cv->col_mode,
+ cv->term ? cv->term->colors : 8 );
+#else
+ cv->col_mode = cv->colset
+ ? cv->colset->col_mode
+ : NormalizeColorMode(cv->col_mode,8);
+#endif
+ if (!cv->colset)
+ cv->colset = GetColorSet(cv->col_mode);
+
+ switch(cv->order)
+ {
+ case 'r': case 'R': cv->order = 0; break;
+ case 'g': case 'G': cv->order = 1; break;
+ case 'b': case 'B': cv->order = 2; break;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupColorViewMode ( ColorView_t *cv, ColorMode_t col_mode )
+{
+ DASSERT(cv);
+ cv->col_mode = col_mode;
+ cv->colset = 0;
+ SetupColorView(cv);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static ccp col_names[] =
+{
+ "black:",
+ "red:",
+ "green:",
+ "yellow:",
+ "blue:",
+ "magenta:",
+ "cyan:",
+ "white:",
+ 0
+};
+
+//-----------------------------------------------------------------------------
+
+void ViewColorsAttrib8 ( ColorView_t *cv )
+{
+ DASSERT(cv);
+ SetupColorView(cv);
+ DASSERT(cv->colset);
+
+ #ifdef TEST
+ fprintf(cv->f,"%*s%s() %s, %u colors, mode %d, stdcol %d, order %d\n",
+ cv->indent,"", __FUNCTION__,
+ GetColorModeName(cv->colset->col_mode,0),
+ cv->colset->n_colors, cv->mode, cv->std_col, cv->order );
+ #endif
+
+ //--------------------------------------------------
+
+ fprintf(cv->f,
+ "%s\n%*sStandard font colors with attributes (ul=underline) for mode %s:\n\n",
+ cv->colset->reset, cv->indent, "",
+ GetColorModeName(cv->col_mode,0) );
+
+ uint fg = 0, bg = 0;
+ switch( (int)cv->std_col )
+ {
+ case TCI_BLACK:
+ case TCI_DARKGRAY: fg = TTM_WHITE|TTM_BOLD; bg = TTM_BG_BLACK; break;
+ case TCI_LIGHTGRAY:
+ case TCI_WHITE: fg = TTM_BLACK; bg = TTM_BG_WHITE; break;
+ case TCI_RED:
+ case TCI_B_RED:
+ case TCI_ORANGE:
+ case TCI_B_ORANGE: fg = TTM_WHITE|TTM_BOLD; bg = TTM_BG_RED; break;
+ case TCI_YELLOW:
+ case TCI_B_YELLOW: fg = TTM_BLACK; bg = TTM_BG_YELLOW; break;
+ case TCI_GREEN:
+ case TCI_B_GREEN: fg = TTM_WHITE|TTM_BOLD; bg = TTM_BG_GREEN; break;
+ case TCI_CYAN:
+ case TCI_B_CYAN: fg = TTM_BLACK; bg = TTM_BG_CYAN; break;
+ case TCI_BLUE:
+ case TCI_B_BLUE: fg = TTM_WHITE|TTM_BOLD; bg = TTM_BG_BLUE; break;
+ case TCI_MAGENTA:
+ case TCI_B_MAGENTA: fg = TTM_WHITE|TTM_BOLD; bg = TTM_BG_MAGENTA; break;
+ }
+
+ ccp *cname;
+ TermTextMode_t mode = TTM_BLACK;
+ for ( cname = col_names; *cname; cname++, mode += 0x01 )
+ {
+ fprintf(cv->f,"%*s%s %u %-9s %snormal %sbold %sbold+ul %sul%s reset %s\n",
+ cv->indent, "",
+ GetTextMode(cv->col_mode,fg|bg),
+ (int)(cname - col_names) + 30,
+ *cname,
+ GetTextMode(cv->col_mode,bg|mode),
+ GetTextMode(cv->col_mode,bg|TTM_BOLD),
+ GetTextMode(cv->col_mode,bg|TTM_UL),
+ GetTextMode(cv->col_mode,bg|TTM_NO_BOLD),
+ GetTextMode(cv->col_mode,bg|TTM_RESET),
+ cv->colset->reset );
+ }
+
+ //--------------------------------------------------
+
+ fprintf(cv->f,
+ "\n%*sStandard background colors with attributes (ul=underline) for mode %s:\n\n",
+ cv->indent, "", GetColorModeName(cv->col_mode,0) );
+
+ mode = TTM_BG_BLACK;
+ for ( cname = col_names; *cname; cname++, mode += 0x10 )
+ {
+ fprintf(cv->f,"%*s %u %-9s %s white %s bold %sul%s black %sbold %sul%s %s\n",
+ cv->indent, "",
+ (int)(cname - col_names) + 40,
+ *cname,
+ GetTextMode(cv->col_mode,mode|TTM_WHITE),
+ GetTextMode(cv->col_mode,TTM_BOLD),
+ GetTextMode(cv->col_mode,TTM_UL|TTM_NO_BOLD),
+ GetTextMode(cv->col_mode,mode|TTM_BLACK|TTM_RESET),
+ GetTextMode(cv->col_mode,TTM_BOLD),
+ GetTextMode(cv->col_mode,TTM_UL|TTM_NO_BOLD),
+ GetTextMode(cv->col_mode,TTM_NO_UL),
+ GetTextMode(cv->col_mode,TTM_RESET) );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ViewColorsCombi8 ( ColorView_t *cv )
+{
+ DASSERT(cv);
+ SetupColorView(cv);
+ DASSERT(cv->colset);
+
+ #ifdef TEST
+ fprintf(cv->f,"%*s%s() %s, %u colors, mode %d, stdcol %d, order %d\n",
+ cv->indent,"", __FUNCTION__,
+ GetColorModeName(cv->colset->col_mode,0),
+ cv->colset->n_colors, cv->mode, cv->std_col, cv->order );
+ #endif
+
+ fprintf(cv->f,
+ "%s\n%*sFont and background color combinations (no=normal, bo=bold) for mode %s:\n\n",
+ cv->colset->reset, cv->indent, "",
+ GetColorModeName(cv->col_mode,0) );
+
+ uint mode = TTM_BG_BLACK;
+ ccp *cname;
+ for ( cname = col_names; *cname; cname++, mode += 0x10 )
+ {
+ fprintf(cv->f,"%*s %u %-9s ",
+ cv->indent, "",
+ (int)(cname - col_names) + 40,
+ *cname );
+
+ TermTextMode_t mode2;
+ for ( mode2 = TTM_BLACK; mode2 <= TTM_WHITE; mode2++ )
+ fprintf(cv->f,"%s no %sbo ",
+ GetTextMode(cv->col_mode,mode|mode2|TTM_NO_BOLD),
+ GetTextMode(cv->col_mode,TTM_BOLD) );
+ fprintf(cv->f," %s\n",cv->colset->reset );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ViewColors18 ( ColorView_t *cv )
+{
+ DASSERT(cv);
+ SetupColorView(cv);
+ DASSERT(cv->colset);
+
+ #ifdef TEST
+ fprintf(cv->f,"%*s%s() %s, %u colors, mode %d, stdcol %d, order %d\n",
+ cv->indent,"", __FUNCTION__,
+ GetColorModeName(cv->colset->col_mode,0),
+ cv->colset->n_colors, cv->mode, cv->std_col, cv->order );
+ #endif
+
+ GetColorOption_t col_option = cv->col_option & ~GCM_ALT;
+
+ fprintf(cv->f,
+ "%s\n%*s18x18 color table ('0'=std, '1'=optimized%s) for mode %s:\n\n",
+ cv->colset->reset, cv->indent, "",
+ col_option & GCM_SHORT ? "; allow short codes" : "",
+ GetColorModeName(cv->col_mode,0) );
+
+ const int *pbg;
+ for ( pbg = Color18Index; *pbg >= 0; pbg++ )
+ {
+ uint bg = *pbg;
+ if ( bg == TCI_RED || bg == TCI_B_RED )
+ fputc('\n',cv->f);
+ fprintf(cv->f,"%*s %-12s ",
+ cv->indent, "", ColorIndexName[bg] );
+
+ const int *pfont;
+ for ( pfont = Color18Index; *pfont >= 0; pfont++ )
+ {
+ uint font = *pfont;
+ if ( font == TCI_RED || font == TCI_B_RED )
+ fprintf(cv->f," %s ",TERM_TEXT_MODE_RESET);
+ fprintf(cv->f,"%s 0%s1",
+ GetColorMode(cv->col_mode,font,bg,col_option),
+ GetColorMode(cv->col_mode,font,bg,col_option|GCM_ALT) );
+ }
+ fprintf(cv->f," %s\n",TERM_TEXT_MODE_RESET);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ViewColorsDC ( ColorView_t *cv, bool optimized )
+{
+ DASSERT(cv);
+ SetupColorView(cv);
+ DASSERT(cv->colset);
+
+ #ifdef TEST
+ fprintf(cv->f,"%*s%s() %s, %u colors, mode %d, stdcol %d, order %d\n",
+ cv->indent,"", __FUNCTION__,
+ GetColorModeName(cv->colset->col_mode,0),
+ cv->colset->n_colors, cv->mode, cv->std_col, cv->order );
+ #endif
+
+ GetColorOption_t col_option = cv->col_option & ~GCM_ALT;
+ if (optimized)
+ col_option |= GCM_ALT;
+
+ fprintf(cv->f,
+ "%s\n%*s%ux%u color table (%s%s) for mode %s:\n\n",
+ cv->colset->reset, cv->indent, "",
+ TCI__N, TCI__N,
+ optimized ? "optimized" : "standard",
+ col_option & GCM_SHORT ? "; allow short codes" : "",
+ GetColorModeName(cv->col_mode,0) );
+
+ uint bg;
+ for ( bg = 0; bg < TCI__N; bg++ )
+ {
+ if ( bg == TCI_RED || bg == TCI_B_RED )
+ fputc('\n',cv->f);
+ fprintf(cv->f,"%*s %-15s ",
+ cv->indent, "", ColorIndexName[bg] );
+
+ ccp fill = " ";
+ uint font;
+ for ( font = 0; font < TCI__N; font++ )
+ {
+ if ( font == TCI_RED || font == TCI_B_RED )
+ {
+ fprintf(cv->f," %s ",TERM_TEXT_MODE_RESET);
+ fill = " ";
+ }
+ fprintf(cv->f,"%s%sx",
+ GetColorMode(cv->col_mode,font,bg,col_option), fill );
+ fill = "";
+ }
+ fprintf(cv->f," %s\n",TERM_TEXT_MODE_RESET);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void ViewColors256_helper
+(
+ ColorView_t *cv, // basic data
+ uint beg, // first color printed
+ uint end, // first color not printed
+ uint step // increment
+)
+{
+ if ( cv->colset->n_colors < 8 )
+ {
+ uint i;
+ for ( i = beg; i < end; i += step )
+ fprintf(cv->f," %03u ",i);
+ }
+ #if 0 // [[2do]]
+ else if ( cv->colset->n_colors < 256 )
+ {
+ }
+ #endif
+ else
+ {
+ const int mode = cv->mode & 1 ? 48 : 38;
+
+ uint i;
+ for ( i = beg; i < end; i += step )
+ fprintf(cv->f,"\e[%u;5;%um %03u ",mode,i,i);
+ fputs("\e[0m",cv->f);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void ViewColors256 ( ColorView_t *cv )
+{
+ DASSERT(cv);
+ SetupColorView(cv);
+ DASSERT(cv->colset);
+
+ #ifdef TEST
+ fprintf(cv->f,"%*s%s() %s, %u colors, mode %d, stdcol %d, order %d\n",
+ cv->indent,"", __FUNCTION__,
+ GetColorModeName(cv->colset->col_mode,0),
+ cv->colset->n_colors, cv->mode, cv->std_col, cv->order );
+ #endif
+
+ fprintf(cv->f,
+ "%s\n%*s256 colors = 16 std colors + 6x6x6 colors + 24 gray for mode %s:\n",
+ cv->colset->reset, cv->indent, "",
+ GetColorModeName(cv->col_mode,0) );
+
+ int step, inc_horz, inc_vert;
+ switch (cv->order)
+ {
+ case 1:
+ step = 1;
+ inc_horz = 6;
+ inc_vert = 36;
+ break;
+
+ case 2:
+ step = 6;
+ inc_horz = 1;
+ inc_vert = 36;
+ break;
+
+ default:
+ step = 1;
+ inc_horz = 36;
+ inc_vert = 6;
+ break;
+ }
+
+ //--- 16 standard colors in 2 rows
+
+ fprintf(cv->f,"\n%*s ",cv->indent,"");
+ ViewColors256_helper(cv,0,8,1);
+ fprintf(cv->f,"\n%*s ",cv->indent,"");
+ ViewColors256_helper(cv,8,16,1);
+ fputs("\e[0m\n\n",cv->f);
+
+ uint bl, num = 16;
+
+ for ( bl = 0; bl < 3; bl++ )
+ {
+ uint i, n = num;
+ for ( i = 0; i < 6; i++ )
+ {
+ fprintf(cv->f,"%*s ",cv->indent,"");
+ ViewColors256_helper(cv,n,n+6*step,step);
+ fputs(" ",cv->f);
+
+ ViewColors256_helper(cv,n+inc_horz,n+inc_horz+6*step,step);
+ fputc('\n',cv->f);
+ n += inc_vert;
+ }
+
+ fputc('\n',cv->f);
+ num += 2*inc_horz;
+ }
+
+ fprintf(cv->f,"%*s ",cv->indent,"");
+ ViewColors256_helper(cv,232,232+12,1);
+ fprintf(cv->f,"\n%*s ",cv->indent,"");
+ ViewColors256_helper(cv,232+12,232+24,1);
+ fputs("\e[0m\n",cv->f);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void ViewColorsPredef_helper
+(
+ ColorView_t *cv, // basic data
+ ccp col_name, // name of color
+ ccp col_string // escape string for the color
+)
+{
+ DASSERT(cv);
+ DASSERT(col_string);
+
+ if ( col_string && *col_string )
+ fprintf(cv->f,"%*s %s %-15s %s \\e%s\n",
+ cv->indent, col_name, col_string, col_name,
+ cv->colset->reset, col_string+1 );
+ else
+ fprintf(cv->f,"%*s %s\n",
+ cv->indent, col_name, col_name );
+}
+
+//-----------------------------------------------------------------------------
+
+void ViewColorsPredef ( ColorView_t *cv, uint mode )
+{
+ DASSERT(cv);
+ SetupColorView(cv);
+ DASSERT(cv->colset);
+
+ #ifdef TEST
+ fprintf(cv->f,"%*s%s() %s, %u colors, mode %d, stdcol %d, order %d\n",
+ cv->indent,"", __FUNCTION__,
+ GetColorModeName(cv->colset->col_mode,0),
+ cv->colset->n_colors, cv->mode, cv->std_col, cv->order );
+ #endif
+
+ fprintf(cv->f,
+ "%s\n%*sPredefined%s color names for mode %s:\n\n",
+ cv->colset->reset, cv->indent, "",
+ ( mode & 3 ) == 1 ? " semantic" : "",
+ GetColorModeName(cv->col_mode,0) );
+
+ cv->indent += 14;
+
+ #undef JOB1
+ #undef JOB2
+ #define JOB1(n) ViewColorsPredef_helper(cv,#n,cv->colset->n)
+ #define JOB2(n,c) ViewColorsPredef_helper(cv,#n,cv->colset->c)
+
+ if ( mode & 1 )
+ {
+ const bool alt = cv->mode & 1;
+
+ JOB1(setup);
+ JOB1(run);
+ JOB1(abort);
+ JOB1(finish);
+ JOB1(script);
+ JOB1(open);
+ JOB1(close);
+ JOB1(file);
+ JOB1(job);
+
+ JOB1(info);
+ JOB1(hint);
+ JOB1(warn);
+ JOB1(debug);
+ JOB1(log);
+
+ JOB1(name);
+ JOB1(value); if (alt) JOB2(val,value);
+ JOB1(success);
+ JOB1(error);
+ JOB1(fail);
+ JOB1(fail2);
+ JOB1(fatal);
+ JOB1(mark);
+ JOB1(bad);
+
+ JOB1(select);
+ JOB1(differ);
+ JOB1(stat_line);
+ JOB1(warn_line);
+ JOB1(proc_line);
+ JOB1(cite);
+ JOB1(status);
+ JOB1(highlight); if (alt) JOB2(hl,highlight);
+ JOB1(hide);
+
+ JOB1(heading);
+ JOB1(caption);
+ JOB1(section);
+ JOB1(syntax);
+ JOB1(cmd);
+ JOB1(option); if (alt) JOB2(opt,option);
+ JOB1(param); if (alt) JOB2(par,param);
+
+ JOB1(off);
+ JOB1(on);
+ }
+
+ if ( mode & 2 )
+ {
+ if ( mode & 1 )
+ fputc('\n',cv->f);
+ JOB1(black);
+ JOB1(b_black);
+ JOB1(white);
+ JOB1(b_white);
+
+ if ( !(mode & 1) )
+ fputc('\n',cv->f);
+ JOB1(red);
+ JOB1(red_orange);
+ JOB1(orange);
+ JOB1(orange_yellow);
+ JOB1(yellow);
+ JOB1(yellow_green);
+ JOB1(green);
+ JOB1(green_cyan);
+ JOB1(cyan);
+ JOB1(cyan_blue);
+ JOB1(blue);
+ JOB1(blue_magenta);
+ JOB1(magenta);
+ JOB1(magenta_red);
+ JOB1(red);
+
+ if ( !(mode & 1) )
+ fputc('\n',cv->f);
+ JOB1(b_red);
+ JOB1(b_red_orange);
+ JOB1(b_orange);
+ JOB1(b_orange_yellow);
+ JOB1(b_yellow);
+ JOB1(b_yellow_green);
+ JOB1(b_green);
+ JOB1(b_green_cyan);
+ JOB1(b_cyan);
+ JOB1(b_cyan_blue);
+ JOB1(b_blue);
+ JOB1(b_blue_magenta);
+ JOB1(b_magenta);
+ JOB1(b_magenta_red);
+ JOB1(b_red);
+ }
+
+ #undef JOB1
+ #undef JOB2
+ cv->indent -= 14;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// old color views (wrapper) ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintTextModes
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ ColorMode_t col_mode // color mode
+)
+{
+ ColorView_t cv;
+ InitializeColorView(&cv);
+ cv.f = f;
+ cv.indent = indent;
+ cv.col_mode = col_mode;
+ ViewColorsAttrib8(&cv);
+ ViewColorsCombi8(&cv);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintColorModes
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ ColorMode_t col_mode, // color mode -> NormalizeColorMode()
+ GetColorOption_t option // execution options (GCM_ALT ignored)
+)
+{
+ ColorView_t cv;
+ InitializeColorView(&cv);
+ cv.f = f;
+ cv.indent = indent;
+ cv.col_mode = col_mode;
+ cv.col_option = option;
+ ViewColors18(&cv);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// stdout, stderr, stdlog and colors ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+FILE *stdlog = 0; // NULL or logfile
+bool close_stdlog = 0; // true: close file behing 'stdlog'
+
+FILE *stdmsg = 0; // 'stdlog' or 'stdout' or 'stderr' or NULL
+FILE *stdwrn = 0; // used by PrintError(), initialized with 'stderr'
+
+ColorMode_t colorize_stdout = COLMD_AUTO;
+ColorMode_t colorize_stderr = COLMD_AUTO;
+ColorMode_t colorize_stdlog = COLMD_AUTO;
+ColorMode_t colorize_stdmsg = COLMD_AUTO;
+ColorMode_t colorize_stdwrn = COLMD_AUTO;
+
+const ColorSet_t *colout = 0; // color set for 'stdout'
+const ColorSet_t *colerr = 0; // color set for 'stderr'
+const ColorSet_t *collog = 0; // color set for 'stdlog'
+const ColorSet_t *colmsg = 0; // color set for 'stdmsg'
+const ColorSet_t *colwrn = 0; // color set for 'stdwrn'
+
+///////////////////////////////////////////////////////////////////////////////
+
+ColorMode_t GetFileColorized ( FILE *f )
+{
+ if (!stdmsg)
+ SetupStdMsg();
+
+ return !f ? COLMD_OFF
+ : f == stdlog && colorize_stdlog != COLMD_AUTO ? colorize_stdlog
+ : f == stdout && colorize_stdout != COLMD_AUTO ? colorize_stdout
+ : f == stderr && colorize_stderr != COLMD_AUTO ? colorize_stderr
+ : f == stdmsg && colorize_stdmsg != COLMD_AUTO ? colorize_stdmsg
+ : f == stdwrn && colorize_stdwrn != COLMD_AUTO ? colorize_stdwrn
+ : opt_colorize >= COLMD_ON ? opt_colorize
+ : opt_colorize == COLMD_AUTO && isatty(fileno(f))
+ ? COLMD_AUTO
+ : COLMD_OFF;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupStdMsg()
+{
+ //--- setup
+
+ static bool active = false; // avoid loops
+ if (!active)
+ {
+ active = true;
+
+ stdwrn = stderr;
+ colwrn = colerr;
+ colorize_stdwrn = colorize_stderr;
+
+ if (!colout)
+ {
+ colorize_stdout = GetFileColorized(stdout);
+ colout = GetColorSet(colorize_stdout);
+ }
+
+ if (!colerr)
+ {
+ colorize_stderr = GetFileColorized(stderr);
+ colerr = GetColorSet(colorize_stderr);
+ }
+
+ if (!collog)
+ {
+ colorize_stdlog = GetFileColorized(stdlog);
+ collog = GetColorSet(colorize_stdlog);
+ }
+
+ active = false;
+ }
+
+ //--- assign to msg
+
+ if (stdlog)
+ {
+ stdmsg = stdlog;
+ colmsg = collog;
+ colorize_stdmsg = colorize_stdlog;
+ }
+ else if (stdout)
+ {
+ stdmsg = stdout;
+ colmsg = colout;
+ colorize_stdmsg = colorize_stdout;
+ }
+ else if (stderr)
+ {
+ stdmsg = stderr;
+ colmsg = colerr;
+ colorize_stdmsg = colorize_stderr;
+ }
+ else
+ {
+ stdmsg = 0;
+ colmsg = GetColorSet0();
+ colorize_stdmsg = 0;
+ }
+
+ noPRINT("SetupStdMsg() : out=%d[%zd], err=%d[%zd], log=%d[%zd], msg=%d[%zd]\n",
+ colorize_stdout, colout ? strlen(colout->reset) : -1,
+ colorize_stderr, colerr ? strlen(colerr->reset) : -1,
+ colorize_stdlog, collog ? strlen(collog->reset) : -1,
+ colorize_stdmsg, colmsg ? strlen(colmsg->reset) : -1 );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError OpenStdLog
+(
+ ccp fname, // If NULL or empty: don't open and use 'fallback'
+ // If first char == '+': open file in append mode
+ FILE *fallback // Fall back to this file, if no file name given
+ // If NULL: just clode 'stdlog'.
+)
+{
+ CloseStdLog();
+
+ bool append = fname && *fname == '+';
+ if (append)
+ fname++;
+
+ if ( !fname || !*fname )
+ {
+ if (fallback)
+ {
+ stdlog = fallback;
+ colorize_stdlog = GetFileColorized(stdlog);
+ PRINT("COLORIZE/LOG/FALLBACK: opt=%d, out=%d, err=%d, log=%d\n",
+ opt_colorize, colorize_stdout, colorize_stderr, colorize_stdlog );
+ }
+ }
+ else
+ {
+ if (!strcmp(fname,"-"))
+ {
+ stdlog = stdout;
+ colorize_stdlog = colorize_stdout;
+ }
+ else
+ {
+ stdlog = fopen( fname, append ? "ab" : "wb" );
+ if (!stdlog)
+ return ERROR1(ERR_CANT_CREATE,
+ "Can't open log file (mode=%s): %s\n",
+ append ? "append" : "create", fname );
+
+ close_stdlog = true;
+ colorize_stdlog = GetFileColorized(stdlog);
+ }
+
+ PRINT("COLORIZE/LOG: opt=%d, out=%d, err=%d, log=%d\n",
+ opt_colorize, colorize_stdout, colorize_stderr, colorize_stdlog );
+ }
+
+ collog = GetColorSet(colorize_stdlog);
+ SetupStdMsg();
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CloseStdLog()
+{
+ if (stdlog)
+ {
+ if (close_stdlog)
+ fclose(stdlog);
+ else
+ fflush(stdlog);
+ stdlog = 0;
+ }
+
+ close_stdlog = false;
+ colorize_stdlog = 0;
+ collog = GetColorSet0();
+
+ SetupStdMsg();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveStdFiles ( SavedStdFiles_t *ssf )
+{
+ DASSERT(ssf);
+
+ ssf->std_out = stdout;
+ ssf->std_err = stderr;
+ ssf->std_log = stdlog;
+ ssf->std_msg = stdmsg;
+ ssf->std_wrn = stdwrn;
+
+ ssf->col_out = colout;
+ ssf->col_err = colerr;
+ ssf->col_log = collog;
+ ssf->col_msg = colmsg;
+ ssf->col_wrn = colwrn;
+
+ ssf->stdout_seq_count = stdout_seq_count;
+
+ ssf->f = 0;
+ ssf->data = 0;
+ ssf->size = 0;
+ ssf->marker = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStdFiles ( SavedStdFiles_t *ssf )
+{
+ DASSERT(ssf);
+
+ stdout = ssf->std_out;
+ stderr = ssf->std_err;
+ stdlog = ssf->std_log;
+ stdmsg = ssf->std_msg;
+ stdwrn = ssf->std_wrn;
+
+ colout = ssf->col_out;
+ colerr = ssf->col_err;
+ collog = ssf->col_log;
+ colmsg = ssf->col_msg;
+ colwrn = ssf->col_wrn;
+
+ stdout_seq_count = ssf->stdout_seq_count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RedirectStdFiles
+(
+ SavedStdFiles_t *ssf, // not NULL: save old output here
+ FILE *f, // use this as out,err,log and msg, never NULL
+ const ColorSet_t *colset,// new colset; if NULL: use GetFileColorSet(f)
+ bool err_too // true: redirect stderr too
+)
+{
+ DASSERT(f);
+ if (ssf)
+ SaveStdFiles(ssf);
+
+ stdout = stdwrn = stdmsg = f;
+ colout = colwrn = colmsg = colset ? colset : GetFileColorSet(f);
+
+ // special handling for stdlog
+ if (stdlog)
+ {
+ stdlog = stdout;
+ collog = colout;
+ }
+
+ if (err_too)
+ {
+ stderr = stdout;
+ colerr = colout;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef __APPLE__
+
+enumError CatchStdFiles
+(
+ SavedStdFiles_t *ssf, // save old output and data here, never NULL
+ const ColorSet_t *colset // new colset; if NULL: no color support
+)
+{
+ DASSERT(ssf);
+ SaveStdFiles(ssf);
+ FILE *f = open_memstream(&ssf->data,&ssf->size);
+ if (!f)
+ return ERR_CANT_CREATE;
+
+ ssf->f = f;
+ RedirectStdFiles(0,f,colset,false);
+ return ERR_OK;
+}
+
+#endif // !__APPLE__
+///////////////////////////////////////////////////////////////////////////////
+
+void TermCatchStdFiles ( SavedStdFiles_t *ssf )
+{
+ DASSERT(ssf);
+ RestoreStdFiles(ssf);
+ if (ssf->f)
+ {
+ fclose(ssf->f);
+ ssf->f = 0;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-color.h b/project/dclib/dclib-color.h
new file mode 100644
index 0000000..6591f2e
--- /dev/null
+++ b/project/dclib/dclib-color.h
@@ -0,0 +1,828 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_COLOR_H
+#define DCLIB_COLOR_H 1
+
+#define _GNU_SOURCE 1
+#include <string.h>
+
+#include "dclib-types.h"
+#include "dclib-debug.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// color modes ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef SUPPORT_36_COLORS
+ #if NEW_FEATURES
+ #define SUPPORT_36_COLORS 1
+ #else
+ #define SUPPORT_36_COLORS 0
+ #endif
+#endif
+
+//-----------------------------------------------------------------------------
+// [[ColorMode_t]]
+
+typedef enum ColorMode_t
+{
+ COLMD_OFF = -1, // don't support colors
+ COLMD_AUTO = 0, // auto detect
+ COLMD_ON, // on, auto select mode
+ COLMD_8_COLORS, // force 8 color mode
+ COLMD_256_COLORS, // force 256 color mode
+}
+ColorMode_t;
+
+//-----------------------------------------------------------------------------
+
+struct KeywordTab_t;
+extern const struct KeywordTab_t color_mode_keywords[];
+extern ColorMode_t opt_colorize; // <0:no color, 0:auto, >0:force color
+
+int ScanOptColorize ( ColorMode_t *opt, ccp arg, ccp err_prefix );
+ccp GetColorModeName ( ColorMode_t col_mode, ccp res_not_found );
+
+//-----------------------------------------------------------------------------
+// returns COLMD_OFF | COLMD_8_COLORS | COLMD_256_COLORS
+
+ColorMode_t NormalizeColorMode ( ColorMode_t col_mode, int n_colors );
+
+ColorMode_t NormalizeColorModeByTermName
+(
+ ColorMode_t col_mode, // predefind mode
+ ccp term_name // find "256color" in terminal name.
+ // if NULL: use getenv("TERM")
+ // assume 8 or 256 colors!
+);
+
+struct TerminalInfo_t;
+ColorMode_t NormalizeColorModeByTerm
+(
+ ColorMode_t col_mode, // predefind mode
+ const struct
+ TerminalInfo_t *term // if NULL: use getenv("TERM") to find terminal
+);
+
+// iteration
+ColorMode_t GetNextColorMode ( ColorMode_t col_mode );
+ColorMode_t GetPrevColorMode ( ColorMode_t col_mode );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// terminal & colors ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern int termios_valid; // valid if >0
+
+void ResetTermios(void);
+bool EnableSingleCharInput(void);
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Sequence counter for output to stdout (and stderr).
+// Functions shall increment it on output. so that other functions
+// know about an destroid screen.
+
+extern uint stdout_seq_count;
+
+//-----------------------------------------------------------------------------
+// [[term_size_t]]
+
+typedef struct term_size_t
+{
+ uint width;
+ uint height;
+}
+term_size_t;
+
+extern uint opt_width, opt_height;
+int ScanOptWidth ( ccp arg );
+int ScanOptHeight ( ccp arg );
+
+term_size_t GetTermSize ( int default_width, int default_height );
+term_size_t GetTermSizeFD ( int fd, int default_width, int default_height );
+
+int GetTermWidth ( int default_value, int min_value );
+int GetTermWidthFD ( int fd, int default_value, int min_value );
+
+int GetTermHeight ( int default_value, int min_value );
+int GetTermHeightFD ( int fd, int default_value, int min_value );
+
+//-----------------------------------
+
+extern term_size_t auto_term_size; // default = {80,25};
+extern uint auto_term_resized; // incremented for each resize, init by 0
+extern uint auto_term_size_dirty; // >0: SIGWINCH received, auto_term_size is dirty
+
+bool GetAutoTermSize(void); // return true if changed
+void EnableAutoTermSize(void);
+
+//-----------------------------------------------------------------------------
+
+//----- some basic terminal codes
+
+#define TERM_CURSOR_UP "\e[A" // move cursor up
+#define TERM_CURSOR_DOWN "\e[B" // move cursor down
+#define TERM_CURSOR_RIGHT "\e[C" // move cursor right
+#define TERM_CURSOR_LEFT "\e[D" // move cursor left
+
+#define TERM_CURSOR_UP_N "\e[%dA" // move cursor N* up
+#define TERM_CURSOR_DOWN_N "\e[%dB" // move cursor N* down
+#define TERM_CURSOR_RIGHT_N "\e[%dC" // move cursor N* right
+#define TERM_CURSOR_LEFT_N "\e[%dD" // move cursor N* left
+
+#define TERM_CLEAR_EOL "\e[K" // clear from cursor to end of line
+#define TERM_CLEAR_BOL "\e[1K" // clear from beginning of line to cursor
+#define TERM_CLEAR_LINE "\e[2K" // clear entire line
+
+#define TERM_CLEAR_EOD "\e[J" // clear from cursor to end of display
+#define TERM_CLEAR_BOD "\e[1J" // clear from beginning of display to cursor
+#define TERM_CLEAR_DISPLAY "\e[2J" // clear Entire display
+
+#define TERM_SET_WRAP "\e[?7h" // enabled line wrapping
+#define TERM_RESET_WRAP "\e[?7l" // disable line wrapping
+
+#define TERM_TEXT_MODE_BEG "\e[" // begin of text mode sequence
+#define TERM_TEXT_MODE_END "m" // end of text mode sequence
+#define TERM_TEXT_MODE_RESET "\e[m" // reset text mode
+
+// pointers are initialized with codes above
+
+extern ccp TermCursorUp; // move cursor up
+extern ccp TermCursorDown; // move cursor down
+extern ccp TermCursorRight; // move cursor right
+extern ccp TermCursorLeft; // move cursor left
+
+extern ccp TermCursorUpN; // move cursor N* up (format string with 1 '%d')
+extern ccp TermCursorDownN; // move cursor N* down (format string with 1 '%d')
+extern ccp TermCursorRightN; // move cursor N* right (format string with 1 '%d')
+extern ccp TermCursorLeftN; // move cursor N* left (format string with 1 '%d')
+
+extern ccp TermClearEOL; // clear from cursor to end of line
+extern ccp TermClearBOL; // clear from beginning of line to cursor
+extern ccp TermClearLine; // clear entire line
+
+extern ccp TermClearEOD; // clear from cursor to end of display
+extern ccp TermClearBOD; // clear from beginning of display to cursor
+extern ccp TermClearDisplay; // clear entire display
+
+extern ccp TermSetWrap; // enabled line wrapping
+extern ccp TermResetWrap; // disable line wrapping
+
+extern ccp TermTextModeBeg; // begin of text mode sequence
+extern ccp TermTextModeEnd; // end of text mode sequence
+extern ccp TermTextModeReset; // reset text mode
+
+//-----------------------------------------------------------------------------
+// [[TermTextMode_t]]
+
+typedef enum TermTextMode_t
+{
+ //--- standard fg colors: \e[30..37m
+
+ TTM_BLACK = 0x08,
+ TTM_RED = 0x09,
+ TTM_GREEN = 0x0a,
+ TTM_YELLOW = 0x0b,
+ TTM_BLUE = 0x0c,
+ TTM_MAGENTA = 0x0d,
+ TTM_CYAN = 0x0e,
+ TTM_WHITE = 0x0f,
+
+
+ //--- standard bg colors: \e[40..47m
+
+ TTM_BG_BLACK = 0x80,
+ TTM_BG_RED = 0x90,
+ TTM_BG_GREEN = 0xa0,
+ TTM_BG_YELLOW = 0xb0,
+ TTM_BG_BLUE = 0xc0,
+ TTM_BG_MAGENTA = 0xd0,
+ TTM_BG_CYAN = 0xe0,
+ TTM_BG_WHITE = 0xf0,
+
+ //--- grayscale: \e[48;5;<VAL>m
+
+ TTM_GRAY0 = 0x04, // black
+ TTM_GRAY1 = 0x05, // dark gray
+ TTM_GRAY2 = 0x06, // light gray
+ TTM_GRAY3 = 0x07, // real white
+
+ TTM_BG_GRAY0 = 0x40, // black
+ TTM_BG_GRAY1 = 0x50, // dark gray
+ TTM_BG_GRAY2 = 0x60, // light gray
+ TTM_BG_GRAY3 = 0x70, // real white
+
+ //--- misc
+
+ TTM_RESET = 0x0100,
+ TTM_BOLD = 0x0200,
+ TTM_NO_BOLD = 0x0400,
+ TTM_UL = 0x0800,
+ TTM_NO_UL = 0x1000,
+
+ //--- flags and masks ans shifts
+
+ TTM_F_COLOR = 0x08,
+ TTM_M_COLOR = 0x07,
+ TTM_F_GRAYSCALE = 0x04, // only valid if !TTM_F_COLOR
+ TTM_M_GRAYSCALE = 0x03,
+
+ TTM_F_BG_COLOR = 0x80,
+ TTM_M_BG_COLOR = 0x70,
+ TTM_F_BG_GRAYSCALE = 0x40, // only valid if !TTM_F_BG_COLOR
+ TTM_M_BG_GRAYSCALE = 0x30,
+
+ TTM_SHIFT_BG = 4,
+
+
+ //--- and her are some recommendations for unique colors
+ //--- these Colors are also used to setup 'ColorSet_t'
+
+ TTM_COL_SETUP = TTM_BOLD | TTM_BLUE | TTM_BG_BLACK,
+ TTM_COL_RUN = TTM_BOLD | TTM_WHITE | TTM_BG_BLACK,
+ TTM_COL_ABORT = TTM_BOLD | TTM_MAGENTA | TTM_BG_BLACK,
+ TTM_COL_FINISH = TTM_BOLD | TTM_CYAN | TTM_BG_BLACK,
+ TTM_COL_SCRIPT = TTM_NO_BOLD | TTM_BLACK | TTM_BG_WHITE,
+ TTM_COL_OPEN = TTM_NO_BOLD | TTM_RED | TTM_BG_BLACK,
+ TTM_COL_CLOSE = TTM_NO_BOLD | TTM_RED | TTM_BG_BLACK,
+ TTM_COL_FILE = TTM_NO_BOLD | TTM_YELLOW | TTM_BG_BLACK,
+ TTM_COL_JOB = TTM_BOLD | TTM_BLUE | TTM_BG_BLACK,
+
+ TTM_COL_INFO = TTM_BOLD | TTM_CYAN | TTM_BG_BLACK,
+ TTM_COL_HINT = TTM_BOLD | TTM_YELLOW | TTM_BG_BLACK,
+ TTM_COL_WARN = TTM_BOLD | TTM_RED | TTM_BG_BLACK,
+ TTM_COL_DEBUG = TTM_BOLD | TTM_RED | TTM_BG_BLACK,
+ TTM_COL_LOG = TTM_BOLD | TTM_WHITE | TTM_BG_BLUE,
+
+ TTM_COL_NAME = TTM_NO_BOLD | TTM_CYAN | TTM_BG_BLACK,
+ TTM_COL_VALUE = TTM_NO_BOLD | TTM_GREEN | TTM_BG_BLACK,
+ TTM_COL_SUCCESS = TTM_BOLD | TTM_GREEN | TTM_BG_BLACK,
+ TTM_COL_ERROR = TTM_NO_BOLD | TTM_BLACK | TTM_BG_YELLOW,
+ TTM_COL_FAIL = TTM_NO_BOLD | TTM_WHITE | TTM_BG_RED,
+ TTM_COL_FAIL2 = TTM_BOLD | TTM_WHITE | TTM_BG_RED,
+ TTM_COL_FATAL = TTM_BOLD | TTM_WHITE | TTM_BG_MAGENTA,
+ TTM_COL_MARK = TTM_BOLD | TTM_WHITE | TTM_BG_BLACK,
+ TTM_COL_BAD = TTM_BOLD | TTM_MAGENTA | TTM_BG_BLACK,
+
+ TTM_COL_SELECT = TTM_NO_BOLD | TTM_YELLOW | TTM_BG_BLACK,
+ TTM_COL_DIFFER = TTM_BOLD | TTM_YELLOW | TTM_BG_BLUE,
+ TTM_COL_STAT_LINE = TTM_BOLD | TTM_WHITE | TTM_BG_BLUE,
+ TTM_COL_WARN_LINE = TTM_BOLD | TTM_RED | TTM_BG_BLUE,
+ TTM_COL_PROC_LINE = TTM_BOLD | TTM_WHITE | TTM_BG_MAGENTA,
+ TTM_COL_CITE = TTM_NO_BOLD | TTM_GREEN | TTM_BG_BLACK,
+ TTM_COL_STATUS = TTM_BOLD | TTM_GREEN | TTM_BG_BLACK,
+ TTM_COL_HIGHLIGHT = TTM_BOLD | TTM_YELLOW | TTM_BG_BLACK,
+ TTM_COL_HIDE = TTM_BOLD | TTM_BLACK | TTM_BG_BLACK,
+
+ TTM_COL_HEADING = TTM_BOLD | TTM_BLUE | TTM_BG_BLACK,
+ TTM_COL_CAPTION = TTM_BOLD | TTM_CYAN | TTM_BG_BLACK,
+ TTM_COL_SECTION = TTM_BOLD | TTM_GREEN | TTM_BG_BLACK,
+ TTM_COL_SYNTAX = TTM_BOLD | TTM_WHITE | TTM_BG_BLACK,
+ TTM_COL_CMD = TTM_NO_BOLD | TTM_CYAN | TTM_BG_BLACK,
+ TTM_COL_OPTION = TTM_NO_BOLD | TTM_GREEN | TTM_BG_BLACK,
+ TTM_COL_PARAM = TTM_NO_BOLD | TTM_YELLOW | TTM_BG_BLACK,
+
+ TTM_COL_ON = TTM_BOLD | TTM_GREEN | TTM_BG_BLACK,
+ TTM_COL_OFF = TTM_BOLD | TTM_RED | TTM_BG_BLACK,
+}
+TermTextMode_t;
+
+//-----------------------------------------------------------------------------
+// [[TermColorIndex_t]]
+
+typedef enum TermColorIndex_t
+{
+ #if SUPPORT_36_COLORS
+
+ TCI_GRAY0, // gray scale
+ TCI_GRAY1,
+ TCI_GRAY2,
+ TCI_GRAY3,
+ TCI_GRAY4,
+ TCI_GRAY5,
+ TCI_GRAY6,
+ TCI_GRAY7,
+
+ TCI_RED, // normal colors in rainbow order
+ TCI_RED_ORANGE,
+ TCI_ORANGE,
+ TCI_ORANGE_YELLOW,
+ TCI_YELLOW,
+ TCI_YELLOW_GREEN,
+ TCI_GREEN,
+ TCI_GREEN_CYAN,
+ TCI_CYAN,
+ TCI_CYAN_BLUE,
+ TCI_BLUE,
+ TCI_BLUE_MAGENTA,
+ TCI_MAGENTA,
+ TCI_MAGENTA_RED,
+
+ TCI_B_RED, // bold colors in rainbow order
+ TCI_B_RED_ORANGE,
+ TCI_B_ORANGE,
+ TCI_B_ORANGE_YELLOW,
+ TCI_B_YELLOW,
+ TCI_B_YELLOW_GREEN,
+ TCI_B_GREEN,
+ TCI_B_GREEN_CYAN,
+ TCI_B_CYAN,
+ TCI_B_CYAN_BLUE,
+ TCI_B_BLUE,
+ TCI_B_BLUE_MAGENTA,
+ TCI_B_MAGENTA,
+ TCI_B_MAGENTA_RED,
+
+ TCI__IGNORE, // ignore this param, for GetColorMode()
+
+ //--- alternative names
+
+ TCI_BLACK = TCI_GRAY0,
+ TCI_DARKGRAY = TCI_GRAY2,
+ TCI_LIGHTGRAY = TCI_GRAY5,
+ TCI_WHITE = TCI_GRAY7,
+
+ //--- counters
+
+ TCI__N = TCI__IGNORE, // number of supported colors
+ TCI__N_FONT = TCI_B_RED, // number of recommended font colors
+ TCI__N_BG = TCI__N, // number of recommended background colors
+
+ #else // !SUPPORT_36_COLORS
+
+ TCI_BLACK, // gray scale
+ TCI_DARKGRAY,
+ TCI_LIGHTGRAY,
+ TCI_WHITE,
+
+ TCI_RED, // normal colors in rainbow order
+ TCI_ORANGE,
+ TCI_YELLOW,
+ TCI_GREEN,
+ TCI_CYAN,
+ TCI_BLUE,
+ TCI_MAGENTA,
+
+ TCI_B_RED, // bold colors in rainbow order
+ TCI_B_ORANGE,
+ TCI_B_YELLOW,
+ TCI_B_GREEN,
+ TCI_B_CYAN,
+ TCI_B_BLUE,
+ TCI_B_MAGENTA,
+
+ TCI__IGNORE, // ignore this param, for GetColorMode()
+
+ //--- counters
+
+ TCI__N = TCI__IGNORE, // number of supported colors
+ TCI__N_FONT = TCI_B_RED, // number of recommended font colors
+ TCI__N_BG = TCI__N, // number of recommended background colors
+
+ #endif // !SUPPORT_36_COLORS
+}
+TermColorIndex_t;
+
+#if SUPPORT_36_COLORS
+ extern const char ColorIndexName[TCI__N][16];
+#else
+ extern const char ColorIndexName[TCI__N][11];
+#endif
+
+extern const int Color18Index[18+1]; // 18 elements + -1 as terminator
+
+//-----------------------------------------------------------------------------
+// [[ColorSet_t]]
+
+typedef struct ColorSet_t
+{
+ //--- header
+
+ ColorMode_t col_mode;
+ u8 colorize;
+ u16 n_colors;
+
+ //--- some strings for formatted output if colors active
+
+ ccp space; // pointer to 'EmptyString' or to 'Space200';
+ ccp tab; // pointer to 'EmptyString' or to 'TAB20';
+ ccp lf; // pointer to 'EmptyString' or to 'LF20';
+
+ //--- data, always begins with 'reset'
+
+ ccp reset;
+
+ ccp setup;
+ ccp run;
+ ccp abort;
+ ccp finish;
+ ccp script;
+ ccp open;
+ ccp close;
+ ccp file;
+ ccp job;
+
+ ccp info;
+ ccp hint;
+ ccp warn;
+ ccp debug;
+ ccp log;
+
+ ccp name;
+ ccp value;
+ ccp success;
+ ccp error;
+ ccp fail;
+ ccp fail2;
+ ccp fatal;
+ ccp mark;
+ ccp bad;
+
+ ccp select;
+ ccp differ;
+ ccp stat_line;
+ ccp warn_line;
+ ccp proc_line;
+ ccp cite;
+ ccp status;
+ ccp highlight;
+ ccp hide;
+
+ ccp heading;
+ ccp caption;
+ ccp section;
+ ccp syntax;
+ ccp cmd;
+ ccp option;
+ ccp param;
+
+ ccp on;
+ ccp off;
+
+ ccp black;
+ ccp b_black;
+ ccp white;
+ ccp b_white;
+
+ ccp red;
+ ccp red_orange;
+ ccp orange;
+ ccp orange_yellow;
+ ccp yellow;
+ ccp yellow_green;
+ ccp green;
+ ccp green_cyan;
+ ccp cyan;
+ ccp cyan_blue;
+ ccp blue;
+ ccp blue_magenta;
+ ccp magenta;
+ ccp magenta_red;
+
+ ccp b_red;
+ ccp b_red_orange;
+ ccp b_orange;
+ ccp b_orange_yellow;
+ ccp b_yellow;
+ ccp b_yellow_green;
+ ccp b_green;
+ ccp b_green_cyan;
+ ccp b_cyan;
+ ccp b_cyan_blue;
+ ccp b_blue;
+ ccp b_blue_magenta;
+ ccp b_magenta;
+ ccp b_magenta_red;
+
+ ccp matrix[TCI__N_FONT][TCI__N_BG];
+}
+ColorSet_t;
+
+//-----------------------------------------------------------------------------
+
+ccp GetTextMode
+(
+ // returns a pointer to an internal circulary buffer (not GetCircBuf())
+ ColorMode_t col_mode, // if < COLMD_ON: return an empty string
+ TermTextMode_t mode // mode for text generation
+);
+
+//-----------------------------------------------------------------------------
+// [[GetColorOption_t]]
+
+typedef enum GetColorOption_t
+{
+ GCM_ALT = 0x01, // use recommended font color if font+bg is not readable
+ GCM_SHORT = 0x02, // allow alternative short codes (codes 30-37 and 40-47)
+}
+GetColorOption_t;
+
+ccp GetColorMode
+(
+ // returns a pointer to an internal circulary buffer (not GetCircBuf())
+
+ ColorMode_t col_mode, // if < COLMD_ON: return an empty string
+ TermColorIndex_t font_index, // index of font color
+ TermColorIndex_t bg_index, // index of background color
+ GetColorOption_t option // execution options
+);
+
+//-----------------------------------------------------------------------------
+
+typedef void (*PrintColorFunc)
+(
+ // On start of group: col_name := NULL, col_string := NULL
+
+ FILE *f, // valid output file
+ int indent, // normalized indention of output
+ const ColorSet_t *cs, // valid color set, never NULL
+ uint mode, // output mode of PrintColorSetHelper()
+ ccp col_name, // name of color
+ ccp col_string // escape string for the color
+);
+
+//-----------------------------------------------------------------------------
+
+const ColorSet_t * GetColorSet ( ColorMode_t col_mode );
+const ColorSet_t * GetColorSet0(void);
+const ColorSet_t * GetColorSet8(void);
+const ColorSet_t * GetColorSet256(void);
+const ColorSet_t * GetColorSetAuto ( bool force_on );
+
+const ColorSet_t * GetFileColorSet ( FILE *f );
+bool SetupColorSet ( ColorSet_t *cc, FILE *f );
+ccp GetColorByName ( const ColorSet_t *colset, ccp name );
+
+void PrintColorSet
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ const ColorSet_t *cs // valid color set; if NULL: use std colors
+);
+
+void PrintColorSetEx
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ const ColorSet_t *cs, // valid color set; if NULL: use std colors
+ uint mode, // output mode => see PrintColorSetHelper()
+ uint format // output format
+ // 0: colored list
+ // 1: shell definitions
+);
+
+void PrintColorSetHelper
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ const ColorSet_t *cs, // valid color set; if NULL: use std colors
+ PrintColorFunc func, // output function, never NULL
+ uint mode // output mode (bit field, NULL=default):
+ // 1: print normal colors (e.g. RED)
+ // 2: print bold colors (e.g. B_RED)
+ // 4: print background (e.g. BLUE_RED)
+ // 8: print color names (e.g. HIGHLIGHT)
+ // 16: include alternative names too (e.g. HL)
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// color helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u32 ColorTab_M0_M15[16]; // first 16 colors of "\e[m"
+
+// return a m256 index
+u8 ConvertColorRGB3ToM256 ( u8 r, u8 g, u8 b );
+u8 ConvertColorRGBToM256 ( u32 rgb );
+
+u32 ConvertColorM256ToRGB ( u8 m256 );
+
+static inline uint SingleColorToM6 ( u8 col )
+ { return col > 114 ? ( col - 35 ) / 40 : col > 47; }
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ViewColor*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[ColorView_t]] : parameters to view color sets
+
+typedef struct ColorView_t
+{
+ FILE *f; // valid output file
+ int indent; // indention of output
+
+ const struct TerminalInfo_t
+ *term; // not NULL: us it to find 'colset'
+ ColorMode_t col_mode; // color mode -> NormalizeColorMode()
+ const ColorSet_t *colset; // not NULL: ignore 'col_mode'
+
+ TermColorIndex_t std_col; // standard bg or fg color, TCI__IGNORE allowed
+ int order; // 0|'r' | 1|'g' | 2|'b'
+ GetColorOption_t col_option; // color option
+ uint mode; // function specific mode (modulo 60)
+
+ void *user_ptr; // any pointer
+ int user_int; // any number or id
+}
+ColorView_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline void InitializeColorView ( ColorView_t *cv )
+ { DASSERT(cv); memset(cv,0,sizeof(*cv)); cv->std_col = TCI__IGNORE; }
+
+void SetupColorView ( ColorView_t *cv );
+void SetupColorViewMode ( ColorView_t *cv, ColorMode_t col_mode );
+
+void ViewColorsAttrib8 ( ColorView_t *cv );
+void ViewColorsCombi8 ( ColorView_t *cv );
+void ViewColors18 ( ColorView_t *cv );
+void ViewColorsDC ( ColorView_t *cv, bool optimized );
+void ViewColors256 ( ColorView_t *cv );
+void ViewColorsPredef ( ColorView_t *cv, uint mode ); // mode: 1=name, 2=colors
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// legacy functions (deprecated) ==> wrapper for ViewColors*()
+
+void PrintTextModes
+(
+ // => ViewColorsAttrib8() & ViewColorsCombi8()
+
+ FILE *f, // valid output file
+ int indent, // indention of output
+ ColorMode_t col_mode // color mode -> NormalizeColorMode()
+);
+
+void PrintColorModes
+(
+ FILE *f, // valid output file
+ int indent, // indention of output
+ ColorMode_t col_mode, // color mode -> NormalizeColorMode()
+ GetColorOption_t option // execution options (GCM_ALT ignored)
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// std*, col* ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////
+//
+// We have 5 standard output files
+//
+// stdout, colout:
+// Standard output like usual.
+//
+// stderr, colerr:
+// Standard error output like usual. It is usually not redirected by
+// RedirectStdFiles() and points always to its original.
+//
+// stdlog, collog:
+// Logging output, may be NULL to tell: no logging.
+// RedirectStdFiles() don't cange it, if it is NULL.
+//
+// stdmsg, colmsg:
+// Special output for catcable messages. Initalized by SetupStdMsg()
+// and set to the first not NULL of stdout, stderr and stdlog.
+//
+// stdwrn, colwrn:
+// Output file of PrintError() functions.
+// Initalized to a copy of stderr.
+//
+////////////////////////////////////////////////////////////////////////////
+
+
+extern FILE *stdlog; // NULL or logfile
+extern bool close_stdlog; // true: close file behing 'stdlog'
+
+extern FILE *stdmsg; // 'stdlog' or 'stdout' or 'stderr' or NULL
+extern FILE *stdwrn; // used by PrintError(), initialized with 'stderr'
+
+extern ColorMode_t colorize_stdout; // <0:no color, 0:auto, >0:force color
+extern ColorMode_t colorize_stderr;
+extern ColorMode_t colorize_stdlog;
+extern ColorMode_t colorize_stdmsg;
+extern ColorMode_t colorize_stdwrn;
+
+extern const ColorSet_t *colout; // color set for 'stdout'
+extern const ColorSet_t *colerr; // color set for 'stderr'
+extern const ColorSet_t *collog; // color set for 'stdlog'
+extern const ColorSet_t *colmsg; // color set for 'stdmsg'
+extern const ColorSet_t *colwrn; // color set for 'stdwrn'
+
+//-----------------------------------------------------------------------------
+
+ColorMode_t GetFileColorized ( FILE *f );
+static inline bool IsFileColorized ( FILE *f )
+ { return GetFileColorized(f) >= COLMD_ON; }
+
+enumError OpenStdLog
+(
+ ccp fname, // If NULL or empty: don't open and use 'fallback'
+ // If first char == '+': open file in append mode
+ FILE *fallback // Fall back to this file, if no file name given
+ // If NULL: just clode 'stdlog'.
+);
+
+void CloseStdLog(void);
+void SetupStdMsg(void);
+
+//-----------------------------------------------------------------------------
+// [[SavedStdFiles_t]]
+
+typedef struct SavedStdFiles_t
+{
+ //-- 5 output streams
+
+ FILE *std_out;
+ FILE *std_err;
+ FILE *std_log;
+ FILE *std_msg;
+ FILE *std_wrn;
+
+ //-- 5 related color sets
+
+ const ColorSet_t *col_out;
+ const ColorSet_t *col_err;
+ const ColorSet_t *col_log;
+ const ColorSet_t *col_msg;
+ const ColorSet_t *col_wrn;
+
+ //--- misc
+
+ uint stdout_seq_count;
+
+ //--- support for CatchStdFiles()
+
+ FILE *f; // NULL or result of open_memstream()
+ char *data; // data pointer
+ size_t size; // size of 'data'
+ uint marker; // marker, free member, e.g. grow_buffer->used
+}
+SavedStdFiles_t;
+
+void SaveStdFiles ( SavedStdFiles_t *ssf );
+void RestoreStdFiles ( SavedStdFiles_t *ssf );
+
+void RedirectStdFiles
+(
+ SavedStdFiles_t *ssf, // not NULL: save old output here
+ FILE *f, // use this as out,err,log and msg, never NULL
+ const ColorSet_t *colset,// new colset; if NULL: use GetFileColorSet(f)
+ bool err_too // true: redirect stderr too
+);
+
+#ifndef __APPLE__
+ enumError CatchStdFiles
+ (
+ SavedStdFiles_t *ssf, // save old output and data here, never NULL
+ const ColorSet_t *colset // new colset; if NULL: no color support
+ );
+#endif
+
+// call XFREE(ssf->data) after 'data' is used.
+void TermCatchStdFiles ( SavedStdFiles_t *ssf );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_COLOR_H
diff --git a/project/dclib/dclib-debug.c b/project/dclib/dclib-debug.c
new file mode 100644
index 0000000..506be18
--- /dev/null
+++ b/project/dclib/dclib-debug.c
@@ -0,0 +1,1891 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define DCLIB_DEBUG_C 1
+#include "dclib-debug.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "dclib-basics.h"
+#include "dclib-file.h"
+
+#if ENABLE_HEXDUMP_WRAPPER > 0
+ #include "dclib-xdump.h"
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// error messages ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp TRACE_PREFIX = "";
+FILE *TRACE_FILE = 0;
+FILE *MEM_LOG_FILE = 0;
+
+ccp (*GetErrorNameHook)( int stat, ccp ret_not_found ) = 0;
+ccp (*GetErrorTextHook)( int stat, ccp ret_not_found ) = 0;
+
+ccp GENERIC_ERROR_MESSAGE = "generic err";
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetErrorName
+(
+ int stat, // error status, err := abs(stat)
+ ccp ret_not_found // not NULL: return this, if no message is found
+ // GENERIC_ERROR_MESSAGE: return a generic message
+)
+{
+ if (GetErrorNameHook)
+ {
+ ccp res = GetErrorNameHook(stat,0);
+ if ( res && *res )
+ return res;
+ }
+
+ if ( stat < 0 )
+ stat = -stat;
+
+ switch(stat)
+ {
+ case ERR_OK: return "OK";
+
+ case ERR_DIFFER: return "DIFFER";
+ case ERR_NOTHING_TO_DO: return "NOTHING TO DO";
+ case ERR_SOURCE_FOUND: return "SOURCE FOUND";
+ case ERR_NO_SOURCE_FOUND: return "NO SOURCE";
+ case ERR_JOB_IGNORED: return "JOB IGNORED";
+ case ERR_SUBJOB_WARNING: return "SUB JOB WARNINGS";
+ case ERR_NOT_EXISTS: return "NOT EXISTS";
+
+ case ERR_WARNING: return "WARNING";
+
+ case ERR_WRONG_FILE_TYPE: return "WRONG FILE TYPE";
+ case ERR_INVALID_FILE: return "INVALID FILE";
+ case ERR_INVALID_VERSION: return "INVALID VERSION";
+ case ERR_INVALID_DATA: return "INVALID DATA";
+
+ case ERR_ENCODING: return "ENCODING FAILED";
+ case ERR_DECODING: return "DECODING FAILED";
+ case ERR_ALREADY_EXISTS: return "FILE ALREADY EXISTS";
+ case ERR_SUBJOB_FAILED: return "SUB JOB FAILED";
+
+ case ERR_CANT_REMOVE: return "CAN'T REMOVE FILE";
+ case ERR_CANT_RENAME: return "CAN'T RENAME FILE";
+ case ERR_CANT_CLOSE: return "CAN'T CLOSE FILE";
+ case ERR_CANT_CONNECT: return "CAN'T CONNECT";
+ case ERR_CANT_OPEN: return "CAN'T OPEN FILE";
+ case ERR_CANT_APPEND: return "CAN'T APPEND FILE";
+ case ERR_CANT_CREATE: return "CAN'T CREATE FILE";
+ case ERR_CANT_CREATE_DIR: return "CAN'T CREATE DIRECTORY";
+
+ case ERR_READ_FAILED: return "READ FILE FAILED";
+ case ERR_REMOVE_FAILED: return "REMOVE FILE FAILED";
+ case ERR_WRITE_FAILED: return "WRITE FILE FAILED";
+ case ERR_DATABASE: return "DATABASE ACCESS FAILED";
+
+ case ERR_MISSING_PARAM: return "MISSING PARAMETERS";
+ case ERR_SEMANTIC: return "SEMANTIC ERROR";
+ case ERR_SYNTAX: return "SYNTAX ERROR";
+
+ case ERR_INTERRUPT: return "INTERRUPT";
+
+ case ERR_ERROR: return "ERROR";
+
+ case ERR_NOT_IMPLEMENTED: return "NOT IMPLEMENTED YET";
+ case ERR_INTERNAL: return "INTERNAL ERROR";
+ case ERR_OUT_OF_MEMORY: return "OUT OF MEMORY";
+
+ case ERR_FATAL: return "FATAL ERROR";
+ }
+
+ if ( ret_not_found != GENERIC_ERROR_MESSAGE )
+ return ret_not_found;
+
+ //--- generic messages
+
+ if (GetErrorNameHook)
+ {
+ ccp res = GetErrorNameHook(stat,GENERIC_ERROR_MESSAGE);
+ if ( res && *res )
+ return res;
+ }
+
+ if ( stat >= ERU_WARN_00 & stat <= ERU_WARN_XX )
+ {
+ const uint buf_size = 20;
+ char *buf = GetCircBuf(buf_size);
+ snprintf(buf,buf_size,"USER WARNING #%02u", stat - ERU_WARN_00 );
+ return buf;
+ }
+
+ if ( stat >= ERU_ERROR1_00 & stat <= ERU_ERROR1_XX )
+ {
+ const uint buf_size = 20;
+ char *buf = GetCircBuf(buf_size);
+ snprintf(buf,buf_size,"USER ERROR #%02u", stat - ERU_ERROR1_00 );
+ return buf;
+ }
+
+ if ( stat >= ERU_ERROR2_00 & stat <= ERU_ERROR2_XX )
+ {
+ const uint buf_size = 20;
+ char *buf = GetCircBuf(buf_size);
+ snprintf(buf,buf_size,"USER ERROR #%02u",
+ stat - ERU_ERROR2_00 + ERU_ERROR1_XX+1 - ERU_ERROR1_00 );
+ return buf;
+ }
+
+ if ( stat >= ERU_FATAL_00 & stat <= ERU_FATAL_XX )
+ {
+ const uint buf_size = 24;
+ char *buf = GetCircBuf(buf_size);
+ snprintf(buf,buf_size,"USER FATAL ERROR #%02u", stat - ERU_FATAL_00 );
+ return buf;
+ }
+
+ ccp res = GetErrorName(stat+1,"");
+ return res && *res ? res : "?";
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetErrorText
+(
+ int stat, // error status, err := abs(stat)
+ ccp ret_not_found // not NULL: return this, if no message is found
+ // GENERIC_ERROR_MESSAGE: return a generic message
+)
+{
+ if (GetErrorTextHook)
+ {
+ ccp res = GetErrorTextHook(stat,0);
+ if (res)
+ return res;
+ }
+
+ if ( stat < 0 )
+ stat = -stat;
+
+ switch(stat)
+ {
+ case ERR_OK: return "Ok";
+
+ case ERR_DIFFER: return "Files differ";
+ case ERR_NOTHING_TO_DO: return "Nothing to do";
+ case ERR_SOURCE_FOUND: return "Source found";
+ case ERR_NO_SOURCE_FOUND: return "No source found";
+ case ERR_JOB_IGNORED: return "Job ignored";
+ case ERR_SUBJOB_WARNING: return "Sub job had warnings";
+ case ERR_NOT_EXISTS: return "File does not exists";
+
+ case ERR_WARNING: return "Unspecific warning";
+
+ case ERR_WRONG_FILE_TYPE: return "Wrong type of file";
+ case ERR_INVALID_FILE: return "Invalid file";
+ case ERR_INVALID_VERSION: return "Invalid file version";
+ case ERR_INVALID_DATA: return "Invalid data";
+
+ case ERR_ENCODING: return "Encoding data failed";
+ case ERR_DECODING: return "Decoding data failed";
+ case ERR_ALREADY_EXISTS: return "File already exists";
+ case ERR_SUBJOB_FAILED: return "Sub job failed";
+
+ case ERR_CANT_REMOVE: return "Can't remove file";
+ case ERR_CANT_RENAME: return "Can't rename file";
+ case ERR_CANT_CLOSE: return "Can't close file";
+ case ERR_CANT_CONNECT: return "Can't connect";
+ case ERR_CANT_OPEN: return "Can't open file";
+ case ERR_CANT_APPEND: return "Can't open file for appending";
+ case ERR_CANT_CREATE: return "Can't create file";
+ case ERR_CANT_CREATE_DIR: return "Can't create directory";
+
+ case ERR_READ_FAILED: return "Reading from file failed";
+ case ERR_REMOVE_FAILED: return "Removing a file failed";
+ case ERR_WRITE_FAILED: return "Writing to file failed";
+ case ERR_DATABASE: return "Access to database failed";
+
+ case ERR_MISSING_PARAM: return "Missing at least one parameter";
+ case ERR_SEMANTIC: return "Semantic error";
+ case ERR_SYNTAX: return "Syntax error";
+
+ case ERR_INTERRUPT: return "Program interrupted by user";
+
+ case ERR_ERROR: return "Unspecific error";
+
+ case ERR_NOT_IMPLEMENTED: return "Not implemented yet";
+ case ERR_INTERNAL: return "Internal error";
+ case ERR_OUT_OF_MEMORY: return "Allocation of dynamic memory failed";
+
+ case ERR_FATAL: return "Unspecific fatal error";
+ }
+
+ if ( ret_not_found != GENERIC_ERROR_MESSAGE )
+ return ret_not_found;
+
+ //--- generic messages
+
+ if (GetErrorTextHook)
+ {
+ ccp res = GetErrorTextHook(stat,GENERIC_ERROR_MESSAGE);
+ if ( res && *res )
+ return res;
+ }
+
+ if ( stat >= ERU_WARN_00 & stat <= ERU_WARN_XX )
+ {
+ const uint buf_size = 20;
+ char *buf = GetCircBuf(buf_size);
+ snprintf(buf,buf_size,"USER WARNING #%02u", stat - ERU_WARN_00 );
+ return buf;
+ }
+
+ if ( stat >= ERU_ERROR1_00 & stat <= ERU_ERROR1_XX )
+ {
+ const uint buf_size = 20;
+ char *buf = GetCircBuf(buf_size);
+ snprintf(buf,buf_size,"USER ERROR #%02u", stat - ERU_ERROR1_00 );
+ return buf;
+ }
+
+ if ( stat >= ERU_ERROR2_00 & stat <= ERU_ERROR2_XX )
+ {
+ const uint buf_size = 20;
+ char *buf = GetCircBuf(buf_size);
+ snprintf(buf,buf_size,"USER ERROR #%02u",
+ stat - ERU_ERROR2_00 + ERU_ERROR1_XX+1 - ERU_ERROR1_00 );
+ return buf;
+ }
+
+ if ( stat >= ERU_FATAL_00 & stat <= ERU_FATAL_XX )
+ {
+ const uint buf_size = 24;
+ char *buf = GetCircBuf(buf_size);
+ snprintf(buf,buf_size,"USER FATAL ERROR #%02u", stat - ERU_FATAL_00 );
+ return buf;
+ }
+
+ ccp res = GetErrorText(stat+1,0);
+ return res && *res ? res : "?";
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ListErrorCodes
+(
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ bool all // true: print all user reserved messages too.
+)
+{
+ if (!f)
+ return;
+
+ indent = NormalizeIndent(indent);
+ fprintf(f,"\n%*sList of error messages:\n\n",indent,"");
+
+ ccp p2 = all ? 0 : "";
+
+ uint i, fw_name = 4, fw_text = 7;
+ for ( i = 0; i < ERR__N; i++ )
+ {
+ ccp name = GetErrorName(i,p2);
+ if (name)
+ {
+ const uint len = strlen(name);
+ if ( fw_name < len )
+ fw_name = len;
+ }
+
+ ccp text = GetErrorText(i,p2);
+ {
+ const uint len = strlen(text);
+ if ( fw_text < len )
+ fw_text = len;
+ }
+ }
+
+ fprintf(f,
+ "%*s err | %-*s | %-*s\n"
+ "%*s-----+%.*s+%.*s\n",
+ indent, "",
+ fw_name, "name",
+ fw_text, "message",
+ indent, "",
+ fw_name+2, Minus300,
+ fw_text+2, Minus300 );
+
+ for ( i = 0; i < ERR__N; i++ )
+ {
+ ccp name = GetErrorName(i,p2);
+ ccp text = GetErrorText(i,p2);
+ if ( name && *name || text && *text )
+ fprintf(f,"%*s%4u | %-*s | %-*s\n",
+ indent, "", i,
+ fw_name, name ? name : "-",
+ fw_text, text ? text : "-" );
+
+ if ( i == ERR_OK || i == ERR_WARNING || i == ERR_ERROR )
+ fprintf(f,"%*s-----+%.*s+%.*s\n",
+ indent, "",
+ fw_name+2, Minus300,
+ fw_text+2, Minus300 );
+ }
+
+ fprintf(f,"%*s-----+%.*s+%.*s\n\n",
+ indent, "",
+ fw_name+2, Minus300,
+ fw_text+2, Minus300 );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// error handling ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp progpath = 0;
+ccp progdir = 0;
+ccp progname = 0;
+
+ccp toolname = 0;
+ccp toolversion = 0;
+ccp tooltitle = 0;
+
+bool multi_processing = false;
+enumError last_error = ERR_OK;
+enumError max_error = ERR_OK;
+u32 error_count = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PrintError ( ccp func, ccp file, uint line,
+ int syserr, enumError err_code, ccp format, ... )
+{
+ va_list arg;
+ va_start(arg,format);
+ const int stat = PrintErrorArg(func,file,line,syserr,err_code,format,arg);
+ va_end(arg);
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PrintErrorFile ( ccp func, ccp file, uint line, struct File_t *F,
+ int syserr, enumError err_code, ccp format, ... )
+{
+ va_list arg;
+ va_start(arg,format);
+ const int stat = PrintErrorArg(func,file,line,syserr,err_code,format,arg);
+ va_end(arg);
+ return F ? RegisterFileError(F,stat) : stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PrintErrorArg ( ccp func, ccp file, unsigned int line,
+ int syserr, enumError err_code, ccp format, va_list arg )
+{
+ fflush(stdout);
+ if (!stdwrn)
+ {
+ SetupStdMsg();
+ if (!stdwrn)
+ {
+ stdwrn = stderr;
+ if (!stdwrn)
+ return err_code;
+ }
+ }
+
+ char msg[1000];
+ if (!progname)
+ progname = "?";
+ const int plen = strlen(progname)+2;
+
+ if ( !format && err_code > ERR_NOT_IMPLEMENTED )
+ format = "Program is aborted immediately!";
+
+ if (format)
+ {
+ vsnprintf(msg,sizeof(msg),format,arg);
+ msg[sizeof(msg)-2] = 0;
+
+ const int mlen = strlen(msg);
+ if ( mlen > 0 && msg[mlen-1] != '\n' )
+ {
+ msg[mlen] = '\n';
+ msg[mlen+1] = 0;
+ }
+ }
+ else
+ StringCat2S(msg,sizeof(msg),GetErrorText(err_code,0),"\n");
+
+ ccp prefix = err_code == ERR_OK ? "" : err_code <= ERR_WARNING ? "! " : "!! ";
+ const int fw = GetTermWidth(80,40) - 1;
+
+ #ifdef DEBUG
+ TRACE("%s%s #%d [%s] in %s() @ %s#%d\n",
+ prefix, err_code <= ERR_WARNING ? "WARNING" : "ERROR",
+ err_code, GetErrorName(err_code,0), func, file, line );
+ TRACE("%s%*s%s",prefix,plen,"",msg);
+ if (syserr)
+ TRACE("!! %s(), ERRNO=%d: %s\n",__FUNCTION__,syserr,strerror(syserr));
+ fflush(TRACE_FILE);
+ #endif
+
+ #if defined(EXTENDED_ERRORS)
+ if ( err_code > ERR_WARNING )
+ #else
+ if ( err_code >= ERR_NOT_IMPLEMENTED )
+ #endif
+ {
+ ccp coln = EmptyString;
+ ccp col1 = EmptyString;
+ ccp col0 = EmptyString;
+ if (IsFileColorized(stdwrn))
+ {
+ coln = GetTextMode(1,TTM_COL_INFO);
+ col1 = GetTextMode(1, err_code > ERR_WARNING ? TTM_COL_WARN : TTM_COL_HINT );
+ col0 = TermTextModeReset;
+ }
+
+ if ( stdwrn == stderr )
+ {
+ if ( err_code > ERR_WARNING )
+ fprintf(stdwrn,"%s%s%s:%s ERROR #%d [%s] in %s() @ %s#%d%s\n",
+ prefix, coln, progname, col1, err_code,
+ GetErrorName(err_code,0), func, file, line, col0 );
+ else
+ fprintf(stdwrn,"%s%s%s:%s WARNING in %s() @ %s#%d%s\n",
+ prefix, coln, progname, col1, func, file, line, col0 );
+ }
+ else
+ {
+ if ( err_code > ERR_WARNING )
+ fprintf(stdwrn,"%s%s%s:%s ERROR #%d [%s]%s\n",
+ prefix, coln, progname, col1, err_code,
+ GetErrorName(err_code,0), col0 );
+ else
+ fprintf(stdwrn,"%s%s%s:%s WARNING%s\n",
+ prefix, coln, progname, col1, col0 );
+ }
+
+ #if defined(EXTENDED_ERRORS) && EXTENDED_ERRORS > 1
+ fprintf(stdwrn,"%s -> %s/%s?annotate=%d#l%d\n",
+ prefix, URI_VIEWVC, file, REVISION_NEXT, line );
+ #endif
+
+ fputs(prefix,stdwrn);
+ PutLines(stdwrn,plen,fw,0,prefix,msg,0);
+ }
+ else
+ {
+ fprintf(stdwrn,"%s%s:",prefix,progname);
+ PutLines(stdwrn,plen,fw,strlen(progname)+1,prefix,msg,0);
+ }
+
+ if (syserr)
+ {
+ fprintf(stdwrn,"%s%*s-> ",prefix,plen,"");
+ snprintf(msg,sizeof(msg),"%s [%d]",strerror(syserr),syserr);
+ PutLines(stdwrn,plen+3,fw,plen+3,prefix,msg,0);
+ }
+ fflush(stdwrn);
+
+ if ( err_code > ERR_OK )
+ error_count++;
+
+ last_error = err_code;
+ if ( max_error < err_code )
+ max_error = err_code;
+
+ if ( err_code > ERR_NOT_IMPLEMENTED )
+ exit(err_code);
+
+ return err_code;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PrintErrorStat ( enumError err, int verbose, ccp cmdname )
+{
+ if ( verbose > 0 && err >= ERR_WARNING
+ || verbose > 1 && err
+ || err == ERR_NOT_IMPLEMENTED )
+ {
+ if (!stdwrn)
+ {
+ SetupStdMsg();
+ if (!stdwrn)
+ {
+ stdwrn = stderr;
+ if (!stdwrn)
+ return err;
+ }
+ }
+
+ fprintf(stdwrn,"%s: Command '%s' returns with status #%d [%s]\n",
+ progname, cmdname, err, GetErrorName(err,0) );
+ }
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool mark_used ( ccp name, ... )
+{
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if HAVE_PRINT
+
+ void PRINT_TIME ( time_t time, ccp title )
+ {
+ struct tm utc, loc;
+ gmtime_r(&time,&utc);
+ localtime_r(&time,&loc);
+ PRINT( "%s: UTC= %4u-%02u-%02u %2u:%02u:%02u dst=%d"
+ " LOCAL= %4u-%02u-%02u %2u:%02u:%02u dst=%d\n",
+ title ? title : "TIME",
+ utc.tm_year+1900, utc.tm_mon+1, utc.tm_mday,
+ utc.tm_hour, utc.tm_min, utc.tm_sec, utc.tm_isdst,
+ loc.tm_year+1900, loc.tm_mon+1, loc.tm_mday,
+ loc.tm_hour, loc.tm_min, loc.tm_sec, loc.tm_isdst );
+ }
+
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// hexdump ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#if ENABLE_HEXDUMP_WRAPPER > 1
+ int enable_xdump_wrapper = 1;
+#elif ENABLE_HEXDUMP_WRAPPER
+ int enable_xdump_wrapper = 0;
+#endif
+
+ccp hexdump_prefix = "";
+ccp hexdump_eol = "\n";
+bool hexdump_align = false;
+
+//-----------------------------------------------------------------------------
+
+#if ENABLE_HEXDUMP_WRAPPER > 0
+
+static void SetupXDumpWrapper
+(
+ XDump_t *xd,
+ FILE *f,
+ int indent,
+ u64 addr,
+ int addr_fw,
+ int row_len
+)
+{
+ DASSERT(xd);
+ InitializeXDump(xd);
+
+ xd->f = f;
+ xd->prefix = hexdump_prefix;
+ xd->eol = hexdump_eol;
+ xd->mode_align = hexdump_align;
+ xd->indent = indent;
+ xd->start_addr = addr;
+ xd->min_addr_fw = addr_fw;
+ xd->min_width = row_len;
+ xd->format = XDUMPF_INT_1;
+ xd->print_format = false;
+ xd->print_summary = false;
+ xd->print_diff_sep = false;
+
+ hexdump_prefix = EmptyString;
+ hexdump_eol = "\n";
+ hexdump_align = false;
+}
+
+#endif
+
+//-----------------------------------------------------------------------------
+
+uint HexDump16 ( FILE * f, int indent, u64 addr,
+ const void * data, size_t count )
+{
+ // return the number of printed lines
+ return HexDump(f,indent,addr,4,16,data,count);
+}
+
+//-----------------------------------------------------------------------------
+
+uint HexDump20 ( FILE * f, int indent, u64 addr,
+ const void * data, size_t count )
+{
+ // return the number of printed lines
+ return HexDump(f,indent,addr,4,20,data,count);
+}
+
+//-----------------------------------------------------------------------------
+
+uint HexDump ( FILE * f, int indent, u64 addr, int addr_fw, int row_len,
+ const void * p_data, size_t count )
+{
+ // return the number of printed lines
+ if ( !f || !p_data || !count )
+ return 0;
+
+ #if ENABLE_HEXDUMP_WRAPPER == 1 || ENABLE_HEXDUMP_WRAPPER == 2
+ if (enable_xdump_wrapper)
+ #endif
+
+ #if ENABLE_HEXDUMP_WRAPPER > 0
+ {
+ XDump_t xd;
+ SetupXDumpWrapper(&xd,f,indent,addr,addr_fw,row_len);
+ const int stat = XDump(&xd,p_data,count,true);
+ return stat < 0 ? 0 : stat;
+ }
+ #endif
+
+ #if ENABLE_HEXDUMP_WRAPPER < 3
+
+ const int MAX_LEN = 256;
+ char buf[MAX_LEN+2];
+
+ const u8 * data = (const u8 *)p_data;
+
+ indent = NormalizeIndent(indent);
+ addr_fw = NormalizeIndent(addr_fw);
+
+ const bool show_ascii = row_len >= 0;
+ if ( row_len < 0 )
+ row_len = -row_len;
+ else if ( row_len < 1 )
+ row_len = 16;
+ else if ( row_len > MAX_LEN )
+ row_len = MAX_LEN;
+
+ uint skip = 0;
+ if ( (s64)addr != -1 )
+ {
+ const int fw = snprintf(buf,sizeof(buf),"%llx",addr+count-1);
+ if ( addr_fw < fw )
+ addr_fw = fw;
+
+ if (hexdump_align)
+ {
+ skip = addr % row_len;
+ addr -= skip;
+ }
+ }
+
+ uint line_count = 0;
+ while ( count > 0 )
+ {
+ if ( (s64)addr == -1 )
+ fprintf(f,"%s%*s", hexdump_prefix, indent,"" );
+ else
+ {
+ fprintf(f,"%s%*s%*llx:", hexdump_prefix, indent,"", addr_fw, addr );
+ addr += row_len;
+ }
+ char * dest = buf;
+ if (!skip)
+ *dest++ = ':';
+
+ int i;
+ for ( i = 0; i < row_len; i++ )
+ {
+ if ( skip > 0 )
+ {
+ fprintf(f,"%s ", i&3 ? "" : " " );
+ *dest++ = ' ';
+ if (!--skip)
+ *dest++ = ':';
+ }
+ else
+ {
+ u8 ch = *data++;
+ if ( count > 0 )
+ {
+ count--;
+ fprintf(f,"%s%02x ", i&3 ? "" : " ", ch );
+ *dest++ = ch < ' ' || ch >= 0x7f ? '.' : ch;
+ }
+ else
+ fprintf(f,"%s ", i&3 ? "" : " " );
+ }
+ }
+ *dest = 0;
+ if (show_ascii)
+ fprintf(f,"%s:%s",buf,hexdump_eol);
+ else
+ fputs(hexdump_eol,f);
+ line_count++;
+ }
+
+ hexdump_prefix = "";
+ hexdump_eol = "\n";
+ hexdump_align = false;
+ return line_count;
+ #endif
+}
+
+//-----------------------------------------------------------------------------
+
+uint HexDump016 ( FILE * f, int indent, u64 addr,
+ const void * data, size_t count )
+{
+ // return the number of printed lines
+ return HexDump0(f,indent,addr,4,16,data,count);
+}
+
+//-----------------------------------------------------------------------------
+
+uint HexDump0 ( FILE * f, int indent, u64 addr, int addr_fw, int row_len,
+ const void * p_data, size_t count )
+{
+ // return the number of printed lines
+ if ( !f || !p_data || !count )
+ return 0;
+
+ #if ENABLE_HEXDUMP_WRAPPER == 1 || ENABLE_HEXDUMP_WRAPPER == 2
+ if (enable_xdump_wrapper)
+ #endif
+
+ #if ENABLE_HEXDUMP_WRAPPER > 0
+ {
+ XDump_t xd;
+ SetupXDumpWrapper(&xd,f,indent,addr,addr_fw,row_len);
+ xd.mode_ignore = true;
+ const int stat = XDump(&xd,p_data,count,true);
+ return stat < 0 ? 0 : stat;
+ }
+ #endif
+
+ #if ENABLE_HEXDUMP_WRAPPER < 3
+ return HexDump( f, indent, addr, addr_fw, row_len, p_data, count );
+ #endif
+}
+
+//-----------------------------------------------------------------------------
+
+void HexDiff16 ( FILE * f, int indent, u64 addr,
+ const void * data1, size_t count1,
+ const void * data2, size_t count2 )
+{
+ HexDiff(f,indent,addr,4,16,data1,count1,data2,count2);
+}
+
+//-----------------------------------------------------------------------------
+
+void HexDiff ( FILE * f, int indent, u64 addr, int addr_fw, int row_len,
+ const void * p_data1, size_t count1,
+ const void * p_data2, size_t count2 )
+{
+ if (!f)
+ return;
+
+ #if ENABLE_HEXDUMP_WRAPPER == 1 || ENABLE_HEXDUMP_WRAPPER == 2
+ if (enable_xdump_wrapper)
+ #endif
+
+ #if ENABLE_HEXDUMP_WRAPPER > 0
+ {
+ XDump_t xd;
+ SetupXDumpWrapper(&xd,f,indent,addr,addr_fw,row_len);
+ XDiff(&xd, p_data1,count1,true, p_data2,count2,true, 0,false );
+ return;
+ }
+ #endif
+
+ #if ENABLE_HEXDUMP_WRAPPER < 3
+
+ const int MAX_LEN = 256;
+ char buf[MAX_LEN+2];
+
+ const u8 * data1 = p_data1 ? (const u8 *)p_data1 : (u8*)EmptyString;
+ const u8 * data2 = p_data2 ? (const u8 *)p_data2 : (u8*)EmptyString;
+
+ indent = NormalizeIndent(indent);
+ addr_fw = NormalizeIndent(addr_fw);
+
+ const bool show_ascii = row_len >= 0;
+ if ( row_len < 0 )
+ row_len = -row_len;
+ else if ( row_len < 1 )
+ row_len = 16;
+ else if ( row_len > MAX_LEN )
+ row_len = MAX_LEN;
+
+ uint skip = 0;
+
+ if ( (s64)addr != -1 )
+ {
+ const size_t count = count1 > count2 ? count1 : count2;
+ const int fw = snprintf(buf,sizeof(buf),"%llx",addr+count-1);
+ if ( addr_fw < fw )
+ addr_fw = fw;
+
+ if (hexdump_align)
+ {
+ skip = addr % row_len;
+ addr -= skip;
+ }
+ }
+
+ while ( count1 > 0 && count2 > 0 )
+ {
+ size_t count = count1 < count2 ? count1 : count2;
+ if ( count > row_len )
+ count = row_len;
+ noPRINT(" cnt=%zd,%zd -> %zd; cmp=%d\n",
+ count1, count2, count, memcmp(data1,data2,count) );
+
+ if (memcmp(data1,data2,count))
+ {
+ //--- print first line
+
+ if (IS_M1(addr))
+ fprintf(f,"%s%*s<", hexdump_prefix, indent,"" );
+ else
+ fprintf(f,"%s%*s<%*llx:", hexdump_prefix, indent,"", addr_fw, addr );
+
+ char * dest = buf;
+ if (!skip)
+ *dest++ = ':';
+
+ int i;
+ for ( i = 0; i < row_len; i++ )
+ {
+ if ( skip > 0 )
+ {
+ fprintf(f,"%s ", i&3 ? "" : " " );
+ *dest++ = ' ';
+ if (!--skip)
+ *dest++ = ':';
+ }
+ else
+ {
+ u8 ch = data1[i];
+ if ( i < count1 )
+ {
+ fprintf(f,"%s%02x ", i&3 ? "" : " ", ch );
+ *dest++ = ch < ' ' || ch >= 0x7f ? '.' : ch;
+ }
+ else
+ fprintf(f,"%s ", i&3 ? "" : " " );
+ }
+ }
+ *dest = 0;
+ if (show_ascii)
+ fprintf(f,"%s:%s",buf,hexdump_eol);
+ else
+ fputs(hexdump_eol,f);
+
+
+ //--- print second line
+
+ if (IS_M1(addr))
+ fprintf(f,"%s%*s>", hexdump_prefix, indent,"" );
+ else
+ fprintf(f,"%s%*s>%*llx:", hexdump_prefix, indent,"", addr_fw, addr );
+
+ dest = buf;
+ if (!skip)
+ *dest++ = ':';
+
+ for ( i = 0; i < row_len; i++ )
+ {
+ if ( skip > 0 )
+ {
+ fprintf(f,"%s ", i&3 ? "" : " " );
+ *dest++ = ' ';
+ if (!--skip)
+ *dest++ = ':';
+ }
+ else
+ {
+ u8 ch = data2[i];
+ if ( i < count2 )
+ {
+ if ( ch == data1[i] )
+ fprintf(f,"%s . ", i&3 ? "" : " " );
+ else
+ fprintf(f,"%s%02x ", i&3 ? "" : " ", ch );
+ *dest++ = ch < ' ' || ch >= 0x7f ? '.' : ch;
+ }
+ else
+ fprintf(f,"%s ", i&3 ? "" : " " );
+ }
+ }
+ *dest = 0;
+ if (show_ascii)
+ fprintf(f,"%s:%s",buf,hexdump_eol);
+ else
+ fputs(hexdump_eol,f);
+ }
+ data1 += count;
+ data2 += count;
+ count1 -= count;
+ count2 -= count;
+ if (!IS_M1(addr))
+ addr += count;
+
+ }
+ hexdump_prefix = "";
+ hexdump_eol = "\n";
+ hexdump_align = false;
+
+ #endif
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// trace functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u_msec_t GetTimerMSec();
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void trace_helper ( int print_stderr, ccp format, va_list arg )
+{
+ static uint last_day = 0;
+ DayTime_t dt = GetDayTime(true);
+
+ if (!TRACE_FILE)
+ {
+ char extend[16];
+ if (multi_processing)
+ snprintf(extend,sizeof(extend),"-%u",getpid());
+ else
+ *extend = 0;
+
+ char fname[1000];
+ if ( progname && *progname && *progname != '?' )
+ snprintf( fname, sizeof(fname), "_trace-%s%s.tmp", progname, extend );
+ else
+ snprintf( fname, sizeof(fname), "_trace-%s.tmp", extend );
+
+ TRACE_FILE = fopen(fname,"wb");
+ if (TRACE_FILE)
+ {
+ fcntl(fileno(TRACE_FILE),F_SETFD,FD_CLOEXEC);
+ fprintf(TRACE_FILE,"\n#\f\n#OPEN %s, pid=%d\n\n",
+ PrintTimeByFormat("%F %T %z",dt.time), getpid() );
+ }
+ else
+ TRACE_FILE = stderr;
+ last_day = dt.day;
+ }
+
+ char pre[200];
+ if ( last_day != dt.day )
+ {
+ SetupTimezone(true);
+ dt = GetDayTime(true);
+ last_day = dt.day;
+ snprintf(pre,sizeof(pre),
+ "\n#DAY: %s, pid=%d\n%02u:%02u:%02u.%03u ",
+ PrintTimeByFormat("%F %T %z",dt.time), getpid(),
+ dt.hour, dt.min, dt.sec, dt.usec/1000 );
+ }
+ else
+ snprintf(pre,sizeof(pre),"%02u:%02u:%02u.%03u ",
+ dt.hour, dt.min, dt.sec, dt.usec/1000 );
+
+ if ( print_stderr || TRACE_FILE == stderr )
+ {
+ fflush(stdout);
+ fputs(pre,stderr);
+ fputs(TRACE_PREFIX,stderr);
+ va_list arg2;
+ va_copy(arg2,arg);
+ vfprintf(stderr,format,arg2);
+ va_end(arg2);
+ fflush(stderr);
+ }
+
+ if ( TRACE_FILE && TRACE_FILE != stderr )
+ {
+ fputs(pre,TRACE_FILE);
+ fputs(TRACE_PREFIX,TRACE_FILE);
+ vfprintf(TRACE_FILE,format,arg);
+ fflush(TRACE_FILE);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef TRACE_ARG_FUNC
+
+void TRACE_ARG_FUNC ( ccp format, va_list arg )
+{
+ trace_helper(0,format,arg);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef TRACE_FUNC
+
+void TRACE_FUNC ( ccp format, ... )
+{
+ va_list arg;
+ va_start(arg,format);
+ trace_helper(0,format,arg);
+ va_end(arg);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef PRINT_ARG_FUNC
+
+void PRINT_ARG_FUNC ( ccp format, va_list arg )
+{
+ trace_helper(1,format,arg);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef PRINT_FUNC
+
+void PRINT_FUNC ( ccp format, ... )
+{
+ va_list arg;
+ va_start(arg,format);
+ trace_helper(1,format,arg);
+ va_end(arg);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef BINGO_FUNC
+
+void BINGO_FUNC ( ccp func, int line, ccp src )
+{
+ #if defined(DEBUG) && defined(TEST)
+ if (colerr)
+ PRINT_FUNC("%sBINGO!%s %s() #%d @ %s\n",
+ colerr->magenta, colerr->reset, func, line, src );
+ else
+ PRINT_FUNC("BINGO! %s() #%d @ %s\n", func, line, src );
+ #else
+ if (colerr)
+ fprintf(stderr,"%sBINGO!%s %s() #%d @ %s\n",
+ colerr->magenta, colerr->reset, func, line, src );
+ else
+ fprintf(stderr,"BINGO! %s() #%d @ %s\n", func, line, src );
+ #endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef WAIT_ARG_FUNC
+
+void WAIT_ARG_FUNC ( ccp format, va_list arg )
+{
+ if ( format && *format )
+ trace_helper(1,format,arg);
+ PRINT_FUNC(">>>>>> PRESS RETURN: ");
+ getchar();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef WAIT_FUNC
+
+void WAIT_FUNC ( ccp format, ... )
+{
+ va_list arg;
+ va_start(arg,format);
+ WAIT_ARG_FUNC(format,arg);
+ va_end(arg);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// alloc/free system ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#if TRACE_ALLOC_MODE > 2
+ #define MEM_FILLER_SIZE 8
+ static u8 mem_filler[MEM_FILLER_SIZE];
+#else
+ #define MEM_FILLER_SIZE 0
+#endif
+
+#if TRACE_ALLOC_MODE > 1
+ #define MPARAM ccp func, ccp file, uint line,
+ #define MCALL func,file,line,
+ #define PRINT_OOM(...) \
+ PrintError(func,file,line,0,ERR_OUT_OF_MEMORY,__VA_ARGS__)
+#else
+ #define MPARAM
+ #define MCALL
+ #define PRINT_OOM(...) \
+ PrintError(__FUNCTION__,__FILE__,__LINE__,0,ERR_OUT_OF_MEMORY,__VA_ARGS__)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+void dclib_free ( void * ptr )
+{
+ free(ptr);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void * dclib_xcalloc ( size_t nmemb, size_t size )
+{
+ void * res = calloc(nmemb,size);
+ if (!res)
+ PrintError(__FUNCTION__,__FILE__,__LINE__,0,ERR_OUT_OF_MEMORY,
+ "Out of memory while calloc() %zu bytes (%zu*%zu=0x%zx)\n",
+ nmemb*size, nmemb, size, nmemb*size );
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * dclib_xmalloc ( size_t size )
+{
+ void * res = malloc(size);
+ if (!res)
+ PrintError(__FUNCTION__,__FILE__,__LINE__,0,ERR_OUT_OF_MEMORY,
+ "Out of memory while malloc() %zu bytes (0x%zx)\n",
+ size, size );
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * dclib_xrealloc ( void * ptr, size_t size )
+{
+ void * res = realloc(ptr,size);
+ if ( !res && size )
+ PrintError(__FUNCTION__,__FILE__,__LINE__,0,ERR_OUT_OF_MEMORY,
+ "Out of memory while realloc() %zu bytes (0x%zx)\n",
+ size, size );
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * dclib_xstrdup ( ccp src )
+{
+ char * res = strdup(src?src:"");
+ if (!res)
+ {
+ const uint size = src ? strlen(src)+1 : 0;
+ PrintError(__FUNCTION__,__FILE__,__LINE__,0,ERR_OUT_OF_MEMORY,
+ "Out of memory while strdup() %u bytes (0x%x)\n",
+ size, size );
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void * dclib_calloc ( MPARAM size_t nmemb, size_t size )
+{
+ void * res = calloc(nmemb,size);
+ if (!res)
+ PRINT_OOM("Out of memory while calloc() %zu bytes (%zu*%zu=0x%zx)\n",
+ nmemb*size, nmemb, size, nmemb*size );
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * dclib_malloc ( MPARAM size_t size )
+{
+ void * res = malloc(size);
+ if (!res)
+ PRINT_OOM("Out of memory while malloc() %zu bytes (0x%zx)\n",
+ size, size );
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * dclib_realloc ( MPARAM void * ptr, size_t size )
+{
+ void * res = realloc(ptr,size);
+ if ( !res && size )
+ PRINT_OOM("Out of memory while realloc() %zu bytes (0x%zx)\n",
+ size, size );
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * dclib_strdup ( MPARAM ccp src )
+{
+ char * res = strdup(src?src:"");
+ if (!res)
+ {
+ const uint size = src ? strlen(src)+1 : 0;
+ PRINT_OOM("Out of memory while strdup() %u bytes (0x%x)\n",
+ size, size );
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * dclib_strdup2 ( MPARAM ccp src1, ccp src2 )
+{
+ const uint len1 = src1 ? strlen(src1) : 0;
+ const uint len2 = src2 ? strlen(src2) : 0;
+ char * res = dclib_malloc( MCALL len1+len2+1 );
+ if (len1)
+ memcpy(res,src1,len1);
+ if (len2)
+ memcpy(res+len1,src2,len2);
+ res[len1+len2] = 0;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * dclib_strdup3 ( MPARAM ccp src1, ccp src2, ccp src3 )
+{
+ const uint len1 = src1 ? strlen(src1) : 0;
+ const uint len2 = src2 ? strlen(src2) : 0;
+ const uint len3 = src3 ? strlen(src3) : 0;
+ char * res = dclib_malloc( MCALL len1+len2+len3+1 );
+ char * dest = res;
+ if (len1)
+ {
+ memcpy(dest,src1,len1);
+ dest += len1;
+ }
+ if (len2)
+ {
+ memcpy(dest,src2,len2);
+ dest += len2;
+ }
+ if (len3)
+ {
+ memcpy(dest,src3,len3);
+ dest += len3;
+ }
+ *dest = 0;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * dclib_memdup ( MPARAM const void * src, size_t copylen )
+{
+ char * dest = dclib_malloc( MCALL copylen+1);
+ memcpy(dest,src,copylen);
+ dest[copylen] = 0;
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * dclib_allocdup ( MPARAM const void * src, size_t copylen )
+{
+ char * dest = dclib_malloc( MCALL copylen);
+ memcpy(dest,src,copylen);
+ return dest;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+#if TRACE_ALLOC_MODE > 2
+///////////////////////////////////////////////////////////////////////////////
+// [[mem_info_t]]
+
+typedef struct mem_info_t
+{
+ ccp func;
+ ccp file;
+ uint line;
+ u8 * data;
+ uint size;
+ bool filler;
+ uint seqnum;
+
+} mem_info_t;
+
+//-----------------------------------------------------------------------------
+
+static mem_info_t * mem_list = 0;
+static uint mem_used = 0;
+static uint mem_size = 0;
+static uint mem_seqnum = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static FILE * OpenMemLog()
+{
+ static bool done = false;
+ if (!done)
+ {
+ done = true;
+ char fname[500];
+ if ( progname && *progname && *progname != '?' )
+ snprintf(fname,sizeof(fname),"_trace_alloc-%s-%u.tmp",progname,getpid());
+ else
+ snprintf(fname,sizeof(fname),"_trace_alloc-%u.tmp",getpid());
+
+ MEM_LOG_FILE = fopen(fname,"ab");
+ if (MEM_LOG_FILE)
+ {
+ fcntl(fileno(MEM_LOG_FILE),F_SETFD,FD_CLOEXEC);
+ fprintf(MEM_LOG_FILE,"#\f\n# OPEN FILE %s\n",
+ PrintTimeByFormat("%F %T %z",time(0)));
+ fflush(MEM_LOG_FILE);
+ }
+ }
+
+ return MEM_LOG_FILE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void MemLog
+ ( ccp func, ccp file, uint line, ccp info, const u8 * ptr, bool no_stderr )
+{
+ static uint line_no = 0;
+
+ if (!no_stderr)
+ {
+ fprintf(stderr,"MEM-ERR[%s,%p] %s() @ %s#%u\n",
+ info, ptr, func, file, line );
+ fflush(stderr);
+ }
+
+ if (OpenMemLog())
+ {
+ if (!line_no)
+ fputs("#index addr type function line file\n",MEM_LOG_FILE);
+
+ fprintf(MEM_LOG_FILE,"%07u %p %s %s %u %s\n",
+ line_no, ptr, info, func, line, file );
+ fflush(MEM_LOG_FILE);
+ }
+
+ line_no++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void MemLogItem
+ ( ccp func, ccp file, uint line, ccp info, const mem_info_t * mi )
+{
+ if (func)
+ fprintf(stderr,"MEM-ERR[%s] %s() @ %s#%u\n",
+ info, func, file, line );
+ fprintf(stderr,"MEM-ERR[%s,%u] %s() @ %s#%u\n",
+ info, mi->seqnum, mi->func, mi->file, mi->line );
+ fflush(stderr);
+
+ if (OpenMemLog())
+ {
+ if (func)
+ fprintf(MEM_LOG_FILE,"[%s] %s() @ %s#%u\n",
+ info, func, file, line );
+ fprintf(MEM_LOG_FILE,"[%s,%u] %s() @ %s#%u\n",
+ info, mi->seqnum, mi->func, mi->file, mi->line );
+ if (mi->filler)
+ {
+ HexDump16( MEM_LOG_FILE, 10, 0,
+ mi->data - MEM_FILLER_SIZE, 2*MEM_FILLER_SIZE );
+ HexDump16( MEM_LOG_FILE, 10, mi->size + MEM_FILLER_SIZE,
+ mi->data + mi->size - MEM_FILLER_SIZE, 2 * MEM_FILLER_SIZE );
+ }
+ fputc('\n',MEM_LOG_FILE);
+ fflush(MEM_LOG_FILE);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint FindMemInfoHelper ( u8 * data, uint size )
+{
+ int beg = 0;
+ int end = mem_used - 1;
+ while ( beg <= end )
+ {
+ uint idx = (beg+end)/2;
+ mem_info_t * mi = mem_list + idx;
+ if ( data < mi->data )
+ end = idx - 1 ;
+ else if ( data > mi->data )
+ beg = idx + 1;
+ else if ( size < mi->size )
+ end = idx - 1 ;
+ else if ( size > mi->size )
+ beg = idx + 1;
+ else
+ {
+ noTRACE("FindMemInfoHelper(%llx,%llx) FOUND=%d/%d/%d\n",
+ (u64)off, (u64)size, idx, mem_used, mem_size );
+ return idx;
+ }
+ }
+
+ noTRACE("FindStringFieldHelper(%llx,%llx) failed=%d/%d/%d\n",
+ (u64)off, (u64)size, beg, mem_used, mem_size );
+ return beg;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeTraceAlloc()
+{
+ static bool done = false;
+ if (done)
+ return;
+ done = true;
+
+ OpenMemLog();
+
+ memset(mem_filler,0xdc,sizeof(mem_filler));
+ mem_filler[MEM_FILLER_SIZE/2] = 0xcd;
+ mem_filler[MEM_FILLER_SIZE/3] = 0xcd;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CheckTraceAlloc ( ccp func, ccp file, uint line )
+{
+ int count = 0;
+
+ mem_info_t *ptr, *end = mem_list + mem_used;
+ for ( ptr = mem_list; ptr < end; ptr++ )
+ {
+ if (!ptr->filler)
+ continue;
+
+ if (memcmp(ptr->data-MEM_FILLER_SIZE,mem_filler,sizeof(mem_filler)))
+ {
+ MemLogItem(0,0,0,"BEG",ptr);
+ memcpy(ptr->data-MEM_FILLER_SIZE,mem_filler,sizeof(mem_filler));
+ count++;
+ }
+
+ if (memcmp(ptr->data+ptr->size,mem_filler,sizeof(mem_filler)))
+ {
+ MemLogItem(0,0,0,"END",ptr);
+ memcpy(ptr->data+ptr->size,mem_filler,sizeof(mem_filler));
+ count++;
+ }
+ }
+
+ if (count)
+ {
+ fprintf(stderr,"MEM-ERR: %u errors found -> %s() @ %s#%u\n",
+ count, func, file, line );
+ fflush(stderr);
+
+ if (OpenMemLog())
+ {
+ fprintf(MEM_LOG_FILE,"--- %u errors found -> %s() @ %s#%u\n\n",
+ count, func, file, line );
+ fflush(MEM_LOG_FILE);
+ }
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpTraceAlloc ( ccp func, ccp file, uint line, FILE * f )
+{
+ CheckTraceAlloc(func,file,line);
+
+ if (!f)
+ return;
+
+ fprintf(f,"---- MEM-DUMP [N=%u] -----\n",mem_used);
+ mem_info_t *ptr, *end = mem_list + mem_used;
+ for ( ptr = mem_list; ptr < end; ptr++ )
+ {
+ ccp file = strrchr(ptr->file,'/');
+ file = file ? file+1 : ptr->file;
+ fprintf(f,"%6u %p %6u %-20.20s %5u %-.25s\n",
+ ptr->seqnum, ptr->data, ptr->size,
+ ptr->func, ptr->line, file );
+ }
+ fflush(stderr);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_info_t * RegisterAlloc
+ ( ccp func, ccp file, uint line, cvp data, uint size, bool filler )
+{
+ if (!mem_seqnum)
+ InitializeTraceAlloc();
+
+ if (filler)
+ {
+ memcpy((u8*)data-MEM_FILLER_SIZE,mem_filler,MEM_FILLER_SIZE);
+ memcpy((u8*)data+size,mem_filler,MEM_FILLER_SIZE);
+ }
+
+ uint idx = FindMemInfoHelper((u8*)data,size);
+
+ ASSERT( mem_used <= mem_size );
+ if ( mem_used == mem_size )
+ {
+ mem_size += 1000 + mem_size/2;
+ const uint alloc_size = mem_size * sizeof(*mem_list);
+ mem_list = realloc(mem_list,alloc_size);
+ if (!mem_list)
+ PRINT_OOM("Out of memory while RegisterAlloc() %u bytes (0x%x)\n",
+ alloc_size, alloc_size );
+ }
+
+ DASSERT( idx <= mem_used );
+ mem_info_t * mi = mem_list + idx;
+ memmove(mi+1,mi,(mem_used-idx)*sizeof(*mi));
+ mem_used++;
+
+ mi->func = func;
+ mi->file = file;
+ mi->line = line;
+ mi->data = (u8*)data;
+ mi->size = size;
+ mi->filler = filler;
+ mi->seqnum = mem_seqnum++;
+
+ #if TRACE_ALLOC_MODE > 3
+ MemLog(func,file,line,"ALLOC",data,true);
+ #endif
+ return mi;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+#endif // TRACE_ALLOC_MODE > 2
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint trace_test_alloc
+ ( ccp func, ccp file, uint line, const void *p_data, bool hexdump )
+{
+ #if TRACE_ALLOC_MODE > 2
+ if (!p_data)
+ return 0;
+ const u8 *data = p_data;
+
+ int beg = 0;
+ int end = mem_used - 1;
+ while ( beg <= end )
+ {
+ uint idx = (beg+end)/2;
+ mem_info_t * mi = mem_list + idx;
+ if ( data < mi->data )
+ end = idx - 1 ;
+ else if ( data > mi->data )
+ beg = idx + 1;
+ else
+ {
+ #if TRACE_ALLOC_MODE > 3
+ MemLog(func,file,line,"TEST",data,true);
+ #endif
+
+ uint stat = 0;
+ if (memcmp(data-MEM_FILLER_SIZE,mem_filler,MEM_FILLER_SIZE))
+ {
+ MemLogItem(func,file,line,"BEG",mi);
+ stat = 1;
+ HexDump16(stderr,0,0,data-MEM_FILLER_SIZE,16);
+ }
+
+ if (memcmp(data+mi->size,mem_filler,MEM_FILLER_SIZE))
+ {
+ MemLogItem(func,file,line,"END",mi);
+ stat |= 2;
+ HexDump16(stderr,0,mi->size+sizeof(mem_filler),data+mi->size,16);
+ }
+
+ return stat;
+ }
+ }
+ MemLog(func,file,line,"NOT FOUND",data,false);
+ return 4;
+ #else
+ return 0;
+ #endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void * UnregisterAlloc ( ccp func, ccp file, uint line, u8 * data )
+{
+ #if TRACE_ALLOC_MODE > 2
+ if (!data)
+ return 0;
+
+ int beg = 0;
+ int end = mem_used - 1;
+ while ( beg <= end )
+ {
+ uint idx = (beg+end)/2;
+ mem_info_t * mi = mem_list + idx;
+ if ( data < mi->data )
+ end = idx - 1 ;
+ else if ( data > mi->data )
+ beg = idx + 1;
+ else
+ {
+ #if TRACE_ALLOC_MODE > 3
+ MemLog(func,file,line,"FREE",data,true);
+ #endif
+
+ if (mi->filler)
+ {
+ if (memcmp(data-MEM_FILLER_SIZE,mem_filler,MEM_FILLER_SIZE))
+ MemLogItem(func,file,line,"BEG",mi);
+
+ if (memcmp(data+mi->size,mem_filler,MEM_FILLER_SIZE))
+ MemLogItem(func,file,line,"END",mi);
+
+ data -= MEM_FILLER_SIZE;
+ }
+
+ memmove(mi,mi+1,(--mem_used-idx)*sizeof(*mi));
+ return data;
+ }
+ }
+ MemLog(func,file,line,"NOT FOUND",data,false);
+ #endif
+ return data;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void trace_free ( ccp func, ccp file, uint line, void * ptr )
+{
+ free(UnregisterAlloc(func,file,line,ptr));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * trace_malloc ( ccp func, ccp file, uint line, size_t size )
+{
+ u8 * res = malloc( size + 2 * MEM_FILLER_SIZE );
+ if (!res)
+ PRINT_OOM("Out of memory while allocate %zu+%u bytes (0x%zx)\n",
+ size, 2 * MEM_FILLER_SIZE, size + 2 * MEM_FILLER_SIZE );
+
+ res += MEM_FILLER_SIZE;
+ #if TRACE_ALLOC_MODE > 2
+ RegisterAlloc(func,file,line,res,size,true);
+ #endif
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * trace_calloc ( ccp func, ccp file, uint line, size_t nmemb, size_t size )
+{
+ uint total_size = nmemb * size;
+ void * res = trace_malloc(func,file,line,total_size);
+ memset(res,0,total_size);
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * trace_realloc ( ccp func, ccp file, uint line, void *ptr, size_t size )
+{
+ ptr = UnregisterAlloc(func,file,line,ptr);
+ u8 * res = realloc( ptr, size + 2 * MEM_FILLER_SIZE );
+ if (!res)
+ PRINT_OOM("Out of memory while re allocate %zu+%u bytes (0x%zx)\n",
+ size, 2 * MEM_FILLER_SIZE, size + 2 * MEM_FILLER_SIZE );
+
+ res += MEM_FILLER_SIZE;
+ #if TRACE_ALLOC_MODE > 2
+ RegisterAlloc(func,file,line,res,size,true);
+ #endif
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * trace_strdup ( ccp func, ccp file, uint line, ccp src )
+{
+ const uint len = src ? strlen(src)+1 : 0;
+ char * res = trace_malloc(func,file,line,len);
+ memcpy(res,src,len);
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * trace_strdup2 ( ccp func, ccp file, uint line, ccp src1, ccp src2 )
+{
+ const uint len1 = src1 ? strlen(src1) : 0;
+ const uint len2 = src2 ? strlen(src2) : 0;
+ char * res = trace_malloc(func,file,line,len1+len2+1);
+ if (len1)
+ memcpy(res,src1,len1);
+ if (len2)
+ memcpy(res+len1,src2,len2);
+ res[len1+len2] = 0;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * trace_strdup3 ( ccp func, ccp file, uint line, ccp src1, ccp src2, ccp src3 )
+{
+ const uint len1 = src1 ? strlen(src1) : 0;
+ const uint len2 = src2 ? strlen(src2) : 0;
+ const uint len3 = src3 ? strlen(src3) : 0;
+ char * res = trace_malloc(func,file,line,len1+len2+len3+1);
+ char * dest = res;
+ if (len1)
+ {
+ memcpy(dest,src1,len1);
+ dest += len1;
+ }
+ if (len2)
+ {
+ memcpy(dest,src2,len2);
+ dest += len2;
+ }
+ if (len3)
+ {
+ memcpy(dest,src3,len3);
+ dest += len3;
+ }
+ *dest = 0;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * trace_memdup
+ ( ccp func, ccp file, uint line, const void * src, size_t copylen )
+{
+ char * dest = trace_malloc(func,file,line,copylen+1);
+ memcpy(dest,src,copylen);
+ dest[copylen] = 0;
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void * trace_allocdup
+ ( ccp func, ccp file, uint line, const void * src, size_t copylen )
+{
+ char * dest = trace_malloc(func,file,line,copylen);
+ memcpy(dest,src,copylen);
+ return dest;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// mem check ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static u8 mem_check_buf[0x100];
+static const void * mem_check_ptr = 0;
+static uint mem_check_size = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MemCheckSetup ( const void * ptr, uint size )
+{
+ if ( ptr && size > 0 )
+ {
+ mem_check_ptr = ptr;
+ if ( size > sizeof(mem_check_buf) )
+ {
+ ERROR0(ERR_WARNING,
+ "MemCheckSetup(), max watch size = %zx (<%x)",
+ sizeof(mem_check_buf), size );
+ size = sizeof(mem_check_buf);
+ }
+ mem_check_size = size;
+ memcpy(mem_check_buf,ptr,size);
+ }
+ else
+ mem_check_size = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MemCheck ( ccp func, ccp file, uint line )
+{
+ if (!mem_check_size)
+ return;
+
+ if ( memcmp(mem_check_buf,mem_check_ptr,mem_check_size) )
+ {
+ fprintf(stderr,"--- MemCheck: %p should be:\n",mem_check_ptr);
+ HexDump16(stderr,0,0,mem_check_buf,mem_check_size);
+ fprintf(stderr,"--- MemCheck: ... but is:\n");
+ HexDump16(stderr,0,0,mem_check_ptr,mem_check_size);
+
+ TRACE("--- MemCheck: %p should be:\n",mem_check_ptr);
+ TRACE_HEXDUMP16(0,0,mem_check_buf,mem_check_size);
+ TRACE("--- MemCheck: ... but is:\n");
+ TRACE_HEXDUMP16(0,0,mem_check_ptr,mem_check_size);
+
+ PrintError(func,file,line,0,ERR_FATAL,"MemCheck() failed!\n");
+ }
+ else
+ TRACE("MemCheck OK: %s @ %s#%u\n",func,file,line);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
diff --git a/project/dclib/dclib-debug.h b/project/dclib/dclib-debug.h
new file mode 100644
index 0000000..34726f1
--- /dev/null
+++ b/project/dclib/dclib-debug.h
@@ -0,0 +1,714 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_DEBUG_H
+#define DCLIB_DEBUG_H 1
+
+#include "dclib-types.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// error handling ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern ccp progpath; // full path of program, use ProgramPath() to access
+extern ccp progdir; // directory part of progpath, use ProgramDirectory() to access
+extern ccp progname; // program name, based on path or directly set
+
+extern ccp toolname; // name of the tool, set by SetupProgname()
+extern ccp toolversion; // version of the tool, set by SetupProgname()
+extern ccp tooltitle; // title of the tool, set by SetupProgname()
+
+extern bool multi_processing;
+extern enumError last_error;
+extern enumError max_error;
+extern u32 error_count;
+
+//-----------------------------------------------------------------------------
+
+extern ccp (*GetErrorNameHook)( int stat, ccp ret_not_found );
+extern ccp (*GetErrorTextHook)( int stat, ccp ret_not_found );
+extern ccp GENERIC_ERROR_MESSAGE;
+
+ccp GetErrorName
+(
+ int stat, // error status, err := abs(stat)
+ ccp ret_not_found // not NULL: return this, if no message is found
+ // GENERIC_ERROR_MESSAGE: return a generic message
+);
+
+ccp GetErrorText
+(
+ int stat, // error status, err := abs(stat)
+ ccp ret_not_found // not NULL: return this, if no message is found
+ // GENERIC_ERROR_MESSAGE: return a generic message
+);
+
+void ListErrorCodes
+(
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ bool all // true: print all user reserved messages too.
+);
+
+enumError PrintErrorArg ( ccp func, ccp file, unsigned int line,
+ int syserr, enumError err_code, ccp format, va_list arg );
+
+enumError PrintError ( ccp func, ccp file, unsigned int line,
+ int syserr, enumError err_code, ccp format, ... )
+ __attribute__ ((__format__(__printf__,6,7)));
+
+struct File_t;
+enumError PrintErrorFile ( ccp func, ccp file, uint line, struct File_t *F,
+ int syserr, enumError err_code, ccp format, ... )
+ __attribute__ ((__format__(__printf__,7,8)));
+
+enumError PrintErrorStat ( enumError err, int verbose, ccp cmdname );
+
+#undef ERROR
+#undef ERROR0
+#undef ERROR1
+#undef FILEERROR
+#undef FILEERROR0
+#undef FILEERROR1
+
+#define ERROR(se,code,...) PrintError(__FUNCTION__,__FILE__,__LINE__,se,code,__VA_ARGS__)
+#define ERROR0(code,...) PrintError(__FUNCTION__,__FILE__,__LINE__,0,code,__VA_ARGS__)
+#define ERROR1(code,...) PrintError(__FUNCTION__,__FILE__,__LINE__,errno,code,__VA_ARGS__)
+
+#define FILEERROR(f,se,code,...) \
+ PrintErrorFile(__FUNCTION__,__FILE__,__LINE__,f,se,code,__VA_ARGS__)
+#define FILEERROR0(f,code,...) \
+ PrintErrorFile(__FUNCTION__,__FILE__,__LINE__,f,0,code,__VA_ARGS__)
+#define FILEERROR1(f,code,...) \
+ PrintErrorFile(__FUNCTION__,__FILE__,__LINE__,f,errno,code,__VA_ARGS__)
+
+//-----------------------------------------------------------------------------
+
+bool mark_used ( ccp name, ... );
+#define MARK_USED(...) mark_used(0,__VA_ARGS__)
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// hexdump ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// 0: don't use XDump
+// 1: use 'enable_xdump_wrapper' to decide and init it with FALSE
+// 2: use 'enable_xdump_wrapper' to decide and init it with TRUE
+// 3: force usage of XDump
+
+#ifndef ENABLE_HEXDUMP_WRAPPER
+ #ifdef TEST
+ #define ENABLE_HEXDUMP_WRAPPER 3
+ #else
+ #define ENABLE_HEXDUMP_WRAPPER 3
+ #endif
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if ENABLE_HEXDUMP_WRAPPER
+ extern int enable_xdump_wrapper;
+#endif
+
+extern ccp hexdump_prefix;
+extern ccp hexdump_eol;
+extern bool hexdump_align;
+
+// HexDump*() return the number of printed lines
+
+uint HexDump ( FILE * f, int indent, u64 addr, int addr_fw, int row_len,
+ const void * data, size_t count );
+
+uint HexDump16 ( FILE * f, int indent, u64 addr,
+ const void * data, size_t count );
+uint HexDump20 ( FILE * f, int indent, u64 addr,
+ const void * data, size_t count );
+
+// HexDump0*() will supress null-only lines.
+
+uint HexDump0 ( FILE * f, int indent, u64 addr, int addr_fw, int row_len,
+ const void * data, size_t count );
+
+uint HexDump016 ( FILE * f, int indent, u64 addr,
+ const void * data, size_t count );
+
+void HexDiff ( FILE * f, int indent, u64 addr, int addr_fw, int row_len,
+ const void * p_data1, size_t count1,
+ const void * p_data2, size_t count2 );
+
+void HexDiff16 ( FILE * f, int indent, u64 addr,
+ const void * data1, size_t count1,
+ const void * data2, size_t count2 );
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// DEBUG and TRACING ///////////////
+///////////////////////////////////////////////////////////////////////////////
+//
+// -------------------
+// DEBUG and TRACING
+// -------------------
+//
+// If symbol 'DEBUG' or symbol _DEBUG' is defined, then and only then
+// DEBUG, DASSERT and TRACING are enabled.
+//
+// There are to function like macros defined:
+//
+// TRACE ( ccp format, ... );
+// Print to console only if TRACING is enabled.
+// Ignored when TRACING is disabled.
+// It works like the well known printf() function and include flushing.
+//
+// TRACE_IF ( bool condition, ccp format, ... );
+// Like TRACE(), but print only if 'condition' is true.
+//
+// TRACELINE
+// Print out current line and source.
+//
+// TRACE_SIZEOF ( object_or_type );
+// Print out `sizeof(object_or_type)´
+//
+// ASSERT(cond);
+// If condition is false: print info and exit program.
+//
+// DASSERT(cond);
+// Like ASSERT; but only active on DEBUG.
+//
+// BINGO
+// Like TRACELINE, but print to stderr instead custom file.
+//
+//
+// There are more macros with a preceding 'no' defined:
+//
+// noTRACE ( ccp format, ... );
+// noTRACE_IF ( bool condition, ccp format, ... );
+// noTRACELINE ( bool condition, ccp format, ... );
+// noTRACE_SIZEOF ( object_or_type );
+// noASSERT(cond);
+//
+// If you add a 'no' before a TRACE-Call it is disabled always.
+// This makes the usage and disabling of multi lines traces very easy.
+//
+//
+// The macros with a preceding 'x' are always active:
+// xTRACELINE
+// xBINGO
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#if defined(RELEASE)
+ #undef TESTTRACE
+ #undef DEBUG
+ #undef _DEBUG
+ #undef DEBUG_ASSERT
+ #undef _DEBUG_ASSERT
+ #undef WAIT_ENABLED
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if defined(IGNORE_DEBUG)
+ #undef DEBUG
+ #undef _DEBUG
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef TRACE
+#undef TRACE_IF
+#undef TRACELINE
+#undef TRACE_SIZEOF
+
+#undef TRACE0
+#undef TRACE_IF0
+#undef TRACELINE0
+#undef TRACE_SIZEOF0
+
+#undef TRACE1
+#undef TRACE_IF1
+#undef TRACELINE1
+#undef TRACE_SIZEOF1
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef WIN_SZS_LIB
+ #define __attribute__(x)
+#endif
+
+extern ccp TRACE_PREFIX; // never NULL
+extern FILE *TRACE_FILE;
+extern FILE *MEM_LOG_FILE;
+
+void TRACE_FUNC ( ccp format, ... )
+ __attribute__ ((__format__(__printf__,1,2)));
+void TRACE_ARG_FUNC ( ccp format, va_list arg );
+
+void PRINT_FUNC ( ccp format, ... )
+ __attribute__ ((__format__(__printf__,1,2)));
+void PRINT_ARG_FUNC ( ccp format, va_list arg );
+
+void BINGO_FUNC ( ccp func, int line, ccp src );
+
+void WAIT_FUNC ( ccp format, ... )
+ __attribute__ ((__format__(__printf__,1,2)));
+void WAIT_ARG_FUNC ( ccp format, va_list arg );
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef TEST0 // never defined
+
+#ifdef TESTTRACE
+
+ #undef DEBUG
+ #define DEBUG 1
+
+ #undef TEST
+ #define TEST 1
+
+ #undef WAIT_ENABLED
+ #define WAIT_ENABLED 1
+
+ #undef NEW_FEATURES
+ #define NEW_FEATURES 1
+
+ #define TRACE_FUNC printf
+ #define PRINT_FUNC printf
+ #define WAIT_FUNC printf
+
+ #define TRACE_ARG_FUNC vprintf
+ #define PRINT_ARG_FUNC vprintf
+ #define WAIT_ARG_FUNC vprintf
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// always disabled
+#define TRACE0(...)
+#define TRACE_IF0(cond,...)
+#define TRACELINE0
+#define TRACE_SIZEOF0(t)
+
+// always enabled
+#define TRACE1(...) TRACE_FUNC(__VA_ARGS__)
+#define TRACE_IF1(cond,...) if (cond) TRACE_FUNC(__VA_ARGS__)
+#define TRACELINE1 TRACE_FUNC("%s() %s #%u\n",__FUNCTION__,__FILE__,__LINE__)
+#define TRACE_SIZEOF1(t) TRACE_FUNC("%7zd ==%6zx/hex == sizeof(%s)\n",sizeof(t),sizeof(t),#t)
+
+#if defined(DEBUG) || defined(_DEBUG)
+
+ #define HAVE_TRACE 1
+
+ #undef DEBUG
+ #define DEBUG 1
+
+ #undef DEBUG_ASSERT
+ #define DEBUG_ASSERT 1
+
+ #define TRACE TRACE1
+ #define TRACE_IF TRACE_IF1
+ #define TRACELINE TRACELINE1
+ #define TRACE_SIZEOF TRACE_SIZEOF1
+
+ #define HEXDUMP(i,a,af,rl,d,c) HexDump(stdout,i,a,af,rl,d,c);
+ #define HEXDUMP16(i,a,d,c) HexDump16(stdout,i,a,d,c);
+ #define TRACE_HEXDUMP(i,a,af,rl,d,c) HexDump(TRACE_FILE,i,a,af,rl,d,c);
+ #define TRACE_HEXDUMP16(i,a,d,c) HexDump16(TRACE_FILE,i,a,d,c);
+
+ #define HEXDIFF(i,a,af,rl,d1,c1,d2,c2) HexDiff(stdout,i,a,af,rl,d1,c1,d2,c2);
+ #define HEXDIFF16(i,a,d1,c1,d2,c2) HexDiff16(stdout,i,a,d1,c1,d2,c2);
+ #define TRACE_HEXDIFF(i,a,af,rl,d1,c1,d2,c2) HexDiff(TRACE_FILE,i,a,af,rl,d1,c1,d2,c2);
+ #define TRACE_HEXDIFF16(i,a,d1,c1,d2,c2) HexDiff16(TRACE_FILE,i,a,d1,c1,d2,c2);
+
+#else
+
+ #define HAVE_TRACE 0
+
+ #undef DEBUG
+
+ #define TRACE(...)
+ #define TRACE_IF(cond,...)
+ #define TRACELINE
+ #define TRACE_SIZEOF(t)
+ #define HEXDUMP(i,a,af,rl,d,c)
+ #define HEXDUMP16(a,i,d,c)
+ #define TRACE_HEXDUMP(i,a,af,rl,d,c)
+ #define TRACE_HEXDUMP16(i,a,d,c)
+ #define HEXDIFF(i,a,af,rl,d1,c1,d2,c2)
+ #define HEXDIFF16(a,i,d1,c1,d2,c2)
+ #define TRACE_HEXDIFF(i,a,af,rl,d1,c1,d2,c2)
+ #define TRACE_HEXDIFF16(i,a,d1,c1,d2,c2)
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef ASSERT
+#undef ASSERT_MSG
+
+#if defined(DEBUG_ASSERT) || defined(_DEBUG_ASSERT) || defined(TEST)
+
+ #define HAVE_ASSERT 1
+
+ #undef DEBUG_ASSERT
+ #define DEBUG_ASSERT 1
+
+ #define ASSERT(a) if (!(a)) ERROR0(ERR_FATAL,"ASSERTION FAILED !!!\n")
+ #define ASSERT_MSG(a,...) if (!(a)) ERROR0(ERR_FATAL,__VA_ARGS__)
+
+#else
+
+ #define HAVE_ASSERT 0
+
+ #undef DEBUG_ASSERT
+ #define ASSERT(cond)
+ #define ASSERT_MSG(a,...)
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef PRINT
+#undef PRINT_IF
+#undef PRINT_SIZEOF
+#undef BINGO
+
+#undef PRINT0
+#undef PRINT_IF0
+#undef PRINT_SIZEOF0
+#undef BINGO0
+
+#undef PRINT1
+#undef PRINT_IF1
+#undef PRINT_SIZEOF1
+#undef BINGO1
+
+#undef xBINGO
+#undef HAVE_PRINT
+#undef PRINT_TIME
+
+#undef HAVE_PRINT0 // always false
+#define HAVE_PRINT0 0
+
+// always disabled
+#define PRINT0 TRACE
+#define PRINT_IF0 TRACE_IF
+#define PRINT_SIZEOF0 TRACE_SIZEOF
+#define BINGO0 TRACELINE
+
+// always enabled
+#define PRINT1(...) PRINT_FUNC(__VA_ARGS__)
+#define PRINT_IF1(cond,...) if (cond) PRINT_FUNC(__VA_ARGS__)
+#define PRINT_SIZEOF1(t) PRINT_FUNC("%7zd ==%6zx/hex == sizeof(%s)\n",sizeof(t),sizeof(t),#t)
+#define BINGO1 BINGO_FUNC(__FUNCTION__,__LINE__,__FILE__)
+
+#if defined(DEBUG) && defined(TEST)
+
+ #define HAVE_PRINT 1
+
+ #define PRINT PRINT1
+ #define PRINT_IF PRINT_IF1
+ #define PRINT_SIZEOF PRINT_SIZEOF1
+ #define BINGO BINGO_FUNC(__FUNCTION__,__LINE__,__FILE__)
+
+ void PRINT_TIME ( time_t time, ccp title );
+
+#else
+
+ #define HAVE_PRINT HAVE_TRACE
+
+ #define PRINT TRACE
+ #define PRINT_IF TRACE_IF
+ #define PRINT_SIZEOF TRACE_SIZEOF
+ #define BINGO TRACELINE
+
+ #define PRINT_TIME(...)
+
+#endif
+
+#define xBINGO BINGO_FUNC(__FUNCTION__,__LINE__,__FILE__)
+#define xTRACELINE TRACE_FUNC("%s() line #%d @ %s\n",__FUNCTION__,__LINE__,__FILE__)
+
+#undef PRINTF
+#define PRINTF PRINT
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef WAIT
+#undef WAIT_IF
+#undef HAVE_WAIT
+
+#if defined(WAIT_ENABLED)
+
+ #define HAVE_WAIT 1
+
+ #define WAIT(...) WAIT_FUNC(__VA_ARGS__)
+ #define WAIT_IF(cond,...) if (cond) WAIT_FUNC(__VA_ARGS__)
+
+#else
+
+ #define HAVE_WAIT 0
+
+ #define WAIT(...)
+ #define WAIT_IF(cond,...)
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef DASSERT
+#undef DASSERT_MSG
+#undef HAVE_DASSERT
+
+#if defined(DEBUG) || defined(TEST)
+
+ #define HAVE_DASSERT 1
+
+ #define DASSERT ASSERT
+ #define DASSERT_MSG ASSERT_MSG
+
+#else
+
+ #define HAVE_DASSERT 0
+
+ #define DASSERT(cond)
+ #define DASSERT_MSG(a,...)
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// prefix 'no' deactivates traces
+
+#undef noTRACE
+#undef noTRACE_IF
+#undef noTRACELINE
+#undef noTRACE_SIZEOF
+#undef noPRINT
+#undef noPRINT_IF
+#undef noPRINT_TIME
+#undef noWAIT
+#undef noWAIT_IF
+#undef noASSERT
+
+#ifdef TESTTRACE
+
+ #define noTRACE TRACE
+ #define noTRACE_IF TRACE_IF
+ #define noTRACELINE TRACELINE
+ #define noTRACE_SIZEOF TRACE_SIZEOF
+ #define noPRINT PRINT
+ #define noPRINT_IF PRINT_IF
+ #define noPRINT_TIME PRINT_TIME
+ #define noWAIT WAIT
+ #define noWAIT_IF WAIT_IF
+ #define noASSERT ASSERT
+ #define noASSERT_MSG ASSERT_MSG
+
+#else
+
+ #define noTRACE(...)
+ #define noTRACE_IF(cond,...)
+ #define noTRACELINE
+ #define noTRACE_SIZEOF(t)
+ #define noPRINT(...)
+ #define noPRINT_IF(...)
+ #define noPRINT_TIME(...)
+ #define noWAIT(...)
+ #define noWAIT_IF(...)
+ #define noASSERT(cond)
+ #define noASSERT_MSG(cond,...)
+
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// alloc/free system ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// define TRACE_ALLOC_MODE
+//
+// 0: standard malloc without 'out of memory' detection (don't use it!)
+// 1: standard malloc with 'out of memory' detection
+// 2: standard malloc with 'out of memory' detection and source identification
+// 3: like mode #2, but alloc debuging enabled too
+// 4: like mode #3, but alloc tracing enabled too
+
+// define ENABLE_MEM_CHECK (check specific memory areas)
+// 0: disabled
+// 1: enable a watch point for a memory location
+
+#ifndef TRACE_ALLOC_MODE
+ #ifdef MEMDEBUG
+ #define TRACE_ALLOC_MODE 4
+ #elif TEST
+ #define TRACE_ALLOC_MODE 3
+ #elif DEBUG
+ #define TRACE_ALLOC_MODE 2
+ #else
+ #define TRACE_ALLOC_MODE 1
+ #endif
+#endif
+
+#ifndef ENABLE_MEM_CHECK
+ #define ENABLE_MEM_CHECK 0
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if ENABLE_MEM_CHECK
+ extern void MemCheckSetup ( const void * ptr, unsigned int size );
+ extern void MemCheck ( ccp func, ccp file, unsigned int line );
+ #define MEM_CHECK_SETUP(p,s) MemCheckSetup(p,s)
+ #define MEM_CHECK MemCheck(__FUNCTION__,__FILE__,__LINE__)
+#else
+ #define MEM_CHECK_SETUP(p,s)
+ #define MEM_CHECK
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+void dclib_free ( void * ptr );
+
+void * dclib_xcalloc ( size_t nmemb, size_t size );
+void * dclib_xmalloc ( size_t size );
+void * dclib_xrealloc ( void * ptr, size_t size );
+char * dclib_xstrdup ( ccp src );
+
+#if TRACE_ALLOC_MODE > 1
+ void * dclib_calloc ( ccp,ccp,uint, size_t nmemb, size_t size );
+ void * dclib_malloc ( ccp,ccp,uint, size_t size );
+ void * dclib_realloc ( ccp,ccp,uint, void * ptr, size_t size );
+ char * dclib_strdup ( ccp,ccp,uint, ccp src );
+ char * dclib_strdup2 ( ccp,ccp,uint, ccp src1, ccp src2 );
+ char * dclib_strdup3 ( ccp,ccp,uint, ccp src1, ccp src2, ccp src3 );
+ void * dclib_memdup ( ccp,ccp,uint, const void * src, size_t copylen );
+ void * dclib_allocdup( ccp,ccp,uint, const void * src, size_t copylen );
+#else
+ void * dclib_calloc ( size_t nmemb, size_t size );
+ void * dclib_malloc ( size_t size );
+ void * dclib_realloc ( void * ptr, size_t size );
+ char * dclib_strdup ( ccp src );
+ char * dclib_strdup2 ( ccp src1, ccp src2 );
+ char * dclib_strdup3 ( ccp src1, ccp src2, ccp src3 );
+ void * dclib_memdup ( const void * src, size_t copylen );
+ void * dclib_allocdup( const void * src, size_t copylen );
+#endif
+
+uint trace_test_alloc ( ccp,ccp,uint, const void * ptr, bool hexdump );
+void trace_free ( ccp,ccp,uint, void * ptr );
+void * trace_calloc ( ccp,ccp,uint, size_t nmemb, size_t size );
+void * trace_malloc ( ccp,ccp,uint, size_t size );
+void * trace_realloc ( ccp,ccp,uint, void *ptr, size_t size );
+char * trace_strdup ( ccp,ccp,uint, ccp src );
+char * trace_strdup2 ( ccp,ccp,uint, ccp src1, ccp src2 );
+char * trace_strdup3 ( ccp,ccp,uint, ccp src1, ccp src2, ccp src3 );
+void * trace_memdup ( ccp,ccp,uint, const void * src, size_t copylen );
+void * trace_allocdup ( ccp,ccp,uint, const void * src, size_t copylen );
+
+#if TRACE_ALLOC_MODE > 2
+ void InitializeTraceAlloc(void);
+ int CheckTraceAlloc ( ccp func, ccp file, unsigned int line );
+ void DumpTraceAlloc ( ccp func, ccp file, unsigned int line, FILE * f );
+ struct mem_info_t *RegisterAlloc
+ ( ccp func, ccp file, uint line, cvp data, uint size, bool filler );
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if TRACE_ALLOC_MODE > 2
+ #define TEST_ALLOC(ptr,hexd) trace_test_alloc(__FUNCTION__,__FILE__,__LINE__,ptr,hexd)
+ #define FREE(ptr) trace_free(__FUNCTION__,__FILE__,__LINE__,ptr)
+ #define CALLOC(nmemb,size) trace_calloc(__FUNCTION__,__FILE__,__LINE__,nmemb,size)
+ #define MALLOC(size) trace_malloc(__FUNCTION__,__FILE__,__LINE__,size)
+ #define REALLOC(ptr,size) trace_realloc(__FUNCTION__,__FILE__,__LINE__,ptr,size)
+ #define STRDUP(src) trace_strdup(__FUNCTION__,__FILE__,__LINE__,src)
+ #define STRDUP2(src1,src2) trace_strdup2(__FUNCTION__,__FILE__,__LINE__,src1,src2)
+ #define STRDUP3(src1,src2,src3) trace_strdup3(__FUNCTION__,__FILE__,__LINE__,src1,src2,src3)
+ #define MEMDUP(src,size) trace_memdup(__FUNCTION__,__FILE__,__LINE__,src,size)
+ #define ALLOCDUP(src,size) trace_allocdup(__FUNCTION__,__FILE__,__LINE__,src,size)
+ #define INIT_TRACE_ALLOC InitializeTraceAlloc()
+ #define CHECK_TRACE_ALLOC CheckTraceAlloc(__FUNCTION__,__FILE__,__LINE__)
+ #define DUMP_TRACE_ALLOC(f) DumpTraceAlloc(__FUNCTION__,__FILE__,__LINE__,f)
+#elif TRACE_ALLOC_MODE > 1
+ #define TEST_ALLOC(ptr,hexd)
+ #define FREE(ptr) dclib_free(ptr)
+ #define CALLOC(nmemb,size) dclib_calloc(__FUNCTION__,__FILE__,__LINE__,nmemb,size)
+ #define MALLOC(size) dclib_malloc(__FUNCTION__,__FILE__,__LINE__,size)
+ #define REALLOC(ptr,size) dclib_realloc(__FUNCTION__,__FILE__,__LINE__,ptr,size)
+ #define STRDUP(src) dclib_strdup(__FUNCTION__,__FILE__,__LINE__,src)
+ #define STRDUP2(src1,src2) dclib_strdup2(__FUNCTION__,__FILE__,__LINE__,src1,src2)
+ #define STRDUP3(src1,src2,src3) dclib_strdup3(__FUNCTION__,__FILE__,__LINE__,src1,src2,src3)
+ #define MEMDUP(src,size) dclib_memdup(__FUNCTION__,__FILE__,__LINE__,src,size)
+ #define ALLOCDUP(src,size) dclib_allocdup(__FUNCTION__,__FILE__,__LINE__,src,size)
+ #define INIT_TRACE_ALLOC
+ #define CHECK_TRACE_ALLOC
+ #define DUMP_TRACE_ALLOC(f)
+#else
+ #define TEST_ALLOC(ptr,hexd)
+ #define FREE(ptr) dclib_free(ptr)
+ #define CALLOC(nmemb,size) dclib_calloc(nmemb,size)
+ #define MALLOC(size) dclib_malloc(size)
+ #define REALLOC(ptr,size) dclib_realloc(ptr,size)
+ #define STRDUP(src) dclib_strdup(src)
+ #define STRDUP2(src1,src2) dclib_strdup2(src1,src2)
+ #define STRDUP3(src1,src2,src3) dclib_strdup3(src1,src2,src3)
+ #define MEMDUP(src,size) dclib_memdup(src,size)
+ #define ALLOCDUP(src,size) dclib_allocdup(src,size)
+ #define INIT_TRACE_ALLOC
+ #define CHECK_TRACE_ALLOC
+ #define DUMP_TRACE_ALLOC(f)
+#endif
+
+#define XFREE(ptr) dclib_free(ptr)
+
+#ifndef DCLIB_DEBUG_C
+ #define free do_not_use_free
+ #define calloc do_not_use_calloc
+ #define malloc do_not_use_malloc
+ #define realloc do_not_use_realloc
+ #undef strdup
+ #define strdup do_not_use_strdup
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_DEBUG_H
diff --git a/project/dclib/dclib-file.c b/project/dclib/dclib-file.c
new file mode 100644
index 0000000..8a69370
--- /dev/null
+++ b/project/dclib/dclib-file.c
@@ -0,0 +1,5360 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <termios.h>
+#include <utime.h>
+#include <errno.h>
+#include <dirent.h>
+#include <poll.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/resource.h>
+
+#include "dclib-basics.h"
+#include "dclib-file.h"
+#include "dclib-debug.h"
+#include "dclib-network.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// termios support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static struct termios termios_data;
+int termios_valid = 0;
+
+void ResetTermios()
+{
+ if ( termios_valid > 0 )
+ tcsetattr(0,TCSANOW,&termios_data);
+}
+
+static bool LoadTermios()
+{
+ if (!termios_valid)
+ {
+ termios_valid = -1;
+ if ( isatty(0) && !tcgetattr(0,&termios_data) )
+ termios_valid = 1;
+ }
+ return termios_valid > 0;
+}
+
+bool EnableSingleCharInput()
+{
+ if (!LoadTermios())
+ return false;
+
+ struct termios tios;
+ memcpy(&tios,&termios_data,sizeof(tios));
+ tios.c_lflag &= ~(ICANON|ECHO);
+ tcsetattr(0,TCSANOW,&tios);
+ return true;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// enum FileMode_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetFileModeStatus
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ FileMode_t file_mode, // filemode to print
+ uint print_mode // 0: short vector: 1 char for 1 attrib
+ // 1: long mode: keyword for each set attrib
+)
+{
+ struct info_t
+ {
+ FileMode_t mode;
+ char ch;
+ ccp name;
+ };
+
+ static const struct info_t info[] =
+ {
+ { FM_TEST, 't', "test" },
+ { FM_SILENT, 's', "silent" },
+ { FM_IGNORE, 'i', "ignore" },
+ { FM_MODIFY, 'M', "modify" },
+ { FM_APPEND, 'A', "append" },
+ { FM_UPDATE, 'U', "update" },
+ { FM_OVERWRITE, 'O', "overwrite" },
+ { FM_NUMBER, 'N', "number" },
+ { FM_REMOVE, 'R', "remove" },
+ { FM_MKDIR, 'M', "mkdir" },
+ { FM_STDIO, 'S', "stdio" },
+ { FM_TCP, 'I', "tcp" },
+ { FM_DEV, 'D', "dev" },
+ { FM_SPC, 'P', "spc" },
+ { FM_TOUCH, 'H', "touch" },
+ { FM_TEMP, 'T', "temp" },
+ {0,0,0}
+ };
+
+ if ( print_mode == 0 )
+ {
+ if ( !buf || !buf_size )
+ buf = GetCircBuf( buf_size = 16 );
+ char *dest = buf, *end = buf + buf_size - 1;
+ const struct info_t *ip;
+ for ( ip = info; ip->mode && dest < end; ip++ )
+ *dest++ = ip->mode & file_mode ? ip->ch : '-';
+ *dest = 0;
+ return buf;
+ }
+
+ if ( !buf || !buf_size )
+ buf = GetCircBuf( buf_size = 100 );
+
+ char *dest = buf, *end = buf + buf_size - 1;
+ const struct info_t *ip;
+ for ( ip = info; ip->mode && dest < end; ip++ )
+ if ( ip->mode & file_mode )
+ {
+ if ( dest > buf )
+ *dest++ = ',';
+ dest = StringCopyE(dest,end,ip->name);
+ }
+ *dest = 0;
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetFileOpenMode
+(
+ bool create, // false: open, true: create
+ FileMode_t file_mode // open modes
+)
+{
+ if ( file_mode & FM_TEST )
+ return "r";
+
+ if (!create)
+ return file_mode & FM_MODIFY ? "r+b" : "rb";
+
+ switch ( (uint)file_mode & (FM_MODIFY|FM_APPEND) )
+ {
+ case FM_MODIFY | FM_APPEND : return "a+b";
+ case FM_MODIFY : return "w+b";
+ case FM_APPEND : return "ab";
+ default : return "wb";
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct FileAttrib_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * ClearFileAttrib
+(
+ FileAttrib_t * dest // NULL or destination attribute
+)
+{
+ if (dest)
+ memset(dest,0,sizeof(*dest));
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * TouchFileAttrib
+(
+ FileAttrib_t * dest // valid destination attribute
+)
+{
+ DASSERT(dest);
+ dest->mtime = dest->ctime = dest->atime = dest->itime = time(0);
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * SetFileAttrib
+(
+ FileAttrib_t * dest, // valid destination attribute
+ const FileAttrib_t * src_fa, // NULL or source attribute
+ const struct stat * src_stat // NULL or source attribute
+ // only used if 'src_fa==NULL'
+)
+{
+ DASSERT(dest);
+
+ if (src_fa)
+ memcpy(dest,src_fa,sizeof(*dest));
+ else if (src_stat)
+ {
+ memset(dest,0,sizeof(*dest));
+ if ( S_ISREG(src_stat->st_mode) )
+ {
+ dest->mtime = src_stat->st_mtime;
+ dest->ctime = src_stat->st_ctime;
+ dest->itime = dest->mtime > dest->ctime ? dest->mtime : dest->ctime;
+ dest->atime = src_stat->st_atime;
+ dest->size = src_stat->st_size;
+ }
+ dest->mode = src_stat->st_mode;
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * MaxFileAttrib
+(
+ FileAttrib_t * dest, // valid source and destination attribute
+ const FileAttrib_t * src_fa, // NULL or second source attribute
+ const struct stat * src_stat // NULL or third source attribute
+)
+{
+ DASSERT(dest);
+
+ if (src_fa)
+ {
+ if ( dest->mtime < src_fa->mtime ) dest->mtime = src_fa->mtime;
+ if ( dest->ctime < src_fa->ctime ) dest->ctime = src_fa->ctime;
+ if ( dest->atime < src_fa->atime ) dest->atime = src_fa->atime;
+ if ( dest->itime < src_fa->itime ) dest->itime = src_fa->itime;
+
+ if ( dest->size < src_fa->size ) dest->size = src_fa->size;
+
+ dest->mode = src_fa->mode;
+ }
+
+ if (src_stat)
+ {
+ if ( S_ISREG(src_stat->st_mode) )
+ {
+ if ( dest->mtime < src_stat->st_mtime ) dest->mtime = src_stat->st_mtime;
+ if ( dest->ctime < src_stat->st_ctime ) dest->ctime = src_stat->st_ctime;
+ if ( dest->atime < src_stat->st_atime ) dest->atime = src_stat->st_atime;
+
+ if ( dest->itime < dest->mtime ) dest->itime = dest->mtime;
+ if ( dest->itime < dest->ctime ) dest->itime = dest->ctime;
+
+ if ( dest->size < src_stat->st_size ) dest->size = src_stat->st_size;
+ }
+ dest->mode = src_stat->st_mode;
+ }
+
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * NormalizeFileAttrib
+(
+ FileAttrib_t * fa // valid attribute
+)
+{
+ DASSERT(fa);
+
+ if ( fa->size < 0 )
+ fa->size = 0;
+
+ time_t mtime = fa->mtime;
+ if (!mtime)
+ {
+ mtime = fa->itime;
+ if (!mtime)
+ {
+ mtime = fa->ctime;
+ if (!mtime)
+ mtime = fa->atime;
+ }
+ }
+ fa->mtime = mtime;
+
+ if (!fa->itime)
+ fa->itime = fa->ctime > mtime ? fa->ctime : mtime;
+
+ if (!fa->ctime)
+ fa->ctime = fa->itime > mtime ? fa->itime : mtime;
+
+ if (!fa->atime)
+ fa->atime = fa->itime > fa->ctime ? fa->itime : fa->ctime;
+
+ return fa;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// simple UNIX+TCP socket connect ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// not stored in dclib-network.c to allow static linking!
+
+int ConnectUnixTCP
+(
+ ccp fname, // unix socket filename
+ bool silent // true: suppress error messages
+)
+{
+ DASSERT(fname);
+
+
+ //--- check file name
+
+ struct sockaddr_un sa;
+ const uint flen = fname ? strlen(fname)+1 : 0;
+ if ( flen > sizeof(sa.sun_path) )
+ {
+ if (!silent)
+ ERROR0(ERR_CANT_CONNECT,
+ "Path name to long for UNIX/STREAM socket: %s\n",fname);
+ return -1;
+ }
+
+ memset(&sa,0,sizeof(sa));
+ memcpy(sa.sun_path,fname,flen);
+ sa.sun_family = AF_UNIX;
+
+
+ //--- create socket
+
+ const int sock = socket(AF_UNIX,SOCK_STREAM,0);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't create UNIX/STREAM socket: %s\n",fname);
+ return -1;
+ }
+
+
+ //--- connect
+
+ if (connect(sock,(struct sockaddr *)&sa,sizeof(sa)))
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't connect to UNIX/STREAM socket: %s\n",fname);
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int (*ConnectTCP_Hook)
+(
+ // if set: ConnectNumericTCP() uses ConnectTCP()
+
+ ccp addr, // TCP address: ['tcp':] IPv4 [:PORT]
+ int default_port, // NULL or default port, if not found in 'addr'
+ bool silent // true: suppress error messages
+) = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanNumericIP4
+(
+ // returns next unread character or NULL on error
+
+ ccp addr, // address to scan
+ u32 *r_ipv4, // not NULL: store result here (local endian)
+ u32 *r_port, // not NULL: scan port too (local endian)
+ uint default_port // return this if no port found
+)
+{
+ if (!addr)
+ {
+ abort:
+ if (r_ipv4)
+ *r_ipv4 = 0;
+ if (r_port)
+ *r_port = 0;
+ return 0;
+ }
+
+ u32 num[4] = {0};
+ uint i;
+ ccp ptr = addr;
+ for ( i = 0; i < 4; )
+ {
+ char *end;
+ uint temp = str2ul(ptr,&end,10);
+ if ( !end || end == ptr )
+ break;
+ num[i++] = temp;
+ ptr = addr = end;
+ if ( *ptr != '.' )
+ break;
+ ptr++;
+ }
+
+ uint ip4;
+ switch (i)
+ {
+ case 1:
+ ip4 = num[0];
+ break;
+
+ case 2:
+ ip4 = num[0] << 24 | num[1];
+ break;
+
+ case 3:
+ ip4 = num[0] << 24 | num[1] << 16 | num[2];
+ break;
+
+ case 4:
+ ip4 = num[0] << 24 | num[1] << 16 | num[2] << 8 | num[3];
+ break;
+
+ default:
+ goto abort;
+ }
+ if (r_ipv4)
+ *r_ipv4 = ip4;
+
+ if (r_port)
+ {
+ if ( *addr == ':' )
+ {
+ char *end;
+ uint num = str2ul(addr+1,&end,10);
+ if ( end && end > addr+1 && num < 0x10000 )
+ {
+ addr = end;
+ default_port = num;
+ }
+ }
+ *r_port = default_port;
+ }
+
+ return (char*)addr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t ScanNumericIP4Mem
+(
+ // returns unread character or NullMem on error
+
+ mem_t addr, // address to scan
+ u32 *r_ipv4, // not NULL: store result here (local endian)
+ u32 *r_port, // not NULL: scan port too (local endian)
+ uint default_port // return this if no port found
+)
+{
+ if ( !addr.ptr || !addr.len )
+ {
+ abort:
+ if (r_ipv4)
+ *r_ipv4 = 0;
+ if (r_port)
+ *r_port = 0;
+ return NullMem;
+ }
+
+ u32 num[4] = {0};
+ uint i;
+ ccp ptr = addr.ptr;
+ ccp end = ptr + addr.len;
+
+ for ( i = 0; i < 4; )
+ {
+ uint temp;
+ ccp next = ScanNumber(&temp,ptr,end,10,10);
+ if ( !next || next == ptr )
+ break;
+ num[i++] = temp;
+ addr = BehindMem(addr,next);
+ ptr = addr.ptr;
+ if ( *ptr != '.' )
+ break;
+ ptr++;
+ }
+
+ uint ip4;
+ switch (i)
+ {
+ case 1:
+ ip4 = num[0];
+ break;
+
+ case 2:
+ ip4 = num[0] << 24 | num[1];
+ break;
+
+ case 3:
+ ip4 = num[0] << 24 | num[1] << 16 | num[2];
+ break;
+
+ case 4:
+ ip4 = num[0] << 24 | num[1] << 16 | num[2] << 8 | num[3];
+ break;
+
+ default:
+ goto abort;
+ }
+ if (r_ipv4)
+ *r_ipv4 = ip4;
+
+ if (r_port)
+ {
+ ptr = addr.ptr;
+ if ( ptr < end && *ptr == ':' )
+ {
+ ptr++;
+ uint num;
+ ccp next = ScanNumber(&num,ptr,end,10,10);
+ if ( next > ptr && num < 0x10000 )
+ {
+ addr = BehindMem(addr,next);
+ default_port = num;
+ }
+ }
+ *r_port = default_port;
+ }
+
+ return addr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Returns a NETWORK MASK "/a.b.c.d" or as CIDR number "/num" between 0 and 32.
+// An optional slash '/' at the beginning is skipped.
+// Returns modified 'source' if a MASK or CDIR is detected.
+// If no one is detected, source is unmodified and returned mask = ~0.
+
+mem_t ScanNetworkMaskMem ( u32 *mask, mem_t source )
+{
+ u32 m = M1(m);
+ if ( source.ptr && source.len )
+ {
+ ccp ptr = source.ptr;
+ ccp end = ptr + source.len;
+ if ( *ptr == '/' )
+ ptr++;
+
+ uint num;
+ ccp next = ScanNumber(&num,ptr,end,10,10);
+ if ( next > ptr )
+ {
+ if ( next < end && *next == '.' )
+ {
+ // is a network mask
+ source = ScanNumericIP4Mem(BehindMem(source,ptr),&m,0,0);
+ }
+ else if ( num <= 32 )
+ {
+ m = num ? ~(( 1 << (32-num) ) - 1) : 0;
+ source = BehindMem(source,next);
+ }
+ }
+ }
+
+ if (mask)
+ *mask = m;
+ return source;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectNumericTCP
+(
+ // like ConnectTCP(), but without name resolution (only numeric ip+port)
+
+ ccp addr, // TCP address: ['tcp':] IPv4 [:PORT]
+ int default_port, // NULL or default port, if not found in 'addr'
+ bool silent // true: suppress error messages
+)
+{
+ if (ConnectTCP_Hook)
+ return ConnectTCP_Hook(addr,default_port,silent);
+
+ ccp unix_path = CheckUnixSocketPath(addr,0);
+ if (unix_path)
+ return ConnectUnixTCP(unix_path,silent);
+
+ if ( !strncasecmp(addr,"tcp:",4) )
+ addr += 4;
+
+ u32 ip4, port;
+ ccp end = ScanNumericIP4(addr,&ip4,&port,default_port);
+ if (!end)
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Invalid IPv4 address: %s\n",addr);
+ return -1;
+ }
+
+ int sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't create socket: %s\n",addr);
+ return -1;
+ }
+
+ struct sockaddr_in sa;
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(ip4);
+ sa.sin_port = htons(port);
+
+ if (connect(sock,(struct sockaddr *)&sa,sizeof(sa)))
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't connect to %s\n",PrintIP4(0,0,ip4,port) );
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct File_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeFile
+(
+ File_t * f // file structure
+)
+{
+ DASSERT(f);
+ memset(f,0,sizeof(*f));
+ f->fname = EmptyString;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ResetFile
+(
+ File_t * f, // file structure
+ uint set_time // 0: don't set
+ // 1: set time before closing using 'fatt'
+ // 2: set current time before closing
+)
+{
+ DASSERT(f);
+ const enumError err = CloseFile(f,set_time);
+ FreeString(f->fname);
+ if (f->data_alloced)
+ FREE(f->data);
+ InitializeFile(f);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CloseFile
+(
+ File_t * f, // file structure
+ uint set_time // 0: don't set
+ // 1: set time before closing using 'fatt'
+ // 2: set current time before closing
+)
+{
+ DASSERT(f);
+ TRACE("CloseFile(%p,%d) fname=%s\n",f,set_time,f->fname);
+
+ if (f->f)
+ {
+ if (!f->is_stdio)
+ {
+ if ( fclose(f->f) && f->max_err <= ERR_WARNING )
+ f->max_err = ERROR1(ERR_WRITE_FAILED,
+ "Error while closing file: %s\n",f->fname);
+
+ if (!f->is_socket)
+ {
+ if ( f->fmode & FM_TEMP )
+ {
+ TRACE("UNLINK TEMP %s\n",f->fname);
+ unlink(f->fname);
+ }
+ else if ( f->is_writing )
+ {
+ if ( f->max_err > ERR_WARNING )
+ {
+ TRACE("UNLINK %s\n",f->fname);
+ unlink(f->fname);
+ }
+ else if ( set_time == 1 && f->fatt.mtime )
+ {
+ struct utimbuf ubuf;
+ ubuf.actime = f->fatt.atime
+ ? f->fatt.atime : f->fatt.mtime;
+ ubuf.modtime = f->fatt.mtime;
+ utime(f->fname,&ubuf);
+ }
+ else if ( set_time > 1 || f->fmode & FM_TOUCH )
+ {
+ utime(f->fname,0);
+ }
+ }
+ }
+ }
+ f->f = 0;
+ }
+
+ return f->max_err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_seekable
+(
+ File_t * F // file structure
+)
+{
+ const mode_t mode = F->st.st_mode;
+ F->is_seekable = ( S_ISREG(mode) || S_ISBLK(mode) || S_ISCHR(mode) )
+ && F->st.st_size
+ && lseek(fileno(F->f),0,SEEK_SET) != (off_t)-1;
+ return F->is_seekable;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError OpenFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp fname, // file to open
+ FileMode_t file_mode, // open modes
+ off_t limit, // >0: don't open, if file size > limit
+ ccp limit_message // NULL or a with LF terminated text.
+ // It is printed after the message.
+)
+{
+ DASSERT(f);
+ DASSERT(fname);
+
+ if (initialize)
+ InitializeFile(f);
+ else
+ ResetFile(f,false);
+ f->fmode = file_mode & FM_M_OPEN;
+
+ TRACE("OpenFile(%p,%d,,%s,%llu) [%s] %s\n",
+ f, initialize, GetFileModeStatus(0,0,file_mode,0), (u64)limit,
+ GetFileModeStatus(0,0,f->fmode,1), fname );
+
+
+ //--- test for TCP
+
+ if ( file_mode & FM_TCP && !strncasecmp(fname,"tcp:",4) )
+ {
+ const int fd = ConnectNumericTCP(fname,0,(f->fmode&FM_SILENT)!=0);
+ if ( fd == -1 )
+ return f->max_err = ERR_CANT_OPEN;
+
+ f->f = fdopen(fd,"rw");
+ f->is_socket = f->is_reading = f->is_writing = true;
+ return f->max_err;
+ }
+
+
+ //--- test for stdin
+
+ if ( *fname == '-' && !fname[1] )
+ {
+ f->fname = MinusString;
+ if ( f->fmode & FM_STDIO )
+ {
+ f->is_stdio = true;
+ f->f = stdin;
+ fstat(fileno(stdin),&f->st);
+ is_seekable(f);
+ return ERR_OK;
+ }
+ }
+
+ f->fname = STRDUP(fname);
+
+
+ //--- test 'unix:' prefix
+
+ bool have_unix = file_mode & FM_TCP && !strncasecmp(fname,"unix:",5);
+ if (have_unix)
+ fname += 5;
+
+
+ //--- does file exist?
+
+ for(;;)
+ {
+ if (!stat(fname,&f->st))
+ break;
+
+ if (have_unix)
+ {
+ have_unix = false;
+ fname -= 5;
+ continue;
+ }
+
+ not_found:
+ memset(&f->st,0,sizeof(f->st));
+ if ( f->fmode & FM_IGNORE )
+ return f->max_err = ERR_NOT_EXISTS;
+
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR1(ERR_CANT_OPEN,"Can't find file: %s\n",fname);
+ return f->max_err = ERR_CANT_OPEN;
+ }
+
+
+ //--- UNIX socket -> try STREAM connect
+
+ if (S_ISSOCK(f->st.st_mode))
+ {
+ f->fmode &= ~(FM_REMOVE|FM_TEMP);
+
+ const int fd = ConnectUnixTCP(fname,(f->fmode&FM_SILENT)!=0);
+ if ( fd == -1 )
+ return f->max_err = ERR_CANT_OPEN;
+
+ f->f = fdopen(fd,"rw");
+ f->is_socket = f->is_reading = f->is_writing = true;
+ return f->max_err;
+ }
+
+ if (have_unix)
+ goto not_found;
+
+
+ //--- check size limit
+
+ SetFileAttrib(&f->fatt,0,&f->st);
+ if ( limit && f->st.st_size > limit )
+ {
+ char sbuf[12], lbuf[12];
+ PrintSize1024(sbuf,sizeof(sbuf),(u64)f->st.st_size,false);
+ PrintSize1024(lbuf,sizeof(lbuf),(u64)limit,false);
+
+ // be tolerant, if both values produce same text
+ if (strcmp(sbuf,lbuf))
+ {
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR0(ERR_CANT_OPEN,
+ "File too large (size=%s, limit=%s): %s\n%s",
+ sbuf, lbuf, fname,
+ limit_message ? limit_message : "" );
+ return f->max_err = ERR_CANT_OPEN;
+ }
+ }
+
+
+ //--- open file
+
+ ccp omode = GetFileOpenMode(false,f->fmode);
+ f->f = fopen(fname,omode);
+ TRACE("OpenFile(%s,%s) : [%s], %p\n",
+ fname, omode, GetFileModeStatus(0,0,f->fmode,1), f->f );
+
+ if (!f->f)
+ {
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",fname);
+ return f->max_err = ERR_CANT_OPEN;
+ }
+
+ f->is_reading = true;
+ if ( f->fmode & FM_MODIFY )
+ f->is_writing = true;
+
+ is_seekable(f);
+ return f->max_err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CheckCreateFile
+(
+ // returns:
+ // ERR_DIFFER: [FM_STDIO] source is "-" => 'st' is zeroed
+ // ERR_WARNING: [FM_DEV,FM_SOCK,FM_SPC] not a regular file
+ // ERR_WRONG_FILE_TYPE: file exists, but is not a regular file
+ // ERR_ALREADY_EXISTS: file already exists
+ // ERR_INVALID_VERSION: already exists, but FM_NUMBER set => no msg printed
+ // ERR_CANT_CREATE: FM_UPDATE is set, but file don't exist
+ // ERR_OK: file not exist or can be overwritten
+
+ ccp fname, // filename to open
+ FileMode_t file_mode, // open modes
+ struct stat *st // not NULL: store file status here
+)
+{
+ DASSERT(fname);
+
+ if ( file_mode & FM_STDIO && fname[0] == '-' && !fname[1] )
+ {
+ if (st)
+ memset(st,0,sizeof(*st));
+ return ERR_DIFFER;
+ }
+
+ struct stat local_st;
+ if (!st)
+ st = &local_st;
+
+ if (!stat(fname,st))
+ {
+ if ( S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode) )
+ {
+ if ( file_mode & FM_DEV )
+ return ERR_WARNING;
+
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0( ERR_ALREADY_EXISTS,
+ "Can't write to %s device: %s\n",
+ S_ISBLK(st->st_mode) ? "block" : "character", fname );
+ return ERR_WRONG_FILE_TYPE;
+ }
+
+ if ( S_ISSOCK(st->st_mode) )
+ {
+ if ( file_mode & FM_SOCK )
+ return ERR_WARNING;
+
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0( ERR_ALREADY_EXISTS,
+ "Can't write to UNIX socket: %s\n", fname );
+ return ERR_WRONG_FILE_TYPE;
+ }
+
+ if (!S_ISREG(st->st_mode))
+ {
+ if ( file_mode & FM_SPC )
+ return ERR_WARNING;
+
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0( ERR_WRONG_FILE_TYPE,
+ "Not a plain file: %s\n", fname );
+ return ERR_WRONG_FILE_TYPE;
+ }
+
+ if (!( file_mode & (FM_OVERWRITE|FM_REMOVE|FM_UPDATE|FM_APPEND) ))
+ {
+ if ( file_mode & FM_NUMBER )
+ return ERR_INVALID_VERSION;
+
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0( ERR_ALREADY_EXISTS,
+ "File already exists: %s\n", fname );
+ return ERR_ALREADY_EXISTS;
+ }
+ }
+ else
+ {
+ memset(st,0,sizeof(*st));
+ if ( file_mode & FM_UPDATE )
+ {
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0(ERR_CANT_CREATE,
+ "Try to update non existing file: %s\n", fname );
+ return ERR_CANT_CREATE;
+ }
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CreateFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp fname, // file to open
+ FileMode_t file_mode // open modes
+)
+{
+ DASSERT(f);
+ DASSERT(fname);
+
+ if (initialize)
+ InitializeFile(f);
+ else
+ ResetFile(f,false);
+ f->fmode = file_mode & FM_M_CREATE;
+
+ noPRINT("CreateFile(%p,%d,,%s) [%s] %s\n",
+ f, initialize, GetFileModeStatus(0,0,file_mode,0),
+ GetFileModeStatus(0,0,f->fmode,1), fname );
+
+
+ //--- test for stdout
+
+ if ( *fname == '-' && !fname[1] )
+ {
+ f->fname = MinusString;
+ if ( f->fmode & FM_STDIO )
+ {
+ f->is_stdio = true;
+ f->f = stdout;
+ fstat(fileno(stdout),&f->st);
+ is_seekable(f);
+ return ERR_OK;
+ }
+ }
+
+
+ //--- mode calculations
+
+// if ( f->fmode & FM_UPDATE )
+// f->fmode = f->fmode & ~(FM_REMOVE|FM_MKDIR) | FM_OVERWRITE;
+
+ enumError err = CheckCreateFile(fname,f->fmode,&f->st);
+
+ char num_fname[PATH_MAX];
+ if ( err == ERR_INVALID_VERSION )
+ {
+ NumberedFilename(num_fname,sizeof(num_fname),fname,0,0,false);
+ fname = num_fname;
+ err = CheckCreateFile( fname, f->fmode & ~FM_NUMBER, &f->st );
+ }
+
+ f->fname = STRDUP(fname);
+ if ( f->fmode & (FM_MODIFY|FM_APPEND) )
+ SetFileAttrib(&f->fatt,0,&f->st);
+
+ if (err)
+ {
+ if ( err > ERR_WARNING )
+ return err;
+
+ // is a device, socket or special file
+ err = ERR_OK;
+ f->fmode = f->fmode & ~(FM_REMOVE|FM_TEMP) | FM_OVERWRITE;
+ }
+
+ if ( f->fmode & FM_TEST )
+ {
+ // never create or remove a file in TEST mode.
+ return ERR_OK;
+ }
+
+ if (S_ISSOCK(f->st.st_mode))
+ {
+ const int fd = ConnectUnixTCP(fname,(f->fmode&FM_SILENT)!=0);
+ if ( fd == -1 )
+ return f->max_err = ERR_CANT_OPEN;
+
+ f->f = fdopen(fd,"rw");
+ f->is_socket = f->is_reading = f->is_writing = true;
+ return ERR_OK;
+ }
+
+ if ( f->st.st_mode && f->fmode & FM_REMOVE )
+ {
+ unlink(fname);
+
+ struct stat st;
+ if (!stat(fname,&st))
+ {
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR0(ERR_REMOVE_FAILED,"Can't remove file: %s\n",fname);
+ return f->max_err = ERR_REMOVE_FAILED;
+ }
+ }
+
+ ccp omode = GetFileOpenMode(true,f->fmode);
+ f->f = fopen(fname,omode);
+ noPRINT("CreateFile(%s,%s) : [%s], %p\n",
+ fname, omode, GetFileModeStatus(0,0,f->fmode,1), f->f );
+
+ if ( !f->f )
+ {
+ if ( f->fmode & FM_MKDIR )
+ {
+ CreatePath(fname,false);
+ f->f = fopen(fname,omode);
+ }
+
+ if ( !f->f )
+ {
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR1(ERR_CANT_CREATE,"Can't create file: %s\n",fname);
+ return f->max_err = ERR_CANT_CREATE;
+ }
+ }
+
+ f->is_writing = true;
+ if ( f->fmode & FM_MODIFY)
+ f->is_reading = true;
+
+ if (!fstat(fileno(f->f),&f->st))
+ {
+ const mode_t mode = f->st.st_mode;
+ f->is_seekable = S_ISREG(mode) || S_ISBLK(mode) || S_ISCHR(mode);
+ }
+
+ return f->max_err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError WriteFileAt
+(
+ File_t * F, // file to write
+ size_t * cur_offset, // pointer to current file offset, modified
+ size_t offset, // offset to write
+ const void * data, // data to write
+ size_t size // size of 'data'
+)
+{
+ DASSERT(F);
+ DASSERT(F->f);
+ DASSERT(data||!size);
+ DASSERT(cur_offset);
+
+ if (!size)
+ return ERR_OK;
+
+ if ( offset != *cur_offset )
+ {
+ if (F->is_seekable)
+ {
+ noPRINT("FSEEK %zx -> %zx\n",*cur_offset,offset);
+ if (fseek(F->f,offset,SEEK_SET))
+ return ERROR1(ERR_WRITE_FAILED,"Can't set file pointer: %s\n",F->fname);
+ }
+ else if ( offset < *cur_offset )
+ {
+ noPRINT("FSEEK %zx -> %zx\n",*cur_offset,offset);
+ return ERROR0(ERR_WRITE_FAILED,
+ "Can't set file pointer on non seekable file: %s\n",F->fname);
+ }
+ else
+ {
+ char buf[0x8000] = {0};
+ size_t fill_size = offset - *cur_offset;
+ while (fill_size)
+ {
+ const size_t write_size = fill_size < sizeof(buf) ? fill_size : sizeof(buf);
+ noPRINT("FILL/ZERO %zx/%zx\n",write_size,fill_size);
+ const size_t written = fwrite(buf,1,write_size,F->f);
+ if ( written != write_size )
+ return ERROR1(ERR_WRITE_FAILED,"Writing %zu NULL bytes failed: %s\n",
+ write_size, F->fname );
+ fill_size -= write_size;
+ }
+ }
+ }
+
+ noPRINT("WRITE %zx @%zx -> %zx\n",size,offset,offset+size);
+ const size_t written = fwrite(data,1,size,F->f);
+ if ( written != size )
+ return ERROR1(ERR_WRITE_FAILED,"Writing %zu bytes at offset %zu failed: %s\n",
+ size,offset,F->fname);
+
+ *cur_offset = offset + size;
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SetFileSize
+(
+ File_t * F, // file to write
+ size_t * cur_offset, // pointer to current file offset, modified
+ size_t size // offset to write
+)
+{
+ DASSERT(F);
+ DASSERT(F->f);
+ DASSERT(cur_offset);
+
+ fflush(F->f);
+ if (!F->is_seekable)
+ return WriteFileAt(F,cur_offset,size,0,0);
+
+ if (ftruncate(fileno(F->f),size))
+ {
+ if ( F->max_err < ERR_WRITE_FAILED )
+ F->max_err = ERR_WRITE_FAILED;
+ return ERROR1( ERR_WRITE_FAILED,
+ "Set file size to %llu failed: %s\n",
+ (u64)size, F->fname );
+ }
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SkipFile
+(
+ File_t *F, // file to write
+ size_t skip // number of bytes to skip
+)
+{
+ DASSERT(F);
+ DASSERT(F->f);
+
+ if (!skip)
+ return ERR_OK;
+
+ if ( F->is_seekable && !fseek(F->f,skip,SEEK_CUR) )
+ return ERR_OK;
+
+ char buf[0x8000];
+ while ( skip > 0 )
+ {
+ size_t max = skip < sizeof(buf) ? skip : sizeof(buf);
+ size_t stat = fread(buf,1,max,F->f);
+ if (!stat)
+ {
+ if (feof(F->f))
+ break;
+ return ERROR1(ERR_READ_FAILED,
+ "Reading %zu to skip failed: %s\n",
+ skip, F->fname );
+ }
+ skip -= stat;
+ }
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RegisterFileError
+(
+ File_t * f, // file structure
+ enumError new_error // new error code
+)
+{
+ DASSERT(f);
+ if ( f->max_err < new_error )
+ f->max_err = new_error;
+ return f->max_err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+s64 GetFileSize
+(
+ ccp path1, // NULL or part 1 of path
+ ccp path2, // NULL or part 2 of path
+ s64 not_found_val, // return value if no regular file found
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store max values to 'fatt'
+)
+{
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ TRACE("GetFileSize(%s,%lld)\n",path,not_found_val);
+
+ struct stat st;
+ if ( stat(path,&st) || !S_ISREG(st.st_mode) )
+ {
+ if ( fatt && !fatt_max )
+ memset(fatt,0,sizeof(*fatt));
+ return not_found_val;
+ }
+
+ if (fatt)
+ {
+ if (fatt_max)
+ MaxFileAttrib(fatt,0,&st);
+ else
+ SetFileAttrib(fatt,0,&st);
+ }
+
+ return st.st_size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError OpenReadFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ off_t limit, // >0: don't open, if file size > limit
+ ccp limit_message, // NULL or a with LF terminated text.
+ // It is printed after the message.
+
+ u8 ** res_data, // store alloced data here (always NULL terminated)
+ uint * res_size, // not NULL: store data size
+ ccp * res_fname, // not NULL: store alloced filename
+ FileAttrib_t * res_fatt // not NULL: store file attributes
+)
+{
+ DASSERT( path1 || path2 );
+ DASSERT(res_data);
+
+ *res_data = 0;
+ if (res_size)
+ *res_size = 0;
+ if (res_fname)
+ *res_fname = 0;
+ if (res_fatt)
+ memset(res_fatt,0,sizeof(*res_fatt));
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ PRINT("OpenReadFile(%s,%s)\n",path,GetFileModeStatus(0,0,file_mode,0));
+
+ File_t F;
+ enumError err = OpenFile(&F,true,path,file_mode,limit,limit_message);
+ if (err)
+ return err;
+
+ uint data_size = F.st.st_size;
+ if (!F.is_seekable)
+ data_size = limit;
+ noPRINT("OpenReadFile() fsize=%u, seekable=%d\n",data_size,F.is_seekable);
+ u8 *data = MALLOC(data_size+1);
+
+ size_t read_size = fread(data,1,data_size,F.f);
+ if ( read_size && !F.is_seekable )
+ {
+ PRINT("non seekable read: %zu\n",read_size);
+ data_size = read_size;
+ data = REALLOC(data,data_size+1);
+ }
+ data[data_size] = 0; // termination for text files
+
+ if ( read_size != data_size )
+ {
+ if (!(file_mode&FM_SILENT))
+ FILEERROR1(&F,ERR_READ_FAILED,"Read failed: %s\n",path);
+ ResetFile(&F,false);
+ FREE(data);
+ return ERR_READ_FAILED;
+ }
+
+ *res_data = data;
+ if (res_size)
+ *res_size = data_size;
+ if (res_fname)
+ {
+ *res_fname = F.fname;
+ F.fname = EmptyString;
+ }
+ if (res_fatt)
+ SetFileAttrib(res_fatt,&F.fatt,0);
+
+ ResetFile(&F,false);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // skip num of bytes before reading
+ void * data, // destination buffer, size = 'size'
+ size_t size, // size to read
+ int silent, // 0: print all error messages
+ // 1: suppress file size warning
+ // 2: suppress all error messages
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store *max* values to 'fatt'
+)
+{
+ ASSERT(data);
+ if (!size)
+ return ERR_OK;
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ TRACE("LoadFile(%s,%zu,%zu,%d)\n",path,skip,size,silent);
+
+ if (fatt)
+ {
+ struct stat st;
+ if (!stat(path,&st))
+ UseFileAttrib(fatt,0,&st,fatt_max);
+ }
+
+ FILE * f = fopen(path,"rb");
+ if (!f)
+ {
+ if (silent<2)
+ ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",path);
+ return ERR_CANT_OPEN;
+ }
+
+ if ( skip > 0 )
+ fseek(f,skip,SEEK_SET);
+
+ size_t read_stat = fread(data,1,size,f);
+ fclose(f);
+
+ if ( read_stat == size )
+ return ERR_OK;
+
+ enumError err = ERR_READ_FAILED;
+ if ( silent == 1 && size > 0 )
+ err = ERR_WARNING;
+ else if ( silent < 2 )
+ ERROR1(ERR_READ_FAILED,"Can't read file: %s\n",path);
+
+ noPRINT("D=%p, s=%zu/%zu: %s\n",data,read_stat,size,path);
+ if ( read_stat >= 0 && read_stat < size )
+ memset((char*)data+read_stat,0,size-read_stat);
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadFileAlloc
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // skip num of bytes before reading
+ u8 ** res_data, // result: free existing data, store ptr to alloc data
+ // always one more byte is alloced and set to NULL
+ size_t * res_size, // result: size of 'res_data'
+ size_t max_size, // >0: a file size limit
+ int silent, // 0: print all error messages
+ // 1: suppress file size warning
+ // 2: suppress all error messages
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store max values to 'fatt'
+)
+{
+ DASSERT(res_data);
+ DASSERT(res_size);
+
+ //--- clear return data
+
+ if (res_data)
+ *res_data = 0;
+
+ if (res_size)
+ *res_size = 0;
+
+ if ( fatt && !fatt_max )
+ memset(fatt,0,sizeof(*fatt));
+
+
+ //--- get file size
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+
+ const s64 size = GetFileSize(path,0,-1,0,0);
+ if ( size == -1 )
+ {
+ if ( silent < 2 )
+ ERROR0(ERR_SYNTAX,"File not found: %s\n",path);
+ return ERR_SYNTAX;
+ }
+
+ if ( max_size && size > max_size )
+ {
+ if ( silent < 2 )
+ ERROR0(ERR_INVALID_FILE,"File too large: %s\n",path);
+ return ERR_INVALID_FILE;
+ }
+
+ u8 *data = MALLOC(size+1);
+ enumError err = LoadFile(path,0,skip,data,size,silent,fatt,fatt_max);
+ if (err)
+ {
+ FREE(data);
+ return err;
+ }
+
+ if (res_data)
+ {
+ data[size] = 0;
+ *res_data = data;
+ }
+ else
+ FREE(data);
+
+ if (res_size)
+ *res_size = size;
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SaveFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ const void * data, // data to write
+ uint data_size, // size of 'data'
+ FileAttrib_t * fatt // not NULL: set timestamps using this attrib
+)
+{
+ DASSERT(data);
+
+ File_t F;
+ enumError err = OpenWriteFile(&F,true,path1,path2,file_mode,data,data_size);
+ if ( err == ERR_OK )
+ {
+ if (fatt)
+ memcpy(&F.fatt,fatt,sizeof(F.fatt));
+ err = ResetFile(&F,true);
+ }
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError OpenWriteFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ const void * data, // data to write
+ uint data_size // size of 'data'
+)
+{
+ DASSERT(f);
+ DASSERT(data);
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ TRACE("SaveFile(%s) %s\n",GetFileModeStatus(0,0,file_mode,0),path);
+
+ enumError err = CreateFile(f,true,path,file_mode);
+ if ( err || !f->f )
+ {
+ ResetFile(f,0);
+ return err;
+ }
+
+ const uint written = fwrite(data,1,data_size,f->f);
+ if ( written != data_size )
+ {
+ ERROR1(ERR_WRITE_FAILED,"Write to file failed: %s\n",path);
+ ResetFile(f,0);
+ return ERR_WRITE_FAILED;
+ }
+
+ return ERR_OK;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static int CompareInt ( const int * a, const int *b )
+{
+ DASSERT( a && b );
+ return *a < *b ? -1 : *a > *b;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CloseAllExcept
+(
+ int *list, // exception list of unordered FD, max contain -1
+ uint n_list, // number of elements in 'list'
+ int min, // close all until and including this fd
+ int add // if a FD was found, incremet 'min' to 'fd+add'
+)
+{
+ //--- use a private copy of the except list
+
+ enum { LOCAL_LIST_SIZE = 1000 };
+ int local_list[LOCAL_LIST_SIZE];
+ int *except_list = n_list < LOCAL_LIST_SIZE
+ ? local_list : MALLOC((n_list+1)*sizeof(*except_list));
+
+ int *ptr = except_list;
+ while ( n_list-- > 0 )
+ {
+ const int fd = *list++;
+ if ( fd != -1 )
+ *ptr++ = fd;
+ }
+ *ptr = -1;
+
+ //--- ... and sort it
+
+ n_list = ptr - except_list;
+ if ( n_list > 1 )
+ qsort(except_list,n_list,sizeof(*except_list),(qsort_func)CompareInt);
+
+
+ //--- adjust 'min' and 'add'
+
+ if ( add <= 0 )
+ add = 1;
+ const int temp = ( n_list ? except_list[n_list-1] : 0 ) + add;
+ if ( min < temp )
+ min = temp;
+
+
+ //--- close files
+
+ ptr = except_list;
+ int *end = ptr + n_list;
+ int i, count = 0;
+ for ( i = 0; i <= min; i++ )
+ {
+ while ( ptr < end && *ptr < i )
+ ptr++;
+ if ( i != *ptr && !close(i) )
+ {
+ count++;
+ const int temp = i + add;
+ if ( min < temp )
+ min = temp;
+ //printf("closed: %d/%d\n",i,min);
+ }
+ }
+
+ if ( except_list != local_list )
+ FREE(local_list);
+ return count;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CopyFile*(), TransferFile() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#undef LOGPRINT
+//#define LOGPRINT PRINT
+#define LOGPRINT noPRINT
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsSameFile ( ccp path1, ccp path2 )
+{
+ if (!strcmp(path1,path2))
+ return true;
+
+ struct stat st1, st2;
+ if ( stat(path1,&st1) || stat(path2,&st2) )
+ return false;
+
+ return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CopyFileHelper
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ int open_flags, // flags for open()
+ mode_t open_mode, // mode for open()
+ uint temp_and_move // >0: ignore open_flags,
+ // create temp, then move temp to dest
+)
+{
+ LOGPRINT("CopyFileHelper(%x,%x,%d) %s -> %s\n",
+ open_flags, open_mode, temp_and_move>0, src, dest );
+
+ int fd_dest, fd_src;
+ char buf[16*1024], temp[PATH_MAX];
+
+ if ( !dest || !src || !*dest || !*src )
+ return ERR_MISSING_PARAM;
+
+ if (IsSameFile(src,dest))
+ return ERR_OK;
+
+ fd_src = open(src,O_RDONLY);
+ if ( fd_src < 0 )
+ return ERR_CANT_OPEN;
+
+ if (temp_and_move)
+ {
+ StringCat2S(temp,sizeof(temp),dest,"XXXXXX");
+ fd_dest = mkstemp(temp);
+ LOGPRINT("CopyFileHelper(TEMP) %s -> %d\n",temp,fd_dest);
+ }
+ else
+ fd_dest = open(dest,open_mode,open_mode);
+ if ( fd_dest < 0 )
+ goto error;
+
+ ssize_t nread;
+ while ( nread = read(fd_src,buf,sizeof(buf)), nread > 0 )
+ {
+ char *ptr = buf;
+ do
+ {
+ ssize_t nwritten = write(fd_dest,ptr,nread);
+
+ if ( nwritten >= 0 )
+ {
+ nread -= nwritten;
+ ptr += nwritten;
+ }
+ else if ( errno != EINTR )
+ goto error;
+ }
+ while (nread > 0);
+ }
+
+ if ( nread == 0 )
+ {
+ if ( close(fd_dest) < 0 )
+ {
+ fd_dest = -1;
+ goto error;
+ }
+ if (temp_and_move)
+ {
+ chmod(temp,open_mode);
+ if (rename(temp,dest))
+ {
+ unlink(temp);
+ goto error;
+ }
+ }
+ close(fd_src);
+ return ERR_OK;
+ }
+
+ error:;
+ int saved_errno = errno;
+
+ close(fd_src);
+ if ( fd_dest >= 0 )
+ {
+ close(fd_dest);
+ if (temp_and_move)
+ unlink(temp);
+ }
+
+ errno = saved_errno;
+ return ERR_WRITE_FAILED;
+}
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFile
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+)
+{
+ return CopyFileHelper(src,dest,O_WRONLY|O_TRUNC|O_EXCL,open_mode,false);
+}
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFileCreate
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+)
+{
+ return CopyFileHelper(src,dest,O_WRONLY|O_CREAT|O_EXCL,open_mode,false);
+}
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFileTemp
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+)
+{
+ return CopyFileHelper(src,dest,0,open_mode,true);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError TransferFile
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ TransferMode_t tfer_mode, // transfer mode
+ mode_t open_mode // mode for CopyFile*() -> open()
+)
+{
+ if ( !dest || !src || !*dest || !*src )
+ return ERR_MISSING_PARAM;
+
+ if (IsSameFile(src,dest))
+ return ERR_OK;
+
+ if ( tfer_mode & TFMD_J_MOVE1 )
+ {
+ LOGPRINT("TransferFile(MOVE1) %s -> %s\n",src,dest);
+ struct stat st;
+ if ( !stat(src,&st) && st.st_nlink == 1 && !rename(src,dest) )
+ return true;
+ }
+ else if ( tfer_mode & TFMD_J_MOVE )
+ {
+ LOGPRINT("TransferFile(MOVE) %s -> %s\n",src,dest);
+ if (!rename(src,dest))
+ return true;
+ }
+
+ if ( tfer_mode & TFMD_J_LINK )
+ {
+ LOGPRINT("TransferFile(RM_DEST) %s\n",dest);
+ if ( tfer_mode & TFMD_J_RM_DEST )
+ unlink(dest);
+
+ LOGPRINT("TransferFile(LINK) %s -> %s\n",src,dest);
+ if (!link(src,dest))
+ return true;
+ }
+
+ if ( tfer_mode & TFMD_J_COPY )
+ {
+ LOGPRINT("TransferFile(COPY) %s -> %s\n",src,dest);
+ enumError err = tfer_mode & TFMD_J_RM_DEST
+ ? CopyFileTemp(src,dest,open_mode)
+ : CopyFile(src,dest,open_mode);
+ if ( err == ERR_OK )
+ {
+ if ( tfer_mode & TFMD_J_RM_SRC )
+ {
+ PRINT("TransferFile(RM_SRC) %s",src);
+ unlink(src);
+ }
+ return true;
+ }
+ }
+
+ return ERR_ERROR;
+}
+
+#undef LOGPRINT
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MemFile_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsValidMemFile ( MemFile_t *mf )
+{
+ return mf
+ && ( mf->data || !mf->size )
+ && mf->size >= mf->fend
+ && mf->fend >= mf->fpos;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpMemFile
+(
+ FILE *f, // valid output stream
+ int indent, // indent of output
+ MemFile_t *mf // valid MemFile_t data
+)
+{
+ DASSERT(f);
+ DASSERT(mf);
+
+ indent = NormalizeIndent(indent);
+ fprintf(f,"%*sfpos=0x%x/0x%x, size=0x%x/0x%x, zero=%d, valid=%d\n",
+ indent,"",
+ mf->fpos, mf->fend, mf->size, mf->max_size,
+ mf->zero_extend, IsValidMemFile(mf) );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool AssertMemFile
+(
+ MemFile_t *mf // valid MemFile_t data
+)
+{
+ if (IsValidMemFile(mf))
+ return true;
+
+ if (mf)
+ DumpMemFile(stderr,0,mf);
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u8 * GetDataMemFile
+(
+ // return NULL on err or a pointer to the data posisiton.
+
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to read from
+ uint size // needed data size
+)
+{
+ DASSERT(mf);
+ uint end = fpos + size;
+ if ( end < fpos || end < size )
+ return 0; // integer overflow
+
+ if ( end > mf->max_size )
+ return 0; // too large
+
+ if ( end > mf->size )
+ {
+ // grow buffer
+ uint new_size = ALIGN32( end + end/10 + 0x1000, 0x1000 );
+ if ( new_size > mf->max_size )
+ new_size = mf->max_size;
+ DASSERT( new_size >= end );
+ mf->data = REALLOC(mf->data,new_size);
+ DASSERT(mf->data);
+ memset(mf->data+mf->size,0,new_size-mf->size);
+ mf->size = new_size;
+ }
+ DASSERT( end <= mf->size );
+
+ mf->fpos = fpos;
+ if ( mf->fend < end )
+ mf->fend = end;
+ DASSERT(AssertMemFile(mf));
+
+ return mf->data + fpos;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeMemFile
+(
+ MemFile_t *mf, // structure to initialize
+ uint start_size, // >0: alloc this for data
+ uint max_size // >0: file never grows above 'max_size'
+)
+{
+ DASSERT(mf);
+ memset(mf,0,sizeof(*mf));
+ mf->max_size = max_size ? max_size : M1(mf->max_size);
+
+ if ( start_size > 0 )
+ {
+ GetDataMemFile( mf, 0, start_size < mf->max_size ? start_size : mf->max_size );
+ mf->fend = 0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetMemFile
+(
+ MemFile_t *mf // valid MemFile_t data
+)
+{
+ DASSERT(mf);
+ FREE(mf->data);
+ if (mf->err_name_alloced)
+ FreeString(mf->err_name);
+ InitializeMemFile(mf,0,mf->max_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError WriteMemFileAt
+(
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to write
+ const void *data, // data to write
+ uint size // size to write
+)
+{
+ DASSERT(mf);
+ DASSERT(data||!size);
+
+ u8 *dest = GetDataMemFile(mf,fpos,size);
+ if (!dest)
+ {
+ if (mf->err_name)
+ ERROR0(ERR_WRITE_FAILED,"Memory file is full: %s\n",mf->err_name);
+ return ERR_WRITE_FAILED;
+ }
+
+ mf->eof = false;
+ if (size)
+ memcpy(dest,data,size);
+ mf->fpos = fpos + size;
+ if ( mf->fend < mf->fpos )
+ mf->fend = mf->fpos;
+
+ DASSERT(AssertMemFile(mf));
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ReadMemFileAt
+(
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to read from
+ void *buf, // destination buffer
+ uint size // size to read
+)
+{
+ DASSERT(mf);
+ DASSERT(buf);
+
+ uint read_count = 0;
+ u8 *dest = buf;
+ if ( fpos < mf->fend )
+ {
+ read_count = mf->fend - fpos;
+ if ( read_count > size )
+ read_count = size;
+ memcpy(dest,mf->data+fpos,read_count);
+ mf->fpos = fpos + read_count;
+ dest += read_count;
+ size -= read_count;
+ }
+
+ mf->eof = size > 0;
+ if ( mf->eof && mf->zero_extend )
+ {
+ memset(dest,0,size);
+ mf->fpos += size;
+ read_count += size;
+ }
+
+ return read_count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadMemFile
+(
+ MemFile_t *mf, // MemFile_t data
+ bool init_mf, // true: initalize 'mf' first
+
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // >0: skip num of bytes before reading
+ size_t size, // >0: max size to read; 0: read all (remaining)
+ bool silent // true: suppress error messages
+)
+{
+ DASSERT(mf);
+
+ if (init_mf)
+ InitializeMemFile(mf,0,0);
+ else
+ ResetMemFile(mf);
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ PRINT("LoadMemFile(silent=%d) %s\n",silent,path);
+
+ struct stat st;
+ if (stat(path,&st))
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't get file status: %s\n",path);
+ return ERR_CANT_OPEN;
+ }
+ UseFileAttrib(&mf->fatt,0,&st,0);
+
+ if ( skip >= st.st_size )
+ return ERR_OK;
+
+ const uint max_size = st.st_size - skip;
+ if ( !size || size > max_size )
+ size = max_size;
+
+ u8 *dest = GetDataMemFile(mf,0,size);
+ if (!dest)
+ {
+ if (!silent)
+ ERROR0(ERR_READ_FAILED,"Memory file is too small: %s\n",path);
+ return ERR_READ_FAILED;
+ }
+
+ FILE * f = fopen(path,"rb");
+ if (!f)
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",path);
+ return ERR_CANT_OPEN;
+ }
+
+ if ( skip > 0 )
+ fseek(f,skip,SEEK_SET);
+
+ mf->fend = fread(dest,1,size,f);
+ fclose(f);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SaveMemFile
+(
+ MemFile_t *mf, // MemFile_t data
+
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t fmode, // mode for file creation
+ bool sparse // true: enable sparse support
+)
+{
+ DASSERT(mf);
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ PRINT("SaveMemFile(fmode=0x%x,sparse=%d) %u/%u/%u %s\n",
+ fmode, sparse, mf->fpos, mf->fend, mf->size, path );
+
+ File_t F;
+ enumError err = CreateFile(&F,true,path,fmode);
+ if ( err || !F.f )
+ {
+ ResetFile(&F,0);
+ return err;
+ }
+
+ if (!sparse)
+ {
+ const uint written = fwrite(mf->data,1,mf->fend,F.f);
+ if ( written != mf->fend )
+ {
+ if (!(fmode&FM_SILENT))
+ ERROR1(ERR_WRITE_FAILED,"Write to file failed: %s\n",path);
+ err_exit:
+ ResetFile(&F,0);
+ return ERR_WRITE_FAILED;
+ }
+ }
+ else
+ {
+ enum { BLOCK_SIZE = 512 };
+ char zero[BLOCK_SIZE];
+ memset(zero,0,sizeof(zero));
+
+ size_t cur_offset = 0;
+ uint off = 0;
+ while ( off < mf->fend )
+ {
+ //--- skip sparse blocks
+
+ while ( off < mf->fend )
+ {
+ uint max = mf->fend - off;
+ if ( max > BLOCK_SIZE )
+ max = BLOCK_SIZE;
+ if (memcmp(mf->data+off,zero,max))
+ break;
+ off += max;
+ }
+
+ //--- search next sparse block
+
+ uint start = off;
+ while ( off < mf->fend )
+ {
+ uint max = mf->fend - off;
+ if ( max > BLOCK_SIZE )
+ max = BLOCK_SIZE;
+ if (!memcmp(mf->data+off,zero,max))
+ break;
+ off += max;
+ }
+
+ err = WriteFileAt(&F,&cur_offset,start,mf->data+start,off-start);
+ if (err)
+ goto err_exit;
+ }
+
+ err = SetFileSize(&F,&cur_offset,mf->fend);
+ if (err)
+ goto err_exit;
+ }
+ return ResetFile(&F,true);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TraceLog_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool OpenTraceLog ( TraceLog_t *tl )
+{
+ if ( !tl || tl->level < 0 )
+ return false;
+
+ if ( !tl->log && tl->fname )
+ {
+ tl->log = fopen(tl->fname,"wb");
+ if (tl->log)
+ {
+ fcntl(fileno(tl->log),F_SETFD,FD_CLOEXEC);
+ fprintf(tl->log,"# %s, pid=%d\n",
+ PrintTimeByFormat("%F %T %z",time(0)), getpid() );
+ }
+ }
+
+ if (!tl->log)
+ {
+ tl->level = -1;
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool TraceLogText ( TraceLog_t *tl, ccp text )
+{
+ if (!OpenTraceLog(tl))
+ return false;
+
+ if (text)
+ {
+ fputs(text,tl->log);
+ fflush(tl->log);
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool TraceLogPrint ( TraceLog_t *tl, ccp format, ... )
+{
+ if (!OpenTraceLog(tl))
+ return false;
+
+ if (format)
+ {
+ va_list arg;
+ va_start(arg,format);
+ vfprintf(tl->log,format,arg);
+ va_end(arg);
+ fflush(tl->log);
+ }
+ return true;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct LineBuffer_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+#ifndef __APPLE__
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeLineBuffer ( LineBuffer_t *lb, uint max_buf_size )
+{
+ DASSERT(lb);
+ memset(lb,0,sizeof(*lb));
+ InitializeGrowBuffer(&lb->buf,max_buf_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetLineBuffer ( LineBuffer_t *lb )
+{
+ DASSERT(lb);
+ CloseLineBuffer(lb);
+ ResetGrowBuffer(&lb->buf);
+ FREE(lb->line);
+ lb->line = 0;
+ lb->seq_count++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearLineBuffer ( LineBuffer_t *lb )
+{
+ DASSERT(lb);
+ ClearGrowBuffer(&lb->buf);
+ lb->used_lines = 0;
+ lb->seq_count++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RedirectLineBuffer
+(
+ LineBuffer_t *lb, // valid line buffer
+ FILE *f, // output file, if NULL use 'lb->old_fd'
+ int max_lines // >=0: max number of redirected lines
+)
+{
+ DASSERT(lb);
+ if ( lb->buf.used && max_lines )
+ {
+ if (!f)
+ f = lb->old_fp;
+ if (f)
+ {
+ if ( max_lines < 0 )
+ fwrite(lb->buf.ptr,1,lb->buf.used,f);
+ else
+ {
+ mem_t *line;
+ uint n = GetMemListLineBuffer(lb,&line,-max_lines);
+ while ( n-- > 0 )
+ {
+ fprintf(f,"%.*s\n",line->len-1,line->ptr);
+ line++;
+ }
+//DEL fprintf(f,"==> %zd %d\n",
+//DEL line[-1].ptr + line[-1].len - (ccp)lb->buf.ptr,
+//DEL lb->buf.used );
+ }
+
+ fflush(f);
+ if ( f == stdout )
+ stdout_seq_count++;
+ }
+ }
+ ClearGrowBuffer(&lb->buf);
+ lb->used_lines = 0;
+ lb->seq_count++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+LineBuffer_t * OpenLineBuffer
+(
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ bool init_lb, // true: initialize 'lb'
+ FILE **fp_pos, // NULL or external place of the file pointer
+ uint max_lines, // max numbers of lines
+ uint max_line_size, // >0: max size of a single line
+ uint max_buf_size // >0: max total buffer size
+)
+{
+ if (!lb)
+ lb = MALLOC(sizeof(*lb));
+ else if (!init_lb)
+ ResetLineBuffer(lb);
+ InitializeLineBuffer(lb,max_buf_size);
+
+ DASSERT(lb);
+ if (fp_pos)
+ {
+ lb->old_fp = *fp_pos;
+ lb->old_fp_pos = fp_pos;
+ }
+
+ lb->max_lines = max_lines > 1 ? max_lines : 1;
+ lb->max_line_size = max_line_size;
+ lb->line = MALLOC(lb->max_lines*sizeof(*lb->line));
+
+ static cookie_io_functions_t funcs =
+ {
+ 0, // read
+ (cookie_write_function_t*)WriteLineBuffer,
+ 0, // seek
+ (cookie_close_function_t*)CloseLineBuffer
+ };
+ lb->fp = fopencookie(lb,"wb",funcs);
+ if ( fp_pos && lb->fp )
+ *fp_pos = lb->fp;
+ return lb;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CloseLineBuffer ( LineBuffer_t *lb )
+{
+ DASSERT(lb);
+ if (lb->fp)
+ {
+ if ( lb->old_fp_pos && *lb->old_fp_pos == lb->fp )
+ *lb->old_fp_pos = lb->old_fp;
+ lb->fp = 0;
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ssize_t WriteLineBuffer ( LineBuffer_t *lb, ccp buf, size_t size )
+{
+ DASSERT(lb);
+
+ if ( lb->redirect > 0 )
+ {
+ if (lb->old_fp)
+ {
+ ssize_t res = fwrite(buf,1,size,lb->old_fp);
+ fflush(lb->old_fp);
+ if ( lb->old_fp == stdout )
+ stdout_seq_count++;
+ return res;
+ }
+ }
+ else if (size)
+ {
+ if ( lb->open_line && lb->buf.used )
+ lb->buf.used--;
+
+ if ( lb->buf.max_size )
+ {
+ const int max_used = lb->buf.max_size - size - 1;
+ if ( max_used <= 0 )
+ {
+ ClearGrowBuffer(&lb->buf);
+ buf -= max_used;
+ size += max_used;
+ }
+ else if ( lb->buf.used > max_used )
+ {
+ u8 *ptr = lb->buf.ptr;
+ u8 *end = ptr + lb->buf.used;
+ ptr += lb->buf.used - max_used;
+ while ( ptr < end )
+ if ( *ptr++ == '\n' )
+ break;
+ DropGrowBuffer(&lb->buf,ptr-lb->buf.ptr);
+ }
+ }
+
+ uint new_lines = 0;
+ ccp ptr = buf, end = ptr + size;
+ while ( ptr < end )
+ if ( *ptr++ == '\n' )
+ new_lines++;
+ lb->line_count += new_lines;
+
+ InsertGrowBuffer(&lb->buf,buf,size);
+ lb->open_line = buf[size-1] != '\n';
+ if (lb->open_line)
+ InsertCharGrowBuffer(&lb->buf,'\n');
+
+ FixMemListLineBuffer(lb,true);
+ lb->seq_count++;
+ PRINT("WRITE %3zu bytes, is_open: %d, total lines: %d\n",
+ size, lb->open_line, lb->used_lines );
+ }
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void FixMemListLineBuffer ( LineBuffer_t *lb, bool force )
+{
+ DASSERT(lb);
+ if (!lb->buf.used)
+ lb->used_lines = 0;
+ else if ( force
+ || lb->prev_buf_ptr != lb->buf.ptr
+ || lb->prev_seq_count != lb->seq_count )
+ {
+ u8 *beg = lb->buf.ptr;
+ u8 *ptr = beg + lb->buf.used;
+
+ mem_t *dest = lb->line + lb->max_lines;
+ while ( beg < ptr && dest > lb->line )
+ {
+ u8* end = ptr--;
+ while ( beg < ptr && ptr[-1] != '\n' )
+ ptr--;
+
+ dest--;
+ dest->ptr = (ccp)ptr;
+ dest->len = end - ptr;
+ }
+
+ lb->used_lines = lb->line + lb->max_lines - dest;
+ if ( dest > lb->line )
+ memmove(lb->line,dest,lb->used_lines*sizeof(*dest));
+
+ if ( ptr > beg )
+ {
+ // don't use DropGrowBuffer() to hold buf stable
+ lb->buf.used -= ptr - beg;
+ lb->buf.ptr = ptr;
+ }
+
+ lb->prev_buf_ptr = lb->buf.ptr;
+ lb->prev_seq_count = lb->seq_count;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetMemListLineBuffer
+(
+ // returns the number of elements
+
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ mem_t **res, // store pointer to first element here
+ int max_lines // !0: limit lines, <0: count from end
+)
+{
+ DASSERT(lb);
+
+ if ( lb->prev_buf_ptr != lb->buf.ptr )
+ FixMemListLineBuffer(lb,false);
+
+ uint pos = 0;
+ if (!max_lines)
+ max_lines = lb->used_lines;
+ else if ( max_lines < 0 )
+ {
+ max_lines = -max_lines;
+ if ( max_lines > lb->used_lines )
+ max_lines = lb->used_lines;
+ pos = lb->used_lines - max_lines;
+ }
+ else if ( max_lines > lb->used_lines )
+ max_lines = lb->used_lines;
+
+ if (res)
+ *res = lb->line + pos;
+ return max_lines;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintLineBuffer
+(
+ // returns number of written lines
+
+ FILE *f, // output stream
+ int indent, // indention
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ int max_lines, // !0: limit lines, <0: count from end
+ ccp open_line // not NULL: append this text for an open line
+)
+{
+ DASSERT(f);
+ DASSERT(lb);
+ indent = NormalizeIndent(indent);
+
+ mem_t *line;
+ uint i, n = GetMemListLineBuffer(lb,&line,max_lines);
+ for ( i = 1; i <= n; i++, line++ )
+ fprintf(f,"%*s%.*s%s\n",
+ indent,"",
+ line->len-1, line->ptr,
+ open_line && i == n && lb->open_line ? open_line : "" );
+ return n;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#endif // !__APPLE__
+///////////////////////////////////////////////////////////////////////////////
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// file helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int IsDirectory
+(
+ // analyse filename (last char == '/') and stat(fname)
+
+ ccp fname, // NULL or path
+ int answer_if_empty // answer, if !fname || !*fname
+)
+{
+ if ( !fname || !*fname )
+ return answer_if_empty;
+
+ if ( *fname == '-' && !fname[1] )
+ return false;
+
+ if ( fname[strlen(fname)-1] == '/' )
+ return true;
+
+ struct stat st;
+ return !stat(fname,&st) && S_ISDIR(st.st_mode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ExistDirectory
+(
+ // check only real file (stat(fname))
+
+ ccp fname, // NULL or path
+ int answer_if_empty // answer, if !fname || !*fname
+)
+{
+ if ( !fname || !*fname )
+ return answer_if_empty;
+
+ if ( *fname == '-' && !fname[1] )
+ return false;
+
+ struct stat st;
+ return !stat(fname,&st) && S_ISDIR(st.st_mode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsSameFilename ( ccp fn1, ccp fn2 )
+{
+ if ( !fn1 || !fn2 )
+ return false;
+
+ while ( fn1[0] == '.' && fn1[1] == '/' )
+ fn1 += 2;
+ while ( fn2[0] == '.' && fn2[1] == '/' )
+ fn2 += 2;
+
+ return !strcmp(fn1,fn2);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool enable_config_search_log = false;
+
+ccp FindConfigFile
+(
+ // returns NULL or found file (alloced)
+
+ FindConfigFile_t mode, // bit field: select search destinations
+ ccp share_name, // NULL or <SHARE>
+ ccp file_name, // NULL or <FILENAME>
+ ccp prog_ext // <EXT>, if NULL use '.' + <FILENAME>
+)
+{
+ struct stat st;
+ char path[PATH_MAX];
+
+ //--- empty strings like NULL
+
+ if ( share_name && !*share_name ) share_name = progname;
+ if ( file_name && !*file_name ) file_name = 0;
+ if ( prog_ext && !*prog_ext ) prog_ext = 0;
+
+ if ( file_name && share_name )
+ {
+ //--- the first attemps need FILENAME
+
+ if ( mode & FCF_HOME )
+ {
+ ccp home = getenv("HOME");
+ if ( home && *home )
+ {
+ snprintf(path,sizeof(path),"%s/.%s/%s",home,share_name,file_name);
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+
+ if ( mode & FCF_REL_SHARE )
+ {
+ ccp pdir = ProgramDirectory();
+ if (pdir)
+ {
+ snprintf(path,sizeof(path),"%s/../share/%s/%s",pdir,share_name,file_name );
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+
+ if ( mode & FCF_LOC_SHARE )
+ {
+ snprintf(path,sizeof(path),"/usr/local/share/%s/%s",share_name,file_name);
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+
+ if ( mode & FCF_USR_SHARE )
+ {
+ snprintf(path,sizeof(path),"/usr/share/%s/%s",share_name,file_name);
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+
+ if ( mode & (FCF_PROG_SHARE|FCF_PROG) && ( prog_ext || file_name ) )
+ {
+ ccp ppath = ProgramPathNoExt();
+ if (ppath)
+ {
+ if ( mode & FCF_PROG_SHARE )
+ {
+ ccp pdir = ProgramDirectory();
+ noPRINT("pdir=%s\n",pdir);
+ if (pdir)
+ {
+ ccp fname = strrchr(ppath,'/');
+ fname = fname ? fname+1 : ppath;
+
+ if (prog_ext)
+ snprintf(path,sizeof(path),"%s/share/%s%s",
+ pdir, fname, prog_ext );
+ else
+ snprintf(path,sizeof(path),"%s/share/%s.%s",
+ pdir, fname, file_name );
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+
+ if ( mode & FCF_PROG )
+ {
+ if (prog_ext)
+ snprintf(path,sizeof(path),"%s%s",ppath,prog_ext);
+ else
+ snprintf(path,sizeof(path),"%s.%s",ppath,file_name);
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SearchFiles
+(
+ ccp path1, // not NULL: part #1 of base path
+ ccp path2, // not NULL: part #2 of base path
+ ccp match, // not NULL: filter files by MatchPattern()
+ // if empty: extract from combined path
+ SearchFilesFunc func, // callback function, never NULL
+ void *param // third parameter of func()
+)
+{
+ DASSERT(func);
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+
+ if ( match && !*match )
+ {
+ match = 0;
+ if ( path != pathbuf )
+ {
+ // path is modified!
+ StringCopyS(pathbuf,sizeof(pathbuf),path);
+ path = pathbuf;
+ }
+
+ char *found = strrchr(pathbuf,'/');
+ if (found)
+ {
+ if ( found == pathbuf )
+ path = "/";
+ *found++ = 0;
+ match = *found ? found : 0;
+ }
+ else
+ {
+ match = pathbuf;
+ path = ".";
+ }
+ }
+
+ if ( !path || !*path )
+ path = ".";
+
+ DIR *fdir = opendir(path);
+ int stat = errno;
+
+ if (fdir)
+ {
+ for(;;)
+ {
+ struct dirent *dent = readdir(fdir);
+ if (!dent)
+ break;
+
+ if ( !match || MatchPatternFull(match,dent->d_name) )
+ {
+ stat = func(path,dent,param);
+ if ( stat < 0 )
+ break;
+ }
+ }
+ closedir(fdir);
+ }
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CreatePath
+(
+ ccp path, // path to create
+ bool is_pure_dir // true: 'path' don't contains a filename part
+)
+{
+ TRACE("CreatePath(%s)\n",path);
+
+ char buf[PATH_MAX];
+ char *dest = StringCopyS(buf,sizeof(buf)-1,path);
+ if (is_pure_dir)
+ {
+ *dest++ = '/';
+ *dest = 0;
+ }
+ dest = buf;
+
+ for(;;)
+ {
+ // skip slashes
+ while ( *dest == '/' )
+ dest++;
+
+ // search end of current directory
+ while ( *dest && *dest != '/' )
+ dest++;
+ if (!*dest)
+ break;
+
+ *dest = 0;
+ if ( mkdir(buf,0777) && errno != EEXIST && !IsDirectory(buf,0) )
+ {
+ noTRACE("CREATE-DIR: %s -> err=%d (ENOTDIR=%d)\n",buf,errno,ENOTDIR);
+ if ( errno == ENOTDIR )
+ {
+ while ( dest > buf && *dest != '/' )
+ dest--;
+ if ( dest > buf )
+ *dest = 0;
+ }
+ return ERROR1( ERR_CANT_CREATE_DIR,
+ errno == ENOTDIR
+ ? "Not a directory: %s\n"
+ : "Can't create directory: %s\n", buf );
+ }
+ TRACE("CREATE-DIR: %s -> OK\n",buf);
+ *dest++ = '/';
+ }
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RemoveSource
+(
+ ccp fname, // file to remove
+ ccp dest_fname, // NULL or dest file name
+ // If real pathes are same: don't remove
+ bool print_log, // true: print a log message
+ bool testmode // true: don't remove, log only
+)
+{
+ DASSERT(fname);
+ if (dest_fname)
+ {
+ if (!strcmp(fname,dest_fname))
+ return ERR_OK;
+
+ char p1[PATH_MAX], p2[PATH_MAX];
+ if ( realpath(fname,p1) && realpath(dest_fname,p2) && !strcmp(p1,p2) )
+ return ERR_OK;
+ }
+
+ if ( print_log || testmode )
+ fprintf( stdlog ? stdlog : stdout,
+ "%sREMOVE %s\n",
+ testmode ? "WOULD " : "", fname );
+
+ return !testmode && unlink(fname)
+ ? ERROR1( ERR_CANT_REMOVE, "Can't remove source file: %s\n", fname )
+ : ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * FindFilename
+(
+ // Find the last part of the path, trailing '/' are ignored
+ // return poiner to found string
+
+ ccp path, // path to analyze, valid string
+ uint * result_len // not NULL: return length of found string
+)
+{
+ DASSERT(path);
+
+ ccp end = path + strlen(path);
+ if ( end > path && end[-1] == '/' )
+ end--;
+
+ ccp start = end;
+ while ( start > path && start[-1] != '/' )
+ start--;
+ if (result_len)
+ *result_len = end - start;
+ return (char*)start;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint NumberedFilename
+(
+ // return used index number or 0, if not modified.
+
+ char * buf, // destination buffer
+ size_t bufsize, // size of 'buf'
+ ccp source, // source filename, may be part of 'buf'
+ // if NULL or empty:
+ ccp ext, // not NULL & not empty: file extension ('.ext')
+ uint ext_mode, // only relevant, if 'ext' is neither NULL not empty
+ // 0: use 'ext' only for detection
+ // 1: replace 'source' extension by 'ext'
+ // 2: append 'ext' if 'source' differ
+ // 3: append 'ext' always
+ bool detect_stdio // true: don't number "-"
+)
+{
+ //--- analyse stdin/stdout
+
+ char *end = StringCopyS(buf,bufsize,source);
+ if ( !source || !*source || detect_stdio && *source == '-' && !source[1] )
+ return 0;
+
+
+ //--- analyse parameter 'ext'
+
+ uint ext_len = 0;
+ if ( ext && *ext )
+ {
+ ext_len = strlen(ext);
+ if ( ext_mode >= 3 )
+ {
+ // append extension always
+ StringCopyE(end,buf+bufsize,ext);
+ }
+ else if ( end - buf <= ext_len || memcmp(end-ext_len,ext,ext_len) )
+ {
+ // extensions differ
+ if (!ext_mode)
+ ext_len = 0;
+ else if ( ext_mode == 2 )
+ StringCopyE(end,buf+bufsize,ext);
+ else
+ {
+ char *slash = strrchr(buf,'/');
+ char *point = strrchr(buf,'.');
+ if ( point && ( !slash || point > slash ) )
+ end = point;
+ StringCopyE(end,buf+bufsize,ext);
+ }
+ }
+ }
+
+
+ //--- does files exist? => if not: return without numbering
+
+ struct stat st;
+ if (stat(buf,&st))
+ return 0;
+
+
+
+ //--- split filename into dir + name + index + ext
+
+ char temp[PATH_MAX];
+
+ ccp dir = buf, search_dir;
+ uint dir_len;
+ ccp name = strrchr(buf,'/');
+ if (name)
+ {
+ dir_len = name++ - dir;
+ StringCopyS(temp,sizeof(temp),buf);
+ if ( dir_len < sizeof(temp) )
+ temp[dir_len] = 0;
+ search_dir = temp;
+ dir_len++;
+ }
+ else
+ {
+ name = buf;
+ dir_len = 0;
+ dir = "";
+ search_dir = ".";
+ }
+
+ uint name_len;
+ if (ext_len)
+ {
+ name_len = strlen(name);
+ if ( ext_len >= name_len )
+ goto find_ext;
+
+ name_len -= ext_len;
+ ext = name + name_len;
+ }
+ else
+ {
+ find_ext:
+ ext = strrchr(name,'.');
+ if ( ext && ext > name )
+ {
+ name_len = ext - name;
+ ext_len = strlen(ext);
+ }
+ else
+ {
+ name_len = strlen(name);
+ ext_len = 0;
+ ext = "";
+ }
+ }
+
+ ccp ptr = name + name_len - 1;
+ while ( ptr >= name && *ptr >= '0' && *ptr <= '9' )
+ ptr--;
+ name_len = ++ptr - name;
+ uint index = strtoul(ptr,0,10);
+
+ PRINT("|%s| : |%.*s|%.*s|#%u|%.*s|\n",
+ search_dir, dir_len, dir, name_len, name, index, ext_len, ext );
+
+
+ //--- iterate directory to find highest index
+
+ DIR *fdir = opendir(search_dir);
+ if (fdir)
+ {
+ for(;;)
+ {
+ struct dirent *dent = readdir(fdir);
+ if (!dent)
+ break;
+
+ ccp fname = dent->d_name;
+ const uint fname_len = strlen(fname);
+ if ( fname_len > name_len + ext_len
+ && !strncasecmp(name,fname,name_len) )
+ {
+ ccp fext = fname + fname_len - ext_len;
+ if (!strncasecmp(ext,fext,ext_len))
+ {
+ char *end;
+ uint idx = strtoul(fname+name_len,&end,10);
+ if ( end == fext && idx > index )
+ index = idx;
+ }
+ }
+ }
+ closedir(fdir);
+ }
+
+
+ //--- create new filename
+
+ index++;
+ snprintf(temp,sizeof(temp),"%.*s%u%s",name_len,name,index,ext);
+ StringCopyE(buf+dir_len,buf+bufsize,temp);
+
+ return index;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// normalize filenames ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * NormalizeFileName
+(
+ // returns a pointer to the NULL terminator within 'buf'
+
+ char * buf, // valid destination buffer
+ uint buf_size, // size of buf
+ ccp source, // NULL or source
+ bool allow_slash, // true: allow '/' in source
+ bool is_utf8 // true: enter UTF-8 mode
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>1);
+
+ char *dest = buf, *end = buf + buf_size - 1;
+ TRACE("NormalizeFileName(%s,%d)\n",source,allow_slash);
+
+ if (source)
+ {
+ #ifdef __CYGWIN__
+ if (allow_slash)
+ {
+ const int drv_len = IsWindowsDriveSpec(source);
+ if (drv_len)
+ {
+ dest = StringCopyE(dest,end,"/cygdrive/c/");
+ if ( dest < end )
+ dest[-2] = tolower((int)*source);
+ source += drv_len;
+ }
+ }
+ #endif
+
+ bool skip_space = true;
+ while ( *source && dest < end )
+ {
+ unsigned char ch = *source++;
+ if ( ch == ':' )
+ {
+ if (!skip_space)
+ *dest++ = ' ';
+ if ( dest + 2 <= end )
+ {
+ *dest++ = '-';
+ *dest++ = ' ';
+ }
+ skip_space = true;
+ }
+ else
+ {
+ if ( isalnum(ch)
+ || ( is_utf8
+ ? ch >= 0x80
+ : ch == 0xe4 // ä
+ || ch == 0xf6 // ö
+ || ch == 0xfc // ü
+ || ch == 0xdf // ß
+ || ch == 0xc4 // Ä
+ || ch == 0xd6 // Ö
+ || ch == 0xdc // Ü
+ )
+ || strchr("_+-=%'\"$%&,.!()[]{}<>",ch)
+ || ch == '/' && allow_slash )
+ {
+ *dest++ = ch;
+ skip_space = false;
+ }
+ #ifdef __CYGWIN__
+ else if ( ch == '\\' && allow_slash )
+ {
+ *dest++ = '/';
+ skip_space = false;
+ }
+ #endif
+ else if (!skip_space)
+ {
+ *dest++ = ' ';
+ skip_space = true;
+ }
+ }
+ }
+ }
+ if ( dest > buf && dest[-1] == ' ' )
+ dest--;
+
+ DASSERT( dest <= end );
+ *dest = 0;
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __CYGWIN__
+
+ uint IsWindowsDriveSpec
+ (
+ // returns the length of the found windows drive specification (0|2|3)
+
+ ccp src // valid string
+ )
+ {
+ DASSERT(src);
+
+ if ( ( *src >= 'a' && *src <= 'z' || *src >= 'A' && *src <= 'Z' )
+ && src[1] == ':' )
+ {
+ if (!src[2])
+ return 2;
+
+ if ( src[2] == '/' || src[2] == '\\' )
+ return 3;
+ }
+ return 0;
+ }
+
+#endif // __CYGWIN__
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __CYGWIN__
+
+ uint NormalizeFilenameCygwin
+ (
+ // returns a pointer to the NULL terminator within 'buf'
+
+ char * buf, // valid destination buffer
+ uint buf_size, // size of buf
+ ccp src // NULL or source
+ )
+ {
+ static char prefix[] = "/cygdrive/";
+
+ if ( buf_size < sizeof(prefix) + 5 || !src )
+ {
+ *buf = 0;
+ return 0;
+ }
+
+ char * end = buf + buf_size - 1;
+ char * dest = buf;
+
+ if ( ( *src >= 'a' && *src <= 'z' || *src >= 'A' && *src <= 'Z' )
+ && src[1] == ':'
+ && ( src[2] == 0 || src[2] == '/' || src[2] == '\\' ))
+ {
+ memcpy(buf,prefix,sizeof(prefix));
+ dest = buf + sizeof(prefix)-1;
+ *dest++ = tolower((int)*src); // cygwin needs the '(int)'
+ *dest = 0;
+ if (IsDirectory(buf,false))
+ {
+ *dest++ = '/';
+ src += 2;
+ if (*src)
+ src++;
+ }
+ else
+ dest = buf;
+ }
+ ASSERT( dest < buf + buf_size );
+
+ while ( dest < end && *src )
+ if ( *src == '\\' )
+ {
+ *dest++ = '/';
+ src++;
+ }
+ else
+ *dest++ = *src++;
+
+ *dest = 0;
+ ASSERT( dest < buf + buf_size );
+ return dest - buf;
+ }
+
+#endif // __CYGWIN__
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __CYGWIN__
+
+ char * AllocNormalizedFilenameCygwin
+ (
+ // returns an alloced buffer with the normalized filename
+
+ ccp source // NULL or string
+ )
+ {
+ char buf[PATH_MAX];
+ const uint len = NormalizeFilenameCygwin(buf,sizeof(buf),source);
+ char * result = MEMDUP(buf,len); // MEMDUP allocs +1 byte and set it to NULL
+ DASSERT(buf[len]==0);
+ return result;
+ }
+
+#endif // __CYGWIN__
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// FDList_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearFDList ( FDList_t *fdl )
+{
+ DASSERT(fdl);
+
+ fdl->max_fd = -1;
+ FD_ZERO(&fdl->readfds);
+ FD_ZERO(&fdl->writefds);
+ FD_ZERO(&fdl->exceptfds);
+
+ fdl->poll_used = 0;
+ fdl->n_sock = 0;
+
+ fdl->now_usec = GetTimeUSec(false);
+ fdl->timeout_usec = M1(fdl->timeout_usec);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeFDList ( FDList_t *fdl, bool use_poll )
+{
+ DASSERT(fdl);
+ memset(fdl,0,sizeof(*fdl));
+ fdl->use_poll = use_poll;
+ ClearFDList(fdl);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetFDList ( FDList_t *fdl )
+{
+ DASSERT(fdl);
+ FREE(fdl->poll_list);
+ InitializeFDList(fdl,fdl->use_poll);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct pollfd * AllocFDList ( FDList_t *fdl, uint n )
+{
+ DASSERT(fdl);
+ uint min_size = fdl->poll_used + n;
+ if ( fdl->poll_size < min_size )
+ {
+ fdl->poll_size = ( min_size/0x18 + 2 ) * 0x20;
+ DASSERT( fdl->poll_size >= min_size );
+ fdl->poll_list = REALLOC(fdl->poll_list,fdl->poll_size*sizeof(*fdl->poll_list));
+ }
+ DASSERT( fdl->poll_used + n <= fdl->poll_size );
+
+ struct pollfd *ptr = fdl->poll_list + fdl->poll_used;
+ fdl->poll_used += n;
+ memset(ptr,0,n*sizeof(*ptr));
+ return ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AnnounceFDList ( FDList_t *fdl, uint n )
+{
+ if (fdl->use_poll)
+ {
+ AllocFDList(fdl,n);
+ fdl->poll_used -= n;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint AddFDList
+(
+ // returns the pool-index if available, ~0 otherwise
+
+ FDList_t *fdl, // valid socket list
+ int sock, // socket to add
+ uint events // bit field: POLLIN|POLLOUT|POLLERR
+)
+{
+ if ( sock == -1 || !(events&(POLLIN|POLLOUT|POLLERR)) )
+ return ~0;
+
+ fdl->n_sock++;
+ if ( fdl->max_fd < sock )
+ fdl->max_fd = sock;
+
+ DASSERT(fdl);
+ if (fdl->use_poll)
+ {
+ struct pollfd *pp = AllocFDList(fdl,1);
+ DASSERT(pp);
+ pp->fd = sock;
+ pp->events = events;
+ return pp - fdl->poll_list;
+ }
+
+ noPRINT("ADD-SOCK %d = %x\n",sock,events);
+ if ( events & POLLIN )
+ FD_SET(sock,&fdl->readfds);
+ if ( events & POLLOUT )
+ FD_SET(sock,&fdl->writefds);
+ if ( events & POLLERR )
+ FD_SET(sock,&fdl->exceptfds);
+ return ~0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetEventFDList
+(
+ // returns bit field: POLLIN|POLLOUT|POLLERR
+
+ FDList_t *fdl, // valid socket list
+ int sock, // socket to look for
+ uint poll_index // if use_poll: use the index for a fast search
+)
+{
+ if ( sock == -1 )
+ return 0;
+
+ DASSERT(fdl);
+ if (fdl->use_poll)
+ {
+ const struct pollfd *pp = fdl->poll_list;
+ if ( poll_index < fdl->poll_used && pp[poll_index].fd == sock )
+ return pp[poll_index].revents;
+
+ const struct pollfd *pend = pp + fdl->poll_used;
+ for ( ; pp < pend; pp++ )
+ if ( pp->fd == sock )
+ return pp->revents;
+ return 0;
+ }
+
+ uint revents = 0;
+ if (FD_ISSET(sock,&fdl->readfds))
+ revents |= POLLIN;
+ if (FD_ISSET(sock,&fdl->writefds))
+ revents |= POLLOUT;
+ if (FD_ISSET(sock,&fdl->exceptfds))
+ revents |= POLLERR;
+ return revents;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int WaitFDList
+(
+ FDList_t *fdl // valid socket list
+)
+{
+ DASSERT(fdl);
+
+ int stat;
+ u64 now_usec = GetTimeUSec(false);
+
+ if (fdl->use_poll)
+ {
+ int timeout;
+ if ( fdl->timeout_usec > now_usec )
+ {
+ u64 delta = ( fdl->timeout_usec - now_usec ) / 1000;
+ timeout = delta > 0x7fffffffull ? -1 : delta;
+ }
+ else
+ timeout = 0;
+
+ stat = poll( fdl->poll_list, fdl->poll_used, timeout );
+ }
+ else
+ {
+ struct timeval tv, *ptv = &tv;
+ if ( fdl->timeout_usec > now_usec )
+ {
+ const u64 delta = fdl->timeout_usec - now_usec;
+ if ( delta > 1000000ull * 0x7fffffffull )
+ ptv = NULL;
+ else
+ {
+ tv.tv_sec = delta / 1000000;
+ tv.tv_usec = delta % 1000000;
+ }
+ }
+ else
+ tv.tv_sec = tv.tv_usec = 0;
+
+//DEL fprintf(stderr,"#SELECT %lld: ptv=%d: %lu.%06lu\n",
+//DEL fdl->timeout_usec-now_usec, ptv!=0, tv.tv_sec, tv.tv_usec );
+ stat = select( fdl->max_fd+1, &fdl->readfds, &fdl->writefds,
+ &fdl->exceptfds, ptv );
+ }
+
+ fdl->now_usec = GetTimeUSec(false);
+ fdl->cur_usec = fdl->now_usec - now_usec;
+ fdl->wait_usec += fdl->cur_usec;
+ fdl->wait_count++;
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int PWaitFDList
+(
+ FDList_t *fdl, // valid socket list
+ const sigset_t *sigmask // NULL or signal mask
+)
+{
+ DASSERT(fdl);
+
+ #ifdef __APPLE__
+ // [[2do]] ??? untested work around
+ if (fdl->use_poll)
+ {
+ sigset_t origmask;
+ if (sigmask)
+ sigprocmask(SIG_SETMASK,sigmask,&origmask);
+ const int stat = WaitFDList(fdl);
+ if (sigmask)
+ sigprocmask(SIG_SETMASK,&origmask,NULL);
+ return stat;
+ }
+ #endif
+
+ struct timespec ts, *pts = &ts;
+ u64 now_usec = GetTimeUSec(false);
+ if ( fdl->timeout_usec > now_usec )
+ {
+ const u64 delta = fdl->timeout_usec - now_usec;
+ if ( delta > 1000000ull * 0x7fffffffull )
+ pts = NULL;
+ else
+ {
+ ts.tv_sec = delta / 1000000;
+ ts.tv_nsec = delta % 1000000 * 1000;
+ }
+ }
+ else
+ ts.tv_sec = ts.tv_nsec = 0;
+
+ #ifdef __APPLE__
+ const int stat = pselect( fdl->max_fd+1, &fdl->readfds, &fdl->writefds,
+ &fdl->exceptfds, pts, sigmask );
+ #else
+ const int stat = fdl->use_poll
+ ? ppoll( fdl->poll_list, fdl->poll_used, pts, sigmask )
+ : pselect( fdl->max_fd+1, &fdl->readfds, &fdl->writefds,
+ &fdl->exceptfds, pts, sigmask );
+ #endif
+
+ fdl->now_usec = GetTimeUSec(false);
+ fdl->cur_usec = fdl->now_usec - now_usec;
+ fdl->wait_usec += fdl->cur_usec;
+ fdl->wait_count++;
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t CheckUnixSocketPathMem
+(
+ mem_t src, // NULL or source path to analyse
+ int tolerance // <1: 'unix:', 'file:', '/', './' and '../' detected
+ // 1: not 'NAME:' && at relast one '/'
+ // 2: not 'NAME:'
+)
+{
+ if ( src.ptr && src.len )
+ {
+ ccp ptr = src.ptr;
+ ccp end = ptr + src.len;
+
+ switch (*ptr)
+ {
+ case '/':
+ return src;
+
+ case '.':
+ {
+ ccp s = ptr+1;
+ if ( s < end && *s == '.' )
+ s++;
+ if ( s < end && *s == '/' )
+ return src;
+ }
+ break;
+
+ case 'f':
+ if ( end-ptr >= 5 && !memcmp(ptr,"file:",5) )
+ return RightMem(src,-5);
+
+ case 'u':
+ if ( end-ptr >= 5 && !memcmp(ptr,"unix:",5) )
+ return RightMem(src,-5);
+ break;
+ }
+
+ if ( tolerance > 0 )
+ {
+ while ( ptr < end && isalnum((int)*ptr) )
+ ptr++;
+ if ( ptr < end && *ptr != ':' && ( tolerance > 1 || strchr(ptr,'/')) )
+ return BehindMem(src,ptr);
+ }
+ }
+
+ return NullMem;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp CheckUnixSocketPath
+(
+ ccp src, // NULL or source path to analyse
+ int tolerance // <1: 'unix:', 'file:', '/', './' and '../' detected
+ // 1: not 'NAME:' && at relast one '/'
+ // 2: not 'NAME:'
+)
+{
+ mem_t res = CheckUnixSocketPathMem(MemByString(src),tolerance);
+ return res.ptr;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Catch Output ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int CatchIgnoreOutput
+(
+ struct CatchOutput_t *ctrl, // control struct incl. data
+ int call_mode // 0:init, 1:new data, 2:term
+)
+{
+ DASSERT(ctrl);
+ ClearGrowBuffer(&ctrl->buf);
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetCatchOutput ( CatchOutput_t *co, uint n )
+{
+ while ( n-- > 0 )
+ {
+ if ( co->pipe_fd[0] != -1 )
+ {
+ close(co->pipe_fd[0]);
+ co->pipe_fd[0] = -1;
+ }
+
+ if ( co->pipe_fd[1] != -1 )
+ {
+ close(co->pipe_fd[1]);
+ co->pipe_fd[1] = -1;
+ }
+
+ ResetGrowBuffer(&co->buf);
+ co++;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CatchOutput
+(
+ ccp command, // command to execute
+ int argc, // num(arguments) in 'argv'; -1: argv is NULL terminated
+ char *const* argv, // list of arguments
+
+ CatchOutputFunc stdout_func, // NULL or function to catch stdout
+ CatchOutputFunc stderr_func, // NULL or function to catch stderr
+ void *user_ptr, // NULL or user defined parameter
+ bool silent // true: suppress error messages
+)
+{
+ if ( !command || !*command )
+ return ERR_NOTHING_TO_DO;
+
+
+ //--- setup streams & pipes
+
+ uint n_catch_out = 0;
+ CatchOutput_t catch_out[2];
+ memset(catch_out,0,sizeof(catch_out));
+
+ uint i;
+ for ( i = 1; i <= 2; i++ )
+ {
+ CatchOutputFunc func = i==1 ? stdout_func : stderr_func;
+ if (!func)
+ continue;
+
+ CatchOutput_t *co = catch_out + n_catch_out++;
+ co->mode = i;
+ co->func = func;
+ co->user_ptr = user_ptr;
+ InitializeGrowBuffer(&co->buf,16*KiB);
+
+ if ( pipe(co->pipe_fd) == -1 )
+ {
+ ResetCatchOutput(catch_out,n_catch_out);
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"Can't create pipe\n");
+ return ERR_CANT_CREATE;
+ }
+ }
+
+
+ //--- setup arguments
+
+ if (!argv)
+ argc = 0;
+ else if ( argc < 0 )
+ for ( argc = 0; argv[argc]; argc++ )
+ ;
+
+ const uint local_args_size = 100;
+ char *local_args[local_args_size+2];
+ char **args = local_args;
+ if ( argc >= local_args_size )
+ args = MALLOC((argc+2)*sizeof(*args));
+
+ char **destarg = args;
+ *destarg++ = (char*)command;
+
+ for ( i = 0; i < argc; i++ )
+ *destarg++ = argv[i];
+ *destarg = 0;
+
+ #ifdef TEST
+ {
+ printf("EXEC '%s' [argc=%u]\n",command,argc);
+ uint i;
+ for ( i = 0; i < argc+2; i++ )
+ printf("%3d: %s\n",i,args[i]);
+ }
+ #endif
+
+
+ //--- fork
+
+ enumError err = ERR_OK;
+
+ int fork_stat = fork();
+ if ( fork_stat < 0 )
+ {
+ if (!silent)
+ ERROR1(ERR_ERROR,"Can't fork: %s\n",command);
+ err = ERR_ERROR;
+ }
+ else if (fork_stat)
+ {
+ PRINT("WAIT FOR %d\n",fork_stat);
+
+ CatchOutput_t *co, *co_end = catch_out + n_catch_out;
+ for ( co = catch_out; co < co_end; co++ )
+ {
+ DASSERT(co->func);
+ co->func(co,0);
+ close(co->pipe_fd[1]); // close childs (write) end
+ co->pipe_fd[1] = -1;
+ }
+
+ FDList_t fdl;
+ InitializeFDList(&fdl,false);
+ char buf[0x1000];
+
+ for(;;)
+ {
+ ClearFDList(&fdl);
+ fdl.timeout_usec = GetTimeUSec(false) + 60*USEC_PER_SEC;
+ for ( co = catch_out; co < co_end; co++ )
+ AddFDList(&fdl,co->pipe_fd[0],POLLIN);
+ if ( fdl.max_fd == -1 )
+ break;
+
+ const int stat = WaitFDList(&fdl);
+ if ( stat > 0 )
+ {
+ for ( co = catch_out; co < co_end; co++ )
+ {
+ int fd = co->pipe_fd[0];
+ uint event = GetEventFDList(&fdl,fd,0);
+ if ( event & POLLIN )
+ {
+ ssize_t read_size = read(fd,buf,sizeof(buf));
+ PRINT("READ %d[%d], %zd bytes\n",co->mode,fd,read_size);
+ if ( read_size > 0 )
+ {
+ PrepareGrowBuffer(&co->buf,read_size,true);
+ InsertGrowBuffer(&co->buf,buf,read_size);
+ if (co->func)
+ co->func(co,1);
+ }
+ else
+ {
+ close(fd);
+ co->pipe_fd[0] = -1;
+ }
+ }
+ }
+ }
+ }
+ ResetFDList(&fdl);
+
+ int wait_stat;
+ for(;;)
+ {
+ int pid = waitpid(fork_stat,&wait_stat,0);
+
+ PRINT("RESUME: %d = %x: exited=%d, wait_stat=%d [%s]\n",
+ wait_stat, wait_stat, WIFEXITED(wait_stat),
+ WEXITSTATUS(wait_stat), GetErrorName(WEXITSTATUS(wait_stat),"?") );
+ if ( pid == fork_stat )
+ break;
+ }
+
+ for ( co = catch_out; co < co_end; co++ )
+ if (co->func)
+ co->func(co,2);
+ ResetCatchOutput(catch_out,n_catch_out);
+
+ if (WIFEXITED(wait_stat))
+ return WEXITSTATUS(wait_stat);
+ if (!silent)
+ ERROR1(ERR_SYNTAX,"Child teminated with error: %s\n",command);
+ return ERR_SYNTAX;
+ }
+ else
+ {
+ PRINT("CHILD\n");
+
+ CatchOutput_t *co, *co_end = catch_out + n_catch_out;
+ for ( co = catch_out; co < co_end; co++ )
+ {
+ close(co->pipe_fd[0]); // close parents (read) end
+ co->pipe_fd[0] = -1;
+
+ if ( dup2(co->pipe_fd[1],co->mode) == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"dup2(,%d) failed: %s\n",
+ co->mode, command );
+ _exit(ERR_CANT_CREATE);
+ }
+ }
+
+ execvp(command,args);
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"Can't create process: %s\n",command);
+ _exit(ERR_CANT_CREATE);
+ }
+
+ if ( args != local_args )
+ FREE(args);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CatchOutputLine
+(
+ ccp command_line, // command line to execute
+ CatchOutputFunc stdout_func, // NULL or function to catch stdout
+ CatchOutputFunc stderr_func, // NULL or function to catch stderr
+ void *user_ptr, // NULL or user defined parameter
+ bool silent // true: suppress error messages
+)
+{
+ SplitArg_t sa;
+ ScanSplitArg(&sa,true,command_line,0,0);
+
+ enumError err = ERR_SYNTAX;
+ if ( sa.argc > 1 )
+ err = CatchOutput( sa.argv[1], sa.argc-2, sa.argv+2,
+ stdout_func, stderr_func, user_ptr, silent );
+ ResetSplitArg(&sa);
+ return err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan sections ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool FindSection
+(
+ // search file until next section found
+ // => read line and store data into 'si'
+ // => return TRUE, if section found
+
+ SectionInfo_t *si, // valid section info
+ char *buf, // use this buffer for line scanning
+ uint buf_size, // size of 'buf'
+ bool scan_buf // true: buffer contains already a valid
+ // and NULL terminated line
+)
+{
+ DASSERT(si);
+ DASSERT(si->f);
+ DASSERT(buf);
+ DASSERT( buf_size > 10 );
+
+ FreeString(si->section);
+ FreeString(si->path);
+ si->section = si->path = 0;
+ si->index = -1;
+ ResetParamField(&si->param);
+
+ buf_size--;
+ for(;;)
+ {
+ if ( !scan_buf && !fgets(buf,buf_size,si->f) )
+ break;
+ buf[buf_size] = 0;
+ scan_buf = false;
+
+ char *ptr = buf;
+ while ( *ptr > 0 && *ptr <= ' ' )
+ ptr++;
+
+ if ( *ptr != '[' )
+ continue;
+
+ ccp sect = ++ptr;
+ while ( isalnum((int)*ptr) || *ptr == '-' || *ptr == '_' )
+ ptr++;
+
+ ccp path = ptr;
+ if ( *ptr == '/' )
+ {
+ *ptr++ = 0;
+ path = ptr;
+ while ( *ptr > ' ' && *ptr != ':' && *ptr != ']' )
+ ptr++;
+ }
+
+ int index = -1;
+ if ( *ptr == ':' )
+ {
+ *ptr++ = 0;
+ index = str2ul(ptr,&ptr,10);
+ }
+
+ if ( *ptr != ']' )
+ continue;
+ *ptr = 0;
+
+ si->section = STRDUP(sect);
+ if (*path)
+ si->path = STRDUP(path);
+ si->index = index;
+ return true;
+ }
+
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanSections
+(
+ // return the last returned value by On*()
+
+ FILE *f, // source file
+ SectionFunc OnSection, // not NULL: call this function for each section
+ // on result: <0: abort, 0:next section, 1:scan param
+ SectionFunc OnParams, // not NULL: call this function after param scan
+ // on result: <0: abort, continue
+ void *user_param // user defined parameter
+
+)
+{
+ DASSERT(f);
+
+ SectionInfo_t si;
+ memset(&si,0,sizeof(si));
+ si.f = f;
+ si.user_param = user_param;
+ InitializeParamField(&si.param);
+ si.param.free_data = true;
+
+ int stat = 0;
+ char buf[10000];
+ bool scan_buf = false;
+ while (FindSection(&si,buf,sizeof(buf),scan_buf))
+ {
+ scan_buf = false;
+ if (OnSection)
+ {
+ int stat = OnSection(&si);
+ if ( stat < 0 )
+ break;
+ if (!stat)
+ continue;
+ }
+
+ if (!OnParams)
+ continue;
+
+ // scan param
+
+ while (fgets(buf,sizeof(buf)-1,f))
+ {
+ buf[sizeof(buf)-1] = 0;
+ char *ptr = buf;
+ while ( *ptr > 0 && *ptr <= ' ' )
+ ptr++;
+ if ( *ptr == '[' )
+ {
+ scan_buf = true;
+ break;
+ }
+
+ //--- scan name
+ ccp name = ptr;
+ while ( *ptr > ' ' && *ptr != '=' )
+ ptr++;
+ char *name_end = ptr;
+
+ //--- scan param
+ while ( *ptr == ' ' || *ptr == '\t' )
+ ptr++;
+ if ( *ptr != '=' )
+ continue;
+ if ( *++ptr == ' ' ) // skip max 1 space
+ ptr++;
+
+ ccp value = ptr;
+ while ( *ptr && *ptr != '\n' && *ptr != '\r' )
+ ptr++;
+
+ *name_end = *ptr = 0;
+ InsertParamField(&si.param,name,false,0,STRDUP(value));
+ }
+
+ stat = OnParams(&si);
+ if ( stat < 0 )
+ break;
+ }
+
+ FreeString(si.section);
+ FreeString(si.path);
+ ResetParamField(&si.param);
+ return stat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan configuration ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RestoreState
+(
+ const RestoreStateTab_t
+ *table, // section table, terminated by name==0
+ void *data, // file data, modified, terminated by NULL or LF
+ uint size, // size of file data
+ RestoreStateLog_t log_mode, // print warnings (bit field)
+ FILE *log_file, // error file; if NULL use stderr
+ cvp user_param // pointer provided by user
+)
+{
+ DASSERT(data||!size);
+ if (!size)
+ return ERR_OK;
+
+ if (!log_file)
+ log_file = stderr;
+
+ char *ptr = data;
+ char *end = ptr + size;
+ if ( !*end && *end != '\n' )
+ end[-1] = 0;
+
+ while ( ptr < end )
+ {
+ //--- search next section
+
+ while ( ptr < end && (uchar)*ptr <= ' ' )
+ ptr++;
+ if ( *ptr != '[' )
+ {
+ while ( ptr < end && *ptr && *ptr != '\n' )
+ ptr++;
+ if ( ptr < end )
+ ptr++;
+ continue;
+ }
+
+ ccp sect = ++ptr;
+ while ( isalnum((int)*ptr) || *ptr == '-' || *ptr == '_' )
+ ptr++;
+
+ ccp path = ptr;
+ if ( *ptr == '/' )
+ {
+ *ptr++ = 0;
+ path = ptr;
+ while ( isalnum((int)*ptr) || *ptr == '-' || *ptr == '_' || *ptr == '/' )
+ ptr++;
+ }
+
+ int index = -1;
+ if ( *ptr == ':' )
+ {
+ *ptr++ = 0;
+ index = strtoul(ptr,0,10);
+ }
+ *ptr = 0;
+
+ const RestoreStateTab_t *tab;
+ for ( tab = table; tab->name; tab++ )
+ if (!strcmp(tab->name,sect))
+ break;
+
+ if (!tab->name)
+ {
+ if ( log_mode & RSL_WARNINGS )
+ fprintf(log_file,"! #RESTORE: Unknown section: %s\n",sect);
+ continue;
+ }
+
+ TRACE("SECT: [%s/%s:%d]\n",sect,path,index);
+ if (!tab->func) // no need to analyse section
+ continue;
+
+
+ //--- scan name=value
+
+ RestoreState_t rs;
+ rs.sect = sect;
+ rs.path = path;
+ rs.index = index;
+ rs.user_param = user_param;
+ rs.user_info = 0;
+ rs.log_mode = log_mode;
+ rs.log_file = log_file;
+ InitializeParamField(&rs.param);
+
+ while ( ptr < end )
+ {
+ //--- next line
+ while ( ptr < end && *ptr && *ptr != '\n' )
+ ptr++;
+
+ //--- skip lines and blanks
+ while ( ptr < end && (uchar)*ptr <= ' ' )
+ ptr++;
+
+ if ( *ptr == '[' )
+ break;
+
+ //--- scan name
+ ccp name = ptr;
+ while ( isalnum((int)*ptr) || *ptr == '-' || *ptr == '_' || *ptr == '@' )
+ ptr++;
+ char *name_end = ptr;
+
+ //--- scan param
+ while ( *ptr == ' ' || *ptr == '\t' )
+ ptr++;
+ if ( *ptr != '=' )
+ continue;
+ if ( *++ptr == ' ' ) // skip max 1 space
+ ptr++;
+
+ ccp value = ptr;
+ while ( (uchar)*ptr >= ' ' || *ptr == '\t' )
+ ptr++;
+
+ *name_end = *ptr = 0;
+ InsertParamField(&rs.param,name,false,0,value);
+ }
+
+ #ifdef TEST0
+ {
+ ParamFieldItem_t *ptr = rs.param.field, *end;
+ for ( end = ptr + rs.param.used; ptr < end; ptr++ )
+ printf("%4u: %20s = %s|\n",ptr->num,ptr->key,(ccp)ptr->data);
+ }
+ #endif
+
+ tab->func(&rs,tab->user_table);
+ if ( log_mode & RSL_UNUSED_NAMES )
+ {
+ ParamFieldItem_t *ptr = rs.param.field, *end;
+ for ( end = ptr + rs.param.used; ptr < end; ptr++ )
+ if (!ptr->num)
+ {
+ const uint len = strlen((ccp)ptr->data);
+ if ( len > 20 )
+ fprintf(log_file,"! #RESTORE[%s]: Unused: %s [%u] %.20s...\n",
+ PrintRestoreStateSection(&rs),
+ ptr->key, len, (ccp)ptr->data );
+ else
+ fprintf(log_file,"! #RESTORE[%s]: Unused: %s [%u] %s\n",
+ PrintRestoreStateSection(&rs),
+ ptr->key, len, (ccp)ptr->data );
+ }
+ }
+ ResetParamField(&rs.param);
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintRestoreStateSection ( const RestoreState_t *rs )
+{
+ // print into cyclic buffer
+ DASSERT(rs);
+
+ uint len = strlen(rs->sect) + strlen(rs->path) + 2;
+
+ char index[20];
+ *index = 0;
+ if ( rs->index >= 0 )
+ len += snprintf(index,sizeof(index),":%u",rs->index);
+
+ char *buf = GetCircBuf(len);
+ if (*rs->path)
+ snprintf(buf,len,"%s/%s%s",rs->sect,rs->path,index);
+ else
+ snprintf(buf,len,"%s%s",rs->sect,index);
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * GetParamField
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name // name of member
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = FindParamField(&rs->param,name);
+ if (it)
+ it->num++;
+ else if ( rs->log_mode & RSL_MISSED_NAMES )
+ fprintf(rs->log_file,"! #RESTORE[%s]: Missed: %s\n",
+ PrintRestoreStateSection(rs), name );
+
+ return it;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetParamFieldINT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ int not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? str2l((ccp)it->data,0,10) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetParamFieldUINT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ uint not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? str2ul((ccp)it->data,0,10) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetParamFieldS64
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ s64 not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? str2ll((ccp)it->data,0,10) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetParamFieldU64
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ u64 not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? str2ull((ccp)it->data,0,10) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+float GetParamFieldFLOAT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ float not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? strtof((ccp)it->data,0) : not_found;
+}
+
+//-----------------------------------------------------------------------------
+
+double GetParamFieldDBL
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ double not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? strtod((ccp)it->data,0) : not_found;
+}
+
+//-----------------------------------------------------------------------------
+
+long double GetParamFieldLDBL
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ long double not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? strtold((ccp)it->data,0) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int GetParamFieldBUF
+(
+ // returns >=0: length of read data; -1:nothing done (not_found==NULL)
+
+ char *buf, // buffer to store result
+ uint buf_size, // size of buffer
+
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ EncodeMode_t decode, // decoding mode, fall back to OFF
+ // supported: STRING, UTF8, BASE64, BASE64X
+ ccp not_found // not NULL: store this value if not found
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>1);
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ if (!it)
+ return not_found ? StringCopyS(buf,buf_size,not_found) - buf : -1;
+
+ return DecodeByMode(buf,buf_size,(ccp)it->data,-1,decode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t GetParamFieldMEM
+(
+ // Returns the decoded 'source'. Result is NULL-terminated.
+ // It points either to 'buf' or is alloced (on buf==NULL or to less space)
+ // If alloced (mem.ptr!=buf) => call FreeMem(&mem)
+
+ char *buf, // buffer to store result
+ uint buf_size, // size of buffer
+
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ EncodeMode_t decode, // decoding mode, fall back to OFF
+ // supported: STRING, UTF8, BASE64, BASE64X
+ mem_t not_found // not NULL: return this value
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it
+ ? DecodeByModeMem(buf,buf_size,(ccp)it->data,-1,decode)
+ : not_found;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// save + restore config by table ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpStateTable
+(
+ FILE *f, // valid output file
+ int indent,
+ const SaveRestoreTab_t
+ *srt // list of variables
+)
+{
+ if ( !f || !srt )
+ return;
+
+ indent = NormalizeIndent(indent);
+ for ( ; srt->type != SRT__TERM; srt++ )
+ {
+ if ( srt->type == SRT__IS_LIST )
+ {
+ if ( srt->name )
+ fprintf(f,"%*s# %s\n",indent,"",srt->name);
+ else
+ putc('\n',f);
+ }
+ else
+ fprintf(f,"%*s%4u %4u *%-3u %2u %2u %s\n",
+ indent,"",
+ srt->offset, srt->size, srt->n_elem, srt->type, srt->emode,
+ srt->name ? srt->name : "-" );
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void print_scs_name ( FILE *f, int fw, int use_tab, ccp format, ... )
+ __attribute__ ((__format__(__printf__,4,5)));
+
+static void print_scs_name ( FILE *f, int fw, int use_tab, ccp format, ... )
+{
+ va_list arg;
+ va_start(arg,format);
+ const int len = vfprintf(f,format,arg);
+ va_end(arg);
+
+ if (use_tab)
+ {
+ const int tablen = ( use_tab - len ) / 8;
+ fprintf(f,"%.*s= ", tablen<0 ? 0 : tablen, Tabs20 );
+ }
+ else
+ fprintf(f,"%*s= ", len < fw ? fw-len : 0, "" );
+}
+
+//-----------------------------------------------------------------------------
+
+void SaveCurrentStateByTable
+(
+ FILE *f, // valid output file
+ cvp data0, // valid pointer to source struct
+ const SaveRestoreTab_t
+ *srt, // list of variables
+ ccp prefix, // NULL or prefix for names
+ uint fw_name // field width of name, 0=AUTO
+ // tabs are used for multiple of 8 and for AUTO
+)
+{
+ DASSERT(data0);
+ DASSERT(srt);
+ if ( !f || !data0 || !srt )
+ return;
+
+ if (!prefix)
+ prefix = EmptyString;
+ const uint prelen = strlen(prefix);
+
+ if (!fw_name)
+ {
+ const SaveRestoreTab_t *ptr;
+ for ( ptr = srt; ptr->type != SRT__TERM; ptr++ )
+ if (ptr->name)
+ {
+ int slen = strlen(ptr->name);
+ switch(srt->type)
+ {
+ case SRT_STRING_FIELD:
+ case SRT_PARAM_FIELD:
+ slen += 3;
+ break;
+ }
+
+ if ( fw_name < slen )
+ fw_name = slen;
+ }
+ fw_name += prelen;
+ fw_name = (fw_name+7)/8*8;
+ }
+
+ const int use_tab = (fw_name&7) ? 0 : (fw_name/8)*8+7;
+
+ //-------------------------------------------------------------------------
+
+ u64 get_uint ( const u8* data, int idx, const SaveRestoreTab_t *srt )
+ {
+ DASSERT(srt);
+ DASSERT(srt->var);
+
+ switch(srt->size)
+ {
+ case 1: return ((u8*) (data+srt->offset))[idx];
+ case 2: return ((u16*)(data+srt->offset))[idx];
+ case 4: return ((u32*)(data+srt->offset))[idx];
+ case 8: return ((u64*)(data+srt->offset))[idx];
+ }
+ return 0;
+ }
+
+ //-------------------------------------------------------------------------
+
+ s64 get_int ( const u8* data, int idx, const SaveRestoreTab_t *srt )
+ {
+ DASSERT(srt);
+ DASSERT(srt->var);
+
+ switch(srt->size)
+ {
+ case 1: return ((s8*) (data+srt->offset))[idx];
+ case 2: return ((s16*)(data+srt->offset))[idx];
+ case 4: return ((s32*)(data+srt->offset))[idx];
+ case 8: return ((s64*)(data+srt->offset))[idx];
+ }
+ return 0;
+ }
+
+ //-------------------------------------------------------------------------
+
+ void save_string ( FILE *f, ccp src, int slen, EncodeMode_t emode )
+ {
+ char buf[4000];
+ mem_t res = EncodeByModeMem(buf,sizeof(buf),src,slen,emode);
+
+ fwrite(res.ptr,1,res.len,f);
+ fputc('\n',f);
+ if ( res.ptr != buf )
+ FreeMem(&res);
+ }
+
+ //-------------------------------------------------------------------------
+
+ const u8 *data = data0;
+
+ for ( ; srt->type != SRT__TERM; srt++ )
+ {
+ if ( srt->type == SRT__IS_LIST )
+ {
+ if ( srt->name )
+ fprintf(f,"# %s\n",srt->name);
+ else
+ putc('\n',f);
+ continue;
+ }
+
+ if (!srt->name)
+ continue;
+
+ if ( srt->type < SRT__IS_LIST )
+ print_scs_name(f,fw_name,use_tab,"%s%s",prefix,srt->name);
+
+ switch(srt->type)
+ {
+ case SRT_BOOL:
+ if ( srt->n_elem > 1 )
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%u", i ? "," : "", get_uint(data,i,srt)!=0 );
+ fputc('\n',f);
+ }
+ else
+ fprintf(f,"%u\n",get_uint(data,0,srt)!=0);
+ break;
+
+ case SRT_UINT:
+ if ( srt->n_elem > 1 )
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%llu", i ? "," : "", get_uint(data,i,srt) );
+ fputc('\n',f);
+ }
+ else
+ fprintf(f,"%llu\n",get_uint(data,0,srt));
+ break;
+
+ case SRT_HEX:
+ if ( srt->n_elem > 1 )
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%#llx", i ? "," : "", get_uint(data,i,srt) );
+ fputc('\n',f);
+ }
+ else
+ fprintf(f,"%#llx\n",get_uint(data,0,srt));
+ break;
+
+ case SRT_INT:
+ if ( srt->n_elem > 1 )
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%lld", i ? "," : "", get_int(data,i,srt) );
+ fputc('\n',f);
+ }
+ else
+ fprintf(f,"%lld\n",get_int(data,0,srt));
+ break;
+
+ case SRT_FLOAT:
+ switch(srt->size)
+ {
+ case sizeof(float):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%.8g", i ? "," : "",
+ ((float*)(data+srt->offset))[i] );
+ }
+ break;
+
+ case sizeof(double):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%.16g", i ? "," : "",
+ ((double*)(data+srt->offset))[i] );
+ }
+ break;
+
+ case sizeof(long double):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%.20Lg", i ? "," : "",
+ ((long double*)(data+srt->offset))[i] );
+ }
+ break;
+
+ default:
+ fputs("0.0",f);
+ }
+ fputc('\n',f);
+ break;
+
+ case SRT_XFLOAT:
+ switch(srt->size)
+ {
+ case sizeof(float):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%a", i ? "," : "",
+ ((float*)(data+srt->offset))[i] );
+ }
+ break;
+
+ case sizeof(double):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%a", i ? "," : "",
+ ((double*)(data+srt->offset))[i] );
+ }
+ break;
+
+ case sizeof(long double):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%La", i ? "," : "",
+ ((long double*)(data+srt->offset))[i] );
+ }
+ break;
+
+ default:
+ fputs("0.0",f);
+ }
+ fputc('\n',f);
+ break;
+
+ //-----
+
+ case SRT_STRING_SIZE:
+ save_string(f,(char*)(data+srt->offset),-1,srt->emode);
+ break;
+
+ case SRT_STRING_ALLOC:
+ save_string(f,*(ccp*)(data+srt->offset),-1,srt->emode);
+ break;
+
+ case SRT_MEM:
+ {
+ mem_t *mem = (mem_t*)(data+srt->offset);
+ save_string(f,mem->ptr,mem->len,srt->emode);
+ break;
+ }
+
+ //-----
+
+ case SRT_STRING_FIELD:
+ {
+ print_scs_name(f,fw_name,use_tab,"%s%s@n",prefix,srt->name);
+ const StringField_t *sf = (StringField_t*)(data+srt->offset);
+ fprintf(f,"%u\n",sf->used);
+
+ int i;
+ ccp *ptr = sf->field;
+ for ( i = 0; i < sf->used; i++, ptr++ )
+ {
+ print_scs_name(f,fw_name,use_tab,"%s%s@%u",prefix,srt->name,i);
+ save_string(f,*ptr,-1,srt->emode);
+ }
+ break;
+ }
+
+ case SRT_PARAM_FIELD:
+ {
+ print_scs_name(f,fw_name,use_tab,"%s%s@n",prefix,srt->name);
+ const ParamField_t *pf = (ParamField_t*)(data+srt->offset);
+ fprintf(f,"%u\n",pf->used);
+
+ int i;
+ ParamFieldItem_t *ptr = pf->field;
+ for ( i = 0; i < pf->used; i++, ptr++ )
+ {
+ print_scs_name(f,fw_name,use_tab,"%s%s@%u",prefix,srt->name,i);
+ fprintf(f,"%d ",ptr->num);
+ save_string(f,ptr->key,-1,srt->emode);
+ }
+ break;
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateByTable
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ void *data0, // valid pointer to destination struct
+ const SaveRestoreTab_t *srt, // list of variables
+ ccp prefix // NULL or prefix for names
+)
+{
+ DASSERT(rs);
+ DASSERT(srt);
+
+ char buf[4000];
+ if (!prefix)
+ prefix = EmptyString;
+
+ for ( ; srt->type != SRT__TERM; srt++ )
+ {
+ if ( srt->type == SRT__IS_LIST || !srt->name )
+ continue;
+
+ const u8 *data = data0;
+
+ if ( srt->type < SRT__IS_LIST )
+ StringCat2S(buf,sizeof(buf),prefix,srt->name);
+
+ switch(srt->type)
+ {
+ case SRT_BOOL:
+ case SRT_UINT:
+ case SRT_HEX:
+ {
+ ParamFieldItem_t *it = GetParamField(rs,buf);
+ if (!it)
+ break;
+
+ char *src = (char*)it->data;
+ u8 *val = (u8*)(data+srt->offset);
+ uint i = 0;
+ while (*src)
+ {
+ u64 num = str2ull(src,&src,10);
+ switch(srt->size)
+ {
+ case 1: *(u8*) val = num; break;
+ case 2: *(u16*)val = num; break;
+ case 4: *(u32*)val = num; break;
+ case 8: *(u64*)val = num; break;
+ }
+ if ( ++i == srt->n_elem )
+ break;
+ val += srt->size;
+ if ( *src == ',' )
+ src++;
+ }
+ break;
+ }
+
+ case SRT_INT:
+ {
+ ParamFieldItem_t *it = GetParamField(rs,buf);
+ if (!it)
+ break;
+
+ char *src = (char*)it->data;
+ u8 *val = (u8*)(data+srt->offset);
+ uint i = 0;
+ while (*src)
+ {
+ s64 num = str2ll(src,&src,10);
+ switch(srt->size)
+ {
+ case 1: *(s8*) val = num; break;
+ case 2: *(s16*)val = num; break;
+ case 4: *(s32*)val = num; break;
+ case 8: *(s64*)val = num; break;
+ }
+ if ( ++i == srt->n_elem )
+ break;
+ val += srt->size;
+ if ( *src == ',' )
+ src++;
+ }
+ break;
+ }
+
+ case SRT_FLOAT:
+ case SRT_XFLOAT:
+ {
+ ParamFieldItem_t *it = GetParamField(rs,buf);
+ if (!it)
+ break;
+
+ char *src = (char*)it->data;
+ u8 *val = (u8*)(data+srt->offset);
+ uint i = 0;
+ while (*src)
+ {
+ switch(srt->size)
+ {
+ case sizeof(float): *(float*)val = strtof(src,&src); break;
+ case sizeof(double): *(double*)val = strtod(src,&src); break;
+ case sizeof(long double): *(long double*)val = strtold(src,&src); break;
+ }
+ if ( ++i == srt->n_elem )
+ break;
+ val += srt->size;
+ if ( *src == ',' )
+ src++;
+ }
+ break;
+ }
+
+ //-----
+
+ case SRT_STRING_SIZE:
+ {
+ char *val = (char*)(data+srt->offset);
+ mem_t res = GetParamFieldMEM(buf,sizeof(buf),rs,buf,
+ srt->emode,MemByString(val));
+ StringCopyS(val,srt->size,res.ptr);
+ if ( res.ptr != buf && res.ptr != val )
+ FreeString(res.ptr);
+ break;
+ }
+
+ case SRT_STRING_ALLOC:
+ {
+ ccp *val = (ccp*)(data+srt->offset);
+ mem_t res = GetParamFieldMEM(buf,sizeof(buf),rs,buf,
+ srt->emode,MemByString0(*val));
+ if ( res.ptr != *val )
+ {
+ FreeString(*val);
+ *val = MEMDUP(res.ptr,res.len);
+ }
+ else if ( res.ptr != buf )
+ FreeString(res.ptr);
+ break;
+ }
+
+ case SRT_MEM:
+ {
+ mem_t *val = (mem_t*)(data+srt->offset);
+ mem_t res = GetParamFieldMEM(buf,sizeof(buf),rs,buf,srt->emode,*val);
+ if ( res.ptr != val->ptr )
+ {
+ FreeString(val->ptr);
+ *val = res.ptr == buf ? DupMem(res) : res;
+ }
+ else if ( res.ptr != buf )
+ FreeString(res.ptr);
+ break;
+ }
+
+ //-----
+
+ case SRT_STRING_FIELD:
+ {
+ StringField_t *sf = (StringField_t*)(data+srt->offset);
+ ResetStringField(sf);
+
+ snprintf(buf,sizeof(buf),"%s%s@n",prefix,srt->name);
+ const int n = GetParamFieldINT(rs,buf,0);
+ int i;
+ for ( i = 0; i < n; i++ )
+ {
+ snprintf(buf,sizeof(buf),"%s%s@%u",prefix,srt->name,i);
+ mem_t res = GetParamFieldMEM(buf,sizeof(buf),rs,buf,srt->emode,NullMem);
+ InsertStringField(sf,res.ptr,res.ptr!=buf);
+ }
+ break;
+ }
+
+ case SRT_PARAM_FIELD:
+ {
+ ParamField_t *sf = (ParamField_t*)(data+srt->offset);
+ ResetParamField(sf);
+
+ snprintf(buf,sizeof(buf),"%s%s@n",prefix,srt->name);
+ const int n = GetParamFieldINT(rs,buf,0);
+ int i;
+ for ( i = 0; i < n; i++ )
+ {
+ snprintf(buf,sizeof(buf),"%s%s@%u",prefix,srt->name,i);
+ ParamFieldItem_t *it = GetParamField(rs,buf);
+ if (it)
+ {
+ char *str;
+ const int num = strtol((ccp)it->data,&str,10);
+ if ( *str == ' ' ) // space should always exist
+ str++;
+
+ mem_t res = DecodeByModeMem(buf,sizeof(buf),str,-1,srt->emode);
+ InsertParamField(sf,res.ptr,false,num,0);
+ if ( res.ptr != buf )
+ FreeString(res.ptr);
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan socket type ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool ScanSocketInfo
+(
+ // Syntax: [ PREFIX ',' ]... [ PREFIX ':' ] address
+ // returns TRUE, if a protocol or a type is found
+
+ socket_info_t *si, // result, not NULL, will be initialized
+ ccp address, // address to analyze
+ uint tolerance // tolerace:
+ // 0: invalid without prefix
+ // 1: analyse beginning of address part:
+ // '/' or './' or '../' -> AF_UNIX
+ // 2: estimate type
+ // '/' before first ':' -> AF_UNIX
+ // 1.2 | 1.2.3 | 1.2.3.4 -> AF_INET
+ // a HEX:IP combi -> AF_INET6
+ // domain name -> AF_INET_ANY
+)
+{
+ DASSERT(si);
+ InitializeSocketInfo(si);
+
+ if (!address)
+ return false;
+
+ while ( *address && (uchar)*address <= ' ' )
+ address++;
+
+ static const KeywordTab_t keytab[] =
+ {
+ { -1, "STREAM", 0, SOCK_STREAM },
+ { -1, "DGRAM", "DATAGRAM", SOCK_DGRAM },
+
+ { AF_UNIX, "UNIX", "FILE", -1 },
+ { AF_UNIX, "UNIX-TCP", "FILE-TCP", SOCK_STREAM },
+ { AF_UNIX, "UNIX-STREAM", "FILE-STREAM", SOCK_STREAM },
+ { AF_UNIX, "UNIX-UDP", "FILE-UDP", SOCK_DGRAM },
+ { AF_UNIX, "UNIX-DGRAM", "FILE-DGRAM", SOCK_DGRAM },
+ { AF_UNIX, "UNIX-DATAGRAM","FILE-DATAGRAM",SOCK_DGRAM },
+ { AF_UNIX, "UNIX-SEQ", "FILE-SEQ", SOCK_SEQPACKET },
+ { AF_UNIX, "SEQ", 0, SOCK_SEQPACKET },
+
+ { AF_INET_ANY, "IP", 0, -1 },
+ { AF_INET_ANY, "TCP", 0, SOCK_STREAM },
+ { AF_INET_ANY, "UDP", 0, SOCK_DGRAM },
+ { AF_INET_ANY, "RAW", 0, SOCK_RAW },
+
+ { AF_INET, "IP4", "IPV4", -1 },
+ { AF_INET, "TCP4", "TCPV4", SOCK_STREAM },
+ { AF_INET, "UDP4", "UDPV4", SOCK_DGRAM },
+ { AF_INET, "RAW4", "RAWV4", SOCK_RAW },
+
+ { AF_INET6, "IP6", "IPV6", -1 },
+ { AF_INET6, "TCP6", "TCPV6", SOCK_STREAM },
+ { AF_INET6, "UDP6", "UDPV6", SOCK_DGRAM },
+ { AF_INET6, "RAW6", "RAWV6", SOCK_RAW },
+
+ {0,0,0,0}
+ };
+
+ bool ok = false;
+ int proto = -1;
+ int type = -1;
+ ccp start = address;
+ for(;;)
+ {
+ char namebuf[10];
+ char *dest = namebuf;
+ ccp ptr = start;
+ while ( dest < namebuf + sizeof(namebuf) - 1
+ && isalnum((int)*ptr) || *ptr == '-' )
+ {
+ *dest++ = *ptr++;
+ }
+ if ( ptr == start || *ptr != ',' && *ptr != ':' )
+ break;
+
+ *dest = 0;
+ int abbrev;
+ const KeywordTab_t *k = ScanKeyword(&abbrev,namebuf,keytab);
+ if ( !k || abbrev )
+ break;
+
+ if ( k->id == AF_INET_ANY ? ( proto == -1 ) : ( k->id >= 0 ) )
+ proto = k->id;
+ if ( k->opt >= 0 )
+ type = k->opt;
+
+ noPRINT("%3lld %3lld |%s|%s| -> %d,%d\n",
+ k->id, k->opt, k->name1, k->name2 ? k->name2 : "",
+ proto, type );
+ start = ptr;
+ if ( *start++ == ':' )
+ {
+ ok = true;
+ break;
+ }
+ }
+
+ if (!ok)
+ {
+ start = address;
+ proto = -1;
+ type = -1;
+ }
+
+ if ( proto < 0 && tolerance > 0 )
+ {
+ if ( *start == '/' || !memcmp(start,"./",2) || !memcmp(start,"../",3) )
+ {
+ proto = AF_UNIX;
+ goto proto_found;
+ }
+
+ if ( tolerance > 1 )
+ {
+ ccp colon = strchr(start,':');
+ if (!colon)
+ colon = start + strlen(start);
+
+ //--- test for UNIX
+
+ ccp slash = strchr(start,'/');
+ if ( slash && slash < colon )
+ {
+ proto = AF_UNIX;
+ goto proto_found;
+ }
+
+ //--- test for IPv4
+
+ ccp ptr = start;
+ uint count = 0;
+ while ( ptr < colon )
+ {
+ const char ch = *ptr;
+ if ( ch == '.' )
+ count++;
+ else if ( ch < '0' || ch > '9' )
+ break;
+ ptr++;
+ }
+ if ( ptr == colon && count < 4 )
+ {
+ proto = AF_INET;
+ if ( count > 0 )
+ goto proto_found;
+ }
+
+ //--- test for IPv6
+
+ ptr = start;
+ const bool have_bracket = *ptr == '[';
+ if (have_bracket)
+ ptr++;
+ count = 0;
+ while (*ptr)
+ {
+ const char ch = *ptr;
+ if ( ch == ':' )
+ count++;
+ else if ( ch != '.'
+ && !( ch >= '0' && ch <= '9' )
+ && !( ch >= 'A' && ch <= 'F' )
+ && !( ch >= 'a' && ch <= 'f' )
+ )
+ {
+ break;
+ }
+ ptr++;
+ }
+ if ( count > 1 && count < 8 && ( !have_bracket || *ptr == ']' ))
+ {
+ proto = AF_INET6;
+ goto proto_found;
+ }
+
+ //--- test for a domain name
+
+ ptr = start;
+ count = 0;
+ ccp beg_name = ptr;
+ while ( ptr < colon )
+ {
+ const int ch = *ptr;
+ if ( ptr > beg_name && ch == '.' )
+ {
+ count++;
+ beg_name = ptr+1;
+ }
+ else if ( ptr == beg_name ? !isalpha(ch) : !isalnum(ch) )
+ break;
+ ptr++;
+ }
+ if ( count > 0 && ptr == colon )
+ {
+ proto = AF_INET_ANY;
+ goto proto_found;
+ }
+ }
+ }
+
+ if ( proto || type )
+ {
+ proto_found:
+ si->is_valid = true;
+ si->protocol = proto;
+ si->type = type;
+ si->address = start;
+ }
+ //printf(">>> v=%d {%d,%d}: %s\n",ok,proto,type,start);
+ return si->is_valid;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintSocketInfo ( int protocol, int type )
+{
+ switch (protocol)
+ {
+ case AF_UNIX:
+ switch(type)
+ {
+ case SOCK_STREAM: return "unix-tcp:";
+ case SOCK_DGRAM: return "unix-udp:";
+ case SOCK_SEQPACKET: return "unix-seq:";
+ default: return "unix:";
+ }
+ break;
+
+ case AF_INET:
+ switch(type)
+ {
+ case SOCK_STREAM: return "tcp4:";
+ case SOCK_DGRAM: return "udp4:";
+ case SOCK_RAW: return "raw4:";
+ default: return "ipv4:";
+ }
+ break;
+
+ case AF_INET6:
+ switch(type)
+ {
+ case SOCK_STREAM: return "tcp6:";
+ case SOCK_DGRAM: return "udp6:";
+ case SOCK_RAW: return "raw6:";
+ default: return "ipv6:";
+ }
+ break;
+
+ case AF_INET_ANY:
+ switch(type)
+ {
+ case SOCK_STREAM: return "tcp:";
+ case SOCK_DGRAM: return "udp:";
+ case SOCK_RAW: return "raw:";
+ default: return "ip:";
+ }
+ break;
+ };
+
+ return "";
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// stat_file_count_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+stat_file_count_t stat_file_count = {0};
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CountOpenFiles()
+{
+ DIR *fdir = opendir("/proc/self/fd");
+ if (fdir)
+ {
+ uint count = 0;
+ for(;;)
+ {
+ struct dirent *dent = readdir(fdir);
+ if (!dent)
+ break;
+ if (dent->d_name[0] != '.' )
+ count++;
+ }
+ closedir(fdir);
+
+ stat_file_count.cur_files = count;
+ if ( stat_file_count.max_files < count )
+ stat_file_count.max_files = count;
+ }
+
+ return stat_file_count.cur_files;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RegisterFileId ( int fd )
+{
+ if ( (int)stat_file_count.max_files <= fd )
+ stat_file_count.max_files = fd+1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void UpdateOpenFiles ( bool count_current )
+{
+ struct rlimit rlim;
+ if (!getrlimit(RLIMIT_NOFILE,&rlim))
+ {
+ stat_file_count.cur_limit = rlim.rlim_cur;
+ stat_file_count.max_limit = rlim.rlim_max;
+ }
+
+ if ( count_current || !stat_file_count.max_files )
+ CountOpenFiles();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SetOpenFilesLimit ( uint limit )
+{
+ struct rlimit rlim;
+ if (!getrlimit(RLIMIT_NOFILE,&rlim))
+ {
+ rlim.rlim_cur = limit;
+ setrlimit(RLIMIT_NOFILE,&rlim);
+ }
+
+ UpdateOpenFiles(false);
+ return stat_file_count.cur_limit;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintOpenFiles ( bool count_current )
+{
+ if (!stat_file_count.cur_limit)
+ UpdateOpenFiles(count_current);
+ else if (count_current)
+ CountOpenFiles();
+
+ return PrintCircBuf("cur=%u, max=%u, limit=%u/%u",
+ stat_file_count.cur_files,
+ stat_file_count.max_files,
+ stat_file_count.cur_limit,
+ stat_file_count.max_limit );
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-file.h b/project/dclib/dclib-file.h
new file mode 100644
index 0000000..532ff5f
--- /dev/null
+++ b/project/dclib/dclib-file.h
@@ -0,0 +1,1278 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_FILE_H
+#define DCLIB_FILE_H 1
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+
+#include "dclib-types.h"
+#include "dclib-basics.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// enum FileMode_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[FileMode_t]]
+
+typedef enum FileMode_t
+{
+ FM_TEST = 0x00001, // test mode, don't modify any file
+ FM_SILENT = 0x00002, // suppress error messages
+ FM_IGNORE = 0x00004, // ignore, if no file on OpenFile()
+ // and return ERR_NOT_EXISTS
+
+ FM_MODIFY = 0x00010, // open file for reading and writing
+ FM_APPEND = 0x00020, // append to existing files
+ FM_UPDATE = 0x00040, // update existing file
+ FM_OVERWRITE = 0x00080, // overwrite existing files silently
+ FM_NUMBER = 0x00100, // renumber filename for existing files
+ FM_REMOVE = 0x00200, // remove file before open
+ FM_MKDIR = 0x00400, // create path automatically
+ FM_TCP = 0x00800, // detect 'tcp:' and 'unix:' prefix on open
+
+ FM_STDIO = 0x01000, // detect and allow "-" as stdin or stdout
+ FM_DEV = 0x02000, // allow character and block devices
+ FM_SOCK = 0x04000, // allow sockets and connect as unix stream
+ FM_SPC = 0x08000, // allow special files like pipes and sockets
+
+ FM_TOUCH = 0x40000, // touch file (set timestamp) on close
+ FM_TEMP = 0x80000, // temporary file: remove file on close
+
+ FM_M_OPEN = FM_TEST
+ | FM_SILENT
+ | FM_IGNORE
+ | FM_MODIFY
+ | FM_STDIO
+ | FM_DEV
+ | FM_SPC
+ | FM_TCP
+ | FM_TEMP,
+
+ FM_M_CREATE = FM_TEST
+ | FM_SILENT
+ | FM_MODIFY
+ | FM_APPEND
+ | FM_UPDATE
+ | FM_OVERWRITE
+ | FM_NUMBER
+ | FM_REMOVE
+ | FM_MKDIR
+ | FM_STDIO
+ | FM_DEV
+ | FM_SOCK
+ | FM_SPC
+ | FM_TOUCH
+ | FM_TEMP,
+
+ FM_M_ALL = FM_M_OPEN
+ | FM_M_CREATE
+}
+FileMode_t;
+
+//-----------------------------------------------------------------------------
+
+ccp GetFileModeStatus
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ FileMode_t file_mode, // filemode to print
+ uint print_mode // 0: short vector, 1 char for 1 attrib
+ // 1: long mode: keyword for each set attrib
+);
+
+
+ccp GetFileOpenMode
+(
+ bool create, // false: open, true: create
+ FileMode_t file_mode // open modes
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct FileAttrib_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[FileAttrib_t]]
+
+typedef struct FileAttrib_t
+{
+ time_t mtime; // time of last content modification
+ time_t ctime; // time of file creation or last status change
+ time_t atime; // last ascces time
+ time_t itime; // insertion time (special time for archives)
+
+ size_t size; // file size
+ mode_t mode; // file mode
+}
+FileAttrib_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * ClearFileAttrib
+(
+ FileAttrib_t * dest // NULL or destination attribute
+);
+
+FileAttrib_t * TouchFileAttrib
+(
+ FileAttrib_t * dest // valid destination attribute
+);
+
+FileAttrib_t * SetFileAttrib
+(
+ FileAttrib_t * dest, // valid destination attribute
+ const FileAttrib_t * src_fa, // NULL or source attribute
+ const struct stat * src_stat // NULL or source attribute
+ // only used if 'src_fa==NULL'
+);
+
+FileAttrib_t * MaxFileAttrib
+(
+ FileAttrib_t * dest, // valid source and destination attribute
+ const FileAttrib_t * src_fa, // NULL or second source attribute
+ const struct stat * src_stat // NULL or third source attribute
+);
+
+static inline FileAttrib_t * UseFileAttrib
+(
+ FileAttrib_t * dest, // valid destination attribute
+ const FileAttrib_t * src_fa, // NULL or source attribute
+ const struct stat * src_stat, // NULL or source attribute
+ bool use_max // switch for SetFileAttrib() | MaxFileAttrib()
+)
+{
+ return ( use_max ? MaxFileAttrib : SetFileAttrib )(dest,src_fa,src_stat);
+}
+
+FileAttrib_t * NormalizeFileAttrib
+(
+ FileAttrib_t * fa // valid attribute
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct File_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[File_t]]
+
+typedef struct File_t
+{
+ //--- file handle
+
+ FILE *f; // file descriptor
+
+ //--- basic status
+
+ ccp fname; // file name, alloced
+ FileMode_t fmode; // file open mode
+ struct stat st; // file status just before opened
+ FileAttrib_t fatt; // file attributes
+
+ //--- advanced status
+
+ bool is_stdio; // true: read from stdin or to stdout
+ bool is_socket; // true: file is a UNIX stream socket
+ bool is_reading; // true: file opened for reading
+ bool is_writing; // true: file opened for writing
+ bool is_seekable; // true: file is seekable
+ enumError max_err; // maximum file related error
+
+ //--- file data
+
+ u8 * data; // NULL or file data
+ size_t size; // size of 'data'
+ bool data_alloced; // true: 'data' is alloced,
+ // free it on ResetFile()
+}
+File_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeFile
+(
+ File_t * f // file structure
+);
+
+enumError ResetFile
+(
+ File_t * f, // file structure
+ uint set_time // 0: don't set
+ // 1: set time before closing using 'fatt'
+ // 2: set current time before closing
+);
+
+enumError CloseFile
+(
+ File_t * f, // file structure
+ uint set_time // 0: don't set
+ // 1: set time before closing using 'fatt'
+ // 2: set current time before closing
+);
+
+//-----------------------------------------------------------------------------
+
+enumError OpenFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp fname, // file to open
+ FileMode_t file_mode, // open modes
+ off_t limit, // >0: don't open, if file size > limit
+ ccp limit_message // NULL or a with LF terminated text.
+ // It is printed after the message.
+);
+
+//-----------------------------------------------------------------------------
+
+enumError CheckCreateFile
+(
+ // returns:
+ // ERR_DIFFER: [FM_STDIO] source is "-" => 'st' is zeroed
+ // ERR_WARNING: [FM_DEV,FM_SPC] not a regular file
+ // ERR_WRONG_FILE_TYPE: file exists, but is not a regular file
+ // ERR_ALREADY_EXISTS: file already exists
+ // ERR_INVALID_VERSION: already exists, but FM_NUMBER set => no msg printed
+ // ERR_CANT_CREATE: FM_UPDATE is set, but file don't exist
+ // ERR_OK: file not exist or can be overwritten
+
+ ccp fname, // filename to open
+ FileMode_t file_mode, // open modes
+ struct stat *st // not NULL: store file status here
+);
+
+enumError CreateFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp fname, // file to open
+ FileMode_t file_mode // open modes
+);
+
+static inline enumError AppendFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp fname, // file to open
+ FileMode_t file_mode // open modes
+)
+{
+ return CreateFile(f,initialize,fname,file_mode|FM_APPEND);
+}
+
+//-----------------------------------------------------------------------------
+
+enumError WriteFileAt
+(
+ File_t * F, // file to write
+ size_t * cur_offset, // pointer to current file offset, modified
+ size_t offset, // offset to write
+ const void * data, // data to write
+ size_t size // size of 'data'
+);
+
+//-----------------------------------------------------------------------------
+
+enumError SetFileSize
+(
+ File_t * F, // file to write
+ size_t * cur_offset, // pointer to current file offset, modified
+ size_t size // offset to write
+);
+
+//-----------------------------------------------------------------------------
+
+enumError SkipFile
+(
+ File_t *F, // file to write
+ size_t skip // number of bytes to skip
+);
+
+//-----------------------------------------------------------------------------
+
+enumError RegisterFileError
+(
+ File_t * f, // file structure
+ enumError new_error // new error code
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+s64 GetFileSize
+(
+ ccp path1, // NULL or part 1 of path
+ ccp path2, // NULL or part 2 of path
+ s64 not_found_val, // return value if no regular file found
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store max values to 'fatt'
+);
+
+//-----------------------------------------------------------------------------
+
+enumError OpenReadFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ off_t limit, // >0: don't open, if file size > limit
+ ccp limit_message, // NULL or a with LF terminated text.
+ // It is printed after the message.
+
+ u8 ** res_data, // store alloced data here (always NULL terminated)
+ uint * res_size, // not NULL: store data size
+ ccp * res_fname, // not NULL: store alloced filename
+ FileAttrib_t * res_fatt // not NULL: store file attributes
+);
+
+//-----------------------------------------------------------------------------
+
+enumError LoadFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // skip num of bytes before reading
+ void * data, // destination buffer, size = 'size'
+ size_t size, // size to read
+ int silent, // 0: print all error messages
+ // 1: suppress file size warning
+ // 2: suppress all error messages
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store *max* values to 'fatt'
+);
+
+//-----------------------------------------------------------------------------
+
+enumError LoadFileAlloc
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // skip num of bytes before reading
+ u8 ** res_data, // result: free existing data, store ptr to alloc data
+ // always one more byte is alloced and set to NULL
+ size_t * res_size, // result: size of 'res_data'
+ size_t max_size, // >0: a file size limit
+ int silent, // 0: print all error messages
+ // 1: suppress file size warning
+ // 2: suppress all error messages
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store max values to 'fatt'
+);
+
+//-----------------------------------------------------------------------------
+
+enumError SaveFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ const void * data, // data to write
+ uint data_size, // size of 'data'
+ FileAttrib_t * fatt // not NULL: set timestamps using this attrib
+);
+
+//-----------------------------------------------------------------------------
+
+enumError OpenWriteFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ const void * data, // data to write
+ uint data_size // size of 'data'
+);
+
+//-----------------------------------------------------------------------------
+
+uint CloseAllExcept
+(
+ int *list, // exception list of unordered FD, max contain -1
+ uint n_list, // number of elements in 'list'
+ int min, // close all until and including this fd
+ int add // if a FD was found, incremet 'min' to 'fd+add'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CopyFile*(), TransferFile() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// compare pathes and by stat()
+bool IsSameFile ( ccp path1, ccp path2 );
+
+///////////////////////////////////////////////////////////////////////////////
+
+// returns:
+// ERR_OK file copied
+// ERR_NOTHING_TO_DO src and dest are the same
+// ERR_MISSING_PARAM invalid parameters
+// ERR_CANT_OPEN can't open dest
+// ERR_WRITE_FAILED write error
+
+enumError CopyFileHelper
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ int open_flags, // flags for open()
+ mode_t open_mode, // mode for open()
+ uint temp_and_move // >0: ignore open_flags,
+ // create temp, then move temp to dest
+);
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFile
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+);
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFileCreate
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+);
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFileTemp
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+);
+
+///////////////////////////////////////////////////////////////////////////////
+// [[TransferMode_t]]
+
+typedef enum TransferMode_t
+{
+ //--- jobs
+
+ TFMD_J_MOVE = 0x01, // move file
+ TFMD_J_MOVE1 = 0x02, // move file, but only if not linked (ignore TFMD_J_MOVE)
+ TFMD_J_RM_DEST = 0x04, // remove dest before TFMD_J_LINK and TFMD_J_COPY
+ TFMD_J_LINK = 0x08, // link file
+ TFMD_J_COPY = 0x10, // copy file
+ TFMD_J_RM_SRC = 0x20, // remove source after TFMD_J_COPY
+// TFMD_J_TOUCH = 0x40, // touch destination => not supported yet
+
+ //--- modes
+
+ TFMD_MOVE = TFMD_J_MOVE | TFMD_J_RM_DEST | TFMD_J_COPY | TFMD_J_RM_SRC,
+ TFMD_MOVE1 = TFMD_J_MOVE1 | TFMD_J_RM_DEST | TFMD_J_COPY | TFMD_J_RM_SRC,
+ TFMD_LINK = TFMD_J_LINK | TFMD_J_RM_DEST | TFMD_J_COPY,
+ TFMD_COPY = TFMD_J_RM_DEST | TFMD_J_COPY,
+ TFMD_CAT = TFMD_J_COPY,
+}
+__attribute__ ((packed)) TransferMode_t;
+
+//-----------------------------------------------------------------------------
+
+// returns:
+// ERR_OK file copied
+// ERR_NOTHING_TO_DO src and dest are the same
+// ERR_MISSING_PARAM invalid parameters
+// ERR_CANT_OPEN can't open dest
+// ERR_WRITE_FAILED write error
+// ERR_ERROR other error
+
+enumError TransferFile
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ TransferMode_t tfer_mode, // transfer mode
+ mode_t open_mode // mode for CopyFile*() -> open()
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MemFile_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[MemFile_t]]
+
+typedef struct MemFile_t
+{
+ u8 *data; // pointer to alloced data
+ uint size; // size of 'data'
+ uint max_size; // >0: max allowed size of 'data'
+ uint fend; // current end of file position, always '<=size'
+ uint fpos; // current file pointer, always '<=fend'
+
+ ccp err_name; // not NULL: print error messages
+ bool err_name_alloced; // true: 'err_name' must be freed
+ bool zero_extend; // true: assume unlimted file on Read*()
+ bool eof; // set by Read*(), if read behind 'fend'
+
+ FileAttrib_t fatt; // file attribute for loaded files
+}
+MemFile_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsValidMemFile ( MemFile_t *mf );
+
+void DumpMemFile
+(
+ FILE *f, // valid output stream
+ int indent, // indent of output
+ MemFile_t *mf // valid MemFile_t data
+);
+
+bool AssertMemFile
+(
+ MemFile_t *mf // valid MemFile_t data
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+u8 * GetDataMemFile
+(
+ // return NULL on err or a pointer to the data posisiton.
+
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to read from
+ uint size // needed data size
+);
+
+void InitializeMemFile
+(
+ MemFile_t *mf, // structure to initialize
+ uint start_size, // >0: alloc this for data
+ uint max_size // >0: file never grows above 'max_size'
+);
+
+void ResetMemFile
+(
+ MemFile_t *mf // valid MemFile_t data
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError WriteMemFileAt
+(
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to write
+ const void *data, // data to write
+ uint size // size to write
+);
+
+static inline enumError WriteMemFile
+(
+ MemFile_t *mf, // valid MemFile_t data
+ const void *data, // data to write
+ uint size // size to write
+)
+{
+ DASSERT(mf);
+ return WriteMemFileAt(mf,mf->fpos,data,size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ReadMemFileAt
+(
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to read from
+ void *buf, // destination buffer
+ uint size // size to read
+);
+
+static inline uint ReadMemFile
+(
+ MemFile_t *mf, // valid MemFile_t data
+ void *buf, // destination buffer
+ uint size // size to read
+)
+{
+ DASSERT(mf);
+ return ReadMemFileAt(mf,mf->fpos,buf,size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadMemFile
+(
+ MemFile_t *mf, // MemFile_t data
+ bool init_mf, // true: initalize 'mf' first
+
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // >0: skip num of bytes before reading
+ size_t size, // >0: max size to read; 0: read all (remaining)
+ bool silent // true: suppress error messages
+);
+
+enumError SaveMemFile
+(
+ MemFile_t *mf, // MemFile_t data
+
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t fmode, // mode for file creation
+ bool sparse // true: enable sparse support
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TraceLog_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[TraceLog_t]]
+
+typedef struct TraceLog_t
+{
+ ccp fname; // name of file to open
+ FILE *log; // NULL or open log file
+ int level; // log level, don't log if <0
+
+}
+TraceLog_t;
+
+//-----------------------------------------------------------------------------
+
+bool OpenTraceLog ( TraceLog_t *tl );
+bool TraceLogText ( TraceLog_t *tl, ccp text );
+bool TraceLogPrint ( TraceLog_t *tl, ccp format, ... )
+ __attribute__ ((__format__(__printf__,2,3)));
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct LineBuffer_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+#ifndef __APPLE__
+///////////////////////////////////////////////////////////////////////////////
+// [[LineBuffer_t]]
+
+typedef struct LineBuffer_t
+{
+ FILE *fp; // current file pointer
+ FILE *old_fp; // NULL or file before opened
+ FILE **old_fp_pos; // not NULL: store 'old_fp' on close()
+ int redirect; // >0: redirect to 'old_fp' (waste if old_fp==0)
+
+ GrowBuffer_t buf; // grow buffer to hold lines
+ u8 *prev_buf_ptr; // previous 'buf.ptr', if changed recalc 'line'
+
+ uint max_lines; // max numbers of lines
+ uint used_lines; // numbers of used lines
+ uint max_line_size; // >0: max size of a single line
+ mem_t *line; // list with 'max_lines' elements
+ bool open_line; // true: last line not terminated yet
+ uint seq_count; // increased by 1 on every change
+ uint prev_seq_count; // previous 'seq_count', if changed recalc 'line'
+ uint line_count; // increased for each insertet LF, no internal impact
+}
+LineBuffer_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeLineBuffer ( LineBuffer_t *lb, uint max_buf_size );
+void ResetLineBuffer ( LineBuffer_t *lb );
+void ClearLineBuffer ( LineBuffer_t *lb );
+
+void RedirectLineBuffer
+(
+ LineBuffer_t *lb, // valid line buffer
+ FILE *f, // output file, if NULL use 'lb->old_fd'
+ int max_lines // >=0: max number of redirected lines
+);
+
+LineBuffer_t * OpenLineBuffer
+(
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ bool init_lb, // true: initialize 'lb'
+ FILE **fp_pos, // NULL or external place of the file pointer
+ uint max_lines, // max numbers of lines
+ uint max_line_size, // >0: max size of a single line
+ uint max_buf_size // >0: max total buffer size
+);
+
+int CloseLineBuffer ( LineBuffer_t *lb );
+ssize_t WriteLineBuffer ( LineBuffer_t *lb, ccp buf, size_t size );
+
+void FixMemListLineBuffer ( LineBuffer_t *lb, bool force );
+uint GetMemListLineBuffer
+(
+ // returns the number of elements
+
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ mem_t **res, // not NULL: store pointer to first element here
+ int max_lines // !0: limit lines, <0: count from end
+);
+
+uint PrintLineBuffer
+(
+ // returns number of written lines
+
+ FILE *f, // output stream
+ int indent, // indention
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ int max_lines, // !0: limit lines, <0: count from end
+ ccp open_line // not NULL: append this text for an open line
+);
+
+///////////////////////////////////////////////////////////////////////////////
+#endif // !__APPLE__
+///////////////////////////////////////////////////////////////////////////////
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// file helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int IsDirectory
+(
+ // analyse filename (last char == '/') and stat(fname)
+
+ ccp fname, // NULL or path
+ int answer_if_empty // answer, if !fname || !*fname
+);
+
+int ExistDirectory
+(
+ // check only real file (stat(fname))
+
+ ccp fname, // NULL or path
+ int answer_if_empty // answer, if !fname || !*fname
+);
+
+bool IsSameFilename ( ccp fn1, ccp fn2 );
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum FindConfigFile_t
+{
+ FCF_HOME = 0x001, // ~/.<SHARE>/<FILENAME>
+ FCF_REL_SHARE = 0x002, // <PROGPATH>/../share/<SHARE>/<FILENAME>
+ FCF_LOC_SHARE = 0x004, // /usr/local/share/<SHARE>/<FILENAME>
+ FCF_USR_SHARE = 0x008, // /usr/share/<SHARE>/<FILENAME>
+ FCF_PROG_SHARE = 0x010, // <PROGPATH>/share/<PROGNAME><EXT>
+ FCF_PROG = 0x020, // <PROGPATH>/<PROGNAME><EXT>
+
+ FCF_STD_SHARE = FCF_REL_SHARE
+ | FCF_LOC_SHARE
+ | FCF_USR_SHARE,
+
+ FCF_STD_PROG = FCF_PROG_SHARE
+ | FCF_PROG,
+
+ FCF_ALL = 0x03f,
+
+ FCF_F_DEBUG = 0x100, // enable logging
+
+}
+FindConfigFile_t;
+
+//-----------------------------------------------------------------------------
+
+// Managed and read by user, ignored by FindConfigFile()
+// Intention: if set globally, call FindConfigFile() with FCF_F_DEBUG
+
+extern bool enable_config_search_log;
+
+//-----------------------------------------------------------------------------
+
+ccp FindConfigFile
+(
+ // returns NULL or found file (alloced)
+
+ FindConfigFile_t mode, // bit field: select search destinations
+ ccp share_name, // NULL or <SHARE>
+ ccp file_name, // NULL or <FILENAME>
+ ccp prog_ext // <EXT>, if NULL use '.' + <FILENAME>
+);
+
+//-----------------------------------------------------------------------------
+
+struct dirent;
+typedef int (*SearchFilesFunc) ( ccp basepath, struct dirent *dent, void * param );
+
+int SearchFiles
+(
+ ccp path1, // not NULL: part #1 of base path
+ ccp path2, // not NULL: part #2 of base path
+ ccp match, // not NULL: filter files by MatchPattern()
+ // if empty: extract from combined path
+ SearchFilesFunc func, // callback function, never NULL
+ void *param // third parameter of func()
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CreatePath
+(
+ ccp path, // path to create
+ bool is_pure_dir // true: 'path' don't contains a filename part
+);
+
+enumError RemoveSource
+(
+ ccp fname, // file to remove
+ ccp dest_fname, // NULL or dest file name
+ // If real pathes are same: don't remove
+ bool print_log, // true: print a log message
+ bool testmode // true: don't remove, log only
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * FindFilename
+(
+ // Find the last part of the path, trailing '/' are ignored
+ // return poiner to found string
+
+ ccp path, // path to analyze, valid string
+ uint * result_len // not NULL: return length of found string
+);
+
+uint NumberedFilename
+(
+ // return used index number or 0, if not modified.
+
+ char * buf, // destination buffer
+ size_t bufsize, // size of 'buf'
+ ccp source, // source filename, may be part of 'buf'
+ // if NULL or empty:
+ ccp ext, // not NULL & not empty: file extension ('.ext')
+ uint ext_mode, // only relevant, if 'ext' is neither NULL not empty
+ // 0: use 'ext' only for detection
+ // 1: replace 'source' extension by 'ext'
+ // 2: append 'ext' if 'source' differ
+ // 3: append 'ext' always
+ bool detect_stdio // true: don't number "-"
+);
+
+///////////////////////////////////////////////////////////////////////////////
+#if 0
+///////////////////////////////////////////////////////////////////////////////
+
+char * SplitSubPath
+(
+ char * buf, // destination buffer
+ size_t buf_size, // size of 'buf'
+ ccp path // source path, if NULL: use 'buf'
+);
+
+enumError LoadFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // skip num of bytes before reading
+ void * data, // destination buffer, size = 'size'
+ size_t size, // size to read
+ int silent, // 0: print all error messages
+ // 1: suppress file size warning
+ // 2: suppress all error messages
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store *max* values to 'fatt'
+);
+
+enumError SaveFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ bool overwrite, // true: force overwriting
+ const void * data, // data to write
+ uint data_size, // size of 'data'
+ FileAttrib_t * fatt // not NULL: set timestamps using this attribs
+);
+
+///////////////////////////////////////////////////////////////////////////////
+#endif
+///////////////////////////////////////////////////////////////////////////////
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// normalize filenames ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * NormalizeFileName
+(
+ // returns a pointer to the NULL terminator within 'buf'
+
+ char * buf, // valid destination buffer
+ uint buf_size, // size of buf
+ ccp source, // NULL or source
+ bool allow_slash, // true: allow '/' in source
+ bool is_utf8 // true: enter UTF-8 mode
+);
+
+///////////////////////////////////////////////////////////////////////////////
+#ifdef __CYGWIN__
+
+ uint IsWindowsDriveSpec
+ (
+ // returns the length of the found windows drive specification (0|2|3)
+
+ ccp src // valid string
+ );
+
+ uint NormalizeFilenameCygwin
+ (
+ // returns a pointer to the NULL terminator within 'buf'
+
+ char * buf, // valid destination buffer
+ uint buf_size, // size of buf
+ ccp src // NULL or source
+ );
+
+ char * AllocNormalizedFilenameCygwin
+ (
+ // returns an alloced buffer with the normalized filename
+
+ ccp source // NULL or string
+ );
+
+#endif // __CYGWIN__
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// FDList_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[FDList_t]]
+
+struct pollfd;
+
+typedef struct FDList_t
+{
+ bool use_poll; // false: use select(), true: use poll()
+
+ u64 now_usec; // set on Clear() and Wait(), result of GetTimeUSec(false)
+ u64 timeout_usec; // next timeout, based on GetTimeUSec(false)
+
+ //--- select() params
+
+ int max_fd; // highest-numbered file descriptor, add 1 on select()
+ fd_set readfds; // ready to read
+ fd_set writefds; // ready to write
+ fd_set exceptfds; // ready to except
+
+ //--- poll() params
+
+ struct pollfd *poll_list; // list of poll parameters
+ uint poll_used; // poll_list: number of used elements
+ uint poll_size; // poll_list: number of alloced elements
+
+ //--- statistics
+
+ uint n_sock; // current number of registered sockets
+ // equals poll_used if poll is used
+ uint wait_count; // total number of waits
+ u64 wait_usec; // total wait time in usec
+ u64 cur_usec; // current wait time in usec
+}
+FDList_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearFDList ( FDList_t *fdl );
+void InitializeFDList ( FDList_t *fdl, bool use_poll );
+void ResetFDList ( FDList_t *fdl );
+
+//-----------------------------------------------------------------------------
+
+// announce new sockets
+void AnnounceFDList ( FDList_t *fdl, uint n );
+struct pollfd * AllocFDList ( FDList_t *fdl, uint n );
+
+uint AddFDList
+(
+ // returns the pool-index if available, ~0 otherwise
+
+ FDList_t *fdl, // valid socket list
+ int sock, // socket to add
+ uint events // bit field: POLLIN|POLLOUT|POLLERR
+);
+
+uint GetEventFDList
+(
+ // returns bit field: POLLIN|POLLOUT|POLLERR
+
+ FDList_t *fdl, // valid socket list
+ int sock, // socket to look for
+ uint poll_index // if use_poll: use the index for a fast search
+);
+
+//-----------------------------------------------------------------------------
+
+// use select() or poll()
+int WaitFDList ( FDList_t *fdl );
+
+// use pselect() or ppoll()
+int PWaitFDList ( FDList_t *fdl, const sigset_t *sigmask );
+
+// return ptr to file path, if begins with 1 of: file: unix: / ./ ../
+ccp CheckUnixSocketPath
+(
+ ccp src, // NULL or source path to analyse
+ int tolerance // <1: 'unix:', 'file:', '/', './' and '../' detected
+ // 1: not 'NAME:' && at relast one '/'
+ // 2: not 'NAME:'
+);
+
+// return mem_t to file path, if begins with 1 of: file: unix: / ./ ../
+mem_t CheckUnixSocketPathMem
+(
+ mem_t src, // NULL or source path to analyse
+ int tolerance // <1: 'unix:', 'file:', '/', './' and '../' detected
+ // 1: not 'NAME:' && at relast one '/'
+ // 2: not 'NAME:'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Catch Output ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+struct CatchOutput_t;
+
+typedef int (*CatchOutputFunc)
+(
+ struct CatchOutput_t *ctrl, // control struct incl. data
+ int call_mode // 0:init, 1:new data, 2:term
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct CatchOutput_t
+{
+ int mode; // -1: disabled, 1:stdout, 2:stderr
+ int pipe_fd[2]; // pipe
+ CatchOutputFunc func; // function
+ GrowBuffer_t buf; // output buffer
+ void *user_ptr; // pointer by user
+}
+CatchOutput_t;
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int CatchIgnoreOutput
+(
+ struct CatchOutput_t *ctrl, // control struct incl. data
+ int call_mode // 0:init, 1:new data, 2:term
+);
+
+//-----------------------------------------------------------------------------
+
+void ResetCatchOutput ( CatchOutput_t *co, uint n );
+
+//-----------------------------------------------------------------------------
+
+enumError CatchOutput
+(
+ ccp command, // command to execute
+ int argc, // num(arguments) in 'argv'; -1: argv is NULL terminated
+ char *const* argv, // list of arguments
+
+ CatchOutputFunc stdout_func, // NULL or function to catch stdout
+ CatchOutputFunc stderr_func, // NULL or function to catch stderr
+ void *user_ptr, // NULL or user defined parameter
+ bool silent // true: suppress error messages
+);
+
+//-----------------------------------------------------------------------------
+
+enumError CatchOutputLine
+(
+ ccp command_line, // command line to execute
+ CatchOutputFunc stdout_func, // NULL or function to catch stdout
+ CatchOutputFunc stderr_func, // NULL or function to catch stderr
+ void *user_ptr, // NULL or user defined parameter
+ bool silent // true: suppress error messages
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan sections ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct SectionInfo_t
+{
+ FILE *f; // source file
+ ccp section; // section name, alloced
+ ccp path; // path name, NULL or alloced
+ int index; // index of section, -1 if not available
+ ParamField_t param; // parameter list
+ void *user_param; // user defined parameter
+}
+SectionInfo_t;
+
+typedef int (*SectionFunc) ( SectionInfo_t *si );
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool FindSection
+(
+ // search file until next section found
+ // => read line and store data into 'si'
+ // => return TRUE, if section found
+
+ SectionInfo_t *si, // valid section info
+ char *buf, // use this buffer for line scanning
+ uint buf_size, // size of 'buf'
+ bool scan_buf // true: buffer contains already a valid
+ // and NULL terminated line
+);
+
+int ScanSections
+(
+ // return the last returned value by On*()
+
+ FILE *f, // source file
+ SectionFunc OnSection, // not NULL: call this function for each section
+ // on result: <0: abort, 0:next section, 1:scan param
+ SectionFunc OnParams, // not NULL: call this function after param scan
+ // on result: <0: abort, continue
+ void *user_param // user defined parameter
+
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan socket type ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[socket_info_t]]
+
+#define AF_INET_ANY (AF_INET6+100)
+
+typedef struct socket_info_t
+{
+ bool is_valid; // true: structure is valid
+
+ int sock; // -1 or used socket
+ int protocol; // -1 | AF_UNIX | AF_INET | AF_INET6 | AF_INET_ANY
+ int type; // -1 | SOCK_STREAM | SOCK_DGRAM | SOCK_RAW | SOCK_SEQPACKET
+ ccp address; // NULL or pointer to relevant address or path
+ bool alloced; // true: address is alloced
+}
+socket_info_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline void InitializeSocketInfo ( socket_info_t *si )
+{
+ DASSERT(si);
+ memset(si,0,sizeof(*si));
+ si->sock = si->protocol = si->type = -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline void ResetSocketInfo ( socket_info_t *si )
+{
+ if (si)
+ {
+ if ( si->alloced )
+ FreeString(si->address);
+ InitializeSocketInfo(si);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ScanSocketInfo
+(
+ // Syntax: [ PREFIX ',' ]... [ PREFIX ':' ] address
+ // returns TRUE, if a protocol or a type is found
+
+ socket_info_t *si, // result, not NULL, will be initialized
+ ccp address, // address to analyze
+ uint tolerance // tolerace:
+ // 0: invalid without prefix
+ // 1: analyse beginning of address part:
+ // '/' or './' or '../' -> AF_UNIX
+ // 2: estimate type
+ // '/' before first ':' -> AF_UNIX
+ // 1.2 | 1.2.3 | 1.2.3.4 -> AF_INET
+ // a HEX:IP combi -> AF_INET6
+ // domain name -> AF_INET_ANY
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool GetSocketInfoBySocket
+(
+ // returns TRUE, if infos retrived
+
+ socket_info_t *si, // result, not NULL, will be initialized
+ int sock, // socket id, maybe -1
+ bool get_name // true: alloc 'address' and copy name
+ // -> call ResetSocketInfo() to free mem
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+// returns a static string
+ccp PrintSocketInfo ( int protocol, int type );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// stat_file_count_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[stat_file_count_t]]
+
+typedef struct stat_file_count_t
+{
+ uint cur_files; // currently open files
+ uint max_files; // max open files
+ uint cur_limit; // current limit
+ uint max_limit; // max limit
+}
+stat_file_count_t;
+
+//-----------------------------------------------------------------------------
+
+extern stat_file_count_t stat_file_count;
+
+uint CountOpenFiles(void);
+void RegisterFileId ( int fd );
+void UpdateOpenFiles ( bool count_current );
+uint SetOpenFilesLimit ( uint limit );
+ccp PrintOpenFiles ( bool count_current ); // print to circ buffer
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_FILE_H
+
diff --git a/project/dclib/dclib-gen-ui.h b/project/dclib/dclib-gen-ui.h
new file mode 100644
index 0000000..482cadd
--- /dev/null
+++ b/project/dclib/dclib-gen-ui.h
@@ -0,0 +1,162 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_GEN_UI_H
+#define DCLIB_GEN_UI_H
+
+#include "dclib-basics.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// text controls ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// \1 : the following text is only for the built in help
+// \2 : the following text is only for the web site
+// \3 : the following text is for both, built in help and web site
+// \4 : replace by '<' for html tags on web site
+
+// Only for tools and commands:
+// \f : Text before: short help for lists
+// Text behind: extended help for tool/command help
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// declarations ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum enumType
+{
+ //----- base types
+
+ T_END = 0x0001, // end of list
+ T_DEF_TOOL = 0x0002, // start of a new tool
+ T_DEF_CMD = 0x0004, // define a command
+ T_SEP_CMD = 0x0008, // define a command separator
+ T_DEF_OPT = 0x0010, // define an option
+ T_SEP_OPT = 0x0020, // define an option separator
+ T_GRP_BEG = 0x0040, // start option definitions for a group
+ T_CMD_BEG = 0x0080, // start option definitions for a command
+ T_CMD_OPT = 0x0100, // allowed option for command
+ T_COPY_CMD = 0x0200, // copy options of other command
+ T_COPY_GRP = 0x0400, // copy options of group
+ T_ALL_OPT = 0x0800, // allow all options
+
+ //----- option flags
+
+ F_OPT_COMMAND = 0x010000, // option is command specific
+ F_OPT_GLOBAL = 0x020000, // option is global
+ F_OPT_MULTIUSE = 0x040000, // multiple usage of option possible
+ F_OPT_PARAM = 0x080000, // option needs a parameter
+ F_OPT_OPTPARAM = 0x100000, // option accepts an optional parameter
+ F_SEPARATOR = 0x200000, // separator element
+ F_SUPERSEDE = 0x400000, // supersedes all other commands and options
+ F_IGNORE = 0x800000, // ignore on command 'HELP OPTIONS'
+
+ F_OPT_XPARAM = F_OPT_PARAM | F_OPT_OPTPARAM,
+
+
+ //----- global flags
+
+ F_HIDDEN = 0x1000000, // tool, command or option is hidden from help
+ F_DEPRECATED = 0x2000000, // command or option is depreacted
+
+
+ //----- option combinations
+
+ T_OPT_C = T_DEF_OPT | F_OPT_COMMAND,
+ T_OPT_CM = T_DEF_OPT | F_OPT_COMMAND | F_OPT_MULTIUSE,
+ T_OPT_CP = T_DEF_OPT | F_OPT_COMMAND | F_OPT_PARAM,
+ T_OPT_CMP = T_DEF_OPT | F_OPT_COMMAND | F_OPT_MULTIUSE | F_OPT_PARAM,
+ T_OPT_CO = T_DEF_OPT | F_OPT_COMMAND | F_OPT_OPTPARAM,
+ T_OPT_CMO = T_DEF_OPT | F_OPT_COMMAND | F_OPT_MULTIUSE | F_OPT_OPTPARAM,
+
+ T_OPT_G = T_DEF_OPT | F_OPT_GLOBAL,
+ T_OPT_GM = T_DEF_OPT | F_OPT_GLOBAL | F_OPT_MULTIUSE,
+ T_OPT_GP = T_DEF_OPT | F_OPT_GLOBAL | F_OPT_PARAM,
+ T_OPT_GMP = T_DEF_OPT | F_OPT_GLOBAL | F_OPT_MULTIUSE | F_OPT_PARAM,
+ T_OPT_GO = T_DEF_OPT | F_OPT_GLOBAL | F_OPT_OPTPARAM,
+ T_OPT_GMO = T_DEF_OPT | F_OPT_GLOBAL | F_OPT_MULTIUSE | F_OPT_OPTPARAM,
+
+ T_OPT_S = T_DEF_OPT | F_OPT_GLOBAL | F_SUPERSEDE,
+
+
+ T_COPT = T_CMD_OPT,
+ T_COPT_M = T_CMD_OPT | F_OPT_MULTIUSE,
+
+
+ //----- hidden options and commands (hide from help)
+
+ H_DEF_TOOL = F_HIDDEN | T_DEF_TOOL,
+ H_DEF_CMD = F_HIDDEN | T_DEF_CMD,
+
+ H_OPT_C = F_HIDDEN | T_OPT_C,
+ H_OPT_CM = F_HIDDEN | T_OPT_CM,
+ H_OPT_CP = F_HIDDEN | T_OPT_CP,
+ H_OPT_CMP = F_HIDDEN | T_OPT_CMP,
+ H_OPT_CO = F_HIDDEN | T_OPT_CO,
+ H_OPT_CMO = F_HIDDEN | T_OPT_CMO,
+
+ H_OPT_G = F_HIDDEN | T_OPT_G,
+ H_OPT_GM = F_HIDDEN | T_OPT_GM,
+ H_OPT_GP = F_HIDDEN | T_OPT_GP,
+ H_OPT_GMP = F_HIDDEN | T_OPT_GMP,
+ H_OPT_GO = F_HIDDEN | T_OPT_GO,
+ H_OPT_GMO = F_HIDDEN | T_OPT_GMO,
+
+ H_COPT = F_HIDDEN | T_COPT,
+ H_COPT_M = F_HIDDEN | T_COPT_M,
+
+} enumType;
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct info_t
+{
+ enumType type; // entry type
+ ccp c_name; // the C name
+ ccp namelist; // list of names
+ ccp param; // name of parameter
+ ccp help; // help text
+
+ int index; // calculated index
+
+} info_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_GEN_UI_H
diff --git a/project/dclib/dclib-gen-ui.inc b/project/dclib/dclib-gen-ui.inc
new file mode 100644
index 0000000..a35f540
--- /dev/null
+++ b/project/dclib/dclib-gen-ui.inc
@@ -0,0 +1,1303 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "dclib-basics.h"
+#include "dclib-ui.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct control_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct control_t
+{
+ FILE * cf; // output c file
+ FILE * hf; // output h file
+ FILE * df; // output def file
+
+ const info_t * info; // pointer to first info_t
+ const info_t * end; // pointer to end of info_t
+
+ int n_cmd; // number of commands
+ int n_grp; // number of helper groups
+ int n_opt_specific; // number of cmd specific options (=OPT__N_SPECIFIC)
+ int n_opt; // number of options (=OPT__N_TOTAL)
+ int n_cmd_opt; // number of options of current command
+ bool need_sep; // separator needed
+ ccp cmd_name; // name of current command
+ ccp opt_prefix; // prefix for option variables, never NULL
+
+ u8 *opt_allow_grp; // grp allowed options: field[n_grp][n_opt_specific]
+ u8 *opt_allow_cmd; // cmd allowed options: field[n_cmd][n_opt_specific]
+
+ StringField_t gopt; // global options
+ StringField_t copt; // command specfic options
+ StringField_t opt_done; // option handled
+
+} control_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static char tabs[] = "\t\t\t\t";
+
+static char sep1[] =
+ "////////////////////////////////////////"
+ "///////////////////////////////////////";
+
+static char sep2[] =
+ "########################################"
+ "#######################################";
+
+//-----------------------------------------------------------------------------
+
+static void print_section ( FILE *f, ccp sep, ccp name )
+{
+ const size_t namelen = strlen(name);
+ const int fill = ( 49 - namelen ) /2;
+ const int fw = 49 - fill;
+ fprintf(f,"\n%.2s\f\n%s\n%.15s%*s%*s%.15s\n%s\n\n",
+ sep, sep, sep, fill, "", -fw, name, sep, sep );
+}
+
+//-----------------------------------------------------------------------------
+
+static void print_info_opt
+(
+ control_t * ctrl,
+ const info_t * info,
+ const info_t * default_info,
+ const info_t * first_info, // NULL or first info of a list of infos
+ ccp cmd_name
+)
+{
+ ASSERT(ctrl);
+ ASSERT(info);
+
+ FILE * cf = ctrl->cf;
+ ASSERT(cf);
+
+ if (cmd_name)
+ fprintf(cf,"static const InfoOption_t option_%s_%s_%s =\n",
+ ctrl->opt_prefix, cmd_name, info->c_name );
+
+ fprintf(cf," {\tOPT_%s, %s, %s, %s, %s, %s, ",
+ info->c_name,
+ info->type & F_OPT_OPTPARAM ? "true" : "false",
+ info->type & F_HIDDEN ? "true" : "false",
+ info->type & F_DEPRECATED ? "true" : "false",
+ info->type & F_IGNORE ? "true" : "false",
+ first_info && info>first_info && info[-1].type&T_SEP_OPT ? "true" : "false" );
+ ccp start = ( default_info ? default_info : info ) -> namelist;
+ if ( *start && start[1] == '|' )
+ {
+ fprintf(cf,"'%c', ",*start);
+ start += 2;
+ }
+ else
+ fprintf(cf,"0, ");
+
+ ccp end = start;
+ while ( *end && *end != '|' )
+ end++;
+ #ifdef TEST0
+ fprintf(cf,"\"%.*s\", // hid=%x, dep=%x, sep=%x\n",
+ (int)(end-start), start,
+ info->type & F_HIDDEN,
+ info->type & F_DEPRECATED,
+ info > ctrl->info ? info[-1].type & T_SEP_OPT : 0 );
+ #else
+ fprintf(cf,"\"%.*s\",\n", (int)(end-start), start );
+ #endif
+
+ ccp param = info->param;
+ ccp help = info->help;
+ if (default_info)
+ {
+ if (!help)
+ help = default_info->help;
+ if (!param)
+ param = default_info->param;
+ }
+
+ if ( info->type & F_OPT_XPARAM || default_info && default_info->type & F_OPT_XPARAM )
+ fprintf(cf,"\t\"%s\",\n", param && *param ? param : "param" );
+ else
+ fprintf(cf,"\t0,\n");
+
+ DumpText(cf,0,0,help,-1,0,"");
+ fprintf(cf,"\n }%c\n\n",cmd_name ? ';' : ',' );
+}
+
+//-----------------------------------------------------------------------------
+
+static void print_opt
+(
+ control_t * ctrl,
+ const info_t * info,
+ const info_t * first_info // NULL or first info of a list of infos
+)
+{
+ ASSERT(ctrl);
+ ASSERT(info);
+
+ ctrl->n_opt++;
+ print_info_opt(ctrl,info,0,first_info,0);
+ fprintf( ctrl->hf, "\tOPT_%s,\n", info->c_name );
+};
+
+//-----------------------------------------------------------------------------
+
+static void print_opt_link ( control_t * ctrl, const info_t * info )
+{
+ ASSERT(ctrl);
+ ASSERT(ctrl->cf);
+ ASSERT(ctrl->opt_prefix);
+ ASSERT(info);
+
+ if (!InsertStringField(&ctrl->opt_done,info->c_name,false))
+ return;
+
+ if ( ctrl->need_sep )
+ {
+ ctrl->need_sep = false;
+ if ( ctrl->n_cmd_opt )
+ fprintf(ctrl->cf,"\n\tOptionInfo + OPT_NONE, // separator\n\n" );
+ }
+
+ if ( !info->help || info->type & T_DEF_OPT )
+ fprintf(ctrl->cf,"\tOptionInfo + OPT_%s,\n", info->c_name );
+ else
+ fprintf(ctrl->cf,"\t&option_%s_%s,\n", ctrl->opt_prefix, info->c_name );
+
+ if (ctrl->cmd_name)
+ {
+ fprintf(ctrl->df,"#:def_cmd_opt( \"%s\", \"%s\", \\\n",
+ ctrl->cmd_name, info->c_name );
+ DumpText(ctrl->df,0,0,info->param,-1,1,", \\\n");
+ DumpText(ctrl->df,0,0,info->help,-1,1," )\n\n");
+ }
+
+ ctrl->n_cmd_opt++;
+}
+
+//-----------------------------------------------------------------------------
+
+static const info_t * print_links_iterator
+ ( control_t * ctrl, const info_t * info, ccp prefix );
+
+//-----------------------------------------------------------------------------
+
+static void print_cmd_links ( control_t * ctrl, const info_t * ref )
+{
+ ASSERT(ctrl);
+ ASSERT(ref);
+
+ const info_t * info;
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ if ( info->type & T_CMD_BEG && !strcmp(info->c_name,ref->c_name) )
+ {
+ print_links_iterator(ctrl,info,"cmd");
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static void print_grp_links ( control_t * ctrl, const info_t * ref )
+{
+ ASSERT(ctrl);
+ ASSERT(ref);
+
+ const info_t * info;
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ if ( info->type & T_GRP_BEG && !strcmp(info->c_name,ref->c_name) )
+ {
+ print_links_iterator(ctrl,info,"grp");
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static const info_t * print_links_iterator
+ ( control_t * ctrl, const info_t * info, ccp prefix )
+{
+ ASSERT(ctrl);
+ ASSERT(info);
+
+ ccp saved_prefix = ctrl->opt_prefix;
+ char buf[100];
+ if (prefix)
+ {
+ snprintf(buf,sizeof(buf),"%s_%s",prefix,info->c_name);
+ ctrl->opt_prefix = buf;
+ };
+
+ for ( info++; info < ctrl->end && !(info->type & (T_CMD_BEG|T_GRP_BEG) ); info++ )
+ {
+ if ( info->type & F_HIDDEN )
+ {
+ // ignored -> do nothing
+ }
+ else if ( info->type & T_SEP_OPT )
+ ctrl->need_sep = true;
+ else if ( info->type & (F_OPT_GLOBAL|T_CMD_OPT) )
+ print_opt_link(ctrl,info);
+ else if ( info->type & T_COPY_CMD )
+ print_cmd_links(ctrl,info);
+ else if ( info->type & T_COPY_GRP )
+ print_grp_links(ctrl,info);
+ }
+
+ ctrl->opt_prefix = saved_prefix;
+ return info;
+}
+
+//-----------------------------------------------------------------------------
+
+static void dump_help ( const info_t * info_cmd )
+{
+ char * temp_param = iobuf;
+ char * temp_help = iobuf + sizeof(iobuf)/8;
+ char * temp_xhelp = iobuf + sizeof(iobuf)/4;
+
+ DumpText(0,temp_param,iobuf+sizeof(iobuf),info_cmd->param,-1,0,"");
+ ccp ff = info_cmd->help ? strchr(info_cmd->help,'\f') : 0;
+ if (ff)
+ {
+ DumpText(0,temp_help,iobuf+sizeof(iobuf),info_cmd->help,ff-info_cmd->help,0,"");
+ DumpText(0,temp_xhelp,iobuf+sizeof(iobuf),ff+1,-1,0,"");
+ }
+ else
+ {
+ DumpText(0,temp_help,iobuf+sizeof(iobuf),info_cmd->help,-1,0,"");
+ StringCopyE(temp_xhelp,iobuf+sizeof(iobuf),"\t0");
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static void print_links ( control_t * ctrl )
+{
+ ASSERT(ctrl);
+ FILE * cf = ctrl->cf;
+ ASSERT(cf);
+
+ print_section(cf,sep1,"InfoOption tabs");
+
+ char * temp_param = iobuf;
+ char * temp_help = iobuf + sizeof(iobuf)/8;
+ char * temp_xhelp = iobuf + sizeof(iobuf)/4;
+ char * sum_beg = iobuf + sizeof(iobuf)/2;
+ char * sum_end = iobuf + sizeof(iobuf);
+ char * sum = sum_beg;
+
+ const info_t * info_cmd = ctrl->info;
+ ASSERT ( info_cmd->type & T_DEF_TOOL );
+ fprintf(cf,"static const InfoOption_t * option_tab_tool[] =\n{\n");
+
+ ctrl->n_cmd_opt = 0;
+ ctrl->need_sep = false;
+
+ print_links_iterator(ctrl,info_cmd,"def");
+ fprintf(cf,"\n\t0\n};\n\n");
+
+ dump_help(info_cmd);
+ sum = snprintfE(sum,sum_end,
+ " {\t0,\n" // id
+ "\tfalse,\n" // hidden
+ "\tfalse,\n" // deprecated
+ "\tfalse,\n" // separator
+ "\t\"%s\",\n" // name1
+ "\t0,\n" // name2
+ "%s,\n" // param
+ "%s,\n" // help
+ "%s,\n" // xhelp
+ "\t%u,\n" // n_opt
+ "\toption_tab_tool,\n" // opt
+ "\t0\n" // opt_allowed
+ " },\n\n"
+ ,info_cmd->c_name
+ ,temp_param
+ ,temp_help
+ ,temp_xhelp
+ ,ctrl->n_cmd_opt
+ );
+
+ bool separator = false;
+ for ( info_cmd++; info_cmd < ctrl->end; info_cmd++ )
+ {
+ if ( info_cmd->type & T_SEP_CMD )
+ separator = true;
+ //if ( !( info_cmd->type & T_DEF_CMD ) || info_cmd->type & F_HIDDEN )
+ if ( !( info_cmd->type & T_DEF_CMD ) )
+ continue;
+
+ fprintf(cf,"static const InfoOption_t * option_tab_cmd_%s[] =\n{\n",
+ info_cmd->c_name);
+
+ ctrl->n_cmd_opt = 0;
+ ctrl->need_sep = false;
+ ctrl->cmd_name = info_cmd->c_name;
+ ResetStringField(&ctrl->opt_done);
+
+ const info_t * info;
+ for ( info = info_cmd; info < ctrl->end; info++ )
+ if ( info->type & T_CMD_BEG && !strcmp(info->c_name,info_cmd->c_name) )
+ {
+ print_links_iterator(ctrl,info,"cmd");
+ break;
+ }
+ fprintf(cf,"\n\t0\n};\n\n");
+
+ ccp name1 = info_cmd->namelist, ptr = name1;
+ while ( *ptr && *ptr != '|' )
+ ptr++;
+ const int len1 = ptr - name1;
+ char name2[100] = "0";
+ if ( *ptr == '|' )
+ {
+ ccp n2 = ++ptr;
+ while ( *ptr && *ptr != '|' )
+ ptr++;
+ if ( ptr > n2 )
+ snprintf(name2,sizeof(name2),"\"%.*s\"",(int)(ptr-n2),n2);
+ }
+
+ dump_help(info_cmd);
+ sum = snprintfE(sum,sum_end,
+ " {\tCMD_%s,\n" // id
+ "\t%s,\n" // hidden
+ "\t%s,\n" // deprecated
+ "\t%s,\n" // separator
+ "\t\"%.*s\",\n" // name1
+ "\t%s,\n" // name2
+ "%s,\n" // param
+ "%s,\n" // help
+ "%s,\n" // xhelp
+ "\t%u,\n" // n_opt
+ "\toption_tab_cmd_%s,\n" // opt
+ "\toption_allowed_cmd_%s\n" // opt_allowed
+ " },\n\n"
+ ,info_cmd->c_name
+ ,info_cmd->type & F_HIDDEN ? "true" : "false"
+ ,info_cmd->type & F_DEPRECATED ? "true" : "false"
+ ,separator ? "true" : "false"
+ ,len1 ,name1
+ ,name2
+ ,temp_param
+ ,temp_help
+ ,temp_xhelp
+ ,ctrl->n_cmd_opt
+ ,info_cmd->c_name
+ ,info_cmd->c_name
+ );
+ separator = false;
+ }
+
+ print_section(cf,sep1,"InfoCommand");
+ fprintf(cf,"static const InfoCommand_t CommandInfo[CMD__N+1] =\n{\n");
+ fputs(sum_beg,cf);
+ fprintf(cf," {0,0,0,0,0,0,0,0,0,0,0,0}\n};\n");
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Generate() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static char var_buf[10000];
+
+//-----------------------------------------------------------------------------
+
+static void CheckUsedName ( StringField_t *list, ccp name, uint len, ccp title )
+{
+ //printf("CHECK: |%.*s|\n",len,name);
+ ccp key = MEMDUP(name,len);
+ if (!InsertStringField(list,key,len))
+ fprintf(stderr,"!UI-WARNING: %s already defined: %.*s\n", title, len, name );
+}
+
+//-----------------------------------------------------------------------------
+
+static enumError Generate ( control_t * ctrl )
+{
+ ASSERT(ctrl);
+ ASSERT(ctrl->info);
+
+ FILE * cf = ctrl->cf;
+ FILE * hf = ctrl->hf;
+ ASSERT(cf);
+ ASSERT(hf);
+
+ FREE(ctrl->opt_allow_grp);
+ FREE(ctrl->opt_allow_cmd);
+ ctrl->opt_allow_grp = ctrl->opt_allow_cmd = 0;
+
+ const info_t *info;
+ StringField_t used_name;
+ InitializeStringField(&used_name);
+
+
+ //----- ui header
+
+ fprintf(cf,text_ui_head);
+ fprintf(hf,text_ui_head);
+
+
+ //----- setup guard
+
+ char guard[100];
+ snprintf(guard,sizeof(guard),"SZS_UI_%s_h",ctrl->info->c_name);
+ char * ptr;
+ for ( ptr = guard; *ptr; ptr++ )
+ *ptr = *ptr == '-' ? '_' : toupper((int)*ptr);
+ fprintf(hf,"\n#ifndef %s\n#define %s\n",guard,guard);
+
+
+ //----- header
+
+ ASSERT( ctrl->info->type & T_DEF_TOOL );
+ ccp tool_name = ctrl->info->c_name;
+
+ fprintf(cf,"#include <getopt.h>\n");
+ fprintf(cf,"#include \"ui-%s.h\"\n",tool_name);
+
+ fprintf(hf,"#include \"dclib-basics.h\"\n");
+ fprintf(hf,"#include \"dclib-ui.h\"\n");
+
+
+ //----- print enum enumOptions & OptionInfo[]
+
+ print_section(cf,sep1,"OptionInfo[]");
+ print_section(hf,sep1,"enum enumOptions");
+
+ char * var_ptr = var_buf;
+ char * var_end = var_buf + sizeof(var_buf);
+ var_ptr = snprintfE(var_ptr,var_end,
+ "//extern const InfoOption_t OptionInfo[OPT__N_TOTAL+1];\n");
+
+ fprintf(cf,
+ "static const InfoOption_t OptionInfo[OPT__N_TOTAL+1] =\n"
+ "{\n"
+ " {0,0,0,0,0,0,0,0,0,0}, // OPT_NONE,\n"
+ "\n"
+ );
+
+ fprintf(hf,
+ "typedef enum enumOptions\n"
+ "{\n"
+ "\tOPT_NONE,\n"
+ "\n"
+ );
+
+ ctrl->n_opt = 1;
+ ctrl->n_opt_specific = 0;
+ if ( ctrl->n_cmd )
+ {
+ fprintf(cf," //----- command specific options -----\n\n");
+ fprintf(hf,"\t//----- command specific options -----\n\n");
+
+ const info_t *first_info = 0;
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ if ( info->type & F_OPT_COMMAND )
+ {
+ if (!first_info)
+ first_info = info;
+ print_opt(ctrl,info,first_info);
+ }
+
+ ctrl->n_opt_specific = ctrl->n_opt;
+
+ fprintf(cf,
+ " {0,0,0,0,0,0,0,0,0,0}, // OPT__N_SPECIFIC == %d\n\n"
+ " //----- global options -----\n\n",
+ ctrl->n_opt_specific );
+
+ fprintf(hf,
+ "\n\tOPT__N_SPECIFIC, // == %d\n\n"
+ "\t//----- global options -----\n\n",
+ ctrl->n_opt_specific );
+ }
+
+ const info_t *first_info = 0;
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ if ( info->type & F_OPT_GLOBAL )
+ {
+ if (!first_info)
+ first_info = info;
+ print_opt(ctrl,info,first_info);
+ }
+
+ fprintf(cf,
+ " {0,0,0,0,0,0,0,0,0,0} // OPT__N_TOTAL == %d\n\n"
+ "};\n"
+ ,ctrl->n_opt );
+
+ fprintf(hf,
+ "\n\tOPT__N_TOTAL // == %d\n\n"
+ "} enumOptions;\n"
+ ,ctrl->n_opt );
+
+ if (ctrl->n_opt_specific)
+ {
+ noTRACE("opt_allowed = ( %2u + %2u ) * %2u\n",
+ ctrl->n_grp, ctrl->n_cmd, ctrl->n_opt_specific );
+ if (ctrl->n_grp)
+ {
+ ctrl->opt_allow_grp = CALLOC(ctrl->n_grp,ctrl->n_opt_specific);
+ }
+ ctrl->opt_allow_cmd = CALLOC(ctrl->n_cmd,ctrl->n_opt_specific);
+ }
+
+
+ //----- print alternate option infos
+
+ bool done = false;
+ const info_t * last_cmd = ctrl->info;
+ ctrl->opt_prefix = "def";
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ {
+ if ( info->type & T_CMD_BEG )
+ {
+ ctrl->opt_prefix = "cmd";
+ last_cmd = info;
+ }
+ else if ( info->type & T_GRP_BEG )
+ {
+ ctrl->opt_prefix = "grp";
+ last_cmd = info;
+ }
+ else if ( info->type & T_CMD_OPT && info->help && *info->help )
+ {
+ if (!done)
+ {
+ print_section(cf,sep1,"alternate option infos");
+ done = true;
+ }
+
+ const info_t * info0;
+ for ( info0 = ctrl->info; info0 < info; info0++ )
+ if ( info0->type & T_DEF_OPT && !strcmp(info->c_name,info0->c_name) )
+ {
+ print_info_opt(ctrl,info,info0,0,last_cmd->c_name);
+ break;
+ }
+ }
+ }
+
+ //----- print enum enumOptionsBit
+
+ if ( ctrl->n_cmd )
+ {
+ print_section(hf,sep1,"enum enumOptionsBit");
+
+ fprintf(hf,
+ "//\t***** only for verification *****\n"
+ "\n"
+ "//typedef enum enumOptionsBit\n"
+ "//{\n"
+ "//\t//----- command specific options -----\n"
+ "//\n"
+ );
+
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ if ( info->type & F_OPT_COMMAND )
+ fprintf(hf,"//\tOB_%s%.*s= 1llu << OPT_%s,\n",
+ info->c_name,
+ ( 28 - (int)strlen(info->c_name) ) / 8, tabs,
+ info->c_name );
+
+ fprintf(hf,"//\n//\t//----- group & command options -----\n");
+
+ for ( info = ctrl->info; info < ctrl->end; )
+ {
+ ccp cmd_name;
+ u8 * opt_allow = 0;
+ if ( info->type & T_CMD_BEG )
+ {
+ cmd_name = info->c_name;
+ fprintf(hf,"//\n//\tOB_CMD_%s%.*s=",
+ info->c_name,
+ ( 24 - (int)strlen(info->c_name) ) / 8, tabs );
+ if (ctrl->opt_allow_cmd)
+ {
+ //PRINT("SELECT ALLOW CMD %u/%s\n",info->index,info->c_name);
+ opt_allow = ctrl->opt_allow_cmd + info->index * ctrl->n_opt_specific;
+ }
+ }
+ else if ( info->type & T_GRP_BEG )
+ {
+ cmd_name = info->c_name;
+ fprintf(hf,"//\n//\tOB_GRP_%s%.*s=",
+ info->c_name,
+ ( 24 - (int)strlen(info->c_name) ) / 8, tabs );
+ if (ctrl->opt_allow_grp)
+ {
+ //PRINT("SELECT ALLOW GRP %u/%s\n",info->index,info->c_name);
+ opt_allow = ctrl->opt_allow_grp + info->index * ctrl->n_opt_specific;
+ }
+ }
+ else
+ {
+ info++;
+ continue;
+ }
+
+ info++;
+ char * dest = iobuf;
+ while ( info < ctrl->end )
+ {
+ if ( info->type & T_ALL_OPT )
+ {
+ dest += sprintf(dest,"\n//\t\t\t\t| ~(u64)0");
+ if (opt_allow)
+ {
+ //PRINT("ALLOW ALL\n");
+ memset(opt_allow,1,ctrl->n_opt_specific);
+ }
+ }
+ else if ( info->type & T_COPY_CMD )
+ {
+ dest += sprintf(dest,"\n//\t\t\t\t| OB_CMD_%s",info->c_name);
+ if (opt_allow)
+ {
+ //PRINT("OR CMD %u/%s\n",info->index,info->c_name);
+ DASSERT(ctrl->opt_allow_cmd);
+ u8 * src = ctrl->opt_allow_cmd + info->index * ctrl->n_opt_specific;
+ u8 * dest = opt_allow;
+ int count = ctrl->n_opt_specific;
+ while ( count-- > 0 )
+ *dest++ |= *src++;
+ }
+ }
+ else if ( info->type & T_COPY_GRP )
+ {
+ dest += sprintf(dest,"\n//\t\t\t\t| OB_GRP_%s",info->c_name);
+ if ( opt_allow && ctrl->opt_allow_grp )
+ {
+ //PRINT("OR GRP %u/%s\n",info->index,info->c_name);
+ u8 * src = ctrl->opt_allow_grp + info->index * ctrl->n_opt_specific;
+ u8 * dest = opt_allow;
+ int count = ctrl->n_opt_specific;
+ while ( count-- > 0 )
+ *dest++ |= *src++;
+ }
+ }
+ else if ( info->type & T_CMD_OPT )
+ {
+ if (FindStringField(&ctrl->copt,info->c_name))
+ {
+ dest += sprintf(dest,"\n//\t\t\t\t| OB_%s",info->c_name);
+ if ( opt_allow && info->index )
+ {
+ //PRINT("ALLOW OPT %u/%s\n",info->index,info->c_name);
+ opt_allow[info->index] = 1;
+ }
+ }
+ else if (!FindStringField(&ctrl->gopt,info->c_name))
+ ERROR0(ERR_SEMANTIC,"Option not defined: %s %s --%s",
+ tool_name, cmd_name, info->c_name );
+ }
+ else if ( info->type & (T_CMD_BEG|T_GRP_BEG) )
+ break;
+ ASSERT( dest < iobuf + sizeof(iobuf) );
+ info++;
+ }
+ if ( dest == iobuf )
+ fprintf(hf," 0,\n");
+ else
+ fprintf(hf,"%s,\n",iobuf+8);
+ }
+
+ fprintf(hf,"//\n//} enumOptionsBit;\n");
+ }
+
+
+ //----- print enum enumCommands & CommandTab[]
+
+ print_section(hf,sep1,"enum enumCommands");
+ fprintf(hf,
+ "typedef enum enumCommands\n"
+ "{\n"
+ "\tCMD__NONE,"
+ );
+
+ if ( ctrl->n_cmd )
+ {
+ print_section(cf,sep1,"CommandTab[]");
+ fputs("\n\n",hf);
+
+ var_ptr = snprintfE(var_ptr,var_end,
+ "//extern const KeywordTab_t CommandTab[];\n");
+
+ fprintf(cf,
+ "static const KeywordTab_t CommandTab[] =\n"
+ "{\n"
+ );
+
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ if ( info->type & T_DEF_CMD )
+ {
+ fprintf( hf, "\tCMD_%s,\n",info->c_name);
+ ccp ptr = info->namelist;
+ while (*ptr)
+ {
+ ccp n1 = ptr;
+ while ( *ptr && *ptr != '|' )
+ ptr++;
+ const int l1 = ptr - n1;
+ CheckUsedName(&used_name,n1,l1,"Command");
+ while ( *ptr == '|' )
+ ptr++;
+ if (*ptr)
+ {
+ ccp n2 = ptr;
+ while ( *ptr && *ptr != '|' )
+ ptr++;
+ const int l2 = ptr - n2;
+ CheckUsedName(&used_name,n2,l2,"Command");
+
+ fprintf(cf,
+ " { CMD_%s,%.*s\"%.*s\",%.*s\"%.*s\",%.*s0 },\n",
+ info->c_name, (20-(int)strlen(info->c_name))/8, tabs,
+ l1, n1, (20-l1)/8, tabs,
+ l2, n2, (20-l2)/8, tabs );
+
+ while ( *ptr == '|' )
+ ptr++;
+ }
+ else
+ fprintf(cf, " { CMD_%s,%.*s\"%.*s\",%.*s0,\t\t0 },\n",
+ info->c_name, (20-(int)strlen(info->c_name))/8, tabs,
+ l1, n1, (20-l1)/8, tabs );
+ }
+ }
+ else if ( info->type == T_SEP_CMD )
+ fprintf(hf,"\n");
+
+ fprintf(cf,
+ "\n { CMD__N,0,0,0 }\n"
+ "};\n"
+ );
+
+ ResetStringField(&used_name);
+ }
+
+ fprintf(hf,
+ "\n\tCMD__N // == %u\n\n"
+ "} enumCommands;\n"
+ , ctrl->n_cmd );
+
+
+ //----- print options
+
+ print_section(cf,sep1,"OptionShort & OptionLong");
+
+ char * dest = iobuf;
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ if ( info->type & T_DEF_OPT && info->namelist[1] == '|' )
+ {
+ CheckUsedName(&used_name,info->namelist,1,"Short option");
+ *dest++ = info->namelist[0];
+ if ( ( info->type & (F_OPT_OPTPARAM|F_OPT_PARAM) ) == F_OPT_PARAM )
+ *dest++ = ':';
+ }
+ *dest = 0;
+ fprintf(cf,"static const char OptionShort[] = \"%s\";\n\n",iobuf);
+ var_ptr = snprintfE(var_ptr,var_end,
+ "//extern const char OptionShort[];\n");
+
+ ccp opt_buf[UIOPT_INDEX_SIZE];
+ memset(opt_buf,0,sizeof(opt_buf));
+ int getopt_idx = UIOPT_LONG_BASE;
+ ResetStringField(&used_name);
+
+ fprintf(cf,"static const struct option OptionLong[] =\n{\n");
+ var_ptr = snprintfE(var_ptr,var_end,
+ "//extern const struct option OptionLong[];\n");
+
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ if ( info->type & T_DEF_OPT )
+ {
+ ccp ptr = info->namelist;
+ const int pmode = (info->type & F_OPT_OPTPARAM)
+ ? 2
+ : (info->type & F_OPT_PARAM)
+ ? 1
+ : 0;
+
+ if ( info->namelist[1] == '|' )
+ {
+ snprintf(iobuf,sizeof(iobuf),"%d, 0, '%c'",
+ pmode, info->namelist[0] );
+ ptr += 2;
+ opt_buf[(u8)(info->namelist[0])] = info->c_name;
+ }
+ else
+ {
+ snprintf(iobuf,sizeof(iobuf),"%d, 0, GO_%s",
+ pmode, info->c_name );
+ ASSERT_MSG( getopt_idx < UIOPT_INDEX_SIZE,
+ "getopt_idx >= UIOPT_INDEX_SIZE [ 0x%x >= 0x%x ]\n",
+ getopt_idx, UIOPT_INDEX_SIZE );
+ if ( getopt_idx < UIOPT_INDEX_SIZE )
+ opt_buf[getopt_idx++] = info->c_name;
+ }
+
+ int indent = 0;
+ while (*ptr)
+ {
+ ccp start = ptr;
+ while ( *ptr && *ptr != '|' )
+ ptr++;
+ const int len = ptr - start;
+ if (len)
+ {
+ CheckUsedName(&used_name,start,len,"Long option");
+ fprintf(cf,"\t%s{ \"%.*s\",%.*s%s },\n",
+ indent ? " " : "", len, start, (26-len-indent)/8, tabs, iobuf );
+ indent = 1;
+ }
+ if (*ptr)
+ ptr++;
+ }
+ }
+ fprintf(cf,"\n\t{0,0,0,0}\n};\n");
+
+
+ //----- print enumGetOpt
+
+ print_section(hf,sep1,"enumGetOpt");
+ fprintf(hf,"typedef enum enumGetOpt\n{");
+
+ // add '?' temporary;
+ ASSERT(!opt_buf['?']);
+ opt_buf['?'] = "_ERR";
+
+ static const int septab[] = { 0, '0', '9'+1, '?', '?'+1,
+ 'A', 'Z'+1, 'a', 'z'+1,
+ UIOPT_LONG_BASE, UIOPT_INDEX_SIZE };
+ const int * sepptr = septab;
+ int i;
+ for ( i = 0; i < UIOPT_INDEX_SIZE; i++ )
+ if ( opt_buf[i] )
+ {
+ if ( i >= *sepptr )
+ {
+ fputc('\n',hf);
+ while ( i >= *sepptr )
+ sepptr++;
+ }
+ if ( i < UIOPT_LONG_BASE )
+ fprintf(hf,"\tGO_%s%.*s= '%c',\n",
+ opt_buf[i], (28-(int)strlen(opt_buf[i]))/8, tabs, i );
+ else if ( i == UIOPT_LONG_BASE )
+ fprintf(hf,"\tGO_%s%.*s= 0x%02x,\n",
+ opt_buf[i], (28-(int)strlen(opt_buf[i]))/8, tabs, i );
+ else
+ fprintf(hf,"\tGO_%s,\n",opt_buf[i]);
+ }
+
+ fprintf(hf,"\n} enumGetOpt;\n");
+ opt_buf['?'] = 0;
+
+
+ //----- print option index
+
+ print_section(cf,sep1,"OptionUsed & OptionIndex");
+
+ fprintf(cf,"static u8 OptionUsed[OPT__N_TOTAL+1] = {0};\n\n");
+ var_ptr = snprintfE(var_ptr,var_end,
+ "//extern u8 OptionUsed[OPT__N_TOTAL+1];\n");
+
+ fprintf(cf,"static const OptionIndex_t OptionIndex[UIOPT_INDEX_SIZE] =\n{\n");
+ var_ptr = snprintfE(var_ptr,var_end,
+ "//extern const OptionIndex_t OptionIndex[UIOPT_INDEX_SIZE];\n"
+ "//UIOPT_INDEX_SIZE := 0x%x = %u\n",
+ UIOPT_INDEX_SIZE, UIOPT_INDEX_SIZE );
+
+ for ( i = 0; i < UIOPT_INDEX_SIZE; )
+ {
+ int start = i;
+ while ( i < UIOPT_INDEX_SIZE && !opt_buf[i] )
+ i++;
+ int len = i - start;
+ while ( len > 0 )
+ {
+ const int now_len = len < 16 ? len : 16 - start % 16;
+ fprintf(cf,"\t/* 0x%0*x */\t %.*s\n",
+ UIOPT_INDEX_FW,
+ start,
+ 2*now_len + now_len/4,
+ "0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0," );
+ start += now_len;
+ len -= now_len;
+ }
+
+ while ( i < UIOPT_INDEX_SIZE && opt_buf[i] )
+ {
+ fprintf(cf,"\t/* 0x%0*x %c */\tOPT_%s,\n",
+ UIOPT_INDEX_FW, i, i > ' ' && i < 0x7f ? i : ' ', opt_buf[i]);
+ i++;
+ }
+ }
+ fprintf(cf,"};\n");
+
+
+ //----- option allowed
+
+ if (ctrl->opt_allow_cmd)
+ {
+ print_section(cf,sep1,"opt_allowed_cmd_*");
+ for ( info = ctrl->info; info < ctrl->end; info++ )
+ {
+ if ( !( info->type & T_DEF_CMD ) )
+ continue;
+
+ fprintf(cf,"static u8 option_allowed_cmd_%s[%u] = // cmd #%u\n{",
+ info->c_name, ctrl->n_opt_specific, info->index );
+
+ int i;
+ u8 * ptr = ctrl->opt_allow_cmd + info->index * ctrl->n_opt_specific;
+ for ( i = 0; i < ctrl->n_opt_specific; i++ )
+ fprintf(cf, "%s%u%s",
+ !(i%30) ? "\n " : !(i%10) ? " " : !(i%5) ? " " : "",
+ ptr[i],
+ i < ctrl->n_opt_specific-1 ? "," : "" );
+
+ fprintf(cf,"\n};\n\n");
+ }
+ }
+
+ //----- InfoCommand
+
+ print_links(ctrl);
+ var_ptr = snprintfE(var_ptr,var_end,
+ "//extern const InfoCommand_t CommandInfo[CMD__N+1];\n");
+
+ //----- InfoUI
+
+ char xname[100];
+ {
+ StringCopyS(xname,sizeof(xname),ctrl->info->c_name);
+ char *ptr;
+ for ( ptr = xname; *ptr; ptr++ )
+ if (!isalnum((int)*ptr))
+ *ptr = '_';
+ }
+
+ print_section(cf,sep1,"InfoUI");
+
+ var_ptr = snprintfE(var_ptr,var_end,
+ "extern const InfoUI_t InfoUI_%s;\n",
+ xname );
+
+ fprintf(cf,
+ "const InfoUI_t InfoUI_%s =\n"
+ "{\n"
+ "\t\"%s\",\n" // tool_name
+ "\t%s\n" // n_cmd
+ "\t%s\n" // cmd_tab
+ "\tCommandInfo,\n" // cmd_info
+ "\t%s\n" // n_opt_specific
+ "\tOPT__N_TOTAL,\n" // n_opt_total
+ "\tOptionInfo,\n" // opt_info
+ "\tOptionUsed,\n" // opt_used
+ "\tOptionIndex,\n" // opt_index
+ "\tOptionShort,\n" // opt_short
+ "\tOptionLong\n" // opt_long
+ "};\n",
+ xname,
+ ctrl->info->c_name,
+ ctrl->n_cmd ? "CMD__N," : "0, // n_cmd",
+ ctrl->n_cmd ? "CommandTab," : "0, // cmd_tab",
+ ctrl->n_cmd ? "OPT__N_SPECIFIC," : "0, // n_opt_specific" );
+
+
+ //----- external vars
+
+ print_section(hf,sep1,"external vars");
+ fputs(var_buf,hf);
+
+
+ //----- terminate
+
+ print_section(cf,sep1,"END");
+ print_section(hf,sep1,"END");
+ fprintf(hf,"#endif // %s\n\n",guard);
+ ResetStringField(&used_name);
+
+ return ERR_OK;
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// dummy functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void print_title ( FILE * f ) {}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// AddTables() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void AddTables ( FILE * df )
+{
+ DASSERT(df);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// main() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ui_main ( int argc, char ** argv )
+{
+ static ccp def_null_name = "/dev/null";
+ FILE * nf = fopen(def_null_name,"wb");
+ if (!nf)
+ {
+ fprintf(stderr,"!!! Can't create file: %s\n",def_null_name);
+ return ERR_CANT_CREATE;
+ }
+
+ static ccp def_fname = "src/ui/ui.def";
+ FILE * df = fopen(def_fname,"wb");
+ if (!df)
+ {
+ fprintf(stderr,"!!! Can't create file: %s\n",def_fname);
+ return ERR_CANT_CREATE;
+ }
+
+ char fname_c[200], fname_c_temp[210];
+ char fname_h[200], fname_h_temp[210];
+ info_t * info = info_tab;
+
+ while ( info->type != T_END )
+ {
+ if ( ! ( info->type & T_DEF_TOOL ) )
+ {
+ fprintf(stderr,"!!! Missing T_DEF_TOOL entry.\n");
+ return ERR_SYNTAX;
+ }
+
+ control_t ctrl;
+ memset(&ctrl,0,sizeof(ctrl));
+ ctrl.df = nf;
+ ctrl.opt_prefix = "";
+ InitializeStringField(&ctrl.gopt);
+ InitializeStringField(&ctrl.copt);
+ InitializeStringField(&ctrl.opt_done);
+
+ if ( !( info->type & F_HIDDEN ) )
+ {
+ ctrl.df = df;
+ snprintf(iobuf,sizeof(iobuf),"Tool '%s'",info->c_name);
+ print_section(df,sep2,iobuf);
+ fprintf(df,"#:def_tool( \"%s\", \\\n",info->c_name);
+ DumpText(df,0,0,info->param,-1,1,", \\\n");
+ DumpText(df,0,0,info->help,-1,1," )\n\n");
+ }
+
+ snprintf(fname_c,sizeof(fname_c),"src/ui/ui-%s.c",info->c_name);
+ StringCat2S(fname_c_temp,sizeof(fname_c_temp),fname_c,".temp");
+ ctrl.cf = fopen(fname_c_temp,"wb");
+ if (!ctrl.cf)
+ {
+ fprintf(stderr,"!!! Can't create file: %s\n",fname_c_temp);
+ return ERR_CANT_CREATE;
+ }
+
+ snprintf(fname_h,sizeof(fname_h),"src/ui/ui-%s.h",info->c_name);
+ StringCat2S(fname_h_temp,sizeof(fname_h_temp),fname_h,".temp");
+ ctrl.hf = fopen(fname_h_temp,"wb");
+ if (!ctrl.hf)
+ {
+ fclose(ctrl.cf);
+ fprintf(stderr,"!!! Can't create file: %s\n",fname_h_temp);
+ return ERR_CANT_CREATE;
+ }
+
+ ctrl.info = info++;
+ while ( ! ( info->type & (T_END|T_DEF_TOOL)) )
+ {
+ if ( info->type & T_DEF_OPT )
+ {
+ if ( info->type & F_OPT_GLOBAL )
+ {
+ InsertStringField(&ctrl.gopt,info->c_name,false);
+ ++ctrl.n_opt;
+ //opt_index = 0 for global options
+ }
+ else
+ {
+ InsertStringField(&ctrl.copt,info->c_name,false);
+ info->index = ++ctrl.n_opt_specific;
+ }
+
+ if ( !info->help )
+ {
+ // copy 'param' and 'help' info from previous tool
+ const info_t * search;
+ for ( search = info; search >= info_tab; search-- )
+ if ( search->type & T_DEF_OPT
+ && search->help
+ && !strcmp(search->c_name,info->c_name) )
+ {
+ info->help = search->help;
+ if (!info->param)
+ info->param = search->param;
+ break;
+ }
+ }
+
+ if ( !( info->type & F_HIDDEN ) )
+ {
+ fprintf(ctrl.df,"#:def_opt( \"%s\", \"%s\", \"%s%s%s%s%s\", \\\n",
+ info->c_name, info->namelist,
+ info->type & F_OPT_COMMAND ? "C" : "",
+ info->type & F_OPT_GLOBAL ? "G" : "",
+ info->type & F_OPT_MULTIUSE ? "M" : "",
+ info->type & F_OPT_PARAM ? "P" : "",
+ info->type & F_OPT_OPTPARAM ? "O" : "" );
+ DumpText(ctrl.df,0,0,info->param,-1,1,", \\\n");
+ DumpText(ctrl.df,0,0,info->help,-1,1," )\n\n");
+ }
+ }
+ else if ( info->type & T_DEF_CMD )
+ {
+ info->index = ++ctrl.n_cmd;
+
+ if ( !info->help )
+ {
+ // copy 'param' and 'help' info from previous tool
+ const info_t * search = info-1;
+ for ( search = info; search >= info_tab; search-- )
+ if ( search->type & T_DEF_CMD
+ && search->help
+ && !strcmp(search->c_name,info->c_name) )
+ {
+ info->help = search->help;
+ if (!info->param)
+ info->param = search->param;
+ break;
+ }
+ }
+
+ if ( !( info->type & F_HIDDEN ) )
+ {
+ fprintf(ctrl.df,"#:def_cmd( \"%s\", \"%s\", \\\n",
+ info->c_name, info->namelist );
+ DumpText(ctrl.df,0,0,info->param,-1,1,", \\\n");
+
+ ccp ff = info->help ? strchr(info->help,'\f') : 0;
+ if (ff)
+ {
+ DumpText(ctrl.df,0,0,info->help,ff-info->help,1,", \\\n");
+ DumpText(ctrl.df,0,0,ff+1,-1,1," )\n\n");
+ }
+ else
+ DumpText(ctrl.df,0,0,info->help,-1,1," )\n\n");
+ }
+ }
+ else if ( info->type & T_GRP_BEG )
+ info->index = ctrl.n_grp++; // NULL based
+ else if ( info->type & (T_CMD_OPT|T_CMD_BEG|T_COPY_GRP|T_COPY_CMD) )
+ {
+ const int type = info->type & T_CMD_OPT ? T_DEF_OPT
+ : info->type & T_COPY_GRP ? T_GRP_BEG
+ : T_DEF_CMD;
+
+ const info_t * search;
+ for ( search = ctrl.info; search < info; search++ )
+ if ( search->type & type
+ && !strcmp(search->c_name,info->c_name) )
+ {
+ //PRINT("COPY INDEX #%x: %s\n",info->type,info->c_name);
+ info->index = search->index;
+ break;
+ }
+ }
+
+ info++;
+ }
+ ctrl.end = info;
+
+ if (ctrl.n_cmd)
+ ctrl.n_cmd++; // one more for CMD_NONE;
+
+ if (ctrl.n_opt_specific)
+ ctrl.n_opt += ++ctrl.n_opt_specific;
+
+ TRACE("N: cmd=%u, grp=%u, opt=%d/%d\n",
+ ctrl.n_cmd, ctrl.n_grp, ctrl.n_opt_specific, ctrl.n_opt );
+ const enumError err = Generate(&ctrl);
+
+ fclose(ctrl.cf);
+ fclose(ctrl.hf);
+ rename(fname_c_temp,fname_c);
+ rename(fname_h_temp,fname_h);
+
+ ResetStringField(&ctrl.gopt);
+ ResetStringField(&ctrl.copt);
+ ResetStringField(&ctrl.opt_done);
+
+ if (err)
+ return err;
+ }
+
+ AddTables(df);
+ print_section(df,sep2,"END");
+ fclose(df);
+ fclose(nf);
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
diff --git a/project/dclib/dclib-mysql.c b/project/dclib/dclib-mysql.c
new file mode 100644
index 0000000..e3ee297
--- /dev/null
+++ b/project/dclib/dclib-mysql.c
@@ -0,0 +1,1560 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <string.h>
+#include <mysql/mysql.h>
+#include <mysql/errmsg.h>
+
+#include "dclib-mysql.h"
+#include "dclib-network.h"
+
+// http://dev.mysql.com/doc/refman/5.1/en/c-api-functions.html
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MySql_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// management
+
+void InitializeMYSQL ( MySql_t *my )
+{
+ #if DEBUG
+ static uint done = 0;
+ if (!done)
+ {
+ done++;
+ TRACE("- MYSQL\n");
+ TRACE_SIZEOF(MySql_t);
+ TRACE_SIZEOF(MySqlResult_t);
+ TRACE_SIZEOF(MySqlStatus_t);
+ TRACE_SIZEOF(st_mysql);
+ TRACE_SIZEOF(st_mysql_res);
+ TRACE_SIZEOF(st_mysql_field);
+ }
+ #endif
+
+ DASSERT(my);
+ memset(my,0,sizeof(*my));
+ my->auto_reconnect = true;
+ my->prefix = EmptyString;
+ InitializeStatusMYSQL(&my->status);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetMYSQL ( MySql_t *my )
+{
+ DASSERT(my);
+ CloseMYSQL(my);
+
+ FREE((char*)my->server);
+ FREE((char*)my->user);
+ FREE((char*)my->password);
+ FREE((char*)my->database);
+ FreeString(my->prefix);
+
+ InitializeMYSQL(my);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError OpenMYSQL ( MySql_t *my, uint loglevel )
+{
+ DASSERT(my);
+ ClearStatusMYSQL(&my->status);
+
+ if (my->mysql)
+ return ERR_OK; // already connected
+
+ if ( !my || !my->server )
+ {
+ if ( loglevel >= MYLL_ERROR )
+ ERROR0(ERR_MISSING_PARAM,"No MySql server defined.\n");
+ return ERR_MISSING_PARAM;
+ }
+
+ NetworkHost_t host;
+ ResolveHost(&host,true,my->server,MYSQL_DEFAULT_PORT,false,true);
+
+ PRINT("MYSQL CONNECT: %s:%u -> %s\n",
+ host.name, host.port, PrintIP4(0,0,host.ip4,host.port) );
+
+ static bool lib_init_done = false;
+ if (!lib_init_done)
+ {
+ noPRINT("mysql_library_init()\n");
+ if (mysql_library_init(0,NULL,NULL))
+ {
+ if ( loglevel >= MYLL_ERROR )
+ ERROR0(ERR_DATABASE,"Can't initialize MySql library.\n");
+ return ERR_DATABASE;
+ }
+ lib_init_done = true;
+ }
+
+ my->mysql = mysql_init(NULL);
+ //HEXDUMP16(0,0,my->mysql,sizeof(*my->mysql));
+ if (!my->mysql)
+ {
+ if ( loglevel >= MYLL_ERROR )
+ ERROR0(ERR_DATABASE,"Can't initialize MySql data.\n");
+ ResetHost(&host);
+ return ERR_DATABASE;
+ }
+
+ if (my->auto_reconnect)
+ {
+ my_bool reconnect = 1;
+ mysql_options(my->mysql,MYSQL_OPT_RECONNECT,&reconnect);
+ }
+
+ noPRINT("connect(%p,%s,%s,%s,%s,%u,,)\n",
+ my->mysql, host.name, my->user, my->password, my->database, host.port );
+ MYSQL *stat = mysql_real_connect ( my->mysql, host.name,
+ my->user, my->password, my->database,
+ host.port, NULL, 0 );
+
+ if (!stat)
+ {
+ GetStatusMYSQL(&my->status,my->mysql);
+ if ( loglevel >= MYLL_ERROR )
+ ERROR0(ERR_CANT_CONNECT,"Can't connect to %s:\n-> %s\n",
+ PrintIP4(0,0,host.ip4,host.port),my->status.message);
+ ResetHost(&host);
+ mysql_close(my->mysql);
+ my->mysql = 0;
+ return ERR_CANT_CONNECT;
+ }
+
+ mysql_set_character_set(my->mysql,"utf8mb4");
+ my->total_connect_count++;
+ ResetHost(&host);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CloseMYSQL ( MySql_t *my )
+{
+ DASSERT(my);
+
+ //--- close results
+
+ FreeResultListMYSQL(my->first_result);
+ my->first_result = 0;
+ FreeResultListMYSQL(my->pool_result);
+ my->pool_result = 0;
+
+
+ //--- close database
+
+ if (my->mysql)
+ {
+ mysql_close(my->mysql);
+ my->mysql = 0;
+ }
+
+
+ //--- reset status
+
+ ClearStatusMYSQL(&my->status);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DefineDatabase
+(
+ MySql_t *my, // valid struct
+ ccp server, // NULL or server name
+ ccp user, // NULL or user name
+ ccp password, // NULL or user password
+ ccp database // NULL or name of database
+)
+{
+ DASSERT(my);
+ CloseMYSQL(my);
+
+ if (server)
+ {
+ FREE((char*)my->server);
+ my->server = STRDUP(server);
+ }
+
+ if (user)
+ {
+ FREE((char*)my->user);
+ my->user = STRDUP(user);
+ }
+
+ if (password)
+ {
+ FREE((char*)my->password);
+ my->password = STRDUP(password);
+ }
+
+ if (database)
+ {
+ FREE((char*)my->database);
+ my->database = STRDUP(database);
+ }
+
+ FreeString(my->prefix);
+ my->prefix = EmptyString;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DefineDatabasePrefix
+(
+ MySql_t *my, // valid struct
+ ccp prefix // NULL or default prefix, only for external usage
+)
+{
+ DASSERT(my);
+
+ FreeString(my->prefix);
+ my->prefix = prefix && *prefix ? STRDUP(prefix) : EmptyString;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static enumError CheckOpenMYSQL ( MySql_t *my, uint loglevel )
+{
+ if (AutoOpenMYSQL(my,loglevel))
+ return ERR_OK;
+
+ if ( loglevel >= MYLL_ERROR )
+ ERROR0(ERR_SEMANTIC,"No database connected.\n");
+ return ERR_SEMANTIC;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// queries
+
+enumError DoQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp query, // query string
+ int query_length // length of 'query', -1 if unknown
+)
+{
+ DASSERT(my);
+ DASSERT(query);
+
+ const u64 start_time = GetTimeUSec(false);
+
+ enumError err = CheckOpenMYSQL(my,loglevel);
+ if (err)
+ return err;
+
+ if ( query_length < 0 )
+ query_length = strlen(query);
+ my->total_query_count++;
+ my->total_query_size += query_length;
+
+ if (mysql_real_query(my->mysql,query,query_length))
+ {
+ TRACE("QUERY: %.*s\n",query_length,query);
+ my->field_count = 0;
+ GetStatusMYSQL(&my->status,my->mysql);
+
+ bool fail = true;
+ if ( my->status.status == CR_SERVER_GONE_ERROR && my->auto_reconnect )
+ {
+ MySqlStatus_t save;
+ MoveStatusMYSQL(&save,true,&my->status);
+ CloseMYSQL(my);
+ if ( OpenMYSQL(my,loglevel) || mysql_real_query(my->mysql,query,query_length) )
+ MoveStatusMYSQL(&my->status,false,&save);
+ else
+ {
+ ClearStatusMYSQL(&save);
+ fail = false;
+ }
+ }
+
+ if (fail)
+ {
+ if ( loglevel >= MYLL_QUERY_ON_ERROR )
+ ERROR0(ERR_DATABASE,"%s (#%d)\n--QUERY--\n%.*s\n",
+ my->status.message, my->status.status,
+ query_length, query );
+ else if ( loglevel >= MYLL_ERROR )
+ ERROR0(ERR_DATABASE,"%s (#%d)\n",
+ my->status.message, my->status.status );
+
+ my->last_duration_usec = GetTimeUSec(false) - start_time;
+ if ( my->max_duration_usec < my->last_duration_usec )
+ my->max_duration_usec = my->last_duration_usec;
+ my->total_duration_usec += my->last_duration_usec;
+ return ERR_DATABASE;
+ }
+ }
+
+ if ( loglevel >= MYLL_QUERY_ALWAYS )
+ ERROR0(ERR_OK,"%.*s\n", query_length, query );
+
+ my->field_count = mysql_field_count(my->mysql);
+ ClearStatusMYSQL(&my->status);
+
+ my->last_duration_usec = GetTimeUSec(false) - start_time;
+ if ( my->max_duration_usec < my->last_duration_usec )
+ my->max_duration_usec = my->last_duration_usec;
+ my->total_duration_usec += my->last_duration_usec;
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PrintArgQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'format'
+)
+{
+ DASSERT(my);
+ DASSERT(format);
+
+ char buf[10000];
+
+ va_list arg2;
+ va_copy(arg2,arg);
+ int stat = vsnprintf(buf,sizeof(buf),format,arg2);
+ va_end(arg2);
+
+ if ( stat < sizeof(buf) )
+ return DoQueryMYSQL(my,loglevel,buf,stat);
+
+
+ //--- buffer too small, use dynamic memory
+
+ noPRINT("PrintQueryMYSQL() -> MALLOC(%u)\n",stat+1);
+
+ char *dest = MALLOC(stat+1);
+ stat = vsnprintf(dest,stat+1,format,arg);
+
+ const enumError err = DoQueryMYSQL(my,loglevel,dest,stat);
+ FREE(dest);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PrintQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+{
+ DASSERT(my);
+ DASSERT(format);
+
+ va_list arg;
+ va_start(arg,format);
+ enumError err = PrintArgQueryMYSQL(my,loglevel,format,arg);
+ va_end(arg);
+ return err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// support of multi queries
+
+mem_t GetFirstQuery ( ccp src, int src_len )
+{
+ if (!src)
+ {
+ mem_t res = {0,0};
+ return res;
+ }
+
+ ccp ptr = src;
+ ccp end = ptr + ( src_len < 0 ? strlen(src) : src_len );
+
+ //--- skip all leading blanks, controls and semicolons
+
+ while ( ptr < end && ( *ptr >= 0 && *ptr <= ' ' || *ptr == ';' ) )
+ ptr++;
+
+ if ( ptr == end )
+ {
+ mem_t res = {ptr,0};
+ return res;
+ }
+
+ ccp start = ptr;
+ while ( ptr < end )
+ {
+ const char ch = *ptr++;
+
+ if ( ch == ';' )
+ {
+ mem_t res = {start,ptr-start-1};
+ return res;
+ }
+
+ if ( ch == '\\' && ptr < end )
+ ptr++;
+ else if ( ch == '\'' || ch == '\"' )
+ {
+ while ( ptr < end )
+ {
+ const char ch2 = *ptr++;
+ if ( ch2 == '\\' && ptr < end )
+ ptr++;
+ else if ( ch2 == ch )
+ break;
+ }
+ }
+ }
+ mem_t res = {start,ptr-start};
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError DoMultiQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp query, // query string
+ int query_length // length of 'query', -1 if unknown
+)
+{
+ DASSERT(my);
+ DASSERT(query);
+
+ mem_t q;
+ q.ptr = query;
+ q.len = query_length < 0 ? strlen(query) : query_length;
+
+ for(;;)
+ {
+ mem_t q1 = GetFirstQuery(q.ptr,q.len);
+ if (!q1.len)
+ break;
+ const enumError err = DoQueryMYSQL(my,loglevel,q1.ptr,q1.len);
+ if (err)
+ return err;
+ q = BehindMem(q,q1.ptr+q1.len);
+ }
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PrintMultiQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+{
+ DASSERT(my);
+ DASSERT(format);
+
+ char buf[10000];
+ va_list arg;
+ va_start(arg,format);
+ int stat = vsnprintf(buf,sizeof(buf),format,arg);
+ va_end(arg);
+
+ if ( stat < sizeof(buf) )
+ return DoMultiQueryMYSQL(my,loglevel,buf,stat);
+
+
+ //--- buffer too small, use dynamic memory
+
+ noPRINT("PrintQueryMYSQL() -> MALLOC(%u)\n",stat+1);
+
+ char *dest = MALLOC(stat+1);
+ va_start(arg,format);
+ stat = vsnprintf(dest,stat+1,format,arg);
+ va_end(arg);
+
+ const enumError err = DoMultiQueryMYSQL(my,loglevel,dest,stat);
+ FREE(dest);
+ return err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// temp buffer support
+
+mem_t CopyMemMYSQL ( MySql_t * my, mem_t * src ) // 'src' may be NULL
+{
+ if ( src && src->len > 0 )
+ {
+ if ( src->len < sizeof(my->tempbuf) && !my->tempbuf_used )
+ {
+ noPRINT("MYSQL: use tempbuf(%u)\n",src->len);
+ memcpy(my->tempbuf,src->ptr,src->len);
+ my->tempbuf[src->len] = 0;
+ my->tempbuf_used = true;
+
+ mem_t res = { my->tempbuf, src->len };
+ return res;
+ }
+
+ return DupMem(*src);
+ }
+
+ mem_t res = {NULL,0};
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FreeMemMYSQL ( MySql_t * my, mem_t mem )
+{
+ DASSERT(my);
+ if ( mem.ptr == my->tempbuf )
+ {
+ noPRINT("MYSQL: free tempbuf\n");
+ my->tempbuf_used = false;
+ }
+ else
+ FREE((char*)mem.ptr);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// fetch single value as result of a query
+
+mem_t FetchScalarMYSQL // FREE the result!
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel // log level for mysql errors and queries
+)
+{
+ DASSERT(my);
+ mem_t ret = {NULL,0};
+
+ MySqlResult_t *res = UseResultMYSQL(my,loglevel);
+ if (res)
+ {
+ ret = CopyMemMYSQL(my,GetNextRowMYSQL(res));
+ while (GetNextRowMYSQL(res)) // get all rows
+ ;
+ FreeResultMYSQL(res);
+ }
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+s64 FetchIntMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ s64 return_default // return this value on non existent result
+)
+{
+ DASSERT(my);
+ mem_t res = FetchScalarMYSQL(my,loglevel);
+ if (!res.len)
+ return return_default;
+
+ char *end;
+ const s64 val = strtoll(res.ptr,&end,10);
+ FreeMemMYSQL(my,res);
+ return end == res.ptr + res.len ? val : return_default;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 FetchUIntMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ u64 return_default // return this value on non existent result
+)
+{
+ DASSERT(my);
+ mem_t res = FetchScalarMYSQL(my,loglevel);
+ if (!res.len)
+ return return_default;
+
+ char *end;
+ const u64 val = strtoull(res.ptr,&end,10);
+ FreeMemMYSQL(my,res);
+ return end == res.ptr + res.len ? val : return_default;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// print query & fetch single value as result
+
+mem_t PrintArgFetchScalarMYSQL
+(
+ // if res.len>0: FREE the result using FreeMemMYSQL(my,mem)
+
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'format'
+)
+{
+ DASSERT(my);
+ DASSERT(format);
+
+ mem_t ret = {NULL,0};
+ if (!PrintArgQueryMYSQL(my,loglevel,format,arg))
+ {
+ MySqlResult_t *res = UseResultMYSQL(my,loglevel);
+ if (res)
+ {
+ ret = CopyMemMYSQL(my,GetNextRowMYSQL(res));
+ while (GetNextRowMYSQL(res)) // get all rows
+ ;
+ FreeResultMYSQL(res);
+ }
+ }
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t PrintFetchScalarMYSQL
+(
+ // if res.len>0: FREE the result using FreeMemMYSQL(my,mem)
+
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+{
+ DASSERT(my);
+ DASSERT(format);
+
+ va_list arg;
+ va_start(arg,format);
+ mem_t res = PrintArgFetchScalarMYSQL(my,loglevel,format,arg);
+ va_end(arg);
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+s64 PrintFetchIntMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ s64 return_default, // return this value on non existent result
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+{
+ DASSERT(my);
+ DASSERT(format);
+
+ va_list arg;
+ va_start(arg,format);
+ mem_t res = PrintArgFetchScalarMYSQL(my,loglevel,format,arg);
+ va_end(arg);
+
+ if (!res.len)
+ return return_default;
+
+ char *end;
+ const s64 val = strtoll(res.ptr,&end,10);
+ FreeMemMYSQL(my,res);
+ return end == res.ptr + res.len ? val : return_default;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 PrintFetchUIntMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ u64 return_default, // return this value on non existent result
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+{
+ DASSERT(my);
+ DASSERT(format);
+
+ va_list arg;
+ va_start(arg,format);
+ mem_t res = PrintArgFetchScalarMYSQL(my,loglevel,format,arg);
+ va_end(arg);
+
+ if (!res.len)
+ return return_default;
+
+ char *end;
+ const u64 val = strtoull(res.ptr,&end,10);
+ FreeMemMYSQL(my,res);
+ return end == res.ptr + res.len ? val : return_default;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// fetch single row
+
+void * FetchSingleRowN
+(
+ // returns NULL on error or emtpy result,
+ // or a temporary buffer => call FREE(result)
+ // following rows will be skipped
+
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ mem_t *dest, // dest buffer, unused elements are set to {0,0}
+ uint n_dest // number of elements in 'dest'
+)
+{
+ DASSERT(my);
+ DASSERT( dest || !n_dest );
+ memset(dest,0,sizeof(*dest)*n_dest);
+
+ char *buf = 0;
+ MySqlResult_t *res = UseResultMYSQL(my,loglevel);
+ if (res)
+ {
+ mem_t *row = GetNextRowMYSQL(res);
+ if (row)
+ {
+ if ( n_dest > res->field_count )
+ n_dest = res->field_count;
+
+ //--- count memory usage and alloc buffer
+
+ uint i, need = 0;
+ for ( i = 0; i < n_dest; i++ )
+ if ( row[i].len )
+ need += row[i].len + 1;
+
+ //--- get row data
+
+ buf = MALLOC(need);
+ char *ptr = buf;
+ for ( i = 0; i < n_dest; i++, row++, dest++ )
+ {
+ if ( row->len )
+ {
+ dest->ptr = ptr;
+ memcpy(ptr,row->ptr,row->len);
+ dest->len = row->len;
+ ptr += row->len;
+ *ptr++ = 0;
+ DASSERT( ptr <= buf+need );
+ }
+ }
+ DASSERT( ptr == buf+need );
+
+
+ //--- get and ignore all remaining rows
+
+ while (GetNextRowMYSQL(res))
+ ;
+ }
+ FreeResultMYSQL(res);
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int FetchSingleRowList
+(
+ // returns -1 on error, 0 on empty result or the number of retrieved field
+ // following rows will be skipped
+
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ mem_list_t *dest, // destination list
+ bool init_dest, // true: initialize 'dest'
+ bool replace_null // true: replace NULL by EmptyString
+)
+{
+ DASSERT(my);
+ DASSERT(dest);
+
+ if (init_dest)
+ InitializeMemList(dest);
+ dest->used = 0;
+
+ int stat = -1;
+ MySqlResult_t *res = UseResultMYSQL(my,loglevel);
+ if (res)
+ {
+ stat = 0;
+ mem_t *row = GetNextRowMYSQL(res);
+ if (row)
+ {
+ stat = res->field_count;
+ AssignMemListN(dest,row,stat,replace_null?3:0);
+
+ //--- get and ignore all remaining rows
+ while (GetNextRowMYSQL(res))
+ ;
+ }
+ FreeResultMYSQL(res);
+ }
+
+ return stat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// count tables and columns
+
+int CountTablesMYSQL
+(
+ MySql_t *my, // valid DB connection
+ ccp table, // like "table"; if NULL|EMPTY: count tables
+ ccp database // database name; if NULL|EMPTY: use default DB
+)
+{
+ DASSERT(my);
+
+ int stat = -1;
+ mem_t db = EscapeStringMYSQL(my, database && *database ? database : my->database );
+ mem_t tab = EscapeStringMYSQL(my, table && *table ? table : "%" );
+
+ stat = PrintFetchIntMYSQL(my,MYLL_QUERY_ON_ERROR,-1,
+ "SELECT COUNT(DISTINCT TABLE_NAME) FROM information_schema.COLUMNS\n"
+ "WHERE TABLE_SCHEMA = \"%s\" && TABLE_NAME like \"%s\"\n",
+ db.ptr, tab.ptr );
+
+ FreeString(tab.ptr);
+ FreeString(db.ptr);
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CountColumnsMYSQL
+(
+ MySql_t *my, // valid DB connection
+ ccp column, // column name; if NULL|EMPTY: count columns
+ ccp table, // like "table"; if NULL|EMPTY: all tables
+ ccp database // database name; if NULL|EMPTY: use default DB
+)
+{
+ DASSERT(my);
+
+ int stat = -1;
+ mem_t db = EscapeStringMYSQL(my, database && *database ? database : my->database );
+ mem_t tab = EscapeStringMYSQL(my, table && *table ? table : "%" );
+
+ if ( column && *column )
+ {
+ mem_t col = EscapeStringMYSQL(my,column);
+ stat = PrintFetchIntMYSQL(my,MYLL_QUERY_ON_ERROR,-1,
+ "SELECT COUNT(*) FROM information_schema.COLUMNS\n"
+ "WHERE TABLE_SCHEMA = \"%s\" && TABLE_NAME like \"%s\""
+ " && COLUMN_NAME = \"%s\"\n",
+ db.ptr, tab.ptr, col.ptr );
+ FreeString(col.ptr);
+ }
+ else
+ {
+ stat = PrintFetchIntMYSQL(my,MYLL_QUERY_ON_ERROR,-1,
+ "SELECT COUNT(*) FROM information_schema.COLUMNS\n"
+ "WHERE TABLE_SCHEMA = \"%s\" && TABLE_NAME like \"%s\"\n",
+ db.ptr, tab.ptr );
+ }
+
+ FreeString(tab.ptr);
+ FreeString(db.ptr);
+ return stat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// helpers
+
+mem_t EscapeMYSQL ( MySql_t *my, cvp ptr, int len )
+{
+ DASSERT(my);
+
+ if ( len < 0 )
+ len = ptr ? strlen(ptr) : 0;
+
+ mem_t res;
+ if ( !ptr || !len )
+ {
+ res.ptr = EmptyString;
+ res.len = 0;
+ }
+ else
+ {
+ char *buf = MALLOC(2*len+1);
+ res.ptr = buf;
+ res.len = mysql_real_escape_string(my->mysql,buf,ptr,len);
+ }
+ return res;
+}
+
+//-----------------------------------------------------------------------------
+
+mem_t EscapeMYSQLCirc ( MySql_t *my, cvp ptr, int len )
+{
+ DASSERT(my);
+
+ if ( len < 0 )
+ len = ptr ? strlen(ptr) : 0;
+
+ mem_t res;
+ if ( !ptr || !len )
+ {
+ res.ptr = EmptyString;
+ res.len = 0;
+ }
+ else if ( len >= CIRC_BUF_MAX_ALLOC )
+ {
+ // no chance
+ res.ptr = 0;
+ res.len = 0;
+ }
+ else
+ {
+ char buf[2*CIRC_BUF_MAX_ALLOC+10];
+ res.len = mysql_real_escape_string(my->mysql,buf,ptr,len);
+ if ( res.len < CIRC_BUF_MAX_ALLOC )
+ res.ptr = CopyCircBuf(buf,res.len+1);
+ else
+ {
+ res.ptr = 0;
+ res.len = 0;
+ }
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t QuoteMYSQL ( MySql_t *my, cvp ptr, int len, bool null_if_empty )
+{
+ if ( len < 0 )
+ len = ptr ? strlen(ptr) : 0;
+
+ mem_t res;
+ if ( !ptr || !len && null_if_empty )
+ {
+ res.ptr = STRDUP("NULL");
+ res.len = 4;
+ }
+ else if (!len)
+ {
+ res.ptr = EmptyQuote;
+ res.len = 0;
+ }
+ else
+ {
+ char *buf = MALLOC(2*len+3);
+ res.ptr = buf;
+ res.len = mysql_real_escape_string(my->mysql,buf+1,ptr,len) + 2;
+ buf[0] = buf[res.len-1] = '"';
+ buf[res.len] = 0;
+ }
+ return res;
+}
+
+//-----------------------------------------------------------------------------
+
+mem_t QuoteMYSQLCirc ( MySql_t *my, cvp ptr, int len, bool null_if_empty )
+{
+ if ( len < 0 )
+ len = ptr ? strlen(ptr) : 0;
+
+ mem_t res;
+ if ( !ptr || !len && null_if_empty )
+ {
+ res.ptr = CopyCircBuf("NULL",5);
+ res.len = 4;
+ }
+ else if (!len)
+ {
+ res.ptr = EmptyQuote;
+ res.len = 2;
+ }
+ else if ( len >= CIRC_BUF_MAX_ALLOC-2 )
+ {
+ // no chance
+ res.ptr = 0;
+ res.len = 0;
+ }
+ else
+ {
+ char buf[2*CIRC_BUF_MAX_ALLOC+10];
+ res.len = mysql_real_escape_string(my->mysql,buf+1,ptr,len) + 2;
+ if ( res.len < CIRC_BUF_MAX_ALLOC )
+ {
+ buf[0] = buf[res.len-1] = '"';
+ buf[res.len] = 0;
+ res.ptr = CopyCircBuf(buf,res.len+1);
+ }
+ else
+ {
+ res.ptr = 0;
+ res.len = 0;
+ }
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TransferStats_t GetStatsMYSQL ( MySql_t *my )
+{
+ TransferStats_t tf;
+ memset(&tf,0,sizeof(tf));
+
+ if (my)
+ {
+ tf.conn_count = my->total_connect_count;
+ tf.recv_count = my->total_row_count;
+ tf.recv_size = my->total_field_size;
+ tf.send_count = my->total_query_count;
+ tf.send_size = my->total_query_size;
+ }
+ return tf;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MySqlResult_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void InsertResultMYSQL ( MySqlResult_t ** first, MySqlResult_t * res )
+{
+ DASSERT(first);
+ DASSERT(res);
+ DASSERT( res != *first ); // already cleared!
+
+ //--- remove res from previous list
+
+ if (res->prev_result)
+ res->prev_result->next_result = res->next_result;
+ if (res->next_result)
+ res->next_result->prev_result = res->prev_result;
+
+ //--- insert at top of the new list
+
+ res->prev_result = 0;
+ res->next_result = *first;
+ if (*first)
+ (*first)->prev_result = res;
+ *first = res;
+
+ FREE(res->row);
+ res->row = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MySqlResult_t * UseResultMYSQL ( MySql_t *my, uint loglevel )
+{
+ DASSERT(my);
+ enumError err = CheckOpenMYSQL(my,loglevel);
+ if (err)
+ {
+ AssignStatusMYSQL(&my->status,1,"No connection to database!",1);
+ return NULL;
+ }
+
+ MYSQL_RES *myres = mysql_use_result(my->mysql);
+ if (!myres)
+ {
+ GetStatusMYSQL(&my->status,my->mysql);
+ if ( loglevel >= MYLL_ERROR )
+ ERROR0(ERR_DATABASE,"%s\n",my->status.message);
+ return NULL;
+ }
+ my->total_result_count++;
+
+ MySqlResult_t *res;
+ if (my->pool_result)
+ {
+ res = my->pool_result;
+ my->pool_result = res->next_result;
+ }
+ else
+ res = CALLOC(1,sizeof(*res));
+ InsertResultMYSQL(&my->first_result,res);
+
+ res->mysql = my;
+ res->myres = myres;
+ res->loglevel = loglevel;
+ res->row_count = 0;
+ res->field_count = mysql_num_fields(myres);
+ res->row = CALLOC(sizeof(*res->row),res->field_count);
+
+ GetStatusMYSQL(&res->status,my->mysql);
+
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FreeResultMYSQL ( MySqlResult_t * res )
+{
+ if (res)
+ {
+ MySql_t *my = res->mysql;
+ DASSERT(my);
+
+ CloseResultMYSQL(res);
+ ClearStatusMYSQL(&res->status);
+ if ( my->first_result == res )
+ my->first_result = res->next_result;
+ InsertResultMYSQL(&my->pool_result,res);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CloseResultMYSQL ( MySqlResult_t * res )
+{
+ DASSERT(res);
+ if (res->myres)
+ {
+ mysql_free_result(res->myres);
+ res->myres = 0;
+ }
+ FREE(res->row);
+ res->row = 0;
+ res->field_count = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void FreeResultListMYSQL ( MySqlResult_t * first )
+{
+ while (first)
+ {
+ MySqlResult_t *res = first;
+ first = res->next_result;
+ CloseResultMYSQL(res);
+ ClearStatusMYSQL(&res->status);
+ FREE(res);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t * GetNextRowMYSQL ( MySqlResult_t * res )
+{
+ DASSERT(res);
+ if ( !res || !res->myres || !res->row )
+ return NULL;
+
+ MYSQL_ROW myrow = mysql_fetch_row(res->myres);
+ ulong *mylen = mysql_fetch_lengths(res->myres);
+ res->myrow = myrow;
+ if ( !myrow || !mylen )
+ {
+ FREE(res->row);
+ res->row = 0;
+ return NULL;
+ }
+ res->mysql->total_row_count++;
+
+ mem_t *dest = res->row;
+ uint n = res->field_count;
+ res->mysql->total_field_count += n;
+ while ( n-- > 0 )
+ {
+ dest->ptr = *myrow++;
+ dest->len = *mylen++;
+ res->mysql->total_field_size += dest->len;
+ noPRINT("%3u: |%.*s|\n",dest->len,dest->len<50?dest->len:50,dest->ptr);
+ dest++;
+ }
+
+ return res->row;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MySqlStatus_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearStatusMYSQL ( MySqlStatus_t *stat )
+{
+ DASSERT(stat);
+ if (stat->alloced)
+ {
+ FREE((char*)stat->message);
+ stat->alloced = false;
+ }
+ stat->message = stat->msgbuf;
+ stat->msgbuf[0] = 0;
+ stat->status = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetStatusMYSQL ( MySqlStatus_t *stat, MYSQL *my )
+{
+ DASSERT(stat);
+ if (my)
+ AssignStatusMYSQL(stat,mysql_errno(my),mysql_error(my),0);
+ else
+ ClearStatusMYSQL(stat);
+
+ return stat->status;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AssignStatusMYSQL
+(
+ MySqlStatus_t *stat, // valid struct
+ int stat_code, // error status to assign
+ ccp message, // NULL or pointer to message
+ int copy_mode // 0:copy message, 1:use ptr, 2:use+free ptr
+)
+{
+ DASSERT(stat);
+ ClearStatusMYSQL(stat);
+ stat->status = stat_code;
+
+ if (message)
+ {
+ if ( copy_mode > 0 )
+ {
+ stat->message = message;
+ stat->alloced = copy_mode > 1;
+ }
+ else
+ {
+ const uint len = strlen(message);
+ if ( len < sizeof(stat->msgbuf) )
+ {
+ memcpy(stat->msgbuf,message,len+1);
+ stat->message = stat->msgbuf;
+ stat->alloced = false;
+ }
+ else
+ {
+ stat->message = MEMDUP(message,len);
+ stat->alloced = true;
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CopyStatusMYSQL
+(
+ MySqlStatus_t *dest, // NULL or initialized status
+ bool init_dest, // true: initialize 'dest'
+ const MySqlStatus_t *src // NULL (clear dest)
+ // or initialized status (assign to dest)
+)
+{
+ if (dest)
+ {
+ if (init_dest)
+ InitializeStatusMYSQL(dest);
+
+ if ( dest != src )
+ {
+ if (src)
+ AssignStatusMYSQL(dest,src->status,src->message,0);
+ else
+ ClearStatusMYSQL(dest);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MoveStatusMYSQL
+(
+ MySqlStatus_t *dest, // NULL or initialized status
+ bool init_dest, // true: initialize 'dest'
+ MySqlStatus_t *src // NULL (clear dest)
+ // or initialized status (move to dest)
+)
+{
+ if ( dest != src )
+ {
+ if (dest)
+ {
+ if (init_dest)
+ InitializeStatusMYSQL(dest);
+ else
+ ClearStatusMYSQL(dest);
+
+ if (src)
+ {
+ memcpy(dest,src,sizeof(*dest));
+ if ( src->message == src->msgbuf )
+ dest->message = dest->msgbuf;
+ memset(src,0,sizeof(*src));
+ }
+ }
+ else if (src)
+ ClearStatusMYSQL(src);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintStatusMYSQL
+(
+ MySqlStatus_t *stat, // valid struct
+ int stat_code, // error status to assign
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+{
+ DASSERT(stat);
+ DASSERT(format);
+
+ char buf[10000];
+ va_list arg;
+ va_start(arg,format);
+ int len = vsnprintf(buf,sizeof(buf),format,arg);
+ va_end(arg);
+
+ if ( len < sizeof(buf) )
+ AssignStatusMYSQL(stat,stat_code,buf,0);
+ else
+ {
+ //--- buffer too small, use dynamic memory
+
+ noPRINT("PrintQueryMYSQL() -> MALLOC(%u)\n",stat+1);
+
+ char *dest = MALLOC(len+1);
+ va_start(arg,format);
+ len = vsnprintf(dest,len+1,format,arg);
+ va_end(arg);
+
+ AssignStatusMYSQL(stat,stat_code,dest,2);
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MySqlServerStats_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError GetMySqlServerStats ( MySql_t *my, MySqlServerStats_t *stat )
+{
+ DASSERT(my);
+ DASSERT(stat);
+ InitializeMySqlServerStats(stat);
+
+ static const char query[]
+ = "show status where variable_name in"
+ " ('Uptime','Max_used_connections','Connections','Queries')";
+
+ enumError err
+ = DoQueryMYSQL(my,MYLL_QUERY_ON_ERROR,query,sizeof(query)-1);
+ if (!err)
+ {
+ MySqlResult_t *res = UseResultMYSQL(my,false);
+ if (res)
+ {
+ for(;;)
+ {
+ mem_t *row = GetNextRowMYSQL(res);
+ if (!row)
+ break;
+
+ //printf("|%s|%s|\n",row[0].ptr,row[1].ptr);
+ switch(row[0].ptr[0])
+ {
+ case 'C':
+ if (!strcmp(row[0].ptr,"Connections"))
+ stat->connections = strtoull(row[1].ptr,0,10);
+ break;
+
+ case 'M':
+ if (!strcmp(row[0].ptr,"Max_used_connections"))
+ stat->max_connections = strtoull(row[1].ptr,0,10);
+ break;
+
+ case 'Q':
+ if (!strcmp(row[0].ptr,"Queries"))
+ stat->queries = strtoull(row[1].ptr,0,10);
+ break;
+
+ case 'U':
+ if (!strcmp(row[0].ptr,"Uptime"))
+ stat->uptime = strtoull(row[1].ptr,0,10);
+ break;
+ }
+ }
+ FreeResultMYSQL(res);
+ }
+ }
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MySqlServerStats_t * Add3MySqlServerStats
+(
+ // return dest
+ // calculate: dest = src1 + src2
+
+ MySqlServerStats_t *dest, // NULL or destination (maybe same as source)
+ const MySqlServerStats_t *src1, // NULL or first source
+ const MySqlServerStats_t *src2 // NULL or second source
+)
+{
+ if (dest)
+ {
+ if (!src1)
+ {
+ if (src2)
+ memcpy(dest,src2,sizeof(*dest));
+ else
+ InitializeMySqlServerStats(dest);
+ }
+ else if (!src2)
+ {
+ DASSERT(src1);
+ memcpy(dest,src1,sizeof(*dest));
+ }
+ else
+ {
+ DASSERT(src1);
+ DASSERT(src2);
+
+ dest->max_connections = src1->max_connections > src2->max_connections
+ ? src1->max_connections : src2->max_connections;
+ dest->uptime = src1->uptime + src2->uptime;
+ dest->connections = src1->connections + src2->connections;
+ dest->queries = src1->queries + src2->queries;
+ }
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MySqlServerStats_t * Sub3MySqlServerStats
+(
+ // return dest
+ // calculate: dest = src1 - src2
+
+ MySqlServerStats_t *dest, // NULL or destination (maybe same as source)
+ const MySqlServerStats_t *src1, // NULL or first source
+ const MySqlServerStats_t *src2 // NULL or second source
+)
+{
+ if (dest)
+ {
+ if (!src1)
+ {
+ if (src2)
+ {
+ MySqlServerStats_t temp;
+ InitializeMySqlServerStats(&temp);
+ Sub3MySqlServerStats(dest,&temp,src2);
+ }
+ else
+ InitializeMySqlServerStats(dest);
+ }
+ else if (!src2)
+ {
+ DASSERT(src1);
+ memcpy(dest,src1,sizeof(*dest));
+ }
+ else
+ {
+ DASSERT(src1);
+ DASSERT(src2);
+ dest->max_connections = src1->max_connections;
+ dest->uptime = src1->uptime - src2->uptime;
+ dest->connections = src1->connections - src2->connections;
+ dest->queries = src1->queries - src2->queries;
+ }
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MySqlServerStats_t * Max2MySqlServerStats
+(
+ // return dest
+ // calculate: dest := max(dest,src)
+
+ MySqlServerStats_t *dest, // NULL or destination (maybe same as source)
+ const MySqlServerStats_t *src // NULL or source
+)
+{
+ if ( src && dest )
+ {
+ if ( dest->max_connections < src->max_connections )
+ dest->max_connections = src->max_connections;
+
+ if ( dest->uptime < src->uptime )
+ dest->uptime = src->uptime;
+
+ if ( dest->connections < src->connections )
+ dest->connections = src->connections;
+
+ if ( dest->queries < src->queries )
+ dest->queries = src->queries;
+ }
+ return dest;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
diff --git a/project/dclib/dclib-mysql.h b/project/dclib/dclib-mysql.h
new file mode 100644
index 0000000..3fdb617
--- /dev/null
+++ b/project/dclib/dclib-mysql.h
@@ -0,0 +1,544 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_MYSQL_H
+#define DCLIB_MYSQL_H 1
+
+#include <mysql/mysql.h>
+
+#include "dclib-types.h"
+#include "dclib-debug.h"
+#include "dclib-basics.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// mysql definitions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct st_mysql st_mysql;
+typedef struct st_mysql_res st_mysql_res;
+typedef struct st_mysql_field st_mysql_field;
+
+#define MYSQL_DEFAULT_PORT 3306
+
+struct MySql_t;
+
+typedef enum LogLevelMYSQL
+{
+ MYLL_SILENT,
+ MYLL_ERROR,
+ MYLL_QUERY_ON_ERROR,
+ MYLL_QUERY_ALWAYS,
+ MYLL_ALL
+}
+LogLevelMYSQL;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MySqlStatus_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct MySqlStatus_t
+{
+ int status; // status of last operation
+ ccp message; // pointer to last error message
+ bool alloced; // 'message' is alloced
+ char msgbuf[120]; // buffer for short messages
+}
+MySqlStatus_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MySqlResult_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[MySqlResult_t]]
+
+typedef struct MySqlResult_t
+{
+ //--- linked double-list for management
+
+ struct MySqlResult_t *prev_result; // NULL or link to previous result
+ struct MySqlResult_t *next_result; // NULL or link to next result
+ struct MySql_t *mysql; // related MySql_t struct
+
+ //--- mysql result and status
+
+ MYSQL_RES *myres; // mysql result
+ LogLevelMYSQL loglevel; // log level for mysql errors and queries
+ MySqlStatus_t status; // last error status
+
+ //--- rows and fields
+
+ int row_count; // number of read rows
+ int field_count; // number of fields in a row
+ mem_t *row; // list with 'field_count' entries
+ MYSQL_ROW myrow; // mysql row
+
+}
+MySqlResult_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MySql_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[MySql_t]]
+
+typedef struct MySql_t
+{
+ //--- database definitions, strings are alloced
+
+ ccp server; // server
+ ccp user; // user
+ ccp password; // password
+ ccp database; // database name
+ ccp prefix; // default prefix, only for external usage
+ bool auto_connect; // true: auto connect if closed
+ bool auto_reconnect; // true(default): try 1 reconnect on connection lost
+
+ //--- mysql data
+
+ MYSQL *mysql; // mysql connection
+ uint field_count; // mysql_field_count() of last query
+ MySqlStatus_t status; // last error status
+
+ MySqlResult_t *first_result; // pointer to first result
+ MySqlResult_t *pool_result; // list with unused objects
+
+ //--- a small temporary buffer for e.g. small results
+
+ char tempbuf[120]; // temporary buffer
+ bool tempbuf_used; // true: 'tempbuf' is used
+
+ //--- statistics
+
+ uint total_connect_count; // total number of connections (usualy 1)
+ uint total_query_count; // total number of queries
+ u64 total_query_size; // total size of all queries
+ uint total_result_count; // total number of fetched results
+ u64 total_row_count; // total number of fetched rows
+ u64 total_field_count; // total number of fetched fields
+ u64 total_field_size; // total size of all fetched fields
+
+ //--- both timers count the execution time of DoQueryMYSQL()
+ //--- inclusive database reopen and error handling.
+ u64 last_duration_usec; // duration of last query
+ u64 max_duration_usec; // max duration of all queries
+ u64 total_duration_usec; // total duration of all queries
+}
+MySql_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// interface for MySql_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeMYSQL ( MySql_t *my );
+void ResetMYSQL ( MySql_t *my );
+enumError OpenMYSQL ( MySql_t *my, uint loglevel );
+void CloseMYSQL ( MySql_t *my );
+
+void DefineDatabase
+(
+ MySql_t *my, // valid struct
+ ccp server, // NULL or server name
+ ccp user, // NULL or user name
+ ccp password, // NULL or user password
+ ccp database // NULL or name of database
+);
+
+void DefineDatabasePrefix
+(
+ MySql_t *my, // valid struct
+ ccp prefix // NULL or default prefix, only for external usage
+);
+
+//-----------------------------------------------------------------------------
+
+static inline bool IsOpenMYSQL ( MySql_t *my ) // 'my' may be NULL
+{
+ return my && my->mysql;
+}
+
+static inline bool AutoOpenMYSQL ( MySql_t *my, uint loglevel ) // 'my' may be NULL
+{
+ return my
+ && ( my->mysql || my->auto_connect && OpenMYSQL(my,loglevel) == ERR_OK );
+}
+
+//-----------------------------------------------------------------------------
+
+enumError DoQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp query, // query string
+ int query_length // length of 'query', -1 if unknown
+);
+
+enumError PrintArgQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'format'
+);
+
+enumError PrintQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+__attribute__ ((__format__(__printf__,3,4)));
+
+//-----------------------------------------------------------------------------
+// multi queries
+
+mem_t GetFirstQuery ( ccp src, int src_len );
+
+enumError DoMultiQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp query, // query string
+ int query_length // length of 'query', -1 if unknown
+);
+
+enumError PrintMultiQueryMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+__attribute__ ((__format__(__printf__,3,4)));
+
+//-----------------------------------------------------------------------------
+// temp buffer for small results as optimization
+
+mem_t CopyMemMYSQL ( MySql_t * my, mem_t * src ); // 'src' may be NULL
+void FreeMemMYSQL ( MySql_t * my, mem_t mem );
+
+//-----------------------------------------------------------------------------
+// fetch single value as result of a query
+
+mem_t FetchScalarMYSQL // call FreeScalarMYSQL() for the result
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel // log level for mysql errors and queries
+);
+
+s64 FetchIntMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ s64 return_default // return this value on non existent result
+);
+
+u64 FetchUIntMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ u64 return_default // return this value on non existent result
+);
+
+//-----------------------------------------------------------------------------
+// print query & fetch single value as result
+
+mem_t PrintArgFetchScalarMYSQL
+(
+ // if res.len>0: FREE the result using FreeMemMYSQL(my,mem)
+
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'format'
+);
+
+mem_t PrintFetchScalarMYSQL
+(
+ // if res.len>0: FREE the result using FreeMemMYSQL(my,mem)
+
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+__attribute__ ((__format__(__printf__,3,4)));
+
+s64 PrintFetchIntMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ s64 return_default, // return this value on non existent result
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+__attribute__ ((__format__(__printf__,4,5)));
+
+u64 PrintFetchUIntMYSQL
+(
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ u64 return_default, // return this value on non existent result
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+__attribute__ ((__format__(__printf__,4,5)));
+
+//-----------------------------------------------------------------------------
+// fetch single row
+
+void * FetchSingleRowN
+(
+ // returns NULL on error or emtpy result,
+ // or a temporary buffer => call FREE(result)
+ // following rows will be skipped
+
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ mem_t *dest, // dest buffer, unused elements are set to {0,0}
+ uint n_dest // number of elements in 'dest'
+);
+
+int FetchSingleRowList
+(
+ // returns -1 on error, 0 on empty result or the number of retrieved field
+ // following rows will be skipped
+
+ MySql_t *my, // valid struct
+ LogLevelMYSQL loglevel, // log level for mysql errors and queries
+ mem_list_t *dest, // destination list
+ bool init_dest, // true: initialize 'dest'
+ bool replace_null // true: replace NULL by EmptyString
+);
+
+//-----------------------------------------------------------------------------
+// count tables and columns
+
+int CountTablesMYSQL
+(
+ MySql_t *my, // valid DB connection
+ ccp table, // like "table"; if NULL|EMPTY: count tables
+ ccp database // database name; if NULL|EMPTY: use default DB
+);
+
+int CountColumnsMYSQL
+(
+ MySql_t *my, // valid DB connection
+ ccp column, // column name; if NULL|EMPTY: count columns
+ ccp table, // like "table"; if NULL|EMPTY: all tables
+ ccp database // database name; if NULL|EMPTY: use default DB
+);
+
+//-----------------------------------------------------------------------------
+// helpers
+
+static inline int LastInsertIdMYSQL ( MySql_t *my )
+ { return mysql_insert_id(my->mysql); }
+
+struct TransferStats_t GetStatsMYSQL ( MySql_t *my );
+
+
+//-----------------------------------------------------------------------------
+// escaping and quoting
+
+// All functions are robust for NULL pointers.
+// If len<0 und ptr!=0, then strlen(ptr) is used.
+// The `circ´ variants return a buffer alloced by GetCircBuf() with
+// valid pointer and null terminated. If result is too large
+// (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+// The result of `circ´ variants must not be freed!
+// All others results must be freed by `FreeString(mem.ptr)´.
+
+//---------------------------------------------------
+// In general: Use faster `circ´ variants if you can
+// guarantee, that src_len < CIRC_BUF_MAX_ALLOC/2.
+//---------------------------------------------------
+
+ mem_t EscapeMYSQL ( MySql_t *my, cvp ptr, int len );
+ mem_t EscapeMYSQLCirc ( MySql_t *my, cvp ptr, int len );
+static inline mem_t EscapeMemMYSQL ( MySql_t *my, mem_t src );
+static inline mem_t EscapeMemMYSQLCirc ( MySql_t *my, mem_t src );
+static inline mem_t EscapeStringMYSQL ( MySql_t *my, ccp src );
+static inline mem_t EscapeStringMYSQLCirc(MySql_t *my, ccp src );
+
+ mem_t QuoteMYSQL ( MySql_t *my, cvp ptr, int len, bool null_if_empty );
+ mem_t QuoteMYSQLCirc ( MySql_t *my, cvp ptr, int len, bool null_if_empty );
+static inline mem_t QuoteMemMYSQL ( MySql_t *my, mem_t src, bool null_if_empty );
+static inline mem_t QuoteMemMYSQLCirc ( MySql_t *my, mem_t src, bool null_if_empty );
+static inline mem_t QuoteStringMYSQL ( MySql_t *my, ccp src, bool null_if_empty );
+static inline mem_t QuoteStringMYSQLCirc( MySql_t *my, ccp src, bool null_if_empty );
+
+//-----------------------------------------------------------------------------
+// inlines of escaping and quoting
+
+static inline mem_t EscapeMemMYSQL ( MySql_t *my, mem_t src )
+ { return EscapeMYSQL(my,src.ptr,src.len); }
+static inline mem_t EscapeMemMYSQLCirc ( MySql_t *my, mem_t src )
+ { return EscapeMYSQLCirc(my,src.ptr,src.len); }
+static inline mem_t EscapeStringMYSQL ( MySql_t *my, ccp src )
+ { return EscapeMYSQL(my,src,-1); }
+static inline mem_t EscapeStringMYSQLCirc( MySql_t *my, ccp src )
+ { return EscapeMYSQLCirc(my,src,-1); }
+
+static inline mem_t QuoteMemMYSQL ( MySql_t *my, mem_t src, bool null_if_empty )
+ { return QuoteMYSQL(my,src.ptr,src.len,null_if_empty); }
+static inline mem_t QuoteMemMYSQLCirc ( MySql_t *my, mem_t src, bool null_if_empty )
+ { return QuoteMYSQLCirc(my,src.ptr,src.len,null_if_empty); }
+static inline mem_t QuoteStringMYSQL ( MySql_t *my, ccp src, bool null_if_empty )
+ { return QuoteMYSQL(my,src,-1,null_if_empty); }
+static inline mem_t QuoteStringMYSQLCirc ( MySql_t *my, ccp src, bool null_if_empty )
+ { return QuoteMYSQLCirc(my,src,-1,null_if_empty); }
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// interface MySqlResult_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+MySqlResult_t * UseResultMYSQL ( MySql_t * my, uint loglevel );
+void FreeResultMYSQL ( MySqlResult_t * res ); // res maybe NULL
+void CloseResultMYSQL ( MySqlResult_t * res );
+
+void FreeResultListMYSQL ( MySqlResult_t * first_result );
+
+mem_t * GetNextRowMYSQL ( MySqlResult_t * res );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// interface for MySqlStatus_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static inline void InitializeStatusMYSQL ( MySqlStatus_t *stat )
+{
+ DASSERT(stat);
+ memset(stat,0,sizeof(*stat));
+ stat->message = stat->msgbuf;
+}
+
+void ClearStatusMYSQL ( MySqlStatus_t *stat );
+uint GetStatusMYSQL ( MySqlStatus_t *stat, MYSQL *my );
+
+void AssignStatusMYSQL
+(
+ MySqlStatus_t *stat, // valid struct
+ int stat_code, // error status to assign
+ ccp message, // NULL or pointer to message
+ int copy_mode // 0:copy message, 1:use ptr, 2:use+free ptr
+);
+
+void CopyStatusMYSQL
+(
+ MySqlStatus_t *dest, // NULL or initialized status
+ bool init_dest, // true: initialize 'dest'
+ const MySqlStatus_t *src // NULL (clear dest)
+ // or initialized status (assign to dest)
+);
+
+void MoveStatusMYSQL
+(
+ MySqlStatus_t *dest, // NULL or initialized status
+ bool init_dest, // true: initialize 'dest'
+ MySqlStatus_t *src // NULL (clear dest)
+ // or initialized status (move to dest)
+);
+
+void PrintStatusMYSQL
+(
+ MySqlStatus_t *stat, // valid struct
+ int stat_code, // error status to assign
+ ccp format, // format string for vsnprintf()
+ ... // parameters for 'format'
+)
+__attribute__ ((__format__(__printf__,3,4)));
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// test_mysql_stats() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[MySqlServerStats_t]]
+
+typedef struct MySqlServerStats_t
+{
+ u32 max_connections; // maximal used connections
+ s32 uptime; // uptime in seconds
+ s64 connections; // number of total connections
+ s64 queries; // number of total queries
+}
+MySqlServerStats_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline void InitializeMySqlServerStats ( MySqlServerStats_t *stat )
+ { DASSERT(stat); memset(stat,0,sizeof(*stat)); }
+
+enumError GetMySqlServerStats ( MySql_t *my, MySqlServerStats_t *stat );
+
+MySqlServerStats_t * Add3MySqlServerStats
+(
+ // return dest
+ // calculate: dest = src1 + src2
+
+ MySqlServerStats_t *dest, // NULL or destination (maybe same as source)
+ const MySqlServerStats_t *src1, // NULL or first source
+ const MySqlServerStats_t *src2 // NULL or second source
+);
+
+MySqlServerStats_t * Sub3MySqlServerStats
+(
+ // return dest
+ // calculate: dest = src1 - src2
+
+ MySqlServerStats_t *dest, // NULL or destination (maybe same as source)
+ const MySqlServerStats_t *src1, // NULL or first source
+ const MySqlServerStats_t *src2 // NULL or second source
+);
+
+MySqlServerStats_t * Max2MySqlServerStats
+(
+ // return dest
+ // calculate: dest := max(dest,src)
+
+ MySqlServerStats_t *dest, // NULL or destination (maybe same as source)
+ const MySqlServerStats_t *src // NULL or source
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_MYSQL_H
+
diff --git a/project/dclib/dclib-network-linux.c b/project/dclib/dclib-network-linux.c
new file mode 100644
index 0000000..900714e
--- /dev/null
+++ b/project/dclib/dclib-network-linux.c
@@ -0,0 +1,461 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ifaddrs.h>
+#include <sys/ioctl.h>
+
+#ifdef SYSTEM_LINUX
+ #include <linux/route.h>
+#endif
+
+#include "dclib-network.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// SendRawUDP() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SendRawUDP
+(
+ // Capability CAP_NET_RAW needed (or effective user ID of 0).
+
+ int sock, // RAW socket to use,
+ // if -1: open and close private socket
+
+ ccp send_addr, // NULL or sender address:port
+ u32 send_ip4, // fall back IP4, if 'send_addr' empty
+ u16 send_port, // default sender port
+
+ ccp recv_addr, // NULL or receiver address:port
+ u32 recv_ip4, // fall back IP4, if 'recv_addr' empty
+ u16 recv_port, // default receiver port
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ uint log_mode // 0:silent, >0:print errors,
+ // >=0x10: hexdump data, 'lmode' bytes max
+)
+{
+ DASSERT( data || !size );
+
+
+ //--- get addresses
+
+ NetworkHost_t send_host;
+ if ( send_addr && *send_addr )
+ {
+ if (!ResolveHost(&send_host,true,send_addr,send_port,false,false))
+ {
+ if (log_mode)
+ ERROR1(ERR_ERROR,"Can't resolve sender host: %s\n",send_addr);
+ return ERR_ERROR;
+ }
+ }
+ else
+ {
+ send_host.sa.sin_family = AF_INET;
+ send_host.sa.sin_addr.s_addr = htonl(send_ip4);
+ send_host.sa.sin_port = htons(send_port);
+ }
+
+
+ NetworkHost_t recv_host;
+ if ( recv_addr && *recv_addr )
+ {
+ if (!ResolveHost(&recv_host,true,recv_addr,recv_port,false,false))
+ {
+ if (log_mode)
+ ERROR1(ERR_ERROR,"Can't resolve sender host: %s\n",recv_addr);
+ return ERR_ERROR;
+ }
+ }
+ else
+ {
+ recv_host.sa.sin_family = AF_INET;
+ recv_host.sa.sin_addr.s_addr = htonl(recv_ip4);
+ recv_host.sa.sin_port = htons(recv_port);
+ }
+
+ return SendRawUDPsa(sock,&send_host.sa,&recv_host.sa,data,size,log_mode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SendRawUDPsa
+(
+ // Capability CAP_NET_RAW needed (or effective user ID of 0).
+
+ int sock, // RAW socket to use,
+ // if -1: open and close private socket
+
+ struct sockaddr_in *sa_send, // sockaddr of sender
+ struct sockaddr_in *sa_recv, // sockaddr of receiver
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ uint log_mode // 0:silent, >0:print errors,
+ // >=0x10: hexdump data, 'lmode' bytes max
+)
+{
+ DASSERT(sa_send);
+ DASSERT(sa_recv);
+ DASSERT( data || !size );
+ if ( size > MAX_UDP_PACKET_DATA )
+ size = MAX_UDP_PACKET_DATA;
+
+ TRACE("Send %zu+%u bytes from %s to %s\n",
+ sizeof(ip4_head_t) + sizeof(udp_head_t), size,
+ PrintIP4sa(0,0,sa_send),
+ PrintIP4sa(0,0,sa_recv) );
+
+
+ //--- open socket
+
+ enumError err = ERR_OK;
+
+ const bool own_sock = sock == -1;
+ if (own_sock)
+ {
+ sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
+ if ( sock == -1 )
+ {
+ if (log_mode)
+ ERROR1(ERR_CANT_CREATE,"Can't create RAW socket.\n");
+ err = ERR_CANT_CREATE;
+ goto abort;
+ }
+
+ int on = 1;
+ setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
+ }
+
+
+ //--- setup packet
+
+ udp_packet_t pkt;
+ const uint total_len = SetupRawUDPsa(&pkt,sa_send,sa_recv,data,size);
+ if ( log_mode >= 0x10 )
+ HexDump16( stdwrn, 0, 0, &pkt,
+ total_len < log_mode ? total_len : log_mode );
+
+
+ //--- send data
+
+ //HEXDUMP16(0,0,&pkt,total_len);
+ //HexDump16(stderr,0,0,&pkt,total_len);
+
+ int stat = sendto(sock,&pkt,total_len,0,(struct sockaddr*)sa_recv,sizeof(*sa_recv));
+ if ( stat == -1 )
+ {
+ ERROR1(ERR_ERROR,"Can't send to sock %d\n",sock);
+ if (log_mode)
+ ERROR1(ERR_WRITE_FAILED,"Can't send to socket %d\n",sock);
+ err = ERR_WRITE_FAILED;
+ goto abort;
+ }
+
+
+ //--- close socket and terminate
+
+ abort:
+ if ( own_sock && sock != -1 )
+ close(sock);
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SetupRawUDPsa
+(
+ // returns total packet len (mabe with limited 'size')
+
+ udp_packet_t *pkt, // paket header to setup (cleared and written)
+
+ struct sockaddr_in *sa_send, // sockaddr of sender
+ struct sockaddr_in *sa_recv, // sockaddr of receiver
+ const void *data, // data to send
+ uint size // size of 'data'
+)
+{
+ DASSERT(pkt);
+ DASSERT(sa_send);
+ DASSERT(sa_recv);
+ DASSERT( data || !size );
+ if ( size > MAX_UDP_PACKET_DATA )
+ size = MAX_UDP_PACKET_DATA;
+
+
+ //--- determine sender ip4
+
+ u32 sender_ip4 = sa_send->sin_addr.s_addr;
+ if (!sender_ip4)
+ {
+ RouteIP4_t rt;
+ if (FindGatewayIP4(&rt,sa_recv->sin_addr.s_addr))
+ sender_ip4 = GetIP4ByInterface(rt.iface);
+ }
+
+
+ //--- create data structure
+
+ static const u8 ip4_head[]
+ = { 0x45,0x00, 0x00,0x00, 0x12,0x34, 0x40,0x00, 0x40,0x11 };
+
+ const uint total_len = sizeof(ip4_head_t) + sizeof(udp_head_t) + size;
+
+ memset(pkt,0,sizeof(*pkt));
+ memcpy(&pkt->ip4,ip4_head,sizeof(ip4_head));
+ memcpy(pkt->data,data,size);
+
+ pkt->ip4.ip_src = sender_ip4;
+ pkt->ip4.ip_dest = sa_recv->sin_addr.s_addr;
+ pkt->ip4.total_len = htons(total_len);
+ pkt->ip4.checksum = CalcChecksumIP4(&pkt->ip4,sizeof(pkt->ip4));
+
+ pkt->udp.port_src = sa_send->sin_port;
+ pkt->udp.port_dest = sa_recv->sin_port;
+ pkt->udp.data_len = htons(size+8);
+ pkt->udp.checksum = CalcChecksumUDP(&pkt->ip4,&pkt->udp,data);
+
+
+ //--- terminate
+
+ //HEXDUMP16(0,0,&pkt,total_len);
+ //HexDump16(stderr,0,0,&pkt,total_len);
+
+ return total_len;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Routing Support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool OpenRouteScanIP4
+(
+ // returns TRUE on success and FALSE if no element is found
+ RouteIP4_t *rt // valid data, will be initialized
+)
+{
+ DASSERT(rt);
+ memset(rt,0,sizeof(*rt));
+
+ rt->index = -1;
+ rt->col_iface = 0;
+ rt->col_dest = 1;
+ rt->col_mask = 7;
+ rt->col_gate = 2;
+ rt->col_flags = 3;
+
+ #ifdef __CYGWIN__
+ return false;
+ #else
+ rt->f = fopen("/proc/net/route","rb");
+ if (!rt->f)
+ return false;
+
+ char *ptr = fgets(rt->buf,sizeof(rt->buf),rt->f);
+ if (!ptr)
+ return false;
+
+ uint col;
+ for ( col = 0; *ptr; col++ )
+ {
+ while ( *ptr == ' ' || *ptr == '\t' )
+ ptr++;
+ if (!*ptr)
+ break;
+ char *beg = ptr;
+ while ( *ptr && *ptr != ' ' && *ptr != '\t' )
+ ptr++;
+ if (*ptr)
+ *ptr++ = 0;
+
+ if (!strcasecmp(beg,"Iface")) rt->col_iface = col;
+ else if (!strcasecmp(beg,"Destination"))rt->col_dest = col;
+ else if (!strcasecmp(beg,"Mask")) rt->col_mask = col;
+ else if (!strcasecmp(beg,"Gateway")) rt->col_gate = col;
+ else if (!strcasecmp(beg,"Flags")) rt->col_flags = col;
+ }
+
+ TRACE("f=%p, COLS: if=%d, dest=%d, mask=%d, gate=%d, flags=%d\n",
+ rt->f, rt->col_iface, rt->col_dest,
+ rt->col_mask, rt->col_gate, rt->col_flags );
+
+ return NextRouteScanIP4(rt);
+
+ #endif // !__CYGWIN__
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool NextRouteScanIP4
+(
+ // returns TRUE on success and FALSE if no more element is found
+ RouteIP4_t *rt // valid data
+)
+{
+ DASSERT(rt);
+
+ #ifdef __CYGWIN__
+ return false;
+ #else
+ char *ptr = fgets(rt->buf,sizeof(rt->buf),rt->f);
+ if (!ptr)
+ {
+ rt->index = -1;
+ return false;
+ }
+
+ uint col;
+ for ( col = 0; *ptr; col++ )
+ {
+ while ( *ptr == ' ' || *ptr == '\t' )
+ ptr++;
+ if (!*ptr)
+ break;
+ char *beg = ptr;
+ while ( *ptr && *ptr != ' ' && *ptr != '\t' )
+ ptr++;
+ if (*ptr)
+ *ptr++ = 0;
+
+ if ( col == rt->col_iface ) rt->iface = beg;
+ else if ( col==rt->col_dest ) rt->dest = strtoul(beg,0,16);
+ else if ( col==rt->col_mask ) rt->mask = strtoul(beg,0,16);
+ else if ( col==rt->col_gate ) rt->gate = strtoul(beg,0,16);
+ else if ( col==rt->col_flags ) rt->flags = strtoul(beg,0,16);
+ }
+
+ return true;
+ #endif // !__CYGWIN__
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CloseRouteScanIP4
+(
+ RouteIP4_t *rt // valid data
+)
+{
+ #ifndef __CYGWIN__
+ DASSERT(rt);
+ if (rt->f)
+ {
+ fclose(rt->f);
+ rt->f = 0;
+ }
+ #endif // !__CYGWIN__
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool FindGatewayIP4
+(
+ // returns TRUE if gateway found
+ RouteIP4_t *rt, // valid data, will be initialized
+ u32 ip4 // search gateway for this address (network byte order)
+)
+{
+ DASSERT(rt);
+
+ bool found;
+ for ( found = OpenRouteScanIP4(rt); found; found = NextRouteScanIP4(rt) )
+ {
+ #ifdef __CYGWIN__
+ if ( rt->dest == ( ip4 & rt->mask ))
+ break;
+ #else
+ if ( !(rt->flags & RTF_UP) )
+ continue;
+
+ if ( rt->flags & RTF_HOST )
+ {
+ if ( rt->dest == ip4 )
+ break;
+ }
+ else if ( rt->dest == ( ip4 & rt->mask ))
+ break;
+ #endif
+ }
+ CloseRouteScanIP4(rt);
+ return found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u32 GetIP4ByInterface
+( // return 0 or IP4 in network byte order
+ ccp iface // interface name. If NULL: Use default gateway
+)
+{
+ RouteIP4_t rt;
+ if ( !iface || !*iface )
+ {
+ if (!FindGatewayIP4(&rt,0))
+ return 0;
+ iface = rt.iface;
+ }
+
+ int sock = socket(AF_INET,SOCK_DGRAM,0);
+ if ( sock == -1 )
+ return 0;
+
+ struct ifreq ifr;
+ memset(&ifr,0,sizeof(ifr));
+ ifr.ifr_addr.sa_family = AF_INET;
+ strncpy(ifr.ifr_name,iface,sizeof(ifr.ifr_name)-1);
+ int stat = ioctl(sock,SIOCGIFADDR,&ifr);
+ close(sock);
+ if ( stat == -1 )
+ return 0;
+
+ struct sockaddr_in *sa = (struct sockaddr_in*)&ifr.ifr_addr;
+ return sa->sin_addr.s_addr;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-network-linux.h b/project/dclib/dclib-network-linux.h
new file mode 100644
index 0000000..fc09ed8
--- /dev/null
+++ b/project/dclib/dclib-network-linux.h
@@ -0,0 +1,171 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_NETWORK_LINUX_H
+#define DCLIB_NETWORK_LINUX_H 1
+
+#ifndef DCLIB_NETWORK_H
+ #include "dclib-network.h"
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// IP interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SendRawUDP
+(
+ // Capability CAP_NET_RAW needed (or effective user ID of 0).
+
+ int sock, // RAW socket to use,
+ // if -1: open and close private socket
+
+ ccp send_addr, // NULL or sender address:port
+ u32 send_ip4, // fall back IP4, if 'send_addr' empty
+ u16 send_port, // default sender port
+
+ ccp recv_addr, // NULL or receiver address:port
+ u32 recv_ip4, // fall back IP4, if 'recv_addr' empty
+ u16 recv_port, // default receiver port
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ uint log_mode // 0:silent, >0:print errors,
+ // >=0x10: hexdump data, 'lmode' bytes max
+);
+
+//-----------------------------------------------------------------------------
+
+enumError SendRawUDPsa
+(
+ // Capability CAP_NET_RAW needed (or effective user ID of 0).
+
+ int sock, // RAW socket to use,
+ // if -1: open and close private socket
+
+ struct sockaddr_in *sa_send, // sockaddr of sender
+ struct sockaddr_in *sa_recv, // sockaddr of receiver
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ uint log_mode // 0:silent, >0:print errors,
+ // >=0x10: hexdump data, 'lmode' bytes max
+);
+
+//-----------------------------------------------------------------------------
+
+uint SetupRawUDPsa
+(
+ // returns total packet len (mabe with limited 'size')
+
+ udp_packet_t *pkt, // paket header to setup (cleared and written)
+
+ struct sockaddr_in *sa_send, // sockaddr of sender
+ struct sockaddr_in *sa_recv, // sockaddr of receiver
+ const void *data, // data to send
+ uint size // size of 'data'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Routing Support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// all IP4 in network byte order
+
+typedef struct RouteIP4_t
+{
+ //--- returned data
+
+ int index; // entry index, starts with 0, incremented for each found
+ // -1: invalid data
+
+ char *iface; // pointer into 'buf': interface name
+ u32 dest; // destination address, network byte order
+ u32 mask; // destination network mask, network byte order
+ u32 gate; // gateway address, network byte order
+ u32 flags; // routing flags
+
+ //--- internal data
+
+ FILE *f; // open file
+ uint col_iface; // column index for 'iface'
+ uint col_dest; // column index for 'dest'
+ uint col_mask; // column index for 'mask'
+ uint col_gate; // column index for 'gate'
+ uint col_flags; // column index for 'flags'
+ char buf[200]; // line buffer for /proc/net/route
+}
+RouteIP4_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool OpenRouteScanIP4
+(
+ // returns TRUE on success and FALSE if no element is found
+ RouteIP4_t *rt // valid data, will be initialized
+);
+
+bool NextRouteScanIP4
+(
+ // returns TRUE on success and FALSE if no more element is found
+ RouteIP4_t *rt // valid data
+);
+
+void CloseRouteScanIP4
+(
+ RouteIP4_t *rt // valid data
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool FindGatewayIP4
+(
+ // returns TRUE if gateway found
+ RouteIP4_t *rt, // valid data, will be initialized
+ u32 ip4 // search gateway for this address (network byte order)
+);
+
+u32 GetIP4ByInterface
+( // return 0 or IP4 in network byte order
+ ccp iface // interface name. If NULL: Use default gateway
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_NETWORK_LINUX_H
+
+
diff --git a/project/dclib/dclib-network.c b/project/dclib/dclib-network.c
new file mode 100644
index 0000000..a1fb253
--- /dev/null
+++ b/project/dclib/dclib-network.c
@@ -0,0 +1,4546 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+
+#include "dclib-network.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __APPLE__
+ #ifndef SOCK_NONBLOCK
+ #define SOCK_NONBLOCK 0
+ #endif
+#endif
+
+#ifndef POLLRDHUP
+ #define POLLRDHUP 0
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// host name support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool ResolveHost
+(
+ // returns TRUE, if a hostname is detected
+ // if result is FALSE => check host->filename
+
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ bool init_host, // true: initialize 'host'
+ ccp name, // name to analyze
+ int default_port, // use this as port, if no other is found
+ bool check_filename, // true: check for unix filename
+ bool store_name // true: setup 'host->name'
+)
+{
+ return ResolveHostMem( host, init_host, MemByString(name),
+ default_port, check_filename, store_name );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ResolveHostMem
+(
+ // returns TRUE, if a hostname is detected
+ // if result is FALSE => check host->filename
+
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ bool init_host, // true: initialize 'host'
+ mem_t name, // name to analyze
+ int default_port, // use this as port, if no other is found
+ bool check_filename, // true: check for unix filename
+ bool store_name // true: setup 'host->name'
+)
+{
+ Setup_ConnectTCP_Hook(false);
+
+ DASSERT(host);
+ if (!init_host)
+ FREE((char*)host->name);
+ InitializeHost(host);
+ host->port = default_port;
+
+ if (check_filename)
+ {
+ mem_t res = CheckUnixSocketPathMem(name,0);
+ host->filename = res.ptr;
+ if (host->filename)
+ return false;
+ }
+
+ char buf[1000];
+
+ ccp ptr = name.ptr;
+ ccp end = ptr + name.len;
+ if ( ptr < end
+ && ( *ptr >= 'a' && *ptr <= 'z' || *ptr >= 'A' && *ptr <= 'Z' ))
+ {
+ while ( ptr < end
+ && ( *ptr >= 'a' && *ptr <= 'z'
+ || *ptr >= 'A' && *ptr <= 'Z'
+ || *ptr >= '0' && *ptr <= '9'
+ || *ptr == '_' || *ptr == '-'
+ ))
+ {
+ ptr++;
+ }
+
+ uint len = ptr - name.ptr;
+ if ( len && len < sizeof(buf) && ptr+1 < end && *ptr == ':' && ptr[1] == '/' )
+ {
+ memcpy(buf,name.ptr,len);
+ buf[len] = 0;
+ struct servent *s = getservbyname(buf,0);
+ if (s)
+ {
+ const int port = ntohs(s->s_port);
+ if ( port > 0 )
+ host->port = port;
+ }
+
+ ptr += 2;
+ if ( *ptr == '/' )
+ ptr++;
+ name = BehindMem(name,ptr);
+ check_filename = false;
+ }
+ else
+ ptr = name.ptr;
+ }
+
+ while ( ptr < end
+ && ( *ptr >= 'a' && *ptr <= 'z'
+ || *ptr >= 'A' && *ptr <= 'Z'
+ || *ptr >= '0' && *ptr <= '9'
+ || *ptr == '_' || *ptr == '-' || *ptr == '.'
+ ))
+ {
+ ptr++;
+ }
+
+ uint len = ptr - name.ptr;
+ if ( len > 0 && len < sizeof(buf) )
+ {
+ memcpy(buf,name.ptr,len);
+ buf[len] = 0;
+ if (store_name)
+ host->name = MEMDUP(buf,len);
+
+ struct hostent *h = gethostbyname(buf);
+ if ( h && h->h_addrtype == AF_INET && h->h_length == 4 )
+ {
+ host->ip4 = ntohl(*(u32*)h->h_addr_list[0]);
+ host->ip4_valid = true;
+ }
+
+ if ( ptr < end && *ptr == ':' )
+ {
+ ccp pptr = ptr+1;
+ if ( pptr < end && *pptr >= '0' && *pptr <= '9' )
+ {
+ char *end;
+ const uint port = strtoul(pptr,&end,10);
+ if ( port <= 0xffff && end > pptr )
+ {
+ host->port = port;
+ ptr = end;
+ }
+ }
+ else
+ {
+ ccp pstart = pptr;
+ while ( pptr < end
+ && ( *pptr >= 'a' && *pptr <= 'z'
+ || *pptr >= 'A' && *pptr <= 'Z'
+ || *pptr >= '0' && *pptr <= '9'
+ || *pptr == '_' || *pptr == '-'
+ ))
+ {
+ pptr++;
+ }
+ len = pptr - pstart;
+ if ( len > 0 && len < sizeof(buf) )
+ {
+ memcpy(buf,pstart,len);
+ buf[len] = 0;
+ struct servent *s = getservbyname(buf,0);
+ if (s)
+ {
+ const int port = ntohs(s->s_port);
+ if ( port > 0 )
+ host->port = port;
+ ptr = pptr;
+ }
+ }
+ }
+ }
+
+ host->sa.sin_family = AF_INET;
+ host->sa.sin_addr.s_addr = htonl(host->ip4);
+ host->sa.sin_port = htons(host->port);
+ }
+
+ if ( *ptr && check_filename )
+ {
+ host->filename = name.ptr;
+ host->ip4_valid = false;
+ }
+
+ host->not_scanned = BehindMem(name,ptr);
+ return host->ip4_valid;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct AllowIP4_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t AllowIP4KeyTable[] =
+{
+ { 0, "0", 0, 1 },
+ { ALLOW_MODE_DENY, "DENY", 0, 1 },
+ { ALLOW_MODE_ALLOW, "ALLOW", 0, 0 },
+ { ALLOW_MODE_AKEY, "AKEY", "JACC", 0 },
+
+ { ALLOW_MODE_LOCAL, "LOCAL", 0, 0 },
+ { ALLOW_MODE_LAN, "LAN", 0, 0 },
+ { ALLOW_MODE_EXTERN, "EXTERN", 0, 0 },
+
+ { ALLOW_MODE_PUBLIC, "PUBLIC", 0, 0 },
+ { ALLOW_MODE_USER, "USER", 0, 0 },
+ { ALLOW_MODE_MOD, "MOD", "MODERATOR", 0 },
+ { ALLOW_MODE_ADMIN, "ADMIN", "ADMINISTRATOR",0 },
+ { ALLOW_MODE_DEVELOP, "DEVELOP", "DEVELOPER", 0 },
+
+ { ALLOW_MODE_LOG, "LOG", 0, 0 },
+ { ALLOW_MODE_VERBOSE, "VERBOSE", 0, 0 },
+
+ { ALLOW_MODE_NOT, "NOT", 0, 0 },
+ { ALLOW_MODE_SET, "SET", 0, ALLOW_MODE__OP },
+ { ALLOW_MODE_AND, "AND", 0, ALLOW_MODE__OP },
+ { 0, "OR", 0, ALLOW_MODE__OP },
+ { ALLOW_MODE_CONTINUE, "CONTINUE", 0, 0 },
+ {0,0,0,0}
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeAllowIP4 ( AllowIP4_t *ai )
+{
+ DASSERT(ai);
+ memset(ai,0,sizeof(*ai));
+ ai->ref_counter = 1;
+ ai->fallback_mode = ALLOW_MODE_DENY;
+ ai->allow_mode = ALLOW_MODE_ALLOW|ALLOW_MODE_AKEY;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetAllowIP4 ( AllowIP4_t *ai )
+{
+ if (ai)
+ {
+ FREE(ai->list);
+ ai->list = 0;
+ ai->used = ai->size = 0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearAllowIP4 ( AllowIP4_t *ai )
+{
+ if (ai)
+ {
+ FREE(ai->list);
+ InitializeAllowIP4(ai);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AllowIP4_t * NewReferenceAllowIP4 ( AllowIP4_t *ai )
+{
+ if (ai)
+ ai->ref_counter++;
+ else
+ {
+ ai = MALLOC(sizeof(*ai));
+ DASSERT(ai);
+ InitializeAllowIP4(ai);
+
+ }
+ return ai;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AllowIP4_t * DeleteReferenceAllowIP4 ( AllowIP4_t *ai )
+{
+ if ( ai && !--ai->ref_counter )
+ {
+ ResetAllowIP4(ai);
+ FREE(ai);
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanLineAllowIP4
+(
+ AllowIP4_t *ai, // valid structure; new elements are appended
+ mem_t line, // single line to analyse
+ const KeywordTab_t *tab // NULL or keyword table.
+ // If NULL, a local table with keywords 0 DENY
+ // ALLOW SET REMOVE AND OR and CONTINUE is used.
+)
+{
+ if ( !line.ptr || !line.len )
+ return ERR_OK;
+
+ ccp ptr = line.ptr;
+ ccp end = ptr + line.len;
+
+ //--- find first character
+ while ( ptr < end && *(uchar*)ptr <= ' ' )
+ ptr++;
+ if ( ptr == end || *ptr == '#' )
+ return ERR_OK;
+
+ NetworkHost_t host;
+ ResolveHostMem(&host,true,BehindMem(line,ptr),0,false,false);
+ if (!host.ip4_valid)
+ return ERR_ERROR;
+ line = host.not_scanned;
+
+ u32 mask = M1(mask);
+ if ( line.len && *line.ptr == '/' )
+ line = ScanNetworkMaskMem(&mask,line);
+
+
+ //--- scan options
+
+ if (!tab)
+ tab = AllowIP4KeyTable;
+
+ char arg[1000];
+ StringCopySMem(arg,sizeof(arg),line); // to be NULL terminated
+ s64 res = ScanKeywordList(arg,tab,0,true,0,0,"AllowIP4",ERR_SYNTAX);
+
+ if ( res == M1(res) )
+ return ERR_SYNTAX;
+
+ if ( res & ALLOW_MODE_NOT )
+ res = res ^ ALLOW_MODE__MASK;
+
+
+ //--- add item
+
+ if ( ai->used == ai->size )
+ {
+ ai->size = 11*ai->size/10 + 10;
+ ai->list = REALLOC(ai->list,ai->size*sizeof(*ai->list));
+ }
+ DASSERT( ai->used < ai->size );
+ DASSERT( ai->list );
+
+ AllowIP4Item_t *it = ai->list + ai->used++;
+ //memset(it,0,sizeof(*it));
+ it->addr = host.ip4 & mask;
+ it->mask = mask;
+ it->mode = res;
+ it->count = 0;
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanFileAllowIP4
+(
+ AllowIP4_t *ai, // valid structure; new elements are appended
+ ccp fname, // ffilename of the source
+ FileMode_t fmode, // flags for OpenFile()
+ const KeywordTab_t *tab // NULL or keyword table -> see ScanLineAllowIP4()
+)
+{
+ File_t F;
+ enumError err = OpenFile(&F,true,fname,fmode,0,0);
+ if (err)
+ return err;
+
+ char iobuf[10000];
+ while (fgets(iobuf,sizeof(iobuf)-1,F.f))
+ {
+ err = ScanLineAllowIP4(ai,MemByString(iobuf),tab);
+ if (err)
+ break;
+ }
+
+ CloseFile(&F,0);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetCountersAllowIP4
+(
+ AllowIP4_t *ai // NULL or dest for addition
+)
+{
+ if (ai)
+ {
+ uint i;
+ AllowIP4Item_t *p = ai->list;
+ for ( i = 0; i < ai->used; i++, p++ )
+ p->count = 0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AddCountersAllowIP4
+(
+ AllowIP4_t *dest, // NULL or dest for addition
+ const AllowIP4_t *src // NULL or source for addition
+)
+{
+ if ( dest && dest->used && src && src->used )
+ {
+ uint is;
+ const AllowIP4Item_t *ps = src->list;
+ for ( is = 0; is < src->used; is++, ps++ )
+ {
+ uint id;
+ AllowIP4Item_t *pd = dest->list;
+ for ( id = 0; id < dest->used; id++, pd++ )
+ if ( pd->addr == ps->addr
+ && pd->mask == ps->mask
+ && pd->mode == ps->mode
+ )
+ {
+ pd->count += ps->count;
+ break;
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static uint print_mode_helper
+ ( char *buf, uint bufsize, const KeywordTab_t *keytab, u64 mode )
+{
+ ASSERT(buf);
+ ASSERT(bufsize>1);
+ char *dest = buf, *end = buf + bufsize-1;
+ if (keytab)
+ {
+ mode &= ~(ALLOW_MODE_SET|ALLOW_MODE_AND);
+ for ( ; mode && keytab->name1; keytab++ )
+ {
+ if ( keytab->id & mode )
+ {
+ dest = StringCat2E(dest,end,keytab->name1," ");
+ mode &= ~keytab->id;
+ }
+ }
+ }
+ *dest = 0;
+ return dest-buf;
+}
+
+//-----------------------------------------------------------------------------
+
+void DumpAllowIP4
+(
+ FILE *f, // NULL or output file
+ int indent, // indention
+ const AllowIP4_t *ai, // NULL or source
+ const KeywordTab_t *keytab, // NULL or key table for output
+ ccp title, // not NULL: Add title
+ bool print_tab_head // true: add table headings and separators
+)
+{
+ if ( !f || !ai )
+ return;
+
+ char buf[200];
+ uint i, fw_mode = 5, fw_settings = 8;
+ for ( i = 0; i < ai->used; i++ )
+ {
+ const AllowIP4Item_t *it = ai->list + i;
+ uint len = snprintf(buf,sizeof(buf),"%llx",it->mode);
+ if ( fw_mode < len )
+ fw_mode = len;
+
+ len = print_mode_helper(buf,sizeof(buf),keytab,it->mode);
+ if ( fw_settings < len )
+ fw_settings = len;
+ }
+
+ indent = NormalizeIndent(indent);
+ fprintf(f, "%*sAllowIP4[%s]: %u reference%s, %u/%u elements,"
+ " fallback:%llx, allow:%llx\n",
+ indent,"", title ? title : "",
+ ai->ref_counter, ai->ref_counter == 1 ? "" : "s",
+ ai->used, ai->size,
+ ai->fallback_mode, ai->allow_mode );
+
+ const uint seplen = 52 + fw_mode + fw_settings;
+ if (print_tab_head)
+ fprintf(f,"\n"
+ "%*s counter ipv4 address ipv4/netmask > %-*s"
+ " settings\n%*s%.*s\n",
+ indent,"", fw_mode+4, "operation",
+ indent,"", seplen, Minus300 );
+
+ for ( i = 0; i < ai->used; i++ )
+ {
+ const AllowIP4Item_t *it = ai->list + i;
+ print_mode_helper(buf,sizeof(buf),keytab,it->mode);
+ fprintf(f, "%*s%8s %-15s %08x/%08x > %s %*llx %s\n",
+ indent, "",
+ PrintNumberU7(0,0,it->count,DC_SFORM_ALIGN|DC_SFORM_DASH),
+ PrintIP4(0,0,it->addr,-1),
+ it->addr, it->mask,
+ it->mode & ALLOW_MODE_SET ? "SET"
+ : it->mode & ALLOW_MODE_AND ? "AND" : "OR ",
+ fw_mode, it->mode, *buf ? buf : "-" );
+ }
+
+ if (print_tab_head)
+ fprintf(f,"%*s%.*s\n", indent,"", seplen, Minus300 );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetAllowIP4ByAddr ( const AllowIP4_t *ai, u32 addr, u64 if_not_found )
+{
+ DASSERT(ai);
+ bool found = false;
+ uint i;
+ u64 res = 0;
+ AllowIP4Item_t *it = (AllowIP4Item_t*)ai->list;
+ for ( i = 0; i < ai->used; i++, it++ )
+ {
+ if ( ( addr & it->mask ) == it->addr )
+ {
+ it->count++;
+ found = true;
+
+ if ( it->mode & ALLOW_MODE_SET )
+ res = it->mode;
+ else if ( it->mode & ALLOW_MODE_AND )
+ res &= it->mode;
+ else
+ res |= it->mode;
+ if ( !(it->mode & ALLOW_MODE_CONTINUE) )
+ break;
+ }
+ }
+
+ return found ? res & ALLOW_MODE__MASK : if_not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetAllowIP4ByName ( const AllowIP4_t *ai, mem_t name, u64 if_not_found )
+{
+ DASSERT(ai);
+ NetworkHost_t host;
+ ResolveHostMem(&host,true,name,0,false,false);
+ return host.ip4_valid
+ ? GetAllowIP4ByAddr(ai,host.ip4,if_not_found)
+ : if_not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetAutoAllowIP4ByAddr ( const AllowIP4_t *ai, u32 addr )
+{
+ if (!ai)
+ return ALLOW_MODE_ALLOW;
+
+ const u64 res = GetAllowIP4ByAddr(ai,addr,M1(res));
+ return IS_M1(res) ? ai->fallback_mode : res & ALLOW_MODE__MASK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetAutoAllowIP4ByName ( const AllowIP4_t *ai, mem_t name )
+{
+ if (!ai)
+ return ALLOW_MODE_ALLOW;
+
+ NetworkHost_t host;
+ ResolveHostMem(&host,true,name,0,false,false);
+ return host.ip4_valid
+ ? GetAllowIP4ByAddr(ai,host.ip4,ai->fallback_mode)
+ : ai->fallback_mode;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetAutoAllowIP4BySock ( const AllowIP4_t *ai, int sock )
+{
+ if (!ai)
+ return ALLOW_MODE_ALLOW;
+
+ struct sockaddr_in sa;
+ socklen_t sa_len = sizeof(sa);
+ if ( getpeername(sock,(struct sockaddr*)&sa,&sa_len) || sa.sin_family != AF_INET )
+ return ai->fallback_mode;
+
+ return GetAutoAllowIP4ByAddr(ai,ntohl(sa.sin_addr.s_addr));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateAllowIP4
+(
+ FILE *f, // output file
+ ccp section, // not NULL and not empty: create own section
+ ccp name_prefix, // NULL or prefix of name
+ uint tab_pos, // tab pos of '='
+ const AllowIP4_t *ai // valid struct
+)
+{
+ DASSERT(f);
+ DASSERT(ai);
+
+ if ( section && *section )
+ fprintf(f,"\n#%.50s\n[%s]\n\n",Minus300,section);
+
+ if (!name_prefix)
+ name_prefix = EmptyString;
+
+ const int base = 8*tab_pos + 7 - strlen(name_prefix);
+
+ fprintf(f,
+ "%s" "fallback%.*s= %#llx\n"
+ "%s" "allow%.*s= %#llx\n"
+ "%s" "n-rules%.*s= %u\n"
+ ,name_prefix, (base-8)/8, Tabs20, ai->fallback_mode
+ ,name_prefix, (base-5)/8, Tabs20, ai->allow_mode
+ ,name_prefix, (base-7)/8, Tabs20, ai->used
+ );
+
+ uint i;
+ char name[30];
+
+ for ( i = 0; i < ai->used; i++ )
+ {
+ const AllowIP4Item_t *it = ai->list + i;
+
+ const uint len = snprintf(name,sizeof(name),"rule-%u",i);
+ fprintf(f, "%s%s%.*s= 0x%08x,0x%08x,%#llx,%llu\n",
+ name_prefix, name, (base-len)/8, Tabs20,
+ it->addr, it->mask, it->mode, it->count );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool ScanAllowIP4Item
+(
+ // return true on success; counter is optional
+
+ AllowIP4Item_t *it, // store data here, cleared first
+ ccp line // NULL or line to scan
+)
+{
+ DASSERT(it);
+ memset(it,0,sizeof(*it));
+ if (line)
+ {
+ char *end;
+ it->addr = str2ul(line,&end,10);
+ if ( end > line )
+ {
+ line = SkipControls1(end,',');
+ it->mask = str2ul(line,&end,10);
+ if ( end > line )
+ {
+ line = SkipControls1(end,',');
+ it->mode = str2ull(line,&end,10);
+ if ( end > line )
+ {
+ line = SkipControls1(end,',');
+ it->count = str2ull(line,0,10); // optional -> no error check
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateAllowIP4
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+)
+{
+ DASSERT(rs);
+ if (user_table)
+ RestoreStateAllowIP4Ex(rs,(AllowIP4_t*)user_table,0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateAllowIP4Ex
+(
+ RestoreState_t *rs, // info data, modified
+ AllowIP4_t *ai, // valid struct
+ ccp name_prefix // NULL or prefix of name
+)
+{
+ DASSERT(ai);
+ DASSERT(rs);
+
+ ClearAllowIP4(ai);
+
+ if (!name_prefix)
+ name_prefix = EmptyString;
+
+ char name[200]; // name buf
+
+ snprintf(name,sizeof(name),"%sfallback",name_prefix);
+ ai->fallback_mode = GetParamFieldU64(rs,name,ai->fallback_mode);
+
+ snprintf(name,sizeof(name),"%sallow",name_prefix);
+ ai->allow_mode = GetParamFieldU64(rs,name,ai->allow_mode);
+
+ snprintf(name,sizeof(name),"%sn-rules",name_prefix);
+ const uint n_rules = GetParamFieldUINT(rs,name,0);
+
+ uint i;
+ for ( i = 0; i < n_rules; i++ )
+ {
+ snprintf(name,sizeof(name),"%srule-%u",name_prefix,i);
+ ParamFieldItem_t *pfi = GetParamField(rs,name);
+ AllowIP4Item_t ait;
+ if ( pfi && ScanAllowIP4Item(&ait,(ccp)pfi->data) )
+ {
+ if ( ai->used == ai->size )
+ {
+ ai->size = 11*ai->size/10 + 10;
+ ai->list = REALLOC(ai->list,ai->size*sizeof(*ai->list));
+ }
+ DASSERT( ai->used < ai->size );
+ DASSERT( ai->list );
+ ai->list[ai->used++] = ait;
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCP + UDP connections ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectByHost
+(
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ int type, // AF_INET type, e.g. SOCK_STREAM, SOCK_DGRAM
+ int protocol, // AF_INET protocol, e.g. IPPROTO_TCP, IPPROTO_UDP
+ bool silent // true: suppress error messages
+)
+{
+ DASSERT(host);
+
+ int sock = socket(AF_INET,type,protocol);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't create socket: %s\n",
+ GetHostName(host));
+ return -1;
+ }
+
+ if (connect(sock,(struct sockaddr *)&host->sa,sizeof(host->sa)))
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't connect to %s:%d\n",
+ GetHostName(host), host->port );
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectByHostTCP
+(
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ bool silent // true: suppress error messages
+)
+{
+ DASSERT(host);
+ return ConnectByHost(host,SOCK_STREAM,IPPROTO_TCP,silent);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectTCP
+(
+ ccp addr, // optional service + server + optional host
+ // e.g.: "service://host:port/....."
+ int default_port, // default port
+ bool silent // true: suppress error messages
+)
+{
+ Setup_ConnectTCP_Hook(false);
+
+ ccp unix_path = CheckUnixSocketPath(addr,0);
+ if (unix_path)
+ return ConnectUnixTCP(unix_path,silent);
+
+ if (!strncasecmp(addr,"tcp:",4))
+ addr += 4;
+
+ NetworkHost_t host;
+ int sock = -1;
+ if (ResolveHost(&host,true,addr,default_port,false,true))
+ sock = ConnectByHost(&host,SOCK_STREAM,IPPROTO_TCP,silent);
+ ResetHost(&host);
+ return sock;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Setup_ConnectTCP_Hook ( bool force )
+{
+ if ( force || !ConnectTCP_Hook )
+ ConnectTCP_Hook = ConnectTCP;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectByHostUDP
+(
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ bool silent // true: suppress error messages
+)
+{
+ DASSERT(host);
+ return ConnectByHost(host,SOCK_DGRAM,IPPROTO_UDP,silent);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectUDP
+(
+ ccp name, // optional service + server + optional host
+ // e.g.: "service://host:port/....."
+ int default_host, // default host
+ bool silent // true: suppress error messages
+)
+{
+ NetworkHost_t host;
+ int sock = -1;
+ if (ResolveHost(&host,true,name,default_host,false,true))
+ sock = ConnectByHost(&host,SOCK_DGRAM,IPPROTO_UDP,silent);
+ ResetHost(&host);
+ return sock;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int WillReceiveNotBlock
+(
+ // returns: -1:error, 0:may block, 1:will not block
+
+ int fd, // file handle of source
+ uint msec // timeout: 0:no timeout, >0: milliseconds
+)
+{
+ fd_set read_set;
+ FD_ZERO(&read_set);
+ FD_SET(fd,&read_set);
+
+ struct timeval timeout;
+ timeout.tv_sec = msec/1000;
+ timeout.tv_usec = (msec%1000) * 1000;
+
+ errno = 0;
+ const int stat = select(fd+1,&read_set,0,0,&timeout);
+ return stat < 0 ? stat : stat > 0 && FD_ISSET(fd,&read_set);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ssize_t ReceiveTimeout
+(
+ // returns: <0: on error, 0:timeout, >0: bytes read
+
+ int fd, // file handle of source
+ void *buf, // valid pointer to buffer
+ uint size, // size to receive
+ int flags, // flags for recv()
+ int msec // timeout: -1:unlimited, 0:no timeout, >0: milliseconds
+)
+{
+ if ( fd == -1 )
+ return -1;
+ if ( !buf || !size )
+ return 0;
+
+ if ( msec >= 0 )
+ {
+ fd_set read_set;
+ FD_ZERO(&read_set);
+ FD_SET(fd,&read_set);
+
+ struct timeval timeout;
+ timeout.tv_sec = msec/1000;
+ timeout.tv_usec = (msec%1000) * 1000;
+
+ errno = 0;
+ const int stat = select(fd+1,&read_set,0,0,&timeout);
+ if ( stat <= 0 )
+ return stat;
+ }
+
+ return recv(fd,buf,size,flags);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int WillSendNotBlock
+(
+ // returns: -1:error, 0:may block, 1:will not block
+
+ int fd, // file handle of source
+ uint msec // timeout: 0:no timeout, >0: milliseconds
+)
+{
+ fd_set write_set;
+ FD_ZERO(&write_set);
+ FD_SET(fd,&write_set);
+
+ struct timeval timeout; // kein Warten
+ timeout.tv_sec = msec/1000;
+ timeout.tv_usec = (msec%1000) * 1000;
+
+ errno = 0;
+ const int stat = select(fd+1,0,&write_set,0,&timeout);
+ return stat < 0 ? stat : stat > 0 && FD_ISSET(fd,&write_set);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ssize_t SendTimeout
+(
+ // returns: <0: on error, 0:timeout, >0: bytes written
+
+ int fd, // file hande of destination
+ const void *data, // valid pointer to data
+ uint size, // size to receive
+ int flags, // flags for send()
+ int msec, // total timeout: -1:unlimited, 0:no timeout, >0: milliseconds
+ bool all // true: send all data until done, total timeout or error
+)
+{
+ if ( fd == -1 )
+ return -1;
+ if ( !data || !size )
+ return 0;
+
+ //--- send loop for complete sending
+
+ if (all)
+ {
+ uint max_time = msec < 0 ? 0 : GetTimerMSec() + msec;
+ ssize_t total = 0;
+ while (size)
+ {
+ ssize_t stat = SendTimeout(fd,data,size,flags,msec,false);
+ if ( stat < 0 )
+ return stat;
+ data = (u8*)data + stat;
+ size -= stat;
+ total += stat;
+
+ if ( msec >= 0 )
+ {
+ msec = max_time - GetTimerMSec();
+ if ( msec < 0 )
+ break;
+ }
+ }
+ return total;
+ }
+
+ //--- send, single try
+
+ if ( msec >= 0 )
+ {
+ fd_set write_set;
+ FD_ZERO(&write_set);
+ FD_SET(fd,&write_set);
+
+ struct timeval timeout;
+ timeout.tv_sec = msec/1000;
+ timeout.tv_usec = (msec%1000) * 1000;
+
+ errno = 0;
+ const int stat = select(fd+1,0,&write_set,0,&timeout);
+ if ( stat <= 0 )
+ return stat;
+ }
+
+ return send(fd,data,size,flags);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// IP interface: check sums ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u16 CalcChecksumIP4
+(
+ const void *data, // ip4 header
+ // make sure, that the checksum member is NULL
+ uint size // size of ip4 header
+)
+{
+ // http://en.wikipedia.org/wiki/IPv4_header_checksum
+
+ DASSERT(data);
+ const u16 *ptr = (u16*)data;
+ u32 csum = 0;
+
+ while ( size > 1 )
+ {
+ csum += ntohs(*ptr++);
+ size -= 2;
+ }
+
+ if (size)
+ csum += *(u8*)ptr << 8;
+
+ return ~( ( csum >> 16 ) + ( csum & 0xffff ));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u16 CalcChecksumUDP
+(
+ const ip4_head_t *ip4, // IP4 header
+ const udp_head_t *udp, // UDP header
+ const void *data // UDP data, size is 'udp->data_len'
+)
+{
+ DASSERT(ip4);
+ DASSERT(udp);
+ DASSERT( data || !udp->data_len );
+
+ int dlen = ntohs(udp->data_len);
+ u32 csum = ntohs(((u16*)&ip4->ip_src)[0])
+ + ntohs(((u16*)&ip4->ip_src)[1])
+ + ntohs(((u16*)&ip4->ip_dest)[0])
+ + ntohs(((u16*)&ip4->ip_dest)[1])
+ + ip4->protocol
+ + ntohs(udp->port_src)
+ + ntohs(udp->port_dest)
+ + 2*dlen;
+
+ dlen -= 8;
+ const u16 *ptr = (u16*)data;
+ while ( dlen > 1 )
+ {
+ csum += ntohs(*ptr++);
+ dlen -= 2;
+ }
+
+ if ( dlen > 0 )
+ csum += *(u8*)ptr << 8;
+
+ while ( csum >= 0x10000 )
+ csum = (u32)((u16*)&csum)[0] + (u32)((u16*)&csum)[1];
+
+ return csum == 0xffff ? 0xffff : htons(~csum);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// UNIX sockets ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SendUnixUDP
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ bool silent, // suppress all error messages
+ cvp data, // data to send
+ uint size // size of 'data'
+)
+{
+ if ( !data || !size )
+ return ERR_NOTHING_TO_DO;
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ PRINT("SendUnixUDP(%s)\n",path);
+
+ static int sock = -1;
+ if ( sock == -1 )
+ {
+ sock = socket(AF_UNIX,SOCK_STREAM|SOCK_CLOEXEC,0);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,
+ "Can't create UNIX stream socket: %s\n",path);
+ return ERR_CANT_CREATE;
+ }
+ }
+
+ struct sockaddr_un sa;
+ memset(&sa,0,sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ StringCopyS(sa.sun_path,sizeof(sa.sun_path),path);
+
+ if ( sendto(sock,data,size,0,(struct sockaddr*)&sa,sizeof(sa)) < 0 )
+ {
+ if (!silent)
+ ERROR1(ERR_WRITE_FAILED,
+ "Can't send %u bytes to UNIX stream socket: %s\n",
+ size, path );
+ return ERR_WRITE_FAILED;
+ }
+
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Transfer Statistics ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+TransferStats_t * Add2TransferStats
+(
+ // return dest
+ // calculate: dest += src
+
+ TransferStats_t *dest, // NULL or destination and first source
+ const TransferStats_t *src // NULL or second source
+)
+{
+ if ( dest && src )
+ {
+ dest->conn_count += src->conn_count;
+ dest->recv_count += src->recv_count;
+ dest->recv_size += src->recv_size;
+ dest->send_count += src->send_count;
+ dest->send_size += src->send_size;
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TransferStats_t * Sub2TransferStats
+(
+ // return dest
+ // calculate: dest -= src
+
+ TransferStats_t *dest, // NULL or destination and first source
+ const TransferStats_t *src // NULL or second source
+)
+{
+ if ( dest && src )
+ {
+ dest->conn_count -= src->conn_count;
+ dest->recv_count -= src->recv_count;
+ dest->recv_size -= src->recv_size;
+ dest->send_count -= src->send_count;
+ dest->send_size -= src->send_size;
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TransferStats_t * Add3TransferStats
+(
+ // return dest
+ // calculate: dest = src1 + src2
+
+ TransferStats_t *dest, // NULL or destination (maybe same as source)
+ const TransferStats_t *src1, // NULL or first source
+ const TransferStats_t *src2 // NULL or second source
+)
+{
+ if (dest)
+ {
+ if (!src1)
+ {
+ if (src2)
+ memcpy(dest,src2,sizeof(*dest));
+ else
+ memset(dest,0,sizeof(*dest));
+ }
+ else if (!src2)
+ {
+ DASSERT(src1);
+ memcpy(dest,src1,sizeof(*dest));
+ }
+ else
+ {
+ DASSERT(src1);
+ DASSERT(src2);
+ dest->conn_count = src1->conn_count + src2->conn_count;
+ dest->recv_count = src1->recv_count + src2->recv_count;
+ dest->recv_size = src1->recv_size + src2->recv_size;
+ dest->send_count = src1->send_count + src2->send_count;
+ dest->send_size = src1->send_size + src2->send_size;
+ }
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TransferStats_t * Sub3TransferStats
+(
+ // return dest
+ // calculate: dest = src1 - src2
+
+ TransferStats_t *dest, // NULL or destination (maybe same as source)
+ const TransferStats_t *src1, // NULL or first source
+ const TransferStats_t *src2 // NULL or second source
+)
+{
+ if (dest)
+ {
+ if (!src1)
+ {
+ if (src2)
+ {
+ TransferStats_t temp;
+ memset(&temp,0,sizeof(temp));
+ Sub3TransferStats(dest,&temp,src2);
+ }
+ else
+ memset(dest,0,sizeof(*dest));
+ }
+ else if (!src2)
+ {
+ DASSERT(src1);
+ memcpy(dest,src1,sizeof(*dest));
+ }
+ else
+ {
+ DASSERT(src1);
+ DASSERT(src2);
+ dest->conn_count = src1->conn_count - src2->conn_count;
+ dest->recv_count = src1->recv_count - src2->recv_count;
+ dest->recv_size = src1->recv_size - src2->recv_size;
+ dest->send_count = src1->send_count - src2->send_count;
+ dest->send_size = src1->send_size - src2->send_size;
+ }
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TransferStats_t * SumTransferStats
+(
+ // return dest
+ // calculate: dest = sum(all_sources)
+
+ TransferStats_t *dest, // NULL or destination (maybe same as source)
+ const TransferStats_t * const *src, // NULL or source list, elements may be NULL
+ int n_src // number of element; if -1: term at first NULL
+)
+{
+ if (dest)
+ {
+ TransferStats_t temp;
+ memset(&temp,0,sizeof(temp));
+
+ if (src)
+ {
+ if ( n_src < 0 )
+ while ( *src )
+ Add2TransferStats(&temp,*src++);
+ else
+ for ( ; *src; src++ )
+ if (*src)
+ Add2TransferStats(&temp,*src);
+ }
+ memcpy(dest,&temp,sizeof(*dest));
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTransferStatsSQL
+(
+ // print statistic as SQL assigning list.
+ // arguments are speparated by a comma.
+
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ const TransferStats_t *ts, // valid source
+ ccp prefix // not NULL: prefix member names
+)
+{
+ DASSERT(ts);
+ const bool alloced = !buf;
+ if (alloced)
+ buf = GetCircBuf( buf_size = CIRC_BUF_MAX_ALLOC );
+
+ if (!prefix)
+ prefix = "";
+
+ const uint len
+ = snprintf( buf, buf_size,
+ "%sconn=%u, %srecv_n=%u, %srecv=%llu, %ssend_n=%u, %ssend=%llu",
+ prefix, ts->conn_count,
+ prefix, ts->recv_count,
+ prefix, ts->recv_size,
+ prefix, ts->send_count,
+ prefix, ts->send_size );
+
+ if ( alloced && len < buf_size )
+ ReleaseCircBuf(buf+buf_size,buf_size-len-1);
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static inline u32 calc_rate ( u64 count, u64 duration )
+ { return ( count * USEC_PER_SEC + duration/2 ) / duration; }
+
+static inline double calc_drate ( u64 count, u64 duration )
+ { return (double)( count * USEC_PER_SEC ) / duration; }
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintTranferStatistics
+(
+ FILE *f, // output file, never NULL
+ ccp prefix, // NULL or prefix for each line
+ ccp name, // statistics name, up to 5 chars including colon
+ const TransferStats_t *stat,// statistics record
+
+ TransferStats_t *prev, // NULL statistics record with previous data
+ u64 duration, // delta duration in usec
+ ccp duration_info, // text representation of 'duration'
+
+ const ColorSet_t *colset // NULL (no colors) or color set to use
+)
+{
+ DASSERT(f);
+ DASSERT(name);
+ DASSERT(stat);
+
+ if (!prefix)
+ prefix = "";
+
+ if (!colset)
+ colset = GetColorSet0();
+ ccp color1 = colset->status;
+ ccp color0 = colset->reset;
+
+ if (stat->conn_count)
+ {
+ fprintf(f,
+ "%s%s#STAT-%-5s %u connect%s,"
+ " %u packet%s (%s) received, %u packet%s (%s) send.%s\n",
+ prefix, color1, name,
+ stat->conn_count, stat->conn_count == 1 ? "" : "s",
+ stat->recv_count, stat->recv_count == 1 ? "" : "s",
+ PrintSize1024(0,0,stat->recv_size,false),
+ stat->send_count, stat->send_count == 1 ? "" : "s",
+ PrintSize1024(0,0,stat->send_size,false),
+ color0 );
+ }
+ else if ( stat->recv_count || stat->send_count )
+ {
+ fprintf(f,
+ "%s%s#STAT-%-5s %u packet%s (%s) received, %u packet%s (%s) send.%s\n",
+ prefix, color1, name,
+ stat->recv_count, stat->recv_count == 1 ? "" : "s",
+ PrintSize1024(0,0,stat->recv_size,false),
+ stat->send_count, stat->send_count == 1 ? "" : "s",
+ PrintSize1024(0,0,stat->send_size,false),
+ color0 );
+ }
+ else
+ return;
+
+ if ( duration > 0 && prev )
+ {
+ TransferStats_t delta;
+ Sub3TransferStats(&delta,stat,prev);
+
+ char duration_buf[10];
+ if (!duration_info)
+ {
+ PrintTimerUSec6(duration_buf,sizeof(duration_buf),duration,false);
+ duration_info = duration_buf;
+ }
+
+ if ( delta.recv_count )
+ {
+ const double crate = calc_drate( delta.recv_count, duration );
+ const u32 srate = calc_rate( delta.recv_size, duration );
+ fprintf(f,
+ "%s%s#STAT-%-5s >Last "
+ "%s: %u (%4.2f/s) packet%s with %s (%s/s) received.%s\n",
+ prefix, color1, name,
+ duration_info,
+ delta.recv_count, crate,
+ delta.recv_count == 1 ? "" : "s",
+ PrintSize1024(0,0,delta.recv_size,false),
+ PrintSize1024(0,0,srate,false),
+ color0 );
+ }
+
+ if ( delta.send_count )
+ {
+ const double crate = calc_drate( delta.send_count, duration );
+ const u32 srate = calc_rate( delta.send_size, duration );
+ fprintf(f,
+ "%s%s#STAT-%-5s >Last "
+ "%s: %u (%4.2f/s) packet%s with %s (%s/s) send.%s\n",
+ prefix, color1, name,
+ duration_info,
+ delta.send_count, crate,
+ delta.send_count == 1 ? "" : "s",
+ PrintSize1024(0,0,delta.send_size,false),
+ PrintSize1024(0,0,srate,false),
+ color0 );
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateTransferStats
+(
+ FILE *f, // output file
+ const TransferStats_t *stat // valid stat object
+)
+{
+ DASSERT(f);
+ DASSERT(stat);
+
+ fprintf(f,
+ "conn-count = %u\n"
+ "recv-count = %u\n"
+ "recv-size = %llu\n"
+ "send-count = %u\n"
+ "send-size = %llu\n"
+ ,stat->conn_count
+ ,stat->recv_count
+ ,stat->recv_size
+ ,stat->send_count
+ ,stat->send_size
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateTransferStats1
+(
+ // print as single line
+ FILE *f, // output file
+ ccp name, // var name; use "tfer-stat" if NULL or EMPTY
+ const TransferStats_t *stat // valid stat object
+)
+{
+ DASSERT(f);
+ DASSERT(stat);
+
+ if ( !name || !*name )
+ name = "tfer-stat";
+ const int tab_size = ( 23 - strlen(name) ) / 8;
+
+ fprintf(f,
+ "%s%.*s= %u,%u,%llu,%u,%llu\n"
+ ,name
+ ,tab_size > 0 ? tab_size : 0
+ ,"\t\t"
+ ,stat->conn_count
+ ,stat->recv_count
+ ,stat->recv_size
+ ,stat->send_count
+ ,stat->send_size
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateTransferStatsN
+(
+ // print multiple stats each as single line
+ FILE *f, // output file
+ ccp name, // var name; use "tfer-stat-" if NULL or EMPTY
+ const TransferStats_t *stat, // list of valid stat objects
+ uint n_stat // number of elements in 'stat'
+)
+{
+ DASSERT(f);
+ DASSERT(stat||!n_stat);
+
+ if ( !name || !*name )
+ name = "tfer-stat-";
+ const int tab_size = ( 23 - strlen(name) ) / 8;
+
+ fprintf(f,"%sn%.*s= %u\n",
+ name, tab_size > 0 ? tab_size : 0, "\t\t", n_stat );
+
+ uint i;
+ char buf[100];
+ for ( i = 0; i < n_stat; i++ )
+ {
+ snprintf(buf,sizeof(buf),"%s%u",name,i);
+ SaveCurrentStateTransferStats1(f,buf,stat++);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateTransferStats
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+)
+{
+ DASSERT(rs);
+
+ TransferStats_t *stat = (TransferStats_t*)user_table;
+ if (!stat)
+ return;
+
+ stat->conn_count = GetParamFieldUINT( rs, "conn-count", stat->conn_count );
+ stat->recv_count = GetParamFieldUINT( rs, "recv-count", stat->recv_count );
+ stat->send_count = GetParamFieldUINT( rs, "send-count", stat->send_count );
+ stat->recv_size = GetParamFieldU64 ( rs, "recv-size", stat->recv_size );
+ stat->send_size = GetParamFieldU64 ( rs, "send-size", stat->send_size );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateTransferStats1
+(
+ // scan single line
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ ccp name, // var name; use "tfer-stat" if NULL or EMPTY
+ TransferStats_t *stat, // valid stat object
+ bool fall_back // true: fall back to RestoreStateTransferStats()
+)
+{
+ DASSERT(rs);
+ DASSERT(stat);
+
+ if ( !name || !*name )
+ name = "tfer-stat";
+
+ char buf[100];
+ int res = GetParamFieldBUF( buf, sizeof(buf), rs, name, ENCODE_OFF, 0 );
+ if ( res > 0 )
+ {
+ memset(stat,0,sizeof(*stat));
+
+ char *ptr;
+ stat->conn_count = strtoul (buf,&ptr,10); if (*ptr == ',' ) ptr++;
+ stat->recv_count = strtoul (ptr,&ptr,10); if (*ptr == ',' ) ptr++;
+ stat->recv_size = strtoull(ptr,&ptr,10); if (*ptr == ',' ) ptr++;
+ stat->send_count = strtoul (ptr,&ptr,10); if (*ptr == ',' ) ptr++;
+ stat->send_size = strtoull(ptr,&ptr,10);
+ return;
+ }
+
+ if (fall_back)
+ RestoreStateTransferStats(rs,stat);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint RestoreStateTransferStatsN
+(
+ // print multiple stats each as single line
+ // returns the number of read elements; all others are set to NULL
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ ccp name, // var name; use "tfer-stat-" if NULL or EMPTY
+ TransferStats_t *stat, // list of valid stat objects
+ uint n_stat // number of elements in 'stat'
+)
+{
+ DASSERT(rs);
+ DASSERT(stat||!n_stat);
+
+ if ( !name || !*name )
+ name = "tfer-stat-";
+
+ memset(stat,0,n_stat*sizeof(*stat));
+
+ char buf[100];
+ snprintf(buf,sizeof(buf),"%sn",name);
+ uint n_read = GetParamFieldUINT(rs,buf,0);
+ if ( n_read > n_stat )
+ n_read = n_stat;
+
+ uint i;
+ for ( i = 0; i < n_read; i++ )
+ {
+ snprintf(buf,sizeof(buf),"%s%u",name,i);
+ RestoreStateTransferStats1(rs,buf,stat++,false);
+ }
+
+ return n_read;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCP HANDLER ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define LOG_TCP HAVE_PRINT0
+
+#undef INC_TCP_INDENT
+#undef DEC_TCP_INDENT
+#undef LOG_TCP_BUFFER
+
+#if LOG_TCP
+ static int tcp_indent = 5;
+ #define INC_TCP_INDENT tcp_indent += 2
+ #define DEC_TCP_INDENT tcp_indent -= 2
+ #define LOG_TCP_BUFFER(tb,f,...) LogGrowBuffer(stderr,tcp_indent,tb,f,__VA_ARGS__)
+ #define LOG_TCP_STREAM(ts,r,f,...) LogTCPStream(stderr,tcp_indent,ts,r,f,__VA_ARGS__)
+ #define LOG_TCP_HANDLER(th,r,f,...) LogTCPHandler(stderr,tcp_indent,th,r,f,__VA_ARGS__)
+#else
+ #define INC_TCP_INDENT
+ #define DEC_TCP_INDENT
+ #define LOG_TCP_BUFFER(tb,f,...)
+ #define LOG_TCP_STREAM(ts,r,f,...)
+ #define LOG_TCP_HANDLER(th,r,f,...)
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCPStream_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeTCPStream ( TCPStream_t *ts, int sock )
+{
+ DASSERT(ts);
+ memset(ts,0,sizeof(*ts));
+ ts->unique_id = CreateUniqueId(1);
+ ts->sock = sock;
+ ts->poll_index = M1(ts->poll_index);
+ LOG_TCP_STREAM(ts,0,"%s","INIT()");
+ INC_TCP_INDENT;
+ InitializeGrowBuffer(&ts->ibuf,0x800);
+ InitializeGrowBuffer(&ts->obuf,0x800);
+ DEC_TCP_INDENT;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetTCPStream
+(
+ TCPStream_t *ts // valid TCP handler
+)
+{
+ DASSERT(ts);
+ LogTCPStreamActivity(ts,"ResetTCPStream");
+ TCPHandler_t *th = ts->handler;
+
+ if (th)
+ {
+ if (th->OnDestroyStream)
+ th->OnDestroyStream(ts);
+ th->used_streams--;
+ }
+
+ if ( ts->sock != -1 )
+ {
+ shutdown(ts->sock,SHUT_RDWR);
+ close(ts->sock);
+ ts->sock = -1;
+ }
+
+ INC_TCP_INDENT;
+ ResetGrowBuffer(&ts->ibuf);
+ ResetGrowBuffer(&ts->obuf);
+ DEC_TCP_INDENT;
+
+ if (th)
+ {
+ // chain is only valid if part of TCP-handler
+ if (ts->prev)
+ ts->prev->next = ts->next;
+ if (ts->next)
+ ts->next->prev = ts->prev;
+ if ( th->first == ts )
+ th->first = ts->next;
+ }
+ memset(ts,0,sizeof(*ts));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DestroyTCPStream
+(
+ TCPStream_t *ts // valid TCP handler
+)
+{
+ DASSERT(ts);
+ LogTCPStreamActivity(ts,"DestroyTCPStream");
+ ResetTCPStream(ts);
+ FREE(ts);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ssize_t UpdateRecvStatTCPStream
+(
+ // returns 'send_stat'
+
+ TCPStream_t *ts, // valid TCP handler
+ ssize_t recv_stat, // result of recv(), update stats on >0
+ u64 now_usec // NULL or current time
+)
+{
+ DASSERT(ts);
+ if ( recv_stat > 0 )
+ {
+ ts->receive_usec = now_usec ? now_usec : GetTimeUSec(false);
+ ts->stat.recv_count++;
+ ts->stat.recv_size += recv_stat;
+
+ if (ts->xstat)
+ {
+ ts->xstat->recv_count++;
+ ts->xstat->recv_size += recv_stat;
+ }
+
+ if (ts->handler)
+ {
+ ts->handler->stat.recv_count++;
+ ts->handler->stat.recv_size += recv_stat;
+ }
+ }
+ return recv_stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ssize_t UpdateSendStatTCPStream
+(
+ // returns 'send_stat'
+
+ TCPStream_t *ts, // valid TCP handler
+ ssize_t send_stat, // result of send(), update stats on >0
+ u64 now_usec // NULL or current time
+)
+{
+ DASSERT(ts);
+ if ( send_stat > 0 )
+ {
+ ts->send_usec = now_usec ? now_usec : GetTimeUSec(false);
+ ts->stat.send_count++;
+ ts->stat.send_size += send_stat;
+
+ if (ts->xstat)
+ {
+ ts->xstat->send_count++;
+ ts->xstat->send_size += send_stat;
+ }
+
+ if (ts->handler)
+ {
+ ts->handler->stat.send_count++;
+ ts->handler->stat.send_size += send_stat;
+ }
+ }
+ return send_stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int WriteDirectGrowBuffer
+(
+ // write() data direct without blocking and without calling any notifier.
+ // If output buf is not empty or send failed, append the data to 'gb'.
+ // Returns the number of bytes added to the grow buffer or -1 on error.
+ // The data is send+stored completely (returns 'size') or not at all
+ // (returns '0'). -1 is returned if the socket is invalid.
+
+ GrowBuffer_t *gb, // grow buffer to cache data
+ int fd, // destination file
+ bool flush_output, // true: try to flush 'gb' before
+ cvp data, // data to send
+ uint size, // size of 'data', If NULL && flush: flush only
+ uint *send_count // not NULL: add num of sent bytes
+)
+{
+ DASSERT(gb);
+ DASSERT(data||!size);
+
+ const u8 *d = data;
+ if ( fd != -1 )
+ {
+ int final_stat = 0;
+ if ( gb->disabled <= 0 )
+ {
+ if ( flush_output && gb->used )
+ {
+ ssize_t stat = write(fd,gb->ptr,gb->used);
+ if ( stat > 0 )
+ {
+ if (send_count)
+ *send_count += stat;
+ DropGrowBuffer(gb,stat);
+ }
+ }
+
+ if ( !gb->used && size )
+ {
+ ssize_t stat = write(fd,d,size);
+ if ( stat > 0 )
+ {
+ if (send_count)
+ *send_count += stat;
+
+ size -= stat;
+ if (!size) // likely
+ return stat;
+
+ d += stat;
+ final_stat = stat;
+
+ // for this case: force growing buffer (because of 'all or none')
+ PrepareGrowBuffer(gb,size,true);
+ }
+ }
+ }
+
+ if ( size && size <= GetSpaceGrowBuffer(gb) )
+ final_stat += InsertGrowBuffer(gb,d,size);
+ return final_stat;
+ }
+
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SendDirectGrowBuffer
+(
+ // send() data direct without blocking and without calling any notifier.
+ // If output buf is not empty or send failed, append the data to 'gb'.
+ // Returns the number of bytes added to the grow buffer or -1 on error.
+ // The data is send+stored completely (returns 'size') or not at all
+ // (returns '0'). -1 is returned if the socket is invalid.
+
+ GrowBuffer_t *gb, // grow buffer to cache data
+ int sock, // destination socket
+ bool flush_output, // true: try to flush 'gb' before
+ cvp data, // data to send
+ uint size, // size of 'data', If NULL && flush: flush only
+ uint *send_count // not NULL: add num of sent bytes
+)
+{
+ DASSERT(gb);
+ DASSERT(data||!size);
+
+ const u8 *d = data;
+ if ( sock != -1 )
+ {
+ int final_stat = 0;
+ if ( gb->disabled <= 0 )
+ {
+ if ( flush_output && gb->used )
+ {
+ ssize_t stat = send(sock,gb->ptr,gb->used,MSG_DONTWAIT);
+ if ( stat > 0 )
+ {
+ if (send_count)
+ *send_count += stat;
+ DropGrowBuffer(gb,stat);
+ }
+ }
+
+ if ( !gb->used && size )
+ {
+ ssize_t stat = send(sock,d,size,MSG_DONTWAIT);
+ if ( stat > 0 )
+ {
+ if (send_count)
+ *send_count += stat;
+
+ size -= stat;
+ if (!size) // likely
+ return stat;
+
+ d += stat;
+ final_stat = stat;
+
+ // for this case: force growing buffer (because of 'all or none')
+ PrepareGrowBuffer(gb,size,true);
+ }
+ }
+ }
+
+ if ( size && size <= GetSpaceGrowBuffer(gb) )
+ final_stat += InsertGrowBuffer(gb,d,size);
+ return final_stat;
+ }
+
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SendDirectTCPStream
+(
+ // Send data direct without blocking and without calling any notifier.
+ // If output buf is not empty or send failed, append the data to output buf.
+ // Returns the number of bytes added to the output buffer or -1 on error.
+ // The data is send+stored completely (returns 'size') or not at all
+ // (returns '0'). -1 is returned if the socket is invalid.
+
+ TCPStream_t *ts, // valid TCP handler
+ bool flush_output, // true: try to flush out-buf before
+ cvp data, // data to send
+ uint size // size of 'data', If NULL && flush: flush only
+)
+{
+ DASSERT(ts);
+ DASSERT(data||!size);
+
+ uint count = 0;
+ const int stat
+ = SendDirectGrowBuffer(&ts->obuf,ts->sock,flush_output,data,size,&count);
+ if ( count > 0 )
+ UpdateSendStatTCPStream(ts,count,0);
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int PrintArgDirectTCPStream
+(
+ // Printing interface for SendDirectTCPStream()
+
+ TCPStream_t *ts, // valid TCP handler
+ bool flush_output, // true: try to flush out-buf before
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'vfprintf(...,format,...)'
+)
+{
+ DASSERT(ts);
+
+ if (!format)
+ return SendDirectTCPStream(ts,flush_output,0,0);
+
+ char buf[10000];
+
+ va_list arg2;
+ va_copy(arg2,arg);
+ int stat = vsnprintf(buf,sizeof(buf),format,arg2);
+ va_end(arg2);
+
+ if ( stat < sizeof(buf) )
+ return SendDirectTCPStream(ts,flush_output,buf,stat);
+
+
+ //--- buffer too small, use dynamic memory
+
+ noPRINT("PrintArgGrowBuffer() -> MALLOC(%u)\n",stat+1);
+
+ char *temp = MALLOC(stat+1);
+ stat = vsnprintf(temp,stat+1,format,arg);
+ stat = SendDirectTCPStream(ts,flush_output,temp,stat);
+ FREE(temp);
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int PrintDirectTCPStream
+(
+ // Printing interface for SendDirectTCPStream()
+
+ TCPStream_t *ts, // valid TCP handler
+ bool flush_output, // true: try to flush out-buf before
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vfprintf(...,format,...)'
+)
+{
+ DASSERT(ts);
+
+ if (format)
+ {
+ va_list arg;
+ va_start(arg,format);
+ const int stat = PrintArgDirectTCPStream(ts,flush_output,format,arg);
+ va_end(arg);
+ return stat;
+ }
+
+ return SendDirectTCPStream(ts,flush_output,0,0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int PrintBinary1TCPStream
+(
+ TCPStream_t *ts, // NULL or destination
+ ccp cmd, // command name
+ cvp bin_data, // pointer to binary data
+ uint bin_size, // size of binary data
+ ccp format, // NULL or format string with final ';'
+ ... // parameters for 'format'
+)
+{
+ DASSERT(cmd);
+ DASSERT(bin_data||!bin_size);
+
+ if (!ts)
+ return -1;
+
+ char buf[10000], *end_buf = buf + sizeof(buf) - 4;
+ char *dest = StringCopyE(buf,end_buf,cmd);
+ if ( dest + 6 + bin_size >= end_buf )
+ return -1;
+
+ *dest++ = ' ';
+ *dest++ = 1;
+ write_be16(dest,bin_size);
+ dest += 2;
+ memcpy(dest,bin_data,bin_size);
+ dest += bin_size;
+
+ *dest++ = ' ';
+ *dest++ = '=';
+
+ int len;
+ if (format)
+ {
+ va_list arg;
+ va_start(arg,format);
+ len = vsnprintf(dest,end_buf-dest,format,arg);
+ va_end(arg);
+
+ if ( len < 0 )
+ return -1;
+ len += dest - buf;
+ if ( len >= sizeof(buf) )
+ return -1;
+ }
+ else
+ {
+ *dest++ = ';';
+ len = dest - buf;
+ }
+
+ return SendDirectTCPStream(ts,false,buf,len);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int PrintBinary2TCPStream
+(
+ TCPStream_t *ts, // NULL or destination
+ ccp cmd, // command name
+ cvp bin1_data, // pointer to binary data
+ uint bin1_size, // size of binary data
+ cvp bin2_data, // pointer to binary data
+ uint bin2_size, // size of binary data
+ ccp format, // NULL or format string with final ';'
+ ... // parameters for 'format'
+)
+{
+ DASSERT(cmd);
+ DASSERT(bin1_data||!bin1_size);
+ DASSERT(bin2_data||!bin2_size);
+
+ if (!ts)
+ return -1;
+
+ char buf[10000], *end_buf = buf + sizeof(buf) - 4;
+ char *dest = StringCopyE(buf,end_buf,cmd);
+ if ( dest + 9 + bin1_size + bin2_size >= end_buf )
+ return -1;
+
+ *dest++ = ' ';
+ *dest++ = 1;
+ write_be16(dest,bin1_size);
+ dest += 2;
+ memcpy(dest,bin1_data,bin1_size);
+ dest += bin1_size;
+
+ *dest++ = 1;
+ write_be16(dest,bin2_size);
+ dest += 2;
+ memcpy(dest,bin2_data,bin2_size);
+ dest += bin2_size;
+
+ *dest++ = ' ';
+ *dest++ = '=';
+
+ int len;
+ if (format)
+ {
+ va_list arg;
+ va_start(arg,format);
+ len = vsnprintf(dest,end_buf-dest,format,arg);
+ va_end(arg);
+
+ if ( len < 0 )
+ return -1;
+ len += dest - buf;
+ if ( len >= sizeof(buf) )
+ return -1;
+ }
+ else
+ {
+ *dest++ = ';';
+ len = dest - buf;
+ }
+
+ return SendDirectTCPStream(ts,false,buf,len);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void LogTCPStream
+(
+ FILE *f, // output file
+ int indent, // indention
+ const TCPStream_t *ts, // valid TCP handler
+ int recurse, // >0: print stream list, >1: print buffer status
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vfprintf(format,...)'
+)
+{
+ DASSERT(f);
+ DASSERT(ts);
+ indent = NormalizeIndent(indent);
+
+ fprintf(f,"%*sTS: id:%x, sock=%d, %u packets received (%s), %u packets send (%s)",
+ indent,"",
+ ts->unique_id, ts->sock,
+ ts->stat.recv_count, PrintSize1024(0,0,ts->stat.recv_size,false),
+ ts->stat.send_count, PrintSize1024(0,0,ts->stat.send_size,false) );
+
+ if (format)
+ {
+ fputs(" : ",f);
+ va_list arg;
+ va_start(arg,format);
+ vfprintf(f,format,arg);
+ va_end(arg);
+ }
+
+ fputc('\n',f);
+
+ if (recurse>0)
+ {
+ LogGrowBuffer(f,indent+2,&ts->ibuf,"in buf");
+ LogGrowBuffer(f,indent+2,&ts->obuf,"out buf");
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void LogTCPStreamActivity
+(
+ const TCPStream_t *ts, // valid TCP handler
+ ccp activity
+)
+{
+ if (!ts->tracelog)
+ return;
+
+ char hbuf[200];
+ if (ts->handler)
+ {
+ const TCPHandler_t *th = ts->handler;
+ const Socket_t *so = th->listen;
+
+ DASSERT( TCP_HANDLER_MAX_LISTEN >= 3 );
+ snprintf(hbuf,sizeof(hbuf),
+ " | TH#%u: %u/%u/%u, sock=%d,%d,%d",
+ th->unique_id,
+ th->used_streams, th->max_used_streams, th->total_streams,
+ so[0].sock, so[1].sock, so[2].sock );
+ }
+ else
+ *hbuf = 0;
+
+ TraceLogPrint(ts->tracelog,
+ "%p %-20s sock=%d%s\n", ts, activity, ts->sock, hbuf );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * BufInfoHeadTCPStream
+(
+ // returns a pointer to the head line
+
+ uint line // 0|1
+)
+{
+ switch (line)
+ {
+ case 0:
+ return "connect in buf out buf"
+ " received data send data";
+
+ case 1:
+ return " time sock status use/ max use/ max"
+ " time pkt size time pkt size stream info";
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * BufInfoTCPStream
+(
+ // returns a pointer to the buffer
+
+ char * buf, // result (BUF_INFO_TCP_STREAM_SIZE bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ const TCPStream_t *ts, // valid TCP handler
+ u64 now_usec // NULL or current time, GetTimeUSec(false)
+)
+{
+ DASSERT(ts);
+
+ if (!now_usec)
+ now_usec = GetTimeUSec(false);
+
+ char connect_usec[7];
+ if ( ts->connect_usec )
+ PrintTimerUSec6(connect_usec,sizeof(connect_usec),
+ now_usec - ts->connect_usec, true );
+ else
+ connect_usec[0] = '-', connect_usec[1] = 0;
+
+ char receive_usec[7];
+ if ( ts->receive_usec )
+ PrintTimerUSec6(receive_usec,sizeof(receive_usec),
+ now_usec - ts->receive_usec, true );
+ else
+ receive_usec[0] = '-', receive_usec[1] = 0;
+
+ char send_usec[7];
+ if ( ts->send_usec )
+ PrintTimerUSec6(send_usec,sizeof(send_usec),
+ now_usec - ts->send_usec, true );
+ else
+ send_usec[0] = '-', send_usec[1] = 0;
+
+ if (!buf)
+ buf = GetCircBuf( buf_size = BUF_INFO_TCP_STREAM_SIZE );
+
+ snprintf(buf,buf_size,
+ "%6.6s%5d %c%c%c%c%c%c%c %s/%s %s/%s %6s %s %s %6s %s %s %s"
+ ,connect_usec
+ ,ts->sock
+ ,ts->protect > 0 ? 'P' : '-'
+ ,ts->auto_close > 0 ? 'A' : '-'
+ ,ts->rescan > 0 ? 'S' : '-'
+ ,ts->eof > 0 ? 'X' : '-'
+ ,ts->error > 0 ? 'E' : '-'
+ ,ts->ibuf.disabled ? 'D' : 'i'
+ ,ts->obuf.disabled ? 'D' : 'o'
+ ,PrintNumberU4(0,0,ts->ibuf.used,true)
+ ,PrintNumberU4(0,0,ts->ibuf.max_used,true)
+ ,PrintNumberU4(0,0,ts->obuf.used,true)
+ ,PrintNumberU4(0,0,ts->obuf.max_used,true)
+ ,receive_usec
+ ,PrintNumberU5(0,0,ts->stat.recv_count,true)
+ ,PrintSize1024(0,0,ts->stat.recv_size,DC_SFORM_TINY|DC_SFORM_ALIGN)
+ ,send_usec
+ ,PrintNumberU5(0,0,ts->stat.send_count,true)
+ ,PrintSize1024(0,0,ts->stat.send_size,DC_SFORM_TINY|DC_SFORM_ALIGN)
+ ,ts->info
+ );
+
+ return buf;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCPHandler_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void AddSocketTCPStream
+(
+ TCPStream_t *ts, // valid TCP handler
+ FDList_t *fdl // valid file descriptor list
+)
+{
+ DASSERT(ts);
+ DASSERT(fdl);
+
+ if ( ts->OnFDList && ts->OnFDList(ts,fdl,TCP_FM_ADD_SOCK,false) < 0 || ts->sock == -1 )
+ return;
+
+ uint events = POLLERR|POLLRDHUP;
+ if ( !ts->eof && !ts->ibuf.disabled && GetSpaceGrowBuffer(&ts->ibuf) )
+ events |= POLLIN;
+ if ( !ts->obuf.disabled && ts->obuf.used )
+ events |= POLLOUT;
+
+ ts->poll_index = AddFDList(fdl,ts->sock,events);
+
+ if ( ts->OnTimeout && ts->trigger_usec && fdl->timeout_usec > ts->trigger_usec )
+ fdl->timeout_usec = ts->trigger_usec;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CheckSocketTCPStream
+(
+ TCPStream_t *ts, // valid TCP handler
+ FDList_t *fdl, // valid socket list
+ bool check_timeout // true: enable timeout checks
+)
+{
+ DASSERT(ts);
+ DASSERT(fdl);
+
+ if ( ts->OnFDList && ts->OnFDList(ts,fdl,TCP_FM_CHECK_SOCK,check_timeout) < 0
+ || ts->sock == -1 )
+ {
+ return;
+ }
+
+ const u64 now_usec = GetTimeUSec(false);
+
+ typeof(ts->rescan) rescan = ts->rescan;
+ ts->rescan = 0;
+
+ const uint revents = GetEventFDList(fdl,ts->sock,ts->poll_index);
+
+ if ( !ts->ibuf.disabled && revents & POLLIN )
+ {
+ noPRINT("RECV: %d\n",ts->sock);
+ OnReceivedStream(ts,now_usec);
+ rescan = 0;
+ }
+
+ if ( !ts->obuf.disabled && ts->obuf.used && revents & POLLOUT )
+ OnWriteStream(ts,now_usec);
+
+ if ( revents & (POLLERR|POLLHUP|POLLRDHUP|POLLNVAL) )
+ {
+ ts->error |= 1;
+ OnCloseStream(ts,now_usec);
+ }
+
+ if ( rescan && ts->OnReceived )
+ {
+ uchar ch = 0;
+ ts->OnReceived(ts,&ch,0);
+ }
+
+ if (check_timeout)
+ CheckTimeoutTCPStream(ts);
+
+ if (ts->rescan)
+ ts->trigger_usec = ts->accept_usec = now_usec;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef PRINT_TIMEOUT
+#define PRINT_TIMEOUT(ts,now,info) \
+ noPRINT("%s[%d]: now=%lld, trig=%lld, accept=%lld, OnTimeout=%d\n", \
+ info, ts->sock, now, ts->trigger_usec ? ts->trigger_usec - now : 0, \
+ ts->accept_usec ? ts->accept_usec - now : 0, ts->OnTimeout != 0 )
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void CheckTimeoutTCPStream ( TCPStream_t *ts )
+{
+ DASSERT(ts);
+
+ if ( ts->OnFDList && ts->OnFDList(ts,0,TCP_FM_TIMEOUT,true) < 0 || ts->sock == -1 )
+ return;
+
+ const u64 now_usec = GetTimeUSec(false);
+ PRINT_TIMEOUT(ts,now_usec,"TIMEOUT");
+
+ if ( ts->trigger_usec && ts->trigger_usec <= now_usec
+ || ts->accept_usec && ts->accept_usec <= now_usec
+ )
+ {
+ ts->trigger_usec = ts->accept_usec = 0;
+ if (ts->OnTimeout)
+ ts->OnTimeout(ts,now_usec);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetLastActivityTCPStream ( TCPStream_t *ts )
+{
+ DASSERT(ts);
+ const u64 ref = ts->connect_usec > ts->receive_usec
+ ? ts->connect_usec : ts->receive_usec;
+ return ref > ts->send_usec ? ref : ts->send_usec;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetTimeoutTCPStream ( TCPStream_t *ts )
+{
+ DASSERT(ts);
+ return ts->timeout_usec
+ ? GetLastActivityTCPStream(ts) + ts->timeout_usec
+ : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int OnTimeoutTCPStream ( TCPStream_t *ts, u64 now_usec )
+{
+ DASSERT(ts);
+ if ( ts->sock != -1 && ts->timeout_usec )
+ {
+ if (!now_usec)
+ now_usec = GetTimeUSec(false);
+
+ if ( now_usec >= GetLastActivityTCPStream(ts) + ts->timeout_usec )
+ OnCloseStream(ts,now_usec);
+ else
+ SetupTriggerTCPStream(ts,now_usec);
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupTriggerTCPStream ( TCPStream_t *ts, u64 now_usec )
+{
+ DASSERT(ts);
+
+ const u64 next = GetTimeoutTCPStream(ts);
+ if (next)
+ {
+ if (!now_usec)
+ now_usec = GetTimeUSec(false);
+
+ if ( ts->accept_usec <= now_usec || ts->accept_usec > next )
+ ts->accept_usec = next;
+ ts->trigger_usec = ts->accept_usec + ts->delay_usec;
+
+ noPRINT("next=%lld [%lld], accept=%lld [%lld]\n",
+ next, next-now_usec, ts->accept_usec, ts->accept_usec-now_usec );
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCPHandler_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeTCPHandler
+(
+ TCPHandler_t *th, // not NULL
+ uint data_size // size of 'TCPStream_t::data'
+)
+{
+ DASSERT(th);
+ memset(th,0,sizeof(*th));
+ th->unique_id = CreateUniqueId(1);
+ th->data_size = data_size;
+ th->OnAllowStream = IsStreamAllowed;
+
+ uint i;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ th->listen[i].sock = -1;
+ LOG_TCP_HANDLER(th,0,"INIT(%u)",data_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetTCPHandler
+(
+ TCPHandler_t *th // valid TCP handler
+)
+{
+ DASSERT(th);
+ th->allow_ip4 = DeleteReferenceAllowIP4(th->allow_ip4);
+ UnlistenAllTCP(th);
+
+ while (th->first)
+ DestroyTCPStream(th->first);
+ usleep(1);
+
+ InitializeTCPHandler(th,th->data_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CloseTCPHandler
+(
+ // returns the number of waiting clients (obuf not empty and !disabled)
+ TCPHandler_t *th // valid TCP handler
+)
+{
+ DASSERT(th);
+ UnlistenAllTCP(th);
+
+ uint count = 0;
+ u64 now = GetTimeUSec(false);
+ TCPStream_t *ts;
+ for ( ts = th->first; ts; ts = ts->next )
+ {
+ OnCloseStream(ts,now);
+ if ( ts->obuf.used && !ts->obuf.disabled )
+ count++;
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * AddTCPStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ TCPStream_t *ts // the new stream
+)
+{
+ DASSERT(th);
+ DASSERT(ts);
+
+ ts->handler = th;
+ ts->prev = 0;
+ ts->next = th->first;
+ if (th->first)
+ th->first->prev = ts;
+ th->first = ts;
+
+ if ( th->max_used_streams < ++th->used_streams )
+ th->max_used_streams = th->used_streams;
+ th->total_streams++;
+ th->stat.conn_count++;
+ ts->connect_usec = GetTimeUSec(false);
+
+ LogTCPStreamActivity(ts,"AddTCPStream");
+ return ts;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * CreateTCPStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ int sock, // -1 or valid socket
+ u64 allow_mode, // stored in 'allow_mode'
+ const Socket_t *listen // NULL or related listen object
+)
+{
+ DASSERT(th);
+
+ TCPStream_t *ts = listen && listen->OnCreateStream
+ ? listen->OnCreateStream(th,sock) : 0;
+ if (!ts)
+ {
+ if (th->OnCreateStream)
+ ts = th->OnCreateStream(th,sock);
+
+ if (!ts)
+ {
+ ts = CALLOC(sizeof(*ts)+th->data_size,1);
+ InitializeTCPStream(ts,sock);
+ }
+ }
+
+ if ( th->tracelog.level >= 0 )
+ {
+ ts->tracelog = &th->tracelog;
+ LogTCPStreamActivity(ts,"CreateTCPStream");
+ }
+
+ ts->allow_mode = allow_mode;
+ AddTCPStream(th,ts);
+
+ if ( listen && listen->OnAddedStream )
+ listen->OnAddedStream(ts);
+ else if (th->OnAddedStream)
+ th->OnAddedStream(ts);
+
+ return ts;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * ConnectUnixTCPStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ ccp path, // NULL or path part 1 to socket file
+ bool silent // suppress error messages
+)
+{
+ DASSERT(th);
+ DASSERT(path);
+
+ int sock = socket(AF_UNIX,SOCK_STREAM,0);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"Can't create UNIX stream socket: %s\n",path);
+ return 0;
+ }
+
+ struct sockaddr_un sa;
+ memset(&sa,0,sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ StringCopyS(sa.sun_path,sizeof(sa.sun_path),path);
+
+ int stat = connect(sock,(struct sockaddr*)&sa,sizeof(sa));
+ if (stat)
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't connect UNIX stream socket: %s\n",path);
+ shutdown(sock,SHUT_RDWR);
+ close(sock);
+ return 0;
+ }
+
+ return CreateTCPStream(th,sock,ALLOW_MODE_ALLOW,0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * ConnectTCPStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ ccp addr, // address -> NetworkHost_t
+ u16 default_port, // default port
+ bool silent // suppress error messages
+)
+{
+ DASSERT(th);
+ DASSERT(addr);
+
+ ccp unix_path = CheckUnixSocketPath(addr,0);
+ if (unix_path)
+ return ConnectUnixTCPStream(th,unix_path,silent);
+
+ if (!strncasecmp(addr,"tcp:",4))
+ addr += 4;
+
+ NetworkHost_t nh;
+ ResolveHost(&nh,true,addr,default_port,false,false);
+ PRINT("SINGLE/CONNECT/TCP: %s -> %s\n",
+ addr, PrintIP4(0,0,nh.ip4,nh.port) );
+
+ int sock = socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK,0);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"Can't create TCP socket: %s\n",
+ PrintIP4(0,0,nh.ip4,nh.port));
+ return 0;
+ }
+
+ struct sockaddr_in sa;
+ memset(&sa,0,sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(nh.ip4);
+ sa.sin_port = htons(nh.port);
+
+ int stat = connect(sock,(struct sockaddr*)&sa,sizeof(sa));
+ if ( stat && errno != EINPROGRESS )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't connect TCP socket: %s\n",
+ PrintIP4(0,0,nh.ip4,nh.port));
+ shutdown(sock,SHUT_RDWR);
+ close(sock);
+ ResetHost(&nh);
+ return 0;
+ }
+
+ return CreateTCPStream(th,sock,ALLOW_MODE_ALLOW,0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * FindTCPStreamByUniqueID
+(
+ TCPHandler_t *th, // valid TCP handler
+ uint unique_id // id to search
+)
+{
+ TCPStream_t *ts;
+ for ( ts = th->first; ts; ts = ts->next )
+ if ( ts->unique_id == unique_id )
+ return ts;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint NumOfSocketsTCP ( const TCPHandler_t *th )
+{
+ uint i, count = 0;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ if ( th->listen[i].sock != -1 )
+ count++;
+
+ const TCPStream_t *ts;
+ for ( ts = th->first; ts; ts = ts->next )
+ if ( ts->sock != -1
+ && ( !ts->eof && !ts->ibuf.disabled
+ || !ts->obuf.disabled && ts->obuf.used ))
+ {
+ count++;
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+Socket_t * GetUnusedListenSocketTCP ( TCPHandler_t *th, bool silent )
+{
+ DASSERT(th);
+
+ uint i;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ if ( th->listen[i].sock == -1 )
+ return th->listen+i;
+
+ if (!silent)
+ ERROR0(ERR_CANT_CREATE,"No socket available.\n");
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ListenUnixTCP
+(
+ TCPHandler_t *th, // valid TCP handler
+ ccp path // path to unix file
+)
+{
+ DASSERT(th);
+ DASSERT(path);
+ PRINT("LISTEN/UNIX: %s\n",path);
+
+ Socket_t *lsock = GetUnusedListenSocketTCP(th,false);
+ if (!lsock)
+ return ERR_CANT_CREATE;
+ unlink(path);
+
+ int sock = socket(AF_UNIX,SOCK_STREAM,0);
+ if ( sock == -1 )
+ return ERROR1(ERR_CANT_CREATE,"Can't create UNIX socket: %s\n",path);
+
+ struct sockaddr_un sa;
+ memset(&sa,0,sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ StringCopyS(sa.sun_path,sizeof(sa.sun_path),path);
+
+ int stat = bind(sock,(struct sockaddr*)&sa,sizeof(sa));
+ if ( stat == -1 )
+ {
+ close(sock);
+ return ERROR1(ERR_CANT_CREATE,"Can't bind UNIX socket: %s\n",path);
+ }
+
+ stat = listen(sock,50);
+ if ( stat == -1 )
+ {
+ close(sock);
+ return ERROR1(ERR_CANT_CREATE,"Can't listen UNIX socket: %s\n",path);
+ }
+ //SetSocketBlocking(cmdi_sock,false);
+
+ DASSERT(lsock);
+ lsock->sock = sock;
+ lsock->is_unix = true;
+ snprintf(lsock->info,sizeof(lsock->info),"UNIX socket %d",sock);
+ PRINT("LISTEN: %s [idx=%zu]\n",lsock->info,lsock-th->listen);
+ LOG_TCP_HANDLER(th,0,"LISTEN/UNIX[%u] %s",listen_idx,path);
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ListenTCP
+(
+ TCPHandler_t *th, // valid TCP handler
+ ccp addr, // IPv4 address with optional port -> NetworkHost_t
+ // fall back to ListenUnixTCP() if addr begins with
+ // 'unix:' or '/' or './ or '../'
+ u16 default_port // default port
+)
+{
+ DASSERT(th);
+ DASSERT(addr);
+
+ Socket_t *lsock = GetUnusedListenSocketTCP(th,false);
+ if (!lsock)
+ return ERR_CANT_CREATE;
+
+ const bool allow_unix = memcmp(addr,"tcp:",4) != 0;
+ if (!allow_unix)
+ {
+ addr += 4;
+ while ( *addr == '/' )
+ addr++;
+ }
+
+ NetworkHost_t nh;
+ if ( !ResolveHost(&nh,true,addr,default_port,allow_unix,true) && nh.filename )
+ {
+ ccp unix_path = nh.filename;
+ ResetHost(&nh);
+ return ListenUnixTCP(th,unix_path);
+ }
+ ccp path = GetHostName(&nh);
+ PRINT("LISTEN/TCP: %s:%u -> %s\n",nh.name,default_port,PrintIP4(0,0,nh.ip4,nh.port));
+
+ int sock = socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK,0);
+ if ( sock == -1 )
+ {
+ ERROR1(ERR_CANT_CREATE,"Can't create TCP socket: %s\n",path);
+ ResetHost(&nh);
+ return ERR_CANT_CREATE;
+ }
+
+ int on = 1;
+ setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, (ccp)&on, sizeof(on) );
+
+ struct sockaddr_in sa;
+ memset(&sa,0,sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(nh.ip4);
+ sa.sin_port = htons(nh.port);
+
+ int stat = bind(sock,(struct sockaddr*)&sa,sizeof(sa));
+ if ( stat == -1 )
+ {
+ ERROR1(ERR_CANT_CREATE,"Can't bind TCP socket: %s\n",
+ PrintIP4(0,0,nh.ip4,nh.port));
+ close(sock);
+ ResetHost(&nh);
+ return ERR_CANT_CREATE;
+ }
+
+ stat = listen(sock,50);
+ if ( stat == -1 )
+ {
+ ERROR1(ERR_CANT_CREATE,"Can't listen TCP socket: %s\n",
+ PrintIP4(0,0,nh.ip4,nh.port));
+ close(sock);
+ ResetHost(&nh);
+ return ERR_CANT_CREATE;
+ }
+ //SetSocketBlocking(cmdi_sock,false);
+
+ DASSERT(lsock);
+ lsock->sock = sock;
+ lsock->is_unix = false;
+ snprintf(lsock->info,sizeof(lsock->info),"TCP socket %d",sock);
+ PRINT("LISTEN: %s [idx=%zu]\n",lsock->info,lsock-th->listen);
+
+ ResetHost(&nh);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int UnlistenTCP
+(
+ // returns the index of the closed socket, or if not found
+
+ TCPHandler_t *th, // valid TCP handler
+ int sock // socket to close
+)
+{
+ uint i;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ if ( th->listen[i].sock == sock )
+ {
+ close(sock);
+ th->listen[i].sock = -1;
+ return i;
+ }
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint UnlistenAllTCP
+(
+ // returns the number of closed sockets
+
+ TCPHandler_t *th // valid TCP handler
+)
+{
+ uint i, count = 0;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ if ( th->listen[i].sock != -1 )
+ {
+ socket_info_t si;
+ GetSocketInfoBySocket(&si,th->listen[i].sock,true);
+ if ( si.is_valid && si.protocol == AF_UNIX
+ && si.address && *si.address )
+ {
+ PRINT("unlink(%s)\n",si.address);
+ unlink(si.address);
+ }
+ ResetSocketInfo(&si);
+
+ close(th->listen[i].sock);
+ th->listen[i].sock = -1;
+ count++;
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void AddSocketsTCP
+(
+ TCPHandler_t *th, // valid TCP handler
+ FDList_t *fdl // valid file descriptor list
+)
+{
+ DASSERT(th);
+ DASSERT(fdl);
+
+ AnnounceFDList(fdl,TCP_HANDLER_MAX_LISTEN+th->used_streams+1);
+
+ if ( !th->max_conn || th->used_streams < th->max_conn )
+ {
+ uint i;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ {
+ Socket_t *lsock = th->listen + i;
+ lsock->poll_index = AddFDList(fdl,lsock->sock,POLLIN);
+ }
+ }
+
+ TCPStream_t *ts;
+ for ( ts = th->first; ts; ts = ts->next )
+ AddSocketTCPStream(ts,fdl);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsStreamAllowed
+(
+ // returns true if access is allowed
+
+ struct TCPHandler_t *th, // valid TCP handler
+ const Socket_t *lsock, // valid listen-socket
+ int sock, // valid socket to verify
+ u64 *allow // not NULL: store allow code here
+)
+{
+ DASSERT(th);
+ if ( !th->allow_ip4 || lsock && lsock->is_unix )
+ {
+ // no checks for unix sockets
+ if (allow)
+ *allow = ALLOW_MODE_ALLOW;
+ return true;
+ }
+
+ const u64 res = GetAutoAllowIP4BySock(th->allow_ip4,sock);
+ if (allow)
+ *allow = res;
+ return ( res & th->allow_ip4->allow_mode ) != 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * OnAcceptStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ const Socket_t *lsock, // listen-socket
+ u64 now_usec // time for timestamps, GetTimeUSec(false)
+)
+{
+ DASSERT(th);
+ DASSERT(lsock);
+ DASSERT( lsock >= th->listen );
+ DASSERT( lsock < th->listen + TCP_HANDLER_MAX_LISTEN );
+ DASSERT( lsock->sock != -1 );
+ PRINT("OnAcceptStream() lsock=%d,%s\n",lsock->sock,lsock->info);
+
+ TCPStream_t *ts = NULL;
+ int new_sock = accept(lsock->sock,0,0);
+ if ( new_sock != -1 )
+ {
+ RegisterFileId(new_sock);
+
+ u64 allow_code = 0;
+ const u64 is_allowed = th->OnAllowStream
+ ? th->OnAllowStream(th,lsock,new_sock,&allow_code)
+ : ALLOW_MODE_ALLOW;
+ if ( !is_allowed || th->OnAcceptStream && th->OnAcceptStream(th,lsock,new_sock) )
+ {
+ PRINT("NOT ACCEPTED: %d -> %d\n",lsock->sock,new_sock);
+ shutdown(new_sock,SHUT_RDWR);
+ close(new_sock);
+ }
+ else
+ {
+ PRINT("ACCEPTED: %d -> %d [alloe=%llx]\n",lsock->sock,new_sock,allow_code);
+ ts = CreateTCPStream(th,new_sock,allow_code,lsock);
+ if (ts)
+ {
+ if (!ts->info[0])
+ {
+ if (lsock->info[0])
+ snprintf(ts->info,sizeof(ts->info),
+ "by %s [%x]",
+ lsock->info, ts->unique_id );
+ else
+ snprintf(ts->info,sizeof(ts->info),
+ "by socket %d [%x]",
+ lsock->sock, ts->unique_id );
+ }
+ }
+ else
+ {
+ shutdown(new_sock,SHUT_RDWR);
+ close(new_sock);
+ }
+ }
+ }
+ return ts;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void OnCloseStream
+(
+ TCPStream_t *ts, // valid TCP stream
+ u64 now_usec // time for timestamps, GetTimeUSec(false)
+)
+{
+ DASSERT(ts);
+ if ( ts->sock != -1 )
+ {
+ PRINT("CLOSE: %d\n",ts->sock);
+ if (ts->OnClose)
+ ts->OnClose(ts,now_usec);
+ if ( ts->sock != -1 )
+ {
+ shutdown(ts->sock,SHUT_RDWR);
+ close(ts->sock);
+ ts->sock = -1;
+ }
+ if ( !ts->protect && ts->handler )
+ ts->handler->need_maintenance |= 1;
+
+ ts->OnReceived = 0;
+ ts->OnSend = 0;
+ ts->OnTimeout = 0;
+ ts->OnClose = 0;
+ ts->OnFDList = 0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetNotSocketStream
+(
+ TCPStream_t *ts // valid TCP stream
+)
+{
+ DASSERT(ts);
+ if ( ts->sock != -1 )
+ {
+ ts->not_socket = true;
+
+ int flags = fcntl(ts->sock,F_GETFL,0);
+ if ( flags != -1 )
+ {
+ flags |= O_NONBLOCK;
+ fcntl(ts->sock,F_SETFL,flags);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void OnReceivedStream
+(
+ TCPStream_t *ts, // valid TCP stream
+ u64 now_usec // time for timestamps, GetTimeUSec(false)
+)
+{
+ DASSERT(ts);
+
+ uint max_read = GetSpaceGrowBuffer(&ts->ibuf);
+ if (max_read)
+ {
+ u8 buf[0x4000];
+ if ( max_read > sizeof(buf) )
+ max_read = sizeof(buf);
+
+ ssize_t stat;
+ retry:
+ if (ts->not_socket)
+ stat = read(ts->sock,buf,max_read);
+ else
+ stat = recv(ts->sock,buf,max_read,MSG_DONTWAIT);
+
+ if ( stat < 0 && !ts->not_socket && errno == ENOTSOCK )
+ {
+ SetNotSocketStream(ts);
+ goto retry;
+ }
+
+ #if HAVE_PRINT
+ noPRINT("RECV[%d] %lld/%u, err=%d\n",ts->sock,(s64)stat,max_read,errno);
+ if ( stat < 0 )
+ PRINT("!! %s(), ERRNO=%d: %s\n",__FUNCTION__,errno,strerror(errno));
+ #endif
+
+ if ( stat >= 0 )
+ {
+ if ( stat > 0 )
+ UpdateRecvStatTCPStream(ts,stat,now_usec);
+ else
+ ts->eof |= 1;
+
+ if (ts->OnReceived)
+ stat = ts->OnReceived(ts,buf,stat);
+ if ( stat > 0 )
+ InsertGrowBuffer(&ts->ibuf,buf,stat);
+
+//X if ( ts->eof && !ts->ibuf.used && !ts->obuf.used )
+//X OnCloseStream(ts,now_usec);
+ }
+ else if ( errno != EWOULDBLOCK )
+ OnCloseStream(ts,now_usec);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void OnWriteStream
+(
+ TCPStream_t *ts, // valid TCP stream
+ u64 now_usec // time for timestamps, GetTimeUSec(false)
+)
+{
+ DASSERT(ts);
+
+ restart:;
+ ssize_t stat;
+ if (ts->not_socket)
+ stat = write(ts->sock,ts->obuf.ptr,ts->obuf.used);
+ else
+ stat = send(ts->sock,ts->obuf.ptr,ts->obuf.used,MSG_DONTWAIT);
+ noPRINT("SEND[%d] %lld/%u, err=%d\n",ts->sock,(s64)stat,ts->obuf.used,errno);
+
+ if ( stat < 0 )
+ {
+ if ( !ts->not_socket && errno == ENOTSOCK )
+ {
+ SetNotSocketStream(ts);
+ goto restart;
+ }
+
+ PRINT("!! %s(), ERRNO=%d: %s\n",__FUNCTION__,errno,strerror(errno));
+ OnCloseStream(ts,now_usec);
+ }
+ else if (!stat)
+ ts->obuf.disabled |= 1;
+ else
+ {
+ UpdateSendStatTCPStream(ts,stat,now_usec);
+
+ if (ts->OnSend)
+ stat = ts->OnSend(ts,ts->obuf.ptr,stat);
+ if ( stat > 0 )
+ DropGrowBuffer(&ts->obuf,stat);
+ if ( ts->auto_close && !ts->obuf.used )
+ OnCloseStream(ts,now_usec);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CheckSocketsTCP
+(
+ TCPHandler_t *th, // valid TCP handler
+ FDList_t *fdl, // valid socket list
+ bool check_timeout // true: enable timeout checks
+)
+{
+ DASSERT(th);
+ DASSERT(fdl);
+
+ const u64 now_usec = GetTimeUSec(false);
+
+ uint i;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ {
+ Socket_t *lsock = th->listen + i;
+ if ( lsock->sock != -1 )
+ {
+ const uint revents = GetEventFDList(fdl,lsock->sock,lsock->poll_index);
+ if ( revents & POLLIN )
+ OnAcceptStream(th,lsock,now_usec);
+ }
+ }
+
+ TCPStream_t *next = th->first;
+ while (next)
+ {
+ // Do it in this way, because 'ts' may become invalid
+ TCPStream_t *ts = next;
+ next = ts->next;
+ CheckSocketTCPStream(ts,fdl,check_timeout);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CheckTimeoutTCP ( TCPHandler_t *th )
+{
+ DASSERT(th);
+
+ TCPStream_t *next = th->first;
+ while (next)
+ {
+ // Do it in this way, because 'ts' may becomes invalid
+ TCPStream_t *ts = next;
+ next = ts->next;
+ CheckTimeoutTCPStream(ts);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool MaintenanceTCP ( TCPHandler_t *th )
+{
+ DASSERT(th);
+
+ th->need_maintenance = 0;
+ const u64 now_usec = GetTimeUSec(false);
+ TCPStream_t *next = th->first;
+ while (next)
+ {
+ // Do it in this way, because 'ts' may becomes invalid
+ TCPStream_t *ts = next;
+ next = ts->next;
+
+ if ( ts->sock == -1 && ts->protect <= 0 )
+ DestroyTCPStream(ts);
+ else if (ts->OnMaintenance)
+ ts->OnMaintenance(ts,now_usec);
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ManageSocketsTCP
+(
+ // call CheckSocketsTCP(), CheckTimeoutTCP(), MaintenanceTCP()
+
+ TCPHandler_t *th, // valid TCP handler
+ FDList_t *fdl, // valid socket list
+ int stat // result of a WAIT function
+)
+{
+ if ( stat > 0 )
+ CheckSocketsTCP(th,fdl,true);
+ else
+ CheckTimeoutTCP(th);
+
+ if (th->need_maintenance)
+ MaintenanceTCP(th);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void LogTCPHandler
+(
+ FILE *f, // output file
+ int indent, // indention
+ const TCPHandler_t *th, // valid TCP handler
+ int recurse, // >0: print stream list, >1: print buffer status
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vfprintf(format,...)'
+)
+{
+ DASSERT(f);
+ DASSERT(th);
+ indent = NormalizeIndent(indent);
+
+ fprintf(f,"%*sTH: id:%x, n=%u/%u/%u, listen=",
+ indent,"", th->unique_id,
+ th->used_streams, th->max_used_streams, th->total_streams );
+
+ uint i;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ fprintf(f,"%d,",th->listen[i].sock);
+
+ fprintf(f," max=%u, dsize=%u, ready=%u",
+ th->max_conn, th->data_size, NumOfSocketsTCP(th) );
+
+ if (format)
+ {
+ fputs(" : ",f);
+ va_list arg;
+ va_start(arg,format);
+ vfprintf(f,format,arg);
+ va_end(arg);
+ }
+
+ fprintf(f,"\n%*s %u connects, %u packets received (%s), %u packets send (%s)\n",
+ indent,"",
+ th->stat.conn_count,
+ th->stat.recv_count, PrintSize1024(0,0,th->stat.recv_size,false),
+ th->stat.send_count, PrintSize1024(0,0,th->stat.send_size,false) );
+
+ if (recurse>0)
+ {
+ recurse--;
+ uint idx = 0;
+ const TCPStream_t *ts;
+ for ( ts = th->first; ts; ts = ts->next )
+ LogTCPStream(f,indent+2,ts,recurse,"Stream #%u",idx++);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintStreamTableTCPHandler
+(
+ FILE *f, // output file
+ const ColorSet_t *colset, // NULL (no colors) or valid color set
+ int indent, // indention
+ const TCPHandler_t *th // valid TCP handler
+)
+{
+ DASSERT(f);
+ DASSERT(th);
+ indent = NormalizeIndent(indent);
+ if (!colset)
+ colset = GetColorSet0();
+
+ const u64 now_usec = GetTimeUSec(false);
+
+ const TCPStream_t *ts;
+ ccp line1 = BufInfoHeadTCPStream(0);
+ ccp line2 = BufInfoHeadTCPStream(1);
+ const uint sep_len = strlen(line2)+sizeof(ts->info)-13;
+
+ fprintf(f,"%s%*s%.*s\n%s%*s%s\n%s%*s%s\n%s%*s%.*s%s\n",
+ colset->heading, indent,"", sep_len, Minus300,
+ colset->heading, indent,"", line1,
+ colset->heading, indent,"", line2,
+ colset->heading, indent,"", sep_len, Minus300,
+ colset->reset );
+
+ char buf[BUF_INFO_TCP_STREAM_SIZE];
+ for ( ts = th->first; ts; ts = ts->next )
+ {
+ BufInfoTCPStream(buf,BUF_INFO_TCP_STREAM_SIZE,ts,now_usec);
+ fprintf(f,"%*s%s\n",indent,"",buf);
+ }
+
+
+ if (th->used_streams)
+ fprintf(f,
+ "%s%*s%.*s%s\n",
+ colset->heading, indent,"", sep_len, Minus300,
+ colset->reset );
+
+ if ( th->total_streams > 1 || th->total_streams > th->used_streams )
+ {
+ snprintf(buf,sizeof(buf),"TOTAL: %u current client%s, %u max",
+ th->used_streams, th->used_streams == 1 ? "" : "s",
+ th->max_used_streams );
+
+ fprintf(f,
+ "%s%*s%-45s%s %s%13s %s Summary of %u stream%s\n%s%*s%.*s%s\n",
+ colset->status, indent,"", buf,
+ PrintNumberU7(0,0,th->stat.recv_count,true),
+ PrintSize1024(0,0,th->stat.recv_size,DC_SFORM_TINY|DC_SFORM_ALIGN),
+ PrintNumberU7(0,0,th->stat.send_count,true),
+ PrintSize1024(0,0,th->stat.send_size,DC_SFORM_TINY|DC_SFORM_ALIGN),
+ th->total_streams, th->total_streams == 1 ? "" : "s",
+ colset->heading, indent,"", sep_len,Minus300,
+ colset->reset );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool GetSocketInfoBySocket
+(
+ // returns TRUE, if infos retrived
+
+ socket_info_t *si, // result, not NULL, will be initialized
+ int sock, // socket id, maybe -1
+ bool get_name // true: alloc 'address' and copy name
+ // -> call ResetSocketInfo() to free mem
+)
+{
+ DASSERT(si);
+ InitializeSocketInfo(si);
+ if ( sock == -1 )
+ return false;
+ si->sock = sock;
+
+ DASSERT( sizeof(struct sockaddr_un) >= sizeof(struct sockaddr_in) );
+ DASSERT( sizeof(struct sockaddr_un) >= sizeof(struct sockaddr_in6) );
+
+ struct sockaddr_un sa;
+ memset(&sa,0,sizeof(sa));
+ socklen_t sa_len = sizeof(sa);
+ if (getsockname(sock,(struct sockaddr*)&sa,&sa_len))
+ return false;
+
+ char buf[INET6_ADDRSTRLEN+10];
+ switch (sa.sun_family)
+ {
+ case AF_UNIX:
+ si->protocol = AF_UNIX;
+ if ( get_name && sa.sun_path[0] )
+ {
+ si->address = STRDUP(sa.sun_path);
+ si->alloced = true;
+ }
+ break;
+
+ case AF_INET:
+ si->protocol = AF_INET;
+ if (get_name)
+ {
+ const struct sockaddr_in *sin = (struct sockaddr_in*)&sa;
+ PrintIP4sa(buf,sizeof(buf),sin);
+ si->address = STRDUP(buf);
+ si->alloced = true;
+ }
+ break;
+
+ case AF_INET6:
+ si->protocol = AF_INET6;
+ if (get_name)
+ {
+ const struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&sa;
+ if (inet_ntop(AF_INET6,&sin6->sin6_addr,buf+1,sizeof(buf)-1))
+ {
+ *buf = '[';
+ char *dest = buf + strlen(buf);
+ snprintf(dest,buf+sizeof(buf)-dest,"]:%u",ntohs(sin6->sin6_port));
+ si->address = STRDUP(buf);
+ si->alloced = true;
+ }
+ }
+ break;
+
+ default:
+ return false;
+ }
+
+ int socktype = -1;
+ socklen_t psize = sizeof(socktype);
+ if (!getsockopt(sock,SOL_SOCKET,SO_TYPE,&socktype,&psize))
+ si->type = socktype;
+
+ return si->is_valid = true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetSocketNameBySA
+(
+ char *buf, // dest buffer
+ uint bufsize, // size of 'dest'
+ const struct sockaddr *sa, // socket address
+ socklen_t sa_len // length of 'sa'
+)
+{
+ DASSERT(buf);
+ DASSERT(bufsize>0);
+ DASSERT(sa);
+ DASSERT(sa_len>0);
+
+ if ( sa_len >= sizeof(struct sockaddr_un) )
+ {
+ const struct sockaddr_un *sun = (struct sockaddr_un*)sa;
+ if ( sun->sun_family == AF_UNIX && sun->sun_path[0] )
+ return snprintfS(buf,bufsize,"unix:%s",sun->sun_path);
+ }
+
+ if ( sa_len >= sizeof(struct sockaddr_in) )
+ {
+ const struct sockaddr_in *sin = (struct sockaddr_in*)sa;
+ if ( sin->sin_family == AF_INET && bufsize > 5 )
+ {
+ strcpy(buf,"tcp:");
+ return PrintIP4sa(buf+4,bufsize-4,sin) - buf;
+ }
+ }
+
+ if ( sa_len >= sizeof(struct sockaddr_in6) )
+ {
+ const struct sockaddr_in6 *sin = (struct sockaddr_in6*)sa;
+ if ( sin->sin6_family == AF_INET6 && bufsize >= 5 + INET6_ADDRSTRLEN )
+ {
+ // [[2do]] ??? "[addr]:port"
+ strcpy(buf,"tcp6:");
+ if (inet_ntop(AF_INET6,&sin->sin6_addr,buf+5,bufsize-5))
+ return strlen(buf);
+ }
+ }
+
+ *buf = 0;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetSocketName ( char *buf, uint bufsize, int sock )
+{
+ DASSERT(buf);
+ DASSERT(bufsize>0);
+
+ if ( sock != -1 )
+ {
+ DASSERT( sizeof(struct sockaddr_un) >= sizeof(struct sockaddr_in) );
+ DASSERT( sizeof(struct sockaddr_un) >= sizeof(struct sockaddr_in6) );
+ struct sockaddr_un sun;
+ memset(&sun,0,sizeof(sun));
+ socklen_t sun_len = sizeof(sun);
+ if (!getsockname(sock,(struct sockaddr*)&sun,&sun_len))
+ return GetSocketNameBySA(buf,bufsize,(struct sockaddr*)&sun,sun_len);
+ }
+ *buf = 0;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetPeerName ( char *buf, uint bufsize, int sock )
+{
+ DASSERT(buf);
+ DASSERT(bufsize>0);
+
+ if ( sock != -1 )
+ {
+ DASSERT( sizeof(struct sockaddr_un) >= sizeof(struct sockaddr_in) );
+ struct sockaddr_un sun;
+ memset(&sun,0,sizeof(sun));
+ socklen_t sun_len = sizeof(sun);
+ if (!getpeername(sock,(struct sockaddr*)&sun,&sun_len))
+ return GetSocketNameBySA(buf,bufsize,(struct sockaddr*)&sun,sun_len);
+ }
+ *buf = 0;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool DeleteSocketFile ( int sock )
+{
+ if ( sock != -1 )
+ {
+ DASSERT( sizeof(struct sockaddr_un) >= sizeof(struct sockaddr_in) );
+ struct sockaddr_un sun;
+ memset(&sun,0,sizeof(sun));
+ socklen_t sun_len = sizeof(sun);
+ if (!getsockname(sock,(struct sockaddr*)&sun,&sun_len))
+ {
+ if ( sun.sun_family == AF_UNIX )
+ {
+ unlink(sun.sun_path);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCP: send single strings ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static int OnTimeoutSingleTCP ( struct TCPStream_t * ts, u64 now_usec )
+{
+ PRINT("OnTimeoutSingleTCP(%p,%llu) sock=%d, to=%lld,%lld d=%lld,%lld\n",
+ ts, now_usec, ts->sock,
+ ts->trigger_usec, ts->trigger_usec - now_usec,
+ ts->accept_usec, ts->accept_usec - now_usec );
+ DASSERT(ts);
+ OnCloseStream(ts,now_usec);
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int OnReceivedSingleTCP ( struct TCPStream_t * ts, u8 *buf, uint size )
+{
+ PRINT("OnReceivedSingleTCP(%p,%p,%u) to=%lld\n",ts,buf,size,*(u64*)ts->data);
+ DASSERT(ts);
+ ts->accept_usec = GetTimeUSec(false) + *(u64*)ts->data;
+ ts->trigger_usec = ts->accept_usec + 100000;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int OnSendSingleTCP ( struct TCPStream_t * ts, u8 *buf, uint size )
+{
+ PRINT("OnSendSingleTCP(%p,%p,%u) to=%lld\n",ts,buf,size,*(u64*)ts->data);
+ DASSERT(ts);
+ ts->accept_usec = GetTimeUSec(false) + *(u64*)ts->data;
+ ts->trigger_usec = ts->accept_usec + 100000;
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static TCPStream_t * SendSingleTCPHelper
+(
+ TCPHandler_t *th, // valid handle
+ int sock, // valid socket
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ u64 timeout_usec, // timeout before closing the connection
+ TransferStats_t *xstat, // NULL or pointer to summary statistics
+
+ bool silent // suppress error messages
+)
+{
+ DASSERT(th);
+ DASSERT(sock!=-1);
+ DASSERT(data||!size);
+
+ PRINT("SendSingleStringTCPHelper(%p,%d,%lld,%d)\n",th,sock,timeout_usec,silent);
+
+ th->stat.conn_count++;
+ TCPStream_t *ts = CALLOC(sizeof(*ts)+sizeof(u64),1);
+ InitializeTCPStream(ts,sock);
+ AddTCPStream(th,ts);
+
+ ts->xstat = xstat;
+ ts->OnTimeout = OnTimeoutSingleTCP;
+ ts->OnReceived = OnReceivedSingleTCP;
+ ts->OnSend = OnSendSingleTCP;
+ ts->accept_usec = GetTimeUSec(false) + timeout_usec;
+ ts->trigger_usec = ts->accept_usec + 100000;
+ *(u64*)ts->data = timeout_usec;
+ snprintf(ts->info,sizeof(ts->info),"send single, %u bytes",size);
+
+ #if 1
+ ts->obuf.max_size = size > 10240 ? size : 10240;
+ SendDirectTCPStream(ts,false,data,size);
+ #else
+ ts->obuf.max_size = size;
+ InsertGrowBuffer(&ts->obuf,data,size);
+ #endif
+ return ts;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * SendSingleUnixTCP
+(
+ TCPHandler_t *th, // valid handle
+ ccp path, // path to socket file
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ u64 timeout_usec, // timeout before closing the connection
+ TransferStats_t *xstat, // NULL or pointer to summary statistics
+
+ bool silent // suppress error messages
+)
+{
+ DASSERT(th);
+ DASSERT(path);
+ DASSERT(data||!size);
+
+ PRINT("SINGLE/CONNECT/UNIX: %s\n",path);
+
+ int sock = socket(AF_UNIX,SOCK_STREAM,0);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"Can't create UNIX socket: %s\n",path);
+ return 0;
+ }
+
+ struct sockaddr_un sa;
+ memset(&sa,0,sizeof(sa));
+ sa.sun_family = AF_UNIX;
+ StringCopyS(sa.sun_path,sizeof(sa.sun_path),path);
+
+ int stat = connect(sock,(struct sockaddr*)&sa,sizeof(sa));
+ if (stat)
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't connect UNIX socket: %s\n",path);
+ shutdown(sock,SHUT_RDWR);
+ close(sock);
+ return 0;
+ }
+
+ return SendSingleTCPHelper(th,sock,data,size,timeout_usec,xstat,silent);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * SendSingleTCP
+(
+ TCPHandler_t *th, // valid handle
+ ccp addr, // address -> NetworkHost_t
+ u16 default_port, // default port
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ u64 timeout_usec, // timeout before closing the connection
+ TransferStats_t *xstat, // NULL or pointer to summary statistics
+
+ bool silent // suppress error messages
+)
+{
+ DASSERT(th);
+ DASSERT(addr);
+ DASSERT(data||!size);
+
+ ccp unix_path = CheckUnixSocketPath(addr,0);
+ if (unix_path)
+ return SendSingleUnixTCP(th,unix_path,data,size,timeout_usec,xstat,silent);
+
+ if (!strncasecmp(addr,"tcp:",4))
+ addr += 4;
+
+ NetworkHost_t nh;
+ ResolveHost(&nh,true,addr,default_port,false,false);
+ PRINT("SINGLE/CONNECT/TCP: %s -> %s\n",
+ addr, PrintIP4(0,0,nh.ip4,nh.port) );
+
+ int sock = socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK,0);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"Can't create TCP socket: %s\n",
+ PrintIP4(0,0,nh.ip4,nh.port));
+ return 0;
+ }
+
+ struct sockaddr_in sa;
+ memset(&sa,0,sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(nh.ip4);
+ sa.sin_port = htons(nh.port);
+
+ int stat = connect(sock,(struct sockaddr*)&sa,sizeof(sa));
+ if ( stat && errno != EINPROGRESS )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't connect TCP socket: %s\n",
+ PrintIP4(0,0,nh.ip4,nh.port));
+ shutdown(sock,SHUT_RDWR);
+ close(sock);
+ ResetHost(&nh);
+ return 0;
+ }
+
+ TCPStream_t *ts = SendSingleTCPHelper(th,sock,data,size,timeout_usec,xstat,silent);
+ ResetHost(&nh);
+ return ts;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCP: CommandTCP ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SetTimeoutCommandTCP
+(
+ TCPStream_t *ts, // valid stream
+ u64 now_usec // NULL or current time in usec
+)
+{
+ DASSERT(ts);
+
+ const CommandTCPInfo_t *ci = (CommandTCPInfo_t*)ts->data;
+ if (!ci->timeout_usec)
+ ts->accept_usec = ts->trigger_usec = 0;
+ else
+ {
+ if (!now_usec)
+ now_usec = GetTimeUSec(false);
+ ts->accept_usec = now_usec + ci->timeout_usec;
+ ts->trigger_usec = ts->accept_usec + 100000;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static char * ScanLineCommandTCP
+(
+ // return first non scanned character, or NULL on error
+
+ struct TCPStream_t *ts,
+ char *line,
+ char *line_end,
+ u64 now_usec
+)
+{
+ DASSERT(ts);
+ DASSERT(line);
+ DASSERT(line_end);
+ DASSERT( line <= line_end );
+
+ *line_end = 0;
+ const CommandTCPInfo_t *ci = (CommandTCPInfo_t*)ts->data;
+
+ if (ci->OnScanArg)
+ {
+ SplitArg_t sa;
+ ScanSplitArg(&sa,true,line,line_end,line);
+ sa.argv[0] = ts->info;
+ const int stat = ci->OnScanArg(ts,sa.argc,sa.argv,now_usec);
+ ResetSplitArg(&sa);
+ return stat > ERR_WARNING ? line : line_end;
+ }
+
+ if (ci->OnScanLine)
+ return ci->OnScanLine(ts,line,line_end,now_usec);
+
+ //--- fall back: mirror
+ InsertGrowBuffer(&ts->obuf,line,line_end-line);
+ InsertCharGrowBuffer(&ts->obuf,'\n');
+ return line_end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void ScanBufferCommandTCP ( struct TCPStream_t * ts, bool finish )
+{
+ DASSERT(ts);
+
+ //--- setup
+
+ const u64 now_usec = GetTimeUSec(false);
+
+ const CommandTCPInfo_t *ci = (CommandTCPInfo_t*)ts->data;
+ const u8 comma = ci->comma_is_eol ? ',' : ';';
+
+
+ //--- skip uneeded data
+
+ u8 *beg = ts->ibuf.ptr, *end = beg + ts->ibuf.used;
+ while ( beg < end )
+ {
+ const u8 ch = *beg;
+ if ( ch > ' ' && ch != ';' && ch != comma )
+ break;
+ beg++;
+ }
+ DropGrowBuffer(&ts->ibuf,beg-ts->ibuf.ptr);
+
+
+ //--- find end of command
+
+ u8 *ptr;
+ for ( ptr = beg; ptr < end; ptr++ )
+ {
+ const u8 ch = *ptr;
+ if ( !ch || ch == '\n' || ch == '\r' || ch == ';' || ch == comma )
+ {
+ DropGrowBuffer(&ts->ibuf,ptr+1-beg);
+ ScanLineCommandTCP(ts,(char*)beg,(char*)ptr,now_usec);
+ return;
+ }
+ }
+
+ if ( ptr > beg && ( finish || ts->eof ) )
+ {
+ DropGrowBuffer(&ts->ibuf,ptr-beg);
+ ScanLineCommandTCP(ts,(char*)beg,(char*)ptr,now_usec);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int OnCommandTCP ( struct TCPStream_t * ts )
+{
+ DASSERT(ts);
+ SetTimeoutCommandTCP(ts,0);
+
+ while ( ts->sock != -1 && !ts->obuf.used )
+ {
+ const uint watermark = ts->ibuf.used;
+ ScanBufferCommandTCP(ts,false);
+ if ( watermark == ts->ibuf.used )
+ break;
+ }
+
+ if ( ts->eof && !ts->obuf.used && !ts->ibuf.used )
+ OnCloseStream(ts,0);
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int OnReceivedCommandTCP ( struct TCPStream_t * ts, u8 *buf, uint size )
+{
+ DASSERT(ts);
+ noPRINT("---------- OnReceivedCommandTCP(%p,%p,%u), used=%d,%d\n",
+ ts, buf, size, ts->ibuf.used, ts->obuf.used );
+ InsertGrowBuffer(&ts->ibuf,buf,size);
+ return OnCommandTCP(ts);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int OnSendCommandTCP ( struct TCPStream_t * ts, u8 *buf, uint size )
+{
+ DASSERT(ts);
+ noPRINT("---------- OnSendCommandTCP(%p,%p,%u), used=%d,%d\n",
+ ts, buf, size, ts->ibuf.used, ts->obuf.used );
+
+ DropGrowBuffer(&ts->obuf,size);
+ return OnCommandTCP(ts);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int OnTimeoutCommandTCP ( struct TCPStream_t * ts, u64 now_usec )
+{
+ DASSERT(ts);
+ noPRINT("---------- OnTimeoutCommandTCP(%p,%llu)\n",ts,now_usec);
+
+ if ( ts->sock != -1 )
+ {
+ if (!ts->obuf.used)
+ {
+ ScanBufferCommandTCP(ts,true);
+ if (ts->obuf.used)
+ {
+ SetTimeoutCommandTCP(ts,now_usec);
+ return 0;
+ }
+ }
+
+ OnCloseStream(ts,now_usec);
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int OnCreateCommandTCP ( TCPStream_t *ts )
+{
+ noPRINT("---------- OnCreateCommandTCP(%p)\n",ts);
+ DASSERT(ts);
+ ts->OnReceived = OnReceivedCommandTCP;
+ ts->OnSend = OnSendCommandTCP;
+ ts->OnTimeout = OnTimeoutCommandTCP;
+
+ CommandTCPInfo_t *ci = (CommandTCPInfo_t*)ts->data;
+ ci->OnScanLine = 0;
+ ci->timeout_usec = 10*USEC_PER_SEC;
+ ci->comma_is_eol = false;
+
+ SetTimeoutCommandTCP(ts,0);
+ return 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// SaveCurrentState*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateSocket
+(
+ FILE *f, // output file
+ const Socket_t *sock // valid socket object
+)
+{
+ DASSERT(f);
+ DASSERT(sock);
+
+ char info[3*sizeof(sock->info)];
+ PrintEscapedString(info,sizeof(info),sock->info,-1,CHMD__ALL,0,0);
+
+ fprintf(f,
+ "sock = %d\n"
+ "is-unix = %d\n"
+ "info = %s\n"
+ ,sock->sock
+ ,sock->is_unix
+ ,info
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateTCPStream
+(
+ FILE *f, // output file
+ const TCPStream_t *ts, // valid TCP stream
+ TCPStreamSaveFunc func // NULL or function for 'TCPStream_t.data' extend
+)
+{
+ DASSERT(f);
+ DASSERT(ts);
+
+ char info[1000];
+ PrintEscapedString(info,sizeof(info),ts->info,-1,CHMD__ALL,0,0);
+
+ fprintf(f,
+ "sock = %d\n"
+ "rescan = %u\n"
+ "eof = %u\n"
+ "protect = %u\n"
+ "auto-close = %u\n"
+ "accept-usec = %llu\n"
+ "trigger-usec = %llu\n"
+ "delay-usec = %llu\n"
+ "timeout-usec = %llu\n"
+ "connect-usec = %llu\n"
+ "receive-usec = %llu\n"
+ "send-usec = %llu\n"
+ "info = %s\n"
+ ,ts->sock
+ ,ts->rescan
+ ,ts->eof
+ ,ts->protect
+ ,ts->auto_close
+ ,ts->accept_usec
+ ,ts->trigger_usec
+ ,ts->delay_usec
+ ,ts->timeout_usec
+ ,ts->connect_usec
+ ,ts->receive_usec
+ ,ts->send_usec
+ ,info
+ );
+
+ if (func)
+ func(f,ts);
+
+ SaveCurrentStateTransferStats1(f,"tfer-stat",&ts->stat);
+ SaveCurrentStateGrowBuffer(f,"ibuf-",2,&ts->ibuf);
+ SaveCurrentStateGrowBuffer(f,"obuf-",2,&ts->obuf);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateCommandTCP
+(
+ FILE *f, // output file
+ const TCPStream_t *ts // valid TCP stream
+)
+{
+ DASSERT(f);
+ DASSERT(ts);
+
+ const CommandTCPInfo_t *ci = (CommandTCPInfo_t*)ts->data;
+
+ fprintf(f,
+ "cmd-comma = %d\n"
+ "cmd-timeout = %llu\n"
+ ,ci->comma_is_eol
+ ,ci->timeout_usec
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateTCPHandler
+(
+ FILE *f, // output file
+ ccp sect_name, // section base name
+ const TCPHandler_t *th, // valid TCP handler
+ TCPStreamSaveFunc func // NULL or function for 'TCPStream_t.data' extend
+)
+{
+ DASSERT(f);
+ DASSERT(sect_name);
+ DASSERT(th);
+
+ fprintf(f,
+ "\n#%.50s\n"
+ "[%s]\n"
+ "\n"
+ "data-size = %u\n"
+ "max-conn = %u\n"
+ "maintenance = %u\n"
+ "used-streams = %u\n"
+ "max-used = %u\n"
+ "total-streams = %u\n"
+ ,Minus300
+ ,sect_name
+ ,th->data_size
+ ,th->max_conn
+ ,th->need_maintenance
+ ,th->used_streams
+ ,th->max_used_streams
+ ,th->total_streams
+ );
+
+ SaveCurrentStateTransferStats1(f,"tfer-stat",&th->stat);
+
+ uint i;
+ for ( i = 0; i < TCP_HANDLER_MAX_LISTEN; i++ )
+ {
+ if ( th->listen[i].sock != -1 )
+ {
+ fprintf(f,"\n[%s/socket:%u]\n\n",sect_name,i);
+ SaveCurrentStateSocket(f,th->listen+i);
+ }
+ }
+
+ const TCPStream_t *ts;
+ for ( ts = th->first, i = 0; ts; ts = ts->next, i++ )
+ {
+ fprintf(f,"\n[%s/stream:%u]\n\n",sect_name,i);
+ SaveCurrentStateTCPStream(f,ts,func);
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// RestoreState*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateSocket
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+)
+{
+ DASSERT(rs);
+
+ Socket_t *sock = (Socket_t*)user_table;
+ if (!sock)
+ return;
+
+ int fd = GetParamFieldINT(rs,"sock",-1);
+ if ( fd != -1 && fcntl(fd,F_GETFD) != -1 )
+ {
+ sock->sock = fd;
+ sock->is_unix = GetParamFieldINT(rs,"is-unix",0);
+ GetParamFieldBUF(sock->info,sizeof(sock->info),rs,"info",ENCODE_STRING,"?");
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * RestoreStateTCPStream
+(
+ // return 'ts' or the new TCPStream_t
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ TCPStream_t *ts, // if NULL: create it
+ uint extra_size // if 'ts' alloced: alloc some byte more
+)
+{
+ DASSERT(rs);
+ TRACE("RestoreStateTCPStream()\n");
+
+ int fd = ts ? ts->sock : -1;
+ if ( fd == -1 )
+ {
+ fd = GetParamFieldINT(rs,"sock",-1);
+ if ( fd != -1 && fcntl(fd,F_GETFD) == -1 )
+ fd = -1;
+ }
+
+ if (!ts)
+ {
+ ts = CALLOC(sizeof(*ts)+extra_size,1);
+ InitializeTCPStream(ts,fd);
+ }
+ else
+ ts->sock = fd;
+
+ DASSERT(ts);
+ ts->protect = GetParamFieldUINT(rs, "protect", ts->protect);
+ ts->auto_close = GetParamFieldUINT(rs, "auto-close", ts->auto_close);
+ ts->rescan = GetParamFieldUINT(rs, "rescan", ts->rescan );
+ ts->eof = GetParamFieldUINT(rs, "eof", ts->eof );
+ ts->accept_usec = GetParamFieldU64 (rs, "accept-usec", ts->accept_usec );
+ ts->trigger_usec = GetParamFieldU64 (rs, "trigger-usec", ts->trigger_usec );
+ ts->delay_usec = GetParamFieldU64 (rs, "delay-usec", ts->delay_usec );
+ ts->timeout_usec = GetParamFieldU64 (rs, "timeout-usec", ts->timeout_usec );
+ ts->connect_usec = GetParamFieldU64 (rs, "connect-usec", ts->connect_usec );
+ ts->receive_usec = GetParamFieldU64 (rs, "receive-usec", ts->receive_usec );
+ ts->send_usec = GetParamFieldU64 (rs, "send-usec", ts->send_usec );
+
+ GetParamFieldBUF(ts->info,sizeof(ts->info),rs,"info",ENCODE_STRING,"?");
+
+ RestoreStateTransferStats1(rs,"tfer-stat",&ts->stat,true);
+ RestoreStateGrowBuffer(&ts->ibuf,"ibuf-",rs);
+ RestoreStateGrowBuffer(&ts->obuf,"obuf-",rs);
+ return ts;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateTCPHandler_base
+(
+ TCPHandler_t *th, // valid TCPHandler_t
+ RestoreState_t *rs // info data, can be modified (cleaned after call)
+)
+{
+ DASSERT(th);
+ DASSERT(rs);
+ TRACE("RestoreStateTCPHandler_base()\n");
+
+ //ResetTCPHandler(th); // don't call, because call back functions are cleared
+
+ struct uint_parm_t
+ {
+ ccp name;
+ uint *param;
+ };
+
+ const struct uint_parm_t uint_parm_tab[] =
+ {
+ { "max-conn", &th->max_conn },
+ { "maintenance", &th->need_maintenance },
+ { "max-used", &th->max_used_streams },
+ { "total-streams", &th->total_streams },
+ {0,0}
+ };
+
+ const struct uint_parm_t *up;
+ for ( up = uint_parm_tab; up->name; up++ )
+ *up->param = GetParamFieldUINT(rs,up->name,*up->param);
+
+ RestoreStateTransferStats1(rs,"tfer-stat",&th->stat,true);
+
+ if ( rs->log_mode & RSL_UNUSED_NAMES )
+ {
+ // known as 'unused'
+ GetParamField(rs,"data-size");
+ GetParamField(rs,"used-streams");
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+Socket_t * RestoreStateTCPHandler_socket
+(
+ TCPHandler_t *th, // valid TCPHandler_t
+ RestoreState_t *rs // info data, can be modified (cleaned after call)
+)
+{
+ DASSERT(th);
+ DASSERT(rs);
+ TRACE("RestoreStateTCPHandler_socket()\n");
+
+ Socket_t *sock = GetUnusedListenSocketTCP(th,false);
+ if (sock)
+ RestoreStateSocket(rs,sock);
+ return sock;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * RestoreStateTCPHandler_stream
+(
+ TCPHandler_t *th, // valid TCPHandler_t
+ RestoreState_t *rs // info data, can be modified (cleaned after call)
+)
+{
+ DASSERT(th);
+ DASSERT(rs);
+ TRACE("RestoreStateTCPHandler_stream()\n");
+
+ int fd = GetParamFieldINT(rs,"sock",-1);
+ bool protect = GetParamFieldUINT(rs,"protect",0);
+ #ifdef TEST
+ if ( !protect && fd == -1 )
+ return 0;
+ #else
+ if ( !protect && ( fd == -1 || fcntl(fd,F_GETFD) == -1 ))
+ return 0;
+ #endif
+
+ TCPStream_t *ts = CreateTCPStream(th,fd,ALLOW_MODE_ALLOW,0);
+ RestoreStateTCPStream(rs,ts,th->data_size);
+ return ts;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateTCPHandler
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+)
+{
+ DASSERT(rs);
+ TRACE("Scan TCP Handler (%s/%s,%d)\n",rs->sect,rs->path,rs->index);
+
+ TCPHandler_t *th = (TCPHandler_t*)user_table;
+ if (!th)
+ return;
+
+ if (!*rs->path)
+ RestoreStateTCPHandler_base(th,rs);
+ else if (!strcmp(rs->path,"socket"))
+ RestoreStateTCPHandler_socket(th,rs);
+ else if (!strcmp(rs->path,"stream"))
+ RestoreStateTCPHandler_stream(th,rs);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateCommandTCP
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+)
+{
+ DASSERT(rs);
+
+ TCPHandler_t *th = (TCPHandler_t*)user_table;
+ if (!th)
+ return;
+
+ if (!strcmp(rs->path,"stream"))
+ {
+ TCPStream_t *ts = RestoreStateTCPHandler_stream(th,rs);
+ if (ts)
+ {
+ CommandTCPInfo_t *ci = (CommandTCPInfo_t*)ts->data;
+ ci->comma_is_eol = GetParamFieldINT( rs, "cmd-comma", ci->comma_is_eol ) > 0;
+ ci->timeout_usec = GetParamFieldU64( rs, "cmd-timeout", ci->timeout_usec );
+ }
+ }
+ else
+ RestoreStateTCPHandler(rs,user_table);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Misc ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool fix_ether_head_vlan ( ether_head_vlan_t *head )
+{
+ DASSERT(head);
+ const u16 vlan_tag_type = ntohs(head->vlan_tag_type);
+ if ( vlan_tag_type == 0x8100 )
+ {
+ head->have_vlan = true;
+ head->head_size = 18;
+ return true;
+ }
+
+ head->ether_type = head->vlan_tag_type;
+ head->have_vlan = false;
+ head->head_size = 14;
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ntoh_ether_head_vlan ( ether_head_vlan_t *dest, const void * src )
+{
+ if (dest)
+ {
+ if (src)
+ memcpy(dest,src,sizeof(*dest));
+
+ fix_ether_head_vlan(dest);
+ dest->vlan_tag_type = ntohs(dest->vlan_tag_type);
+ dest->vlan_param = ntohs(dest->vlan_param);
+ dest->ether_type = ntohs(dest->ether_type);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ntoh_ether_head ( ether_head_t *dest, const void * src )
+{
+ if (dest)
+ {
+ if (src)
+ memcpy(dest,src,sizeof(*dest));
+
+ dest->ether_type = ntohs(dest->ether_type);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ntoh_ip4_head ( ip4_head_t *dest, const void * src )
+{
+ if (dest)
+ {
+ if (src)
+ memcpy(dest,src,sizeof(*dest));
+
+ dest->total_len = ntohs(dest->total_len);
+ dest->id = ntohs(dest->id);
+ dest->frag_off = ntohs(dest->frag_off);
+ dest->checksum = ntohs(dest->checksum);
+ dest->ip_src = ntohl(dest->ip_src);
+ dest->ip_dest = ntohl(dest->ip_dest);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ntoh_udp_head ( udp_head_t *dest, const void * src )
+{
+ if (dest)
+ {
+ const udp_head_t *usrc = src ? src : dest;
+
+ dest->port_src = ntohs(usrc->port_src);
+ dest->port_dest = ntohs(usrc->port_dest);
+ dest->data_len = ntohs(usrc->data_len);
+ dest->checksum = ntohs(usrc->checksum);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ntoh_tcp_head ( tcp_head_t *dest, const void * src )
+{
+ if (dest)
+ {
+ const tcp_head_t *usrc = src ? src : dest;
+
+ dest->port_src = ntohs(usrc->port_src);
+ dest->port_dest = ntohs(usrc->port_dest);
+ dest->seq_num = ntohl(usrc->seq_num);
+ dest->acc_num = ntohl(usrc->acc_num);
+ dest->data_off = usrc->data_off;
+ dest->flags = usrc->flags;
+ dest->window = ntohs(usrc->window);
+ dest->checksum = ntohs(usrc->checksum);
+ dest->urgent = ntohs(usrc->urgent);
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Linux support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#if defined(SYSTEM_LINUX) || defined(__CYGWIN__)
+ #include "dclib-network-linux.c"
+#endif // SYSTEM_LINUX
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
diff --git a/project/dclib/dclib-network.h b/project/dclib/dclib-network.h
new file mode 100644
index 0000000..b533761
--- /dev/null
+++ b/project/dclib/dclib-network.h
@@ -0,0 +1,1486 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_NETWORK_H
+#define DCLIB_NETWORK_H 1
+
+#include "dclib-debug.h"
+#include "dclib-system.h"
+#include "dclib-basics.h"
+#include "dclib-file.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// host name support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[NetworkHost_t]]
+
+typedef struct NetworkHost_t
+{
+ bool ip4_valid; // true: 'ip4' is valid
+ u32 ip4; // ip4 or 0 if invalid
+ int port; // port number, <0: invalid
+ ccp name; // NULL or pointer to canonicalized name
+ struct sockaddr_in sa; // setup for AF_INET if 'ip4_valid'
+
+ ccp filename; // not NULL: unix filename detected
+ // in this case: ip4_valid := FALSE
+ mem_t not_scanned; // (0,0) or not by ResolveHost*() scanned name
+}
+NetworkHost_t;
+
+//-----------------------------------------------------------------------------
+
+static inline void InitializeHost ( NetworkHost_t *host )
+ { memset(host,0,sizeof(*host)); }
+
+static inline void ResetHost ( NetworkHost_t *host )
+ { FREE((char*)host->name); memset(host,0,sizeof(*host)); }
+
+static inline ccp GetHostName ( NetworkHost_t *host )
+ { return host->name ? host->name : PrintIP4(0,0,host->ip4,-1); }
+
+//-----------------------------------------------------------------------------
+
+bool ResolveHost
+(
+ // returns TRUE (=ip4_valid), if a hostname is detected
+ // if result is FALSE => check host->filename
+
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ bool init_host, // true: initialize 'host'
+ ccp name, // name to analyze
+ int default_port, // use this as port, if no other is found
+ bool check_filename, // true: check for unix filename
+ bool store_name // true: setup 'host->name'
+);
+
+bool ResolveHostMem
+(
+ // returns TRUE (=ip4_valid), if a hostname is detected
+ // if result is FALSE => check host->filename
+
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ bool init_host, // true: initialize 'host'
+ mem_t name, // name to analyze
+ int default_port, // use this as port, if no other is found
+ bool check_filename, // true: check for unix filename
+ bool store_name // true: setup 'host->name'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct AllowIP4_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// standard values
+#define ALLOW_MODE_DENY 0 // access denied
+#define ALLOW_MODE_ALLOW 0x001 // access allowed
+#define ALLOW_MODE_AKEY 0x002 // access allowed by access key
+
+// locations
+#define ALLOW_MODE_EXTERN 0x004 // keyword 'EXTERN' set
+#define ALLOW_MODE_LAN 0x008 // keyword 'LAN' set
+#define ALLOW_MODE_LOCAL 0x010 // keyword 'LOCAL' set
+
+// users
+#define ALLOW_MODE_PUBLIC 0x020 // keyword 'PUBLIC' set
+#define ALLOW_MODE_USER 0x040 // keyword 'USER' set
+#define ALLOW_MODE_MOD 0x080 // keyword 'MOD' or 'MODERATOR' set
+#define ALLOW_MODE_ADMIN 0x100 // keyword 'ADMIN' or 'ADMINISTRATOR' set
+#define ALLOW_MODE_DEVELOP 0x200 // keyword 'DEVELOP' or 'DEVELOPER' set
+
+// logging
+#define ALLOW_MODE_LOG 0x400 // keyword 'LOG' set
+#define ALLOW_MODE_VERBOSE 0x800 // keyword 'VERBOSE' set
+
+// special processing values
+#define ALLOW_MODE_NOT 0x1000000000000000ull // negate bits
+#define ALLOW_MODE_SET 0x2000000000000000ull // set bits, ignore AND
+#define ALLOW_MODE_AND 0x4000000000000000ull // use AND operation (otherwise OR)
+#define ALLOW_MODE_CONTINUE 0x8000000000000000ull // continue search (otherwise break)
+
+#define ALLOW_MODE__OP 0x6000000000000000ull // mask for operation
+#define ALLOW_MODE__MASK 0x0fffffffffffffffull // mask out control bits
+
+//-----------------------------------------------------------------------------
+
+extern const KeywordTab_t AllowIP4KeyTable[];
+
+//-----------------------------------------------------------------------------
+// [[AllowIP4Item_t]]
+
+typedef struct AllowIP4Item_t
+{
+ u32 addr; // IP address
+ u32 mask; // netmask
+ u64 mode; // new mode, see ALLOW_MODE_*
+ u64 count; // incremented on IP match
+}
+AllowIP4Item_t;
+
+//-----------------------------------------------------------------------------
+// [[AllowIP4_t]]
+
+typedef struct AllowIP4_t
+{
+ AllowIP4Item_t *list; // list of addresses, alloced
+ uint used; // number of used elements
+ uint size; // number of alloced elements
+
+ int ref_counter; // reference counter
+
+ // for automatic usage
+ u64 fallback_mode; // mode if IP not found
+ u64 allow_mode; // allow, if any of these bits is set
+}
+AllowIP4_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeAllowIP4 ( AllowIP4_t *ai );
+void ResetAllowIP4 ( AllowIP4_t *ai );
+void ClearAllowIP4 ( AllowIP4_t *ai );
+
+// if ai==NULL: create a new ai
+AllowIP4_t * NewReferenceAllowIP4 ( AllowIP4_t *ai );
+
+//returns always NULL
+AllowIP4_t * DeleteReferenceAllowIP4 ( AllowIP4_t *ai );
+
+void DumpAllowIP4
+(
+ FILE *f, // NULL or output file
+ int indent, // indention
+ const AllowIP4_t *ai, // NULL or source
+ const KeywordTab_t *keytab, // NULL or key table for output
+ ccp title, // not NULL: Add title
+ bool print_tab_head // true: add table headings and separators
+);
+
+//-----------------------------------------------------------------------------
+
+enumError ScanLineAllowIP4
+(
+ AllowIP4_t *ai, // valid structure; new elements are appended
+ mem_t line, // single line to analyse
+ const KeywordTab_t *tab // NULL or keyword table.
+ // If NULL, a local table with keywords 0 DENY
+ // ALLOW SET REMOVE AND OR and CONTINUE is used.
+);
+
+enumError ScanFileAllowIP4
+(
+ AllowIP4_t *ai, // valid structure; new elements are appended
+ ccp fname, // ffilename of the source
+ FileMode_t fmode, // flags for OpenFile()
+ const KeywordTab_t *tab // NULL or keyword table -> see ScanLineAllowIP4()
+);
+
+//-----------------------------------------------------------------------------
+
+void ResetCountersAllowIP4
+(
+ AllowIP4_t *ai // NULL or dest for addition
+);
+
+void AddCountersAllowIP4
+(
+ AllowIP4_t *dest, // NULL or dest for addition
+ const AllowIP4_t *src // NULL or source for addition
+);
+
+//-----------------------------------------------------------------------------
+
+// if found, result is masked by ALLOWIP4_MODE__MASK
+u64 GetAllowIP4ByAddr ( const AllowIP4_t *ai, u32 addr, u64 if_not_found );
+u64 GetAllowIP4ByName ( const AllowIP4_t *ai, mem_t name, u64 if_not_found );
+
+// if ai==NULL => return ALLOW_MODE_ALLOW
+// if found, result is masked by ALLOWIP4_MODE__MASK and by 'ai->allow_mode'
+// if not found, 'ai->fallback_mode' is returned.
+u64 GetAutoAllowIP4ByAddr ( const AllowIP4_t *ai, u32 addr );
+u64 GetAutoAllowIP4ByName ( const AllowIP4_t *ai, mem_t name );
+u64 GetAutoAllowIP4BySock ( const AllowIP4_t *ai, int sock );
+
+//-----------------------------------------------------------------------------
+
+void SaveCurrentStateAllowIP4
+(
+ FILE *f, // output file
+ ccp section, // not NULL: create own section
+ ccp name_prefix, // NULL or prefix of name
+ uint tab_pos, // tab pos of '='
+ const AllowIP4_t *ai // valid struct
+);
+
+//-----------------------------------------------------------------------------
+
+bool ScanAllowIP4Item
+(
+ // return true on success; counter is optional
+
+ AllowIP4Item_t *it, // store data here, cleared first
+ ccp line // NULL or line to scan
+);
+
+void RestoreStateAllowIP4
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+);
+
+void RestoreStateAllowIP4Ex
+(
+ RestoreState_t *rs, // info data, modified
+ AllowIP4_t *ai, // valid struct
+ ccp name_prefix // NULL or prefix of name
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCP + UDP connections ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectByHost
+(
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ int type, // AF_INET type, e.g. SOCK_STREAM, SOCK_DGRAM
+ int protocol, // AF_INET protocol, e.g. IPPROTO_TCP, IPPROTO_UDP
+ bool silent // true: suppress error messages
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectByHostTCP
+(
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ bool silent // true: suppress error messages
+);
+
+int ConnectTCP
+(
+ ccp name, // optional service + server + optional host
+ // e.g.: "service://host:port/....."
+ int default_host, // default host
+ bool silent // true: suppress error messages
+);
+
+int ConnectNumericTCP
+(
+ // like ConnectTCP(), but without name resolution (only numeric ip+port)
+
+ ccp addr, // TCP address: ['tcp':] IPv4 [:PORT]
+ int default_port, // NULL or default port, if not found in 'addr'
+ bool silent // true: suppress error messages
+);
+
+extern int (*ConnectTCP_Hook)
+(
+ // if set: ConnectNumericTCP() uses ConnectTCP()
+
+ ccp addr, // TCP address: ['tcp':] IPv4 [:PORT]
+ int default_port, // NULL or default port, if not found in 'addr'
+ bool silent // true: suppress error messages
+);
+
+void Setup_ConnectTCP_Hook ( bool force );
+
+int ConnectUnixTCP
+(
+ ccp fname, // unix socket filename
+ bool silent // true: suppress error messages
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectByHostUDP
+(
+ NetworkHost_t *host, // valid pointer to NetworkHost_t
+ bool silent // true: suppress error messages
+);
+
+int ConnectUDP
+(
+ ccp name, // optional service + server + optional host
+ // e.g.: "service://host:port/....."
+ int default_host, // default host
+ bool silent // true: suppress error messages
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+int WillReceiveNotBlock
+(
+ // returns: -1:error, 0:may block, 1:will not block
+
+ int fd, // file handle of source
+ uint msec // timeout: 0:no timeout, >0: milliseconds
+);
+
+int WillSendNotBlock
+(
+ // returns: -1:error, 0:may block, 1:will not block
+
+ int fd, // file handle of source
+ uint msec // timeout: 0:no timeout, >0: milliseconds
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+ssize_t ReceiveTimeout
+(
+ // returns: <0: on error, 0:timeout, >0: bytes read
+
+ int fd, // file handle of source
+ void *buf, // valid pointer to buffer
+ uint size, // size to receive
+ int flags, // flags for recv()
+ int msec // timeout: -1:unlimited, 0:no timeout, >0: milliseconds
+);
+
+ssize_t SendTimeout
+(
+ // returns: <0: on error, 0:timeout, >0: bytes written
+
+ int fd, // file hande of destination
+ const void *data, // valid pointer to data
+ uint size, // size to receive
+ int flags, // flags for send()
+ int msec, // total timeout: -1:unlimited, 0:no timeout, >0: milliseconds
+ bool all // true: send all data until done, total timeout or error
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// IP data structures ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[ether_head_t]]
+
+typedef struct ether_head_t
+{
+ // http://en.wikipedia.org/wiki/Ethertype
+
+ u8 mac_dest[6];
+ u8 mac_src[6];
+ u16 ether_type;
+}
+__attribute__ ((packed)) ether_head_t;
+
+//-----------------------------------------------------------------------------
+// [[ether_head_vlan_t]]
+
+typedef struct ether_head_vlan_t
+{
+ // http://en.wikipedia.org/wiki/Ethertype
+
+ u8 mac_dest[6];
+ u8 mac_src[6];
+ u16 vlan_tag_type; // 0x8100 for VLAN
+ u16 vlan_param; // Bits 0-11: ID / Bits 12-15: flags
+ u16 ether_type;
+
+ //-- secondary params
+ bool have_vlan; // true: VLAN trag available
+ u8 head_size; // >0: size of ethernet header (local endian): 14|18
+}
+__attribute__ ((packed)) ether_head_vlan_t;
+
+//-----------------------------------------------------------------------------
+// [[arp_head_t]]
+
+typedef struct arp_head_t
+{
+ // http://de.wikipedia.org/wiki/Address_Resolution_Protocol
+
+ /* 0x00 */ u16 hardware_addr_type; // always 0x0001 for IPv4
+ /* 0x02 */ u16 protocol_addr_type; // always 0x8000 for IPv4
+ /* 0x04 */ u8 mac_size; // always 6 for IPv4
+ /* 0x05 */ u8 addr_size; // always 4 for IPv4
+ /* 0x06 */ u16 operation;
+ /* 0x08 */ u8 src_mac[6]; // MAC of source
+ /* 0x0e */ u32 src_ip; // IPv4 of source
+ /* 0x12 */ u8 dest_mac[6]; // MAC of destination
+ /* 0x18 */ u32 dest_ip; // IPv4 of destination
+ /* 0x1c */
+}
+__attribute__ ((packed)) arp_head_t;
+
+//-----------------------------------------------------------------------------
+// [[ip4_head_t]]
+
+typedef struct ip4_head_t
+{
+ // http://de.wikipedia.org/wiki/IP-Paket
+
+ u8 vers_ihl; // high nibble: version, low nibble: IHL (Ip Header Len)
+ u8 tos;
+ u16 total_len;
+ u16 id;
+ u16 frag_off; // low 12 bits: fragment offset, high 4 bits: flags
+ u8 ttl;
+ u8 protocol;
+ u16 checksum;
+ u32 ip_src;
+ u32 ip_dest;
+ // optional: options + padding
+}
+__attribute__ ((packed)) ip4_head_t;
+
+//-----------------------------------------------------------------------------
+// [[udp_head_t]]
+
+typedef struct udp_head_t
+{
+ // http://de.wikipedia.org/wiki/User_Datagram_Protocol#UDP-Datagramm
+
+ u16 port_src;
+ u16 port_dest;
+ u16 data_len;
+ u16 checksum;
+}
+__attribute__ ((packed)) udp_head_t;
+
+//-----------------------------------------------------------------------------
+// [[udp_packet_t]]
+
+// ethernet packet limit is 1500
+#define MAX_UDP_PACKET_DATA ( 1500 - sizeof(ip4_head_t) - sizeof(udp_head_t) )
+
+typedef struct udp_packet_t
+{
+ ip4_head_t ip4;
+ udp_head_t udp;
+ u8 data[MAX_UDP_PACKET_DATA];
+}
+__attribute__ ((packed)) udp_packet_t;
+
+//-----------------------------------------------------------------------------
+// [[tcp_flags_t]]
+
+typedef enum tcp_flags_t
+{
+ TCP_F_FINISH = 0x01,
+ TCP_F_SYNC = 0x02,
+ TCP_F_RESET = 0x04,
+ TCP_F_PSH = 0x08,
+ TCP_F_ACK = 0x10,
+ TCP_F_URGENT = 0x20,
+ TCP_M_RESERVED = 0xc0,
+}
+__attribute__ ((packed)) tcp_flags_t;
+
+//-----------------------------------------------------------------------------
+// [[tcp_head_t]]
+
+typedef struct tcp_head_t
+{
+ // http://de.wikipedia.org/wiki/Transmission_Control_Protocol#Aufbau_des_TCP-Headers
+
+ u16 port_src;
+ u16 port_dest;
+ u32 seq_num;
+ u32 acc_num;
+ u8 data_off; // bit 4-7 only, multiply it by 4
+ u8 flags; // bit field (think big endian) -> tcp_flags_t
+ u16 window;
+ u16 checksum;
+ u16 urgent;
+ u8 data[];
+}
+__attribute__ ((packed)) tcp_head_t;
+
+//-----------------------------------------------------------------------------
+
+bool fix_ether_head_vlan ( ether_head_vlan_t *head );
+void ntoh_ether_head_vlan ( ether_head_vlan_t *dest, const void *src );
+void ntoh_ether_head ( ether_head_t *dest, const void *src );
+void ntoh_ip4_head ( ip4_head_t *dest, const void *src );
+void ntoh_udp_head ( udp_head_t *dest, const void *src );
+void ntoh_tcp_head ( tcp_head_t *dest, const void *src );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// IP interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanNumericIP4
+(
+ // returns next unread character or NULL on error
+
+ ccp addr, // address to scan
+ u32 *r_ipv4, // not NULL: store result here (local endian)
+ u32 *r_port, // not NULL: scan port too (local endian)
+ uint default_port // return this if no port found
+);
+
+//-----------------------------------------------------------------------------
+
+mem_t ScanNumericIP4Mem
+(
+ // returns unread character or NullMem on error
+
+ mem_t addr, // address to scan
+ u32 *r_ipv4, // not NULL: store result here (local endian)
+ u32 *r_port, // not NULL: scan port too (local endian)
+ uint default_port // return this if no port found
+);
+
+//-----------------------------------------------------------------------------
+
+// Returns a NETWORK MASK "/a.b.c.d" or as CIDR number "/num" between 0 and 32.
+// An optional slash '/' at the beginning is skipped.
+// Returns modified 'source' if a MASK or CDIR is detected.
+// If no one is detected, source is unmodified and returned mask = ~0.
+
+mem_t ScanNetworkMaskMem ( u32 *mask, mem_t source );
+
+//-----------------------------------------------------------------------------
+
+u16 CalcChecksumIP4
+(
+ const void *data, // ip4 header
+ // make sure, that the checksum member is NULL
+ uint size // size of ip4 header
+);
+
+//-----------------------------------------------------------------------------
+
+u16 CalcChecksumUDP
+(
+ const ip4_head_t *ip4, // IP4 header
+ const udp_head_t *udp, // UDP header
+ const void *data // UDP data, size is 'udp->data_len'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// UNIX sockets ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SendUnixUDP
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ bool silent, // suppress all error messages
+ cvp data, // data to send
+ uint size // size of 'data'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Transfer Statistics ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[TransferStats_t]]
+
+typedef struct TransferStats_t
+{
+ u32 conn_count; // number of connections
+ u32 recv_count; // number of received packets
+ u64 recv_size; // total size of received packets
+ u32 send_count; // number of send packets
+ u64 send_size; // total size of send packets
+}
+TransferStats_t;
+
+//-----------------------------------------------------------------------------
+
+static inline void InitializeTransferStats ( TransferStats_t *ts )
+ { DASSERT(ts); memset(ts,0,sizeof(*ts)); }
+
+//-----------------------------------------------------------------------------
+
+TransferStats_t * Add2TransferStats
+(
+ // return dest
+ // calculate: dest += src
+
+ TransferStats_t *dest, // NULL or destination and first source
+ const TransferStats_t *src // NULL or second source
+);
+
+TransferStats_t * Sub2TransferStats
+(
+ // return dest
+ // calculate: dest -= src
+
+ TransferStats_t *dest, // NULL or destination and first source
+ const TransferStats_t *src // NULL or second source
+);
+
+TransferStats_t * Add3TransferStats
+(
+ // return dest
+ // calculate: dest = src1 + src2
+
+ TransferStats_t *dest, // NULL or destination (maybe same as source)
+ const TransferStats_t *src1, // NULL or first source
+ const TransferStats_t *src2 // NULL or second source
+);
+
+TransferStats_t * Sub3TransferStats
+(
+ // return dest
+ // calculate: dest = src1 - src2
+
+ TransferStats_t *dest, // NULL or destination (maybe same as source)
+ const TransferStats_t *src1, // NULL or first source
+ const TransferStats_t *src2 // NULL or second source
+);
+
+TransferStats_t * SumTransferStats
+(
+ // return dest
+ // calculate: dest = sum(all_sources)
+
+ TransferStats_t *dest, // NULL or destination (maybe same as source)
+ const TransferStats_t * const *src, // NULL or source list, elements may be NULL
+ int n_src // number of element; if -1: term at first NULL
+);
+
+ccp PrintTransferStatsSQL
+(
+ // print statistic as SQL assigning list.
+ // arguments are speparated by a comma.
+
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ const TransferStats_t *ts, // valid source
+ ccp prefix // not NULL: prefix memebr names
+);
+
+//-----------------------------------------------------------------------------
+
+void PrintTranferStatistics
+(
+ FILE *f, // output file, never NULL
+ ccp prefix, // NULL or prefix for each line
+ ccp name, // statistics name, up to 5 chars including colon
+ const TransferStats_t *stat,// statistics record
+
+ TransferStats_t *prev, // NULL statistics record with previous data
+ u64 duration, // delta duration in usec
+ ccp duration_info, // text representation of 'duration'
+
+ const ColorSet_t *colset // NULL (no colors) or color set to use
+);
+
+//-----------------------------------------------------------------------------
+
+void SaveCurrentStateTransferStats
+(
+ FILE *f, // output file
+ const TransferStats_t *stat // valid stat object
+);
+
+void SaveCurrentStateTransferStats1
+(
+ // print as single line
+ FILE *f, // output file
+ ccp name, // var name; use "tfer-stat" if NULL or EMPTY
+ const TransferStats_t *stat // valid stat object
+);
+
+void SaveCurrentStateTransferStatsN
+(
+ // print multiple stats each as single line
+ FILE *f, // output file
+ ccp name, // var name; use "tfer-stat-" if NULL or EMPTY
+ const TransferStats_t *stat, // list of valid stat objects
+ uint n_stat // number of elements in 'stat'
+);
+
+//-----------------------------------------------------------------------------
+
+void RestoreStateTransferStats
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+);
+
+void RestoreStateTransferStats1
+(
+ // scan single line
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ ccp name, // var name; use "tfer-stat" if NULL or EMPTY
+ TransferStats_t *stat, // valid stat object
+ bool fall_back // true: fall back to RestoreStateTransferStats()
+);
+
+uint RestoreStateTransferStatsN
+(
+ // print multiple stats each as single line
+ // returns the number of read elements; all others are set to NULL
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ ccp name, // var name; use "tfer-stat-" if NULL or EMPTY
+ TransferStats_t *stat, // list of valid stat objects
+ uint n_stat // number of elements in 'stat'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Socket_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// define some TCP call back function types
+
+struct TCPStream_t;
+typedef int (*TCPStreamFunc) ( struct TCPStream_t * ts );
+typedef int (*TCPIOFunc) ( struct TCPStream_t * ts, u8 *buf, uint size );
+typedef int (*TCPTimeFunc) ( struct TCPStream_t * ts, u64 now_usec );
+
+typedef enum TCPFDList_t
+{
+ TCP_FM_ADD_SOCK, // add sockets to 'fdl'
+ TCP_FM_CHECK_SOCK, // check sockts of 'fdl'
+ TCP_FM_TIMEOUT, // on timeout (fdl is NULL)
+}
+TCPFDList_t;
+
+typedef int (*TCPFDListFunc)
+(
+ // if return <0, then don't do standard action with the stream
+ struct TCPStream_t *ts, // valid TCP stream
+ FDList_t *fdl, // valid socket list
+ TCPFDList_t mode, // execution mode
+ bool check_timeout // true: enable timeout checks
+);
+
+struct TCPHandler_t;
+typedef struct TCPStream_t * (*TCPCreateFunc) ( struct TCPHandler_t * th, int sock );
+
+///////////////////////////////////////////////////////////////////////////////
+// [[Socket_t]]
+
+typedef struct Socket_t
+{
+ int sock; // tcp socket
+ uint poll_index; // if poll() is used: index of 'poll_list'
+ bool is_unix; // true: Is a unix file socket
+ char info[23]; // info about this socket, for debugging
+
+ // if defined, these function replace the TCPHandler_t functions.
+
+ TCPCreateFunc OnCreateStream; // not NULL: called to create
+ // an initialize a stream object
+
+ TCPStreamFunc OnAddedStream; // not NULL: called after the stream
+ // is created and added
+}
+Socket_t;
+
+uint GetSocketNameBySA
+ ( char *buf, uint bufsize, const struct sockaddr *sa, socklen_t sa_len );
+uint GetSocketName ( char *buf, uint bufsize, int sock );
+uint GetPeerName ( char *buf, uint bufsize, int sock );
+bool DeleteSocketFile ( int sock );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCPStream_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[TCPStream_t]]
+
+typedef struct TCPStream_t
+{
+ //--- double linked list
+
+ struct TCPStream_t *prev; // link to previous stream, unused for the pool
+ struct TCPStream_t *next; // link to next stream
+ struct TCPHandler_t *handler; // related TCP handler
+
+
+ //--- base data
+
+ uint unique_id; // unique id, created by CreateUniqueId()
+ int sock; // tcp socket
+ uchar protect; // >0: protect member, don't destroy it
+ uchar auto_close; // >0: close stream after last byte sent
+ uchar rescan; // rescan input buffer also without new data
+ uchar eof; // >0: end of file, bit-0 is set by framework
+ uchar error; // >0: connection error, bit-0 is set by framework
+ uchar not_socket; // >0: fd is not a socket! // =0: unknown
+ GrowBuffer_t ibuf; // input buffer
+ GrowBuffer_t obuf; // output buffer
+ u64 accept_usec; // >0: next trigger, not used for timeout
+ u64 trigger_usec; // >0: next timeout and trigger time
+ u64 delay_usec; // >0: delay of trigger after accept_usec
+ u64 timeout_usec; // >0: auto disconnect after inactivity
+ u64 allow_mode; // >0: access allowed with this code
+ uint poll_index; // if 'use_poll': index of 'poll_list'
+
+
+ //--- call back functions
+
+ TCPTimeFunc OnMaintenance; // not NULL: called on maintenance
+ TCPTimeFunc OnTimeout; // not NULL: called on timeout
+ TCPIOFunc OnReceived; // not NULL: called after read, but before buffer insert
+ TCPIOFunc OnSend; // not NULL: called after write, but before buffer drop
+ TCPTimeFunc OnClose; // not NULL: called when the stream is closed
+ TCPFDListFunc OnFDList; // not NULL: Call this for FDList actions
+
+ //--- logging
+
+ TraceLog_t *tracelog; // NULL or valid trace-log data
+
+ //--- statistics
+
+ char info[32]; // info about this stream, for debugging
+ u64 connect_usec; // time of connection, GetTimeUSec(false)
+ u64 receive_usec; // time of last receive, GetTimeUSec(false)
+ u64 send_usec; // time of last send, GetTimeUSec(false)
+
+ TransferStats_t stat; // transfer statistics
+ TransferStats_t *xstat; // NULL or pointer to summary statistics
+
+ //--- data extension
+
+ u8 data[0]; // user specific data
+}
+TCPStream_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void LogTCPStream
+(
+ FILE *f, // output file
+ int indent, // indention
+ const TCPStream_t *ts, // valid TCP handler
+ int recurse, // >0: print stream list, >1: print buffer status
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vfprintf(format,...)'
+)
+__attribute__ ((__format__(__printf__,5,6)));
+
+//-----------------------------------------------------------------------------
+
+void LogTCPStreamActivity
+(
+ const TCPStream_t *ts, // valid TCP handler
+ ccp activity
+);
+
+//-----------------------------------------------------------------------------
+
+char * BufInfoHeadTCPStream
+(
+ // returns a pointer to the head line
+
+ uint line // 0|1
+);
+
+#define BUF_INFO_TCP_STREAM_SIZE 120
+
+char * BufInfoTCPStream
+(
+ // returns a pointer to the buffer
+
+ char * buf, // result (BUF_INFO_TCP_STREAM_SIZE bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ const TCPStream_t *ts, // valid TCP handler
+ u64 now_usec // NULL or current time, GetTimeUSec(false)
+);
+
+//-----------------------------------------------------------------------------
+
+void InitializeTCPStream ( TCPStream_t *ts, int sock );
+void ResetTCPStream ( TCPStream_t *ts );
+void DestroyTCPStream ( TCPStream_t *ts );
+
+int SendDirectTCPStream
+(
+ // Send data direct without blocking and without calling any notifier.
+ // If output buf is not empty or send failed, append the data to output buf.
+ // Returns the number of bytes added to the output buffer or -1 on error.
+ // The data is send+stored completely (returns 'size') or not at all
+ // (returns '0'). -1 is returned if the socket is invalid.
+
+ TCPStream_t *ts, // valid TCP handler
+ bool flush_output, // true: try to flush out-buf before
+ cvp data, // data to send
+ uint size // size of 'data', If NULL && flush: flush only
+);
+
+int PrintArgDirectTCPStream
+(
+ // Printing interface for SendDirectTCPStream()
+
+ TCPStream_t *ts, // valid TCP handler
+ bool flush_output, // true: try to flush out-buf before
+ ccp format, // format string for vsnprintf()
+ va_list arg // parameters for 'vfprintf(...,format,...)'
+);
+
+int PrintDirectTCPStream
+(
+ // Printing interface for SendDirectTCPStream()
+
+ TCPStream_t *ts, // valid TCP handler
+ bool flush_output, // true: try to flush out-buf before
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vfprintf(...,format,...)'
+)
+__attribute__ ((__format__(__printf__,3,4)));
+
+//-----------------------------------------------------------------------------
+
+int PrintBinary1TCPStream
+(
+ TCPStream_t *ts, // NULL or destination
+ ccp cmd, // command name
+ cvp bin_data, // pointer to binary data
+ uint bin_size, // size of binary data
+ ccp format, // NULL or format string with final ';'
+ ... // parameters for 'format'
+)
+__attribute__ ((__format__(__printf__,5,6)));
+
+int PrintBinary2TCPStream
+(
+ TCPStream_t *ts, // NULL or destination
+ ccp cmd, // command name
+ cvp bin1_data, // pointer to binary data
+ uint bin1_size, // size of binary data
+ cvp bin2_data, // pointer to binary data
+ uint bin2_size, // size of binary data
+ ccp format, // NULL or format string with final ';'
+ ... // parameters for 'format'
+)
+__attribute__ ((__format__(__printf__,7,8)));
+
+//-----------------------------------------------------------------------------
+
+static inline int FlushTCPStream
+(
+ // Flush the output buffer using SendDirectTCPStream() without data
+
+ TCPStream_t *ts // valid TCP handler
+)
+{
+ return SendDirectTCPStream(ts,true,0,0);
+}
+
+//-----------------------------------------------------------------------------
+
+ssize_t UpdateRecvStatTCPStream
+(
+ // returns 'send_stat'
+
+ TCPStream_t *ts, // valid TCP handler
+ ssize_t recv_stat, // result of recv(), update stats on >0
+ u64 now_usec // NULL or current time
+);
+
+ssize_t UpdateSendStatTCPStream
+(
+ // returns 'send_stat'
+
+ TCPStream_t *ts, // valid TCP handler
+ ssize_t send_stat, // result of send(), update stats on >0
+ u64 now_usec // NULL or current time
+);
+
+//-----------------------------------------------------------------------------
+
+void AddSocketTCPStream
+(
+ TCPStream_t *ts, // valid TCP handler
+ FDList_t *fdl // valid file descriptor list
+);
+
+void CheckSocketTCPStream
+(
+ TCPStream_t *ts, // valid TCP handler
+ FDList_t *fdl, // valid socket list
+ bool check_timeout // true: enable timeout checks
+);
+
+//-----------------------------------------------------------------------------
+
+void CheckTimeoutTCPStream ( TCPStream_t *ts );
+u64 GetLastActivityTCPStream ( TCPStream_t *ts );
+u64 GetTimeoutTCPStream ( TCPStream_t *ts );
+int OnTimeoutTCPStream ( TCPStream_t *ts, u64 now_usec );
+void SetupTriggerTCPStream ( TCPStream_t *ts, u64 now_usec );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCPHandler_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef int (*TCPHandlerFunc)
+(
+ // returns 0 on success, !=0 otherwise
+
+ struct TCPHandler_t *th, // valid TCP handler
+ const Socket_t *lsock, // valid listen-socket
+ int sock // valid socket to verify
+);
+
+typedef bool (*TCPAllowFunc)
+(
+ // returns true if access is allowed
+
+ struct TCPHandler_t *th, // valid TCP handler
+ const Socket_t *lsock, // valid listen-socket
+ int sock, // valid socket to verify
+ u64 *allow // not NULL: store allow code here
+);
+
+///////////////////////////////////////////////////////////////////////////////
+// [[TCPHandler_t]]
+
+#define TCP_HANDLER_MAX_LISTEN 3
+
+typedef struct TCPHandler_t
+{
+ uint unique_id; // unique id, created by CreateUniqueId()
+ uint data_size; // size of TCPStream_t::data
+ uint max_conn; // max allowed connections
+
+ TCPStream_t *first; // pointer to first active stream
+ uint need_maintenance; // >0: a stream is ready for maintenance
+
+ TCPAllowFunc OnAllowStream; // not NULL: called before allowing a connection
+ // initialized with IsStreamAllowed()
+ TCPHandlerFunc OnAcceptStream; // not NULL: called before accpeting a connection
+ TCPCreateFunc OnCreateStream; // not NULL: called to create
+ // and initialize a stream object
+ TCPStreamFunc OnAddedStream; // not NULL: called after the stream
+ // is created and added
+ TCPStreamFunc OnDestroyStream; // not NULL: called by ResetTCPStream()
+
+ Socket_t listen[TCP_HANDLER_MAX_LISTEN];
+ // sockets to listen
+
+ AllowIP4_t *allow_ip4; // NULL or filter for accept (not for unix files).
+ // Analysis is done by OnAllowStream()
+ // before calling OnAcceptStream().
+
+ //--- logging
+
+ TraceLog_t tracelog; // trace activities
+
+ //--- statistics
+
+ uint used_streams; // number of currently used streams
+ uint max_used_streams; // number of max used streams
+ uint total_streams; // total number of used streams
+
+ TransferStats_t stat; // transfer statistics
+}
+TCPHandler_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void LogTCPHandler
+(
+ FILE *f, // output file
+ int indent, // indention
+ const TCPHandler_t *th, // valid TCP handler
+ int recurse, // >0: print stream list, >1: print buffer status
+ ccp format, // format string for vfprintf()
+ ... // arguments for 'vfprintf(format,...)'
+)
+__attribute__ ((__format__(__printf__,5,6)));
+
+//-----------------------------------------------------------------------------
+
+void PrintStreamTableTCPHandler
+(
+ FILE *f, // output file
+ const ColorSet_t *colset, // NULL (no colors) or valid color set
+ int indent, // indention
+ const TCPHandler_t *th // valid TCP handler
+);
+
+//-----------------------------------------------------------------------------
+
+void InitializeTCPHandler
+(
+ TCPHandler_t *th, // not NULL
+ uint data_size // size of 'TCPStream_t::data'
+);
+
+void ResetTCPHandler
+(
+ TCPHandler_t *th // valid TCP handler
+);
+
+uint CloseTCPHandler
+(
+ // returns the number of waiting clients (obuf not empty and !disabled)
+ TCPHandler_t *th // valid TCP handler
+);
+
+TCPStream_t * AddTCPStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ TCPStream_t *ts // the new stream
+);
+
+TCPStream_t * CreateTCPStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ int sock, // related socket
+ u64 allow_mode, // stored in 'allow_mode'
+ const Socket_t *listen // NULL or related listen object
+);
+
+TCPStream_t * ConnectUnixTCPStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ ccp path, // NULL or path part 1 to socket file
+ bool silent // suppress error messages
+);
+
+TCPStream_t * ConnectTCPStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ ccp addr, // address -> NetworkHost_t
+ u16 default_port, // default port
+ bool silent // suppress error messages
+);
+
+TCPStream_t * FindTCPStreamByUniqueID
+(
+ TCPHandler_t *th, // valid TCP handler
+ uint unique_id // id to search
+);
+
+//-----------------------------------------------------------------------------
+
+uint NumOfSocketsTCP ( const TCPHandler_t *th );
+Socket_t * GetUnusedListenSocketTCP ( TCPHandler_t *th, bool silent );
+
+//-----------------------------------------------------------------------------
+
+enumError ListenUnixTCP
+(
+ TCPHandler_t *th, // valid TCP handler
+ ccp path // path to unix file
+);
+
+enumError ListenTCP
+(
+ TCPHandler_t *th, // valid TCP handler
+ ccp addr, // IPv4 address with optional port -> NetworkHost_t
+ // fall back to ListenUnixTCP() if addr begins with
+ // 'unix:' or '/' or './ or '../'
+ u16 default_port // default port
+);
+
+int UnlistenTCP
+(
+ // returns the index of the closed socket, or if not found
+
+ TCPHandler_t *th, // valid TCP handler
+ int sock // socket to close
+);
+
+uint UnlistenAllTCP
+(
+ // returns the number of closed sockets
+
+ TCPHandler_t *th // valid TCP handler
+);
+
+//-----------------------------------------------------------------------------
+
+void AddSocketsTCP
+(
+ TCPHandler_t *th, // valid TCP handler
+ FDList_t *fdl // valid file descriptor list
+);
+
+//-----------------------------------------------------------------------------
+
+bool IsStreamAllowed
+(
+ // returns true if access is allowed
+
+ struct TCPHandler_t *th, // valid TCP handler
+ const Socket_t *lsock, // valid listen-socket
+ int sock, // valid socket to verify
+ u64 *allow // not NULL: store allow code here
+);
+
+TCPStream_t * OnAcceptStream
+(
+ TCPHandler_t *th, // valid TCP handler
+ const Socket_t *lsock, // listen-socket
+ u64 now_usec // time for timestamps, GetTimeUSec(false)
+);
+
+void OnCloseStream
+(
+ TCPStream_t *ts, // valid TCP stream
+ u64 now_usec // time for timestamps, GetTimeUSec(false)
+);
+
+void SetNotSocketStream
+(
+ TCPStream_t *ts // valid TCP stream
+);
+
+void OnReceivedStream
+(
+ TCPStream_t *ts, // valid TCP stream
+ u64 now_usec // time for timestamps, GetTimeUSec(false)
+);
+
+void OnWriteStream
+(
+ TCPStream_t *ts, // valid TCP stream
+ u64 now_usec // time for timestamps, GetTimeUSec(false)
+);
+
+void CheckSocketsTCP
+(
+ TCPHandler_t *th, // valid TCP handler
+ FDList_t *fdl, // valid socket list
+ bool check_timeout // true: enable timeout checks
+);
+
+void CheckTimeoutTCP ( TCPHandler_t *th );
+bool MaintenanceTCP ( TCPHandler_t *th );
+
+void ManageSocketsTCP
+(
+ // call CheckSocketsTCP(), CheckTimeoutTCP(), MaintenanceTCP()
+
+ TCPHandler_t *th, // valid TCP handler
+ FDList_t *fdl, // valid socket list
+ int stat // result of a WAIT function
+);
+
+///////////////////////////////////////////////////////////////////////////////
+// special case: send single data packtes
+
+TCPStream_t * SendSingleUnixTCP
+(
+ TCPHandler_t *th, // valid handle
+ ccp path, // path to socket file
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ u64 timeout_usec, // timeout before closing the connection
+ TransferStats_t *xstat, // NULL or pointer to summary statistics
+
+ bool silent // suppress error messages
+);
+
+TCPStream_t * SendSingleTCP
+(
+ TCPHandler_t *th, // valid handle
+ ccp addr, // address -> NetworkHost_t
+ u16 default_port, // default port
+
+ const void *data, // data to send
+ uint size, // size of 'data'
+ u64 timeout_usec, // timeout before closing the connection
+ TransferStats_t *xstat, // NULL or pointer to summary statistics
+
+ bool silent // suppress error messages
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TCP: CommandTCP ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef char * (*CommandTCPLineFunc)
+(
+ // return first non scanned character, or NULL on error
+
+ TCPStream_t *ts, // valid stream data
+ char *line, // begin of line
+ char *line_end, // end of line
+ u64 now_usec // NULL or current time
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enumError (*CommandTCPArgFunc)
+(
+ TCPStream_t *ts, // valid stream data
+ int argc, // number of arguments in 'argv'
+ char **argv, // array with 'argc' arguments + a NULL term
+ u64 now_usec // NULL or current time in usec
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct CommandTCPInfo_t
+{
+ //--- base parameters
+
+ bool comma_is_eol; // true: comma is 'end of command line'
+ u64 timeout_usec; // wanted timeout, default 10s
+
+ //--- scanning functions, high priority first
+
+ CommandTCPArgFunc OnScanArg; // function to scan arguments
+ CommandTCPLineFunc OnScanLine; // function to scan line
+
+ //--- user specific data extension
+
+ u8 data[0];
+}
+CommandTCPInfo_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+int OnCreateCommandTCP ( TCPStream_t *ts );
+
+void SetTimeoutCommandTCP
+(
+ TCPStream_t *ts, // valid stream
+ u64 now_usec // NULL or current time in usec
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// SaveCurrentState*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SaveCurrentStateSocket
+(
+ FILE *f, // output file
+ const Socket_t *sock // valid socket object
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef void (*TCPStreamSaveFunc)
+(
+ FILE *f, // output file
+ const TCPStream_t *ts // valid TCP stream
+);
+
+//-----------------------------------------------------------------------------
+
+void SaveCurrentStateTCPStream
+(
+ FILE *f, // output file
+ const TCPStream_t *ts, // valid TCP stream
+ TCPStreamSaveFunc func // NULL or function for 'TCPStream_t.data' extend
+);
+
+void SaveCurrentStateTCPHandler
+(
+ FILE *f, // output file
+ ccp sect_name, // section base name
+ const TCPHandler_t *th, // valid TCP handler
+ TCPStreamSaveFunc func // NULL or function for 'TCPStream_t.data' extend
+);
+
+void SaveCurrentStateCommandTCP
+(
+ FILE *f, // output file
+ const TCPStream_t *ts // valid TCP stream
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// RestoreState*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateSocket
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+TCPStream_t * RestoreStateTCPStream
+(
+ // return 'ts' or the new TCPStream_t
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ TCPStream_t *ts, // if NULL: create it
+ uint extra_size // if 'ts' alloced: alloc some byte more
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateTCPHandler_base
+(
+ TCPHandler_t *th, // valid TCPHandler_t
+ RestoreState_t *rs // info data, can be modified (cleaned after call)
+);
+
+Socket_t * RestoreStateTCPHandler_socket
+(
+ TCPHandler_t *th, // valid TCPHandler_t
+ RestoreState_t *rs // info data, can be modified (cleaned after call)
+);
+
+TCPStream_t * RestoreStateTCPHandler_stream
+(
+ TCPHandler_t *th, // valid TCPHandler_t
+ RestoreState_t *rs // info data, can be modified (cleaned after call)
+);
+
+void RestoreStateTCPHandler
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateCommandTCP
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ cvp user_table // pointer provided by RestoreStateTab_t[]
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Linux support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#if defined(SYSTEM_LINUX) || defined(__CYGWIN__)
+ #include "dclib-network-linux.h"
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_NETWORK_H
+
diff --git a/project/dclib/dclib-numeric.c b/project/dclib/dclib-numeric.c
new file mode 100644
index 0000000..572211a
--- /dev/null
+++ b/project/dclib/dclib-numeric.c
@@ -0,0 +1,6102 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+#include "dclib-basics.h"
+#include "dclib-debug.h"
+#include "dclib-utf8.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// bits ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint Count1Bits ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ uint count = 0;
+ const uchar *ptr = addr;
+ while ( size-- > 0 )
+ count += TableBitCount[*ptr++];
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int FindLowest0BitLE ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ const uchar *ptr = addr;
+ uint idx;
+ for ( idx = 0; idx < size; idx++ )
+ {
+ const int val = TableLowest0Bit[*ptr++];
+ if ( val >= 0 )
+ return 8*idx + val;
+ }
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindLowest1BitLE ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ const uchar *ptr = addr;
+ uint idx;
+ for ( idx = 0; idx < size; idx++ )
+ {
+ const int val = TableLowest1Bit[*ptr++];
+ if ( val >= 0 )
+ return 8*idx + val;
+ }
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindHighest0BitLE ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ const uchar *ptr = addr + size;
+ while ( size-- > 0 )
+ {
+ const int val = TableHighest0Bit[*--ptr];
+ if ( val >= 0 )
+ return 8*size + val;
+ }
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindHighest1BitLE ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ const uchar *ptr = addr + size;
+ while ( size-- > 0 )
+ {
+ const int val = TableHighest1Bit[*--ptr];
+ if ( val >= 0 )
+ return 8*size + val;
+ }
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u32 GetAlign32 ( u32 data )
+{
+ const int index = FindLowest1Bit(&data,sizeof(data));
+ return index < 0 ? 0 : 1 << index;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetAlign64 ( u64 data )
+{
+ const int index = FindLowest1Bit(&data,sizeof(data));
+ return index < 0 ? 0 : 1llu << index;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int FindLowest0BitBE ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ const uchar *ptr = addr + size;
+ uint idx;
+ for ( idx = 0; idx < size; idx++ )
+ {
+ const int val = TableLowest0Bit[*--ptr];
+ if ( val >= 0 )
+ return 8*idx + val;
+ }
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindLowest1BitBE ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ const uchar *ptr = addr + size;
+ uint idx;
+ for ( idx = 0; idx < size; idx++ )
+ {
+ const int val = TableLowest1Bit[*--ptr];
+ if ( val >= 0 )
+ return 8*idx + val;
+ }
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindHighest0BitBE ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ const uchar *ptr = addr;
+ while ( size-- > 0 )
+ {
+ const int val = TableHighest0Bit[*ptr++];
+ if ( val >= 0 )
+ return 8*size + val;
+ }
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int FindHighest1BitBE ( cvp addr, uint size )
+{
+ DASSERT( CHAR_BIT == 8 );
+
+ const uchar *ptr = addr;
+ while ( size-- > 0 )
+ {
+ const int val = TableHighest1Bit[*ptr++];
+ if ( val >= 0 )
+ return 8*size + val;
+ }
+ return -1;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// bit manipulation ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool TestSetBit ( void *addr, uint bit_num )
+{
+ const u8 mask = 1 << ( bit_num & 7 );
+ u8 *ptr = ((u8*)addr) + ( bit_num >> 3 );
+ const bool result = ( *ptr & mask ) != 0;
+ *ptr |= mask;
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool TestClearBit ( void *addr, uint bit_num )
+{
+ const u8 mask = 1 << ( bit_num & 7 );
+ u8 *ptr = ((u8*)addr) + ( bit_num >> 3 );
+ const bool result = ( *ptr & mask ) != 0;
+ *ptr &= ~mask;
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool TestInvertBit ( void *addr, uint bit_num )
+{
+ const u8 mask = 1 << ( bit_num & 7 );
+ u8 *ptr = ((u8*)addr) + ( bit_num >> 3 );
+ const bool result = ( *ptr & mask ) != 0;
+ *ptr ^= mask;
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void SetBits ( void *addr, uint beg_bitnum, uint end_bitnum )
+{
+ static uchar tab1[] = { 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80 };
+ static uchar tab2[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
+
+ if ( beg_bitnum < end_bitnum )
+ {
+ const uint lo8 = beg_bitnum >> 3;
+ const uint hi8 = --end_bitnum >> 3;
+
+ if ( lo8 < hi8 )
+ {
+
+ uchar * dest = (uchar*)addr + lo8;
+ *dest++ |= tab1[ beg_bitnum & 7 ];
+
+ uchar * end = (uchar*)addr + hi8;
+ *end |= tab2[ end_bitnum & 7 ];
+
+ while ( dest < end )
+ *dest++ = 0xff;
+ }
+ else
+ {
+ uchar * dest = (uchar*)addr + lo8;
+ *dest |= tab1[ beg_bitnum & 7 ] & tab2[ end_bitnum & 7 ];
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearBits ( void *addr, uint beg_bitnum, uint end_bitnum )
+{
+ static uchar tab1[] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f };
+ static uchar tab2[] = { 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00 };
+
+ if ( beg_bitnum < end_bitnum )
+ {
+ const uint lo8 = beg_bitnum >> 3;
+ const uint hi8 = --end_bitnum >> 3;
+
+ if ( lo8 < hi8 )
+ {
+
+ uchar * dest = (uchar*)addr + lo8;
+ *dest++ &= tab1[ beg_bitnum & 7 ];
+
+ uchar * end = (uchar*)addr + hi8;
+ *end &= tab2[ end_bitnum & 7 ];
+
+ while ( dest < end )
+ *dest++ = 0;
+ }
+ else
+ {
+ uchar * dest = (uchar*)addr + lo8;
+ *dest &= tab1[ beg_bitnum & 7 ] | tab2[ end_bitnum & 7 ];
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InvertBits ( void *addr, uint beg_bitnum, uint end_bitnum )
+{
+ static uchar tab1[] = { 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80 };
+ static uchar tab2[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
+
+ if ( beg_bitnum < end_bitnum )
+ {
+ const uint lo8 = beg_bitnum >> 3;
+ const uint hi8 = --end_bitnum >> 3;
+
+ if ( lo8 < hi8 )
+ {
+
+ uchar * dest = (uchar*)addr + lo8;
+ *dest++ ^= tab1[ beg_bitnum & 7 ];
+
+ uchar * end = (uchar*)addr + hi8;
+ *end ^= tab2[ end_bitnum & 7 ];
+
+ while ( dest < end )
+ *dest++ ^= 0xff;
+ }
+ else
+ {
+ uchar * dest = (uchar*)addr + lo8;
+ *dest ^= tab1[ beg_bitnum & 7 ] & tab2[ end_bitnum & 7 ];
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#undef GET_NEXT
+#define GET_NEXT ch = *src++; \
+ if ( ++min_idx > max_idx ) goto term; \
+ if ( min_idx == max_idx ) ch &= max_mask
+
+int PrintBitfieldEx
+(
+ // returns the number of printed characters, or -1 if buffer is to small
+
+ char *buf, // buffer
+ uint buf_size, // size of 'buf'
+ cvp bit_field, // address of bit field
+ uint min_idx, // minimal index (inclusive) to check
+ uint max_idx, // maximal index (inclusive) to check
+ bool use_ranges, // true: create ranges 'a:b'
+ int mult, // each printed index is first multiplyed by 'mult'
+ int add // ... and then added to 'add'.
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size);
+ DASSERT(bit_field);
+
+ char *dest = buf;
+ char *end = buf + buf_size;
+ char *sep = "";
+
+ const u8 *src = (u8*)bit_field + (min_idx >> 3);
+ u8 ch = *src++; // always preload the current byte
+ if ( min_idx & 7 )
+ ch &= ~((1<<(min_idx&7))-1);
+ min_idx >>= 3;
+ const u8 max_mask = (2<<(max_idx&7))-1;
+ max_idx >>= 3;
+ noPRINT("SETUP[%zx] %02x, %u..%u, max_mask=%02x\n",
+ src-(u8*)bit_field-1, ch, min_idx, max_idx, max_mask );
+
+ while ( min_idx <= max_idx )
+ {
+ //--- first skip bytes with value 0
+
+ if (!ch)
+ do { GET_NEXT; } while (!ch);
+
+
+ //-- now find the first set bit
+
+ DASSERT(ch);
+ noPRINT("CHECK[%zx] %02x, %u..%u\n", src-(u8*)bit_field-1, ch, min_idx, max_idx);
+ u8 mask = 1;
+ uint bit_idx = 0;
+ while ( !(ch & mask) )
+ mask <<= 1, bit_idx++;
+
+ uint first = 8*min_idx + bit_idx;
+ noPRINT("FOUND-BEG[%zx.%u=%u] %02x, %u..%u\n",
+ src-(u8*)bit_field-1, bit_idx, first, ch, min_idx, max_idx );
+
+
+ //-- now find the first bit not set
+
+ while ( ch & mask )
+ mask <<= 1, bit_idx++;
+
+ if ( bit_idx == 8 )
+ {
+ do { GET_NEXT; } while (ch == 0xff);
+
+ mask = 1;
+ bit_idx = 0;
+ while ( ch & mask )
+ mask <<= 1, bit_idx++;
+ }
+
+ uint last = 8*min_idx + bit_idx - 1;
+ noPRINT("FOUND-END[%zx.%u=%u] %02x, %u..%u\n",
+ src-(u8*)bit_field-1, bit_idx, last, ch, min_idx, max_idx );
+
+ if ( first == last )
+ dest = snprintfE(dest,end,"%s%d",sep,first*mult+add);
+ else if ( use_ranges )
+ dest = snprintfE(dest,end,"%s%d:%d",
+ sep,
+ first * mult + add,
+ last * mult + add );
+ else
+ while ( first <= last )
+ {
+ dest = snprintfE(dest,end,"%s%d",sep,first++*mult+add);
+ sep = ",";
+ }
+
+ if ( dest >= end-1 )
+ {
+ *dest = 0;
+ return -1;
+ }
+
+ sep = ",";
+ ch &= ~((1<<bit_idx)-1);
+ }
+
+ term:
+ *dest = 0;
+ return dest - buf;
+}
+
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// low level endian conversions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void write_be16 ( void * be_data_ptr, u16 data )
+{
+ DASSERT(be_data_ptr);
+ u8 * dest = be_data_ptr;
+ *dest++ = data >> 8;
+ *dest = data;
+}
+
+void write_be24 ( void * be_data_ptr, u32 data )
+{
+ DASSERT(be_data_ptr);
+ u8 * dest = be_data_ptr + 2;
+ *dest-- = data; data >>= 8;
+ *dest-- = data;
+ *dest = data >> 8;
+}
+
+void write_be32 ( void * be_data_ptr, u32 data )
+{
+ DASSERT(be_data_ptr);
+ u8 * dest = be_data_ptr + 3;
+ *dest-- = data; data >>= 8;
+ *dest-- = data; data >>= 8;
+ *dest-- = data;
+ *dest = data >> 8;
+}
+
+void write_be40 ( void * be_data_ptr, u64 data )
+{
+ DASSERT(be_data_ptr);
+ *(u8*)be_data_ptr = data>>32;
+ write_be32( (u8*)be_data_ptr+1, data );
+}
+
+void write_be48 ( void * be_data_ptr, u64 data )
+{
+ DASSERT(be_data_ptr);
+ write_be16( be_data_ptr, data>>32 );
+ write_be32( (u8*)be_data_ptr+2, data );
+}
+
+void write_be56 ( void * be_data_ptr, u64 data )
+{
+ DASSERT(be_data_ptr);
+ write_be24( be_data_ptr, data>>32 );
+ write_be32( (u8*)be_data_ptr+3, data );
+}
+
+void write_be64 ( void * be_data_ptr, u64 data )
+{
+ DASSERT(be_data_ptr);
+ write_be32( be_data_ptr, data >> 32 );
+ write_be32( (u8*)be_data_ptr+4, data );
+}
+
+void write_bef4 ( void * be_data_ptr, float data )
+{
+ // assume: local system supports IEEE 754
+
+ write_be32(be_data_ptr,*(u32*)&data);
+}
+
+void write_bef8 ( void * be_data_ptr, double data )
+{
+ // assume: local system supports IEEE 754
+
+ write_be64(be_data_ptr,*(u64*)&data);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void write_le16 ( void * le_data_ptr, u16 data )
+{
+ DASSERT(le_data_ptr);
+ u8 * dest = le_data_ptr;
+ *dest++ = data;
+ *dest = data >> 8;
+}
+
+void write_le24 ( void * le_data_ptr, u32 data )
+{
+ DASSERT(le_data_ptr);
+ u8 * dest = le_data_ptr;
+ *dest++ = data; data >>= 8;
+ *dest++ = data;
+ *dest = data >> 8;
+}
+
+void write_le32 ( void * le_data_ptr, u32 data )
+{
+ DASSERT(le_data_ptr);
+ u8 * dest = le_data_ptr;
+ *dest++ = data; data >>= 8;
+ *dest++ = data; data >>= 8;
+ *dest++ = data;
+ *dest = data >> 8;
+}
+
+void write_le40 ( void * le_data_ptr, u64 data )
+{
+ DASSERT(le_data_ptr);
+ *(u8*)le_data_ptr = (u8)data;
+ write_le32( (u8*)le_data_ptr+1, data >> 8 );
+}
+
+void write_le48 ( void * le_data_ptr, u64 data )
+{
+ DASSERT(le_data_ptr);
+ write_le16( le_data_ptr, data );
+ write_le32( (u8*)le_data_ptr+2, data >> 16 );
+}
+
+void write_le56 ( void * le_data_ptr, u64 data )
+{
+ DASSERT(le_data_ptr);
+ write_le32( le_data_ptr, data );
+ write_le24( (u8*)le_data_ptr+4, data >> 32 );
+}
+
+void write_le64 ( void * le_data_ptr, u64 data )
+{
+ DASSERT(le_data_ptr);
+ write_le32( le_data_ptr, data );
+ write_le32( (u8*)le_data_ptr+4, data >> 32 );
+}
+
+void write_lef4 ( void * le_data_ptr, float data )
+{
+ // assume: local system supports IEEE 754
+
+ write_le32(le_data_ptr,*(u32*)&data);
+}
+
+void write_lef8 ( void * le_data_ptr, double data )
+{
+ // assume: local system supports IEEE 754
+
+ write_le64(le_data_ptr,*(u64*)&data);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// convert lists
+
+void be16n ( u16 * dest, const u16 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ *dest++ = be16(src++);
+}
+
+void be32n ( u32 * dest, const u32 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ *dest++ = be32(src++);
+}
+
+void bef4n ( float32 * dest, const float32 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ *dest++ = bef4(src++);
+}
+
+void write_be16n ( u16 * dest, const u16 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ write_be16(dest++,*src++);
+}
+
+void write_be32n ( u32 * dest, const u32 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ write_be32(dest++,*src++);
+}
+
+void write_bef4n ( float32 * dest, const float32 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ write_bef4(dest++,*src++);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// convert lists
+
+void le16n ( u16 * dest, const u16 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ *dest++ = le16(src++);
+}
+
+void lef4n ( float32 * dest, const float32 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ *dest++ = lef4(src++);
+}
+
+void write_le16n ( u16 * dest, const u16 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ write_le16(dest++,*src++);
+}
+
+void write_lef4n ( float32 * dest, const float32 * src, int n )
+{
+ DASSERT( dest );
+ DASSERT( n >= 0 );
+ DASSERT( src || !n );
+
+ while ( n-- > 0 )
+ write_lef4(dest++,*src++);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const endian_func_t be_func =
+{
+ {0xfe,0xff}, true, false, DC_BIG_ENDIAN,
+ be16, be24, be32, be40, be48, be56, be64, bef4, bef8,
+ write_be16, write_be24, write_be32,
+ write_be40, write_be48, write_be56, write_be64,
+ write_bef4, write_bef8
+};
+
+const endian_func_t le_func =
+{
+ {0xff,0xfe}, false, true, DC_LITTLE_ENDIAN,
+ le16, le24, le32, le40, le48, le56, le64, lef4, lef8,
+ write_le16, write_le24, write_le32,
+ write_le40, write_le48, write_le56, write_le64,
+ write_lef4, write_lef8
+};
+
+const endian_func_t * GetEndianFunc ( const void * byte_order_mark )
+{
+ DASSERT(byte_order_mark);
+ const u8 * bom = byte_order_mark;
+ return bom[0] == 0xfe && bom[1] == 0xff
+ ? &be_func
+ : bom[0] == 0xff && bom[1] == 0xfe
+ ? &le_func
+ : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetTextBOMLen ( const void * data, uint data_size )
+{
+ static const u8 be_bom[] = { 0xef, 0xbb, 0xbf };
+ static const u8 le_bom[] = { 0xef, 0xbf, 0xbe };
+
+ if ( data_size >= 3 && ( !memcmp(data,be_bom,3) || !memcmp(data,le_bom,3) ))
+ return 3;
+
+ if ( data_size >= 2 && ( *(u16*)data == 0xfeff || *(u16*)data == 0xfffe ))
+ return 2;
+
+ return 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// check index ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int CheckIndex1 ( int max, int index )
+{
+ DASSERT( max >= 0 );
+
+ if ( index >= 0 )
+ return index <= max ? index : max;
+
+ index += max;
+ return index >= 0 ? index : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CheckIndex1End ( int max, int index )
+{
+ DASSERT( max >= 0 );
+ if ( index > 0 )
+ return index <= max ? index : max;
+
+ index += max;
+ return index >= 0 ? index : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CheckIndex2 ( int max, int * p_begin, int * p_end )
+{
+ DASSERT( max >= 0 );
+ DASSERT(p_begin);
+ DASSERT(p_end);
+
+ int begin = *p_begin;
+ if ( begin < 0 )
+ {
+ begin += max;
+ if ( begin < 0 )
+ begin = 0;
+ }
+ else if ( begin > max )
+ begin = max;
+
+ int end = *p_end;
+ if ( end < 0 )
+ {
+ end += max;
+ // don't check for 'end<0' here, because 'end<begin' is checked later
+ }
+ else if ( end > max )
+ end = max;
+
+ if ( end < begin )
+ end = begin;
+
+ *p_begin = begin;
+ *p_end = end;
+ return end - begin;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CheckIndex2End ( int max, int * p_begin, int * p_end )
+{
+ DASSERT( max >= 0 );
+ DASSERT(p_begin);
+ DASSERT(p_end);
+
+ int begin = *p_begin;
+ if ( begin <= 0 )
+ {
+ begin += max;
+ if ( begin < 0 )
+ begin = 0;
+ }
+ else if ( begin > max )
+ begin = max;
+
+ int end = *p_end;
+ if ( end <= 0 )
+ {
+ end += max;
+ // don't check for 'end<0' here, because 'end<begin' is checked later
+ }
+ else if ( end > max )
+ end = max;
+
+ if ( end < begin )
+ end = begin;
+
+ *p_begin = begin;
+ *p_end = end;
+ return end - begin;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// encoding/decoding ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintEscapedString
+(
+ // returns 'buf'
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 10
+ ccp source, // NULL string to print
+ int len, // length of string. if -1, str is null terminated
+ CharMode_t char_mode, // modes, bit field (CHMD_*)
+ char quote, // NULL or quotation char, that must be quoted
+ uint *dest_len // not NULL: Store length of result here
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>=10);
+
+ const CharMode_t utf8 = char_mode & CHMD_UTF8;
+ const CharMode_t allow_e = char_mode & CHMD_ESC;
+
+ char *dest = buf;
+ char *dest_end = dest + buf_size - 4;
+
+ if (!source)
+ source = "";
+ ccp str = source;
+ ccp end = len < 0 ? 0 : str + len;
+
+ while ( dest < dest_end && ( !end || str < end ) )
+ {
+ const u8 ch = (u8)*str++;
+ switch (ch)
+ {
+ case 0:
+ if (!end)
+ {
+ str--;
+ goto eos;
+ }
+ *dest++ = '\\';
+ *dest++ = 'x';
+ *dest++ = '0';
+ *dest++ = '0';
+ break;
+
+ case '\\': *dest++ = '\\'; *dest++ = '\\'; break;
+ case '\a': *dest++ = '\\'; *dest++ = 'a'; break;
+ case '\b': *dest++ = '\\'; *dest++ = 'b'; break;
+ case '\f': *dest++ = '\\'; *dest++ = 'f'; break;
+ case '\n': *dest++ = '\\'; *dest++ = 'n'; break;
+ case '\r': *dest++ = '\\'; *dest++ = 'r'; break;
+ case '\t': *dest++ = '\\'; *dest++ = 't'; break;
+ case '\v': *dest++ = '\\'; *dest++ = 'v'; break;
+
+ case '\033':
+ *dest++ = '\\';
+ if (allow_e)
+ *dest++ = 'e';
+ else
+ {
+ *dest++ = 'x';
+ *dest++ = '1';
+ *dest++ = 'B';
+ }
+ break;
+
+ default:
+ if ( ch == quote )
+ {
+ *dest++ = '\\';
+ *dest++ = quote;
+ }
+ else if ( ch < ' ' || !utf8 && (ch&0x7f) < ' ' || (ch&0x7f) == 0x7f )
+ {
+ *dest++ = '\\';
+ *dest++ = 'x';
+ *dest++ = HiDigits[ch>>4];
+ *dest++ = HiDigits[ch&15];
+ }
+ else
+ *dest++ = ch;
+ }
+ }
+ eos:
+ *dest = 0;
+ if (dest_len)
+ *dest_len = dest - buf;
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ScanEscapedString
+(
+ // returns the number of valid bytes in 'buf' (NULL term not counted)
+
+ char *buf, // valid destination buffer, maybe source
+ uint buf_size, // size of 'buf'
+ ccp source, // string to scan
+ int len, // length of string. if -1, str is null terminated
+ bool utf8, // true: source and output is UTF-8
+ int quote, // 0:none, -1:auto, >0: quotation char
+ uint *scanned_len // not NULL: Store number of scanned 'source' bytes here
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>3);
+ DASSERT(source);
+
+ char *dest = buf;
+ char *dest_end = dest + buf_size - ( utf8 ? 4 : 1 );
+
+ ccp src = source;
+ ccp src_end = src + ( len < 0 ? strlen(src) : len );
+
+ bool have_quote = quote > 0;
+ if ( quote == -1 && src < src_end && ( *src == '"' || *src == '\'' ))
+ {
+ quote = *src++;
+ have_quote = true;
+ }
+
+ while ( dest < dest_end && src < src_end )
+ {
+ uint code;
+ if ( *src == '\\' )
+ src = ScanEscape(&code,src+1,src_end);
+ else
+ {
+ if (utf8)
+ code = ScanUTF8AnsiCharE(&src,src_end);
+ else
+ code = *src++;
+
+ if ( code == quote && have_quote )
+ break;
+ }
+
+ if (utf8)
+ dest = PrintUTF8Char(dest,code);
+ else
+ *dest++ = code;
+ }
+
+ if (scanned_len)
+ *scanned_len = src - source;
+
+ *dest = 0;
+ return dest - buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u32 CalcCRC32 ( u32 crc, cvp buf, uint size )
+{
+ #if HAVE_PRINT0
+ static bool done = false;
+ if ( !done && TRACE_FILE )
+ {
+ done = true;
+ fprintf(TRACE_FILE,"\nstatic const u32 TableCRC32[0x100] =\n{\n");
+ uint i;
+ for ( i = 0; i < 0x100; i += 8 )
+ fprintf(TRACE_FILE," %#010x,%#010x,%#010x,%#010x, %#010x,%#010x,%#010x,%#010x,\n",
+ TableCRC32[i+0],
+ TableCRC32[i+1],
+ TableCRC32[i+2],
+ TableCRC32[i+3],
+ TableCRC32[i+4],
+ TableCRC32[i+5],
+ TableCRC32[i+6],
+ TableCRC32[i+7] );
+ fprintf(TRACE_FILE,"};\n");
+ }
+ #endif
+
+ crc = ~crc;
+ const u8 *p = buf;
+ while ( size-- )
+ crc = TableCRC32[ (crc ^ *p++) & 0xff ] ^ crc >> 8;
+
+ return ~crc;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u64 DecodeU64
+(
+ ccp source, // string to decode
+ int len, // length of string. if -1, str is null terminated
+ uint base, // number of possible digits (2..36):
+ uint *scanned_len // not NULL: Store number of scanned 'str' bytes here
+)
+{
+ DASSERT(source);
+ DASSERT( base <= 36 );
+
+ u64 num = 0;
+ ccp src = source;
+ ccp end = len < 0 ? 0 : src + len;
+
+ while ( !end || src < end )
+ {
+ u8 digit = TableNumbers[(u8)*src];
+ if ( digit >= base )
+ break;
+ num = num * base + digit;
+ src++;
+ }
+
+ if (scanned_len)
+ *scanned_len = src - source;
+ return num;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp EncodeU64
+(
+ char * buf, // result buffer (size depends on base)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 num, // number to encaode
+ uint base // number of possible digits (2..64):
+)
+{
+ DASSERT( base >= 2 );
+ DASSERT( base <= 64 );
+ if ( base < 2 )
+ base = 2;
+ else if ( base > 64 )
+ base = 64;
+
+ char digits[65], *dest = digits + sizeof(digits);
+ *--dest = 0;
+
+ while ( num )
+ {
+ *--dest = LoDigits[ num % base ];
+ num /= base;
+ }
+
+ if (!buf)
+ buf = GetCircBuf ( buf_size = digits + sizeof(digits)-1 - dest );
+
+ StringCopyS(buf,buf_size,dest);
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// Default tables for DecodeBase64() and EncodeBase64(), if no table is defined.
+
+ccp TableDecode64default = TableDecode64;
+ccp TableEncode64default = TableEncode64;
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint DecodeBase64
+(
+ // returns the number of valid bytes in 'buf'
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 3
+ ccp source, // NULL or string to decode
+ int len, // length of string. if -1, str is null terminated
+ const char decode64[256], // decoding table; if NULL: use TableDecode64default
+ bool allow_white_spaces, // true: skip white spaces
+ uint *scanned_len // not NULL: Store number of scanned 'str' bytes here
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>=3);
+ u8 *dest = (u8*)buf;
+ u8 *dest_end = dest + ( scanned_len ? buf_size / 3 * 3 : buf_size );
+
+ if (!source)
+ source = "";
+ ccp src = source;
+ ccp end = src + ( len < 0 ? strlen(src) : len );
+ int ch1, ch2;
+
+ if (!decode64)
+ decode64 = TableDecode64default;
+
+ while ( dest < dest_end && src < end )
+ {
+ ch1 = decode64[(u8)*src++];
+ if (allow_white_spaces)
+ while ( ch1 == DECODE_SPACE && src < end )
+ ch1 = decode64[(u8)*src++];
+ if ( ch1 < 0 )
+ {
+ src--;
+ break;
+ }
+ if ( src == end )
+ break;
+
+ ch2 = decode64[(u8)*src++];
+ if (allow_white_spaces)
+ while ( ch2 == DECODE_SPACE && src < end )
+ ch2 = decode64[(u8)*src++];
+ if ( ch2 < 0 )
+ {
+ src--;
+ break;
+ }
+
+ *dest++ = ch1 << 2 | ch2 >> 4;
+ if ( src == end || dest == dest_end )
+ break;
+
+ ch1 = decode64[(u8)*src++];
+ if (allow_white_spaces)
+ while ( ch1 == DECODE_SPACE && src < end )
+ ch1 = decode64[(u8)*src++];
+ if ( ch1 < 0 )
+ {
+ src--;
+ break;
+ }
+
+ *dest++ = ch2 << 4 | ch1 >> 2;
+ if ( src == end || dest == dest_end )
+ break;
+
+ ch2 = decode64[(u8)*src++];
+ if (allow_white_spaces)
+ while ( ch2 == DECODE_SPACE && src < end )
+ ch2 = decode64[(u8)*src++];
+ if ( ch2 < 0 )
+ {
+ src--;
+ break;
+ }
+ *dest++ = ch1 << 6 | ch2;
+ }
+
+ if (scanned_len)
+ {
+ while ( src < end && decode64[(u8)*src] == DECODE_FILLER )
+ src++;
+ *scanned_len = src - source;
+ }
+ return dest - (u8*)buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t DecodeBase64Circ
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ ccp source, // NULL or string to decode
+ int len, // length of string. if -1, str is null terminated
+ const char decode64[256] // decoding table; if NULL: use TableDecode64default
+)
+{
+ char buf[CIRC_BUF_MAX_ALLOC+10];
+ mem_t mem;
+ mem.len = DecodeBase64(buf,sizeof(buf),source,len,decode64,false,0);
+ if ( mem.len < CIRC_BUF_MAX_ALLOC )
+ mem.ptr = CopyCircBuf0(buf,mem.len);
+ else
+ {
+ mem.ptr = 0;
+ mem.len = 0;
+ }
+ return mem;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint EncodeBase64
+(
+ // returns the number of scanned bytes of 'source'
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 4
+ const void *source, // NULL or data to encode
+ int source_len, // length of 'source'; if <0: use strlen(source)
+ const char encode64[64+1], // encoding table; if NULL: use TableEncode64default
+ bool use_filler, // use filler for aligned output
+ ccp next_line, // not NULL: use this string as new line sep
+ uint next_line_trigger // >0: use 'next_line' every # input bytes
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>3);
+ DASSERT(source||!source_len);
+
+ noPRINT("EncodeBase64(sz=%u,len=%d,fil=%d)\n",buf_size,source_len,use_filler);
+
+ if (!next_line)
+ next_line_trigger = ~0;
+ else if (!next_line_trigger)
+ next_line_trigger = 19;
+ else
+ next_line_trigger /= 3;
+
+ char *dest = buf;
+ char *dest_end = buf + buf_size - 4;
+
+ if (!source)
+ source = "";
+ const u8 *src = source;
+ const u8 *src_end = src + ( source_len < 0 ? strlen(source) : source_len );
+
+ if (!encode64)
+ encode64 = TableEncode64default;
+
+ uint n_tupel = 0;
+
+ while ( src < src_end && dest < dest_end )
+ {
+ if ( ++n_tupel > next_line_trigger && next_line )
+ {
+ char *next = StringCopyE(dest,(char*)buf+buf_size,next_line);
+ if ( next >= dest_end )
+ break;
+ dest = next;
+ n_tupel = 1;
+ }
+
+ //----- start: no pending bits
+
+ u8 ch1 = *src++;
+ *dest++ = encode64 [ ch1 >> 2 ];
+ ch1 &= 0x03;
+ DASSERT( (ch1 & ~0x03) == 0 );
+ if ( src == src_end )
+ {
+ *dest++ = encode64[ch1<<4];
+ if (use_filler)
+ {
+ *dest++ = encode64[64];
+ *dest++ = encode64[64];
+ }
+ break;
+ }
+
+ //----- 2 pending bits in 'ch1'
+
+ u8 ch2 = *src++;
+ *dest++ = encode64 [ ch1 << 4 | ch2 >> 4 ];
+ ch2 &= 0x0f;
+ DASSERT( (ch2 & ~0x0f) == 0 );
+ if ( src == src_end )
+ {
+ *dest++ = encode64[ch2<<2];
+ if (use_filler)
+ *dest++ = encode64[64];
+ break;
+ }
+
+ //----- 4 pending bits in 'ch2'
+
+ ch1 = *src++;
+ *dest++ = encode64 [ ch2 << 2 | ch1 >> 6 ];
+
+ //----- 6 pending bits in 'ch1'
+
+ *dest++ = encode64[ch1&0x3f];
+ }
+
+ *dest = 0;
+
+ return src - (const u8*)source;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t EncodeBase64Circ
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ const void *source, // NULL or data to encode
+ int source_len, // length of 'source'; if <0: use strlen(source)
+ const char encode64[64+1] // encoding table; if NULL: use TableEncode64default
+)
+{
+ char buf[CIRC_BUF_MAX_ALLOC+10];
+ EncodeBase64(buf,sizeof(buf),source,source_len,encode64,false,0,0);
+ mem_t mem;
+ mem.len = strlen(buf);
+ if ( mem.len < CIRC_BUF_MAX_ALLOC )
+ mem.ptr = CopyCircBuf(buf,mem.len+1);
+ else
+ {
+ mem.ptr = 0;
+ mem.len = 0;
+ }
+ return mem;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint EncodeJSON
+(
+ // returns the number of valid bytes in 'buf'. Result is NULL-terminated.
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >2 and 2 bytes longer than needed
+ const void *source, // NULL or data to encode
+ int source_len // length of 'source'; if <0: use strlen(source)
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>2);
+
+ char *dest = buf;
+ char *dest_end = dest + buf_size - 2;
+
+ if (!source)
+ source = "";
+ ccp str = source;
+ ccp end = str + ( source_len < 0 ? strlen(str) : source_len );
+
+ while ( dest < dest_end && str < end )
+ {
+ const u8 ch = (u8)*str++;
+ switch (ch)
+ {
+ case '"': *dest++ = '\\'; *dest++ = '"'; break;
+ case '\\': *dest++ = '\\'; *dest++ = '\\'; break;
+ case '\b': *dest++ = '\\'; *dest++ = 'b'; break;
+ case '\f': *dest++ = '\\'; *dest++ = 'f'; break;
+ case '\n': *dest++ = '\\'; *dest++ = 'n'; break;
+ case '\r': *dest++ = '\\'; *dest++ = 'r'; break;
+ case '\t': *dest++ = '\\'; *dest++ = 't'; break;
+
+ default:
+ if ( ch >= ' ' )
+ *dest++ = ch;
+ else if ( dest + 5 > dest_end )
+ goto eos;
+ else
+ {
+ *dest++ = '\\';
+ *dest++ = 'u';
+ *dest++ = '0';
+ *dest++ = '0';
+ *dest++ = HiDigits[ch>>4];
+ *dest++ = HiDigits[ch&15];
+ }
+ break;
+ }
+ }
+
+ eos:
+ *dest = 0;
+ return dest - buf;
+}
+
+//-----------------------------------------------------------------------------
+
+mem_t EncodeJSONCirc
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ const void *source, // NULL or data to encode
+ int source_len // length of 'source'; if <0: use strlen(source)
+)
+{
+ char buf[CIRC_BUF_MAX_ALLOC+10];
+ mem_t mem;
+ mem.len = EncodeJSON(buf,sizeof(buf),source,source_len);
+ if ( mem.len < CIRC_BUF_MAX_ALLOC )
+ mem.ptr = CopyCircBuf(buf,mem.len+1);
+ else
+ {
+ mem.ptr = 0;
+ mem.len = 0;
+ }
+ return mem;
+}
+
+//-----------------------------------------------------------------------------
+
+mem_t QuoteJSONCirc
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ const void *source, // NULL or data to encode
+ int source_len, // length of 'source'; if <0: use strlen(source)
+ int null_if // return 'null' without quotes ...
+ // <=0: never
+ // >=1: if source == NULL
+ // >=2: if source_len == 0
+)
+{
+ if ( source_len < 0 )
+ source_len = source ? strlen(source) : 0;
+
+ mem_t mem;
+ if ( null_if > 0 && !source || null_if > 1 && !source_len )
+ {
+ mem.ptr = "null";
+ mem.len = 4;
+ }
+ else
+ {
+ char buf[CIRC_BUF_MAX_ALLOC+10];
+ mem.len = EncodeJSON(buf+1,sizeof(buf)-2,source,source_len) + 2;
+ if ( mem.len < CIRC_BUF_MAX_ALLOC - 2 )
+ {
+ buf[0] = buf[mem.len-1] = '"';
+ buf[mem.len] = 0;
+ mem.ptr = CopyCircBuf(buf,mem.len+1);
+ }
+ else
+ {
+ mem.ptr = 0;
+ mem.len = 0;
+ }
+ }
+ return mem;
+}
+
+//-----------------------------------------------------------------------------
+
+mem_t DecodeJSONCirc
+(
+ // Returns a buffer alloced by GetCircBuf()
+ // with valid pointer and null terminated.
+ // If result is too large (>CIRC_BUF_MAX_ALLOC) then (0,0) is returned.
+
+ ccp source, // NULL or string to decode
+ int source_len // length of 'source'. If -1, str is NULL terminated
+)
+{
+ char buf[CIRC_BUF_MAX_ALLOC+10];
+ mem_t mem;
+ mem.len = DecodeJSON(buf,sizeof(buf),source,source_len,0);
+ if ( mem.len < CIRC_BUF_MAX_ALLOC )
+ mem.ptr = CopyCircBuf(buf,mem.len+1);
+ else
+ {
+ mem.ptr = 0;
+ mem.len = 0;
+ }
+ return mem;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint DecodeByMode
+(
+ // returns the number of valid bytes in 'buf'. Result is NULL-terminated.
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 3
+ ccp source, // string to decode
+ int slen, // length of string. if -1, str is null terminated
+ EncodeMode_t emode // encoding mode
+)
+{
+ DASSERT(buf);
+ DASSERT( buf_size >= 1 );
+ DASSERT( source || !slen );
+ DASSERT( (uint)emode < ENCODE__N );
+
+ uint len = 0;
+ if ( buf_size < 4 )
+ {
+ char temp[4];
+ len = DecodeByMode(temp,sizeof(temp),source,slen,emode);
+ memcpy(buf,temp,buf_size);
+ goto term;
+ }
+
+ DASSERT( buf_size >= 3 );
+ switch (emode)
+ {
+ case ENCODE_STRING:
+ len = ScanEscapedString(buf,buf_size,source,slen,false,0,0);
+ break;
+
+ case ENCODE_UTF8:
+ len = ScanEscapedString(buf,buf_size,source,slen,true,0,0);
+ break;
+
+ case ENCODE_BASE64:
+ len = DecodeBase64(buf,buf_size-1,source,slen,TableDecode64,true,0);
+ break;
+
+ case ENCODE_BASE64URL:
+ case ENCODE_BASE64STAR:
+ len = DecodeBase64(buf,buf_size-1,source,slen,TableDecode64url,true,0);
+ break;
+
+ case ENCODE_BASE64XML:
+ len = DecodeBase64(buf,buf_size-1,source,slen,TableDecode64xml,true,0);
+ break;
+
+ case ENCODE_JSON:
+ len = DecodeJSON(buf,buf_size,source,slen,0);
+ break;
+
+ default:
+ if ( slen < 0 )
+ len = StringCopyS(buf,buf_size,source) - buf;
+ else
+ len = StringCopySM(buf,buf_size,source,slen) - buf;
+ break;
+ }
+
+ //--- terminate with NULL
+
+ term:;
+ if ( len >= buf_size )
+ len = buf_size - 1;
+ buf[len] = 0;
+ return len;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t DecodeByModeMem
+(
+ // Returns the decoded 'source'. Result is NULL-terminated.
+ // It points either to 'buf' or is alloced (on buf==NULL or to less space)
+ // If alloced (mem.ptr!=buf) => call FreeMem(&mem)
+
+ char *buf, // NULL or destination buffer
+ uint buf_size, // size of 'buf'
+ ccp source, // string to decode
+ int slen, // length of string. if -1, str is null terminated
+ EncodeMode_t emode // encoding mode
+)
+{
+ DASSERT( source || !slen );
+ DASSERT( (uint)emode < ENCODE__N );
+
+ if ( slen < 0 )
+ slen = source ? strlen(source) : 0;
+
+ uint need;
+ switch (emode)
+ {
+ case ENCODE_STRING:
+ case ENCODE_UTF8:
+ need = slen + 5;
+ break;
+
+ case ENCODE_BASE64:
+ case ENCODE_BASE64URL:
+ case ENCODE_BASE64STAR:
+ case ENCODE_BASE64XML:
+ need = ( 3*slen ) / 4 + 12;
+ break;
+
+ //case ENCODE_JSON: // [[json]]
+ default:
+ need = slen + 3; // at least 3 bytes
+ break;
+ }
+
+ const bool alloced = !buf || buf_size < need;
+ if (alloced)
+ {
+ buf = MALLOC(need);
+ buf_size = need;
+ }
+
+ mem_t mem;
+ mem.len = DecodeByMode(buf,buf_size,source,slen,emode);
+ if ( alloced && mem.len+10 < buf_size )
+ buf = REALLOC( buf, mem.len+1 );
+ mem.ptr = buf;
+ return mem;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint EncodeByMode
+(
+ // returns the number of valid bytes in 'buf'. Result is NULL-terminated.
+
+ char *buf, // valid destination buffer
+ uint buf_size, // size of 'buf', >= 4
+ ccp source, // string to encode
+ int slen, // length of string. if -1, str is null terminated
+ EncodeMode_t emode // encoding mode
+)
+{
+ DASSERT(buf);
+ DASSERT( buf_size >= 3 );
+ DASSERT( source || !slen );
+ DASSERT( (uint)emode < ENCODE__N );
+
+ noPRINT("EncodeByMode(,%u,%d,%d)\n",buf_size,slen,emode);
+
+ uint len = 0;
+ switch (emode)
+ {
+ case ENCODE_STRING:
+ PrintEscapedString(buf,buf_size,source,slen,0,0,&len);
+ break;
+
+ case ENCODE_UTF8:
+ PrintEscapedString(buf,buf_size,source,slen,CHMD__ALL,0,&len);
+ break;
+
+ case ENCODE_BASE64:
+ len = EncodeBase64(buf,buf_size,source,slen,TableEncode64,true,0,0);
+ len = EncodeBase64FillLen(len);
+ break;
+
+ case ENCODE_BASE64URL:
+ len = EncodeBase64(buf,buf_size,source,slen,TableEncode64url,true,0,0);
+ len = EncodeBase64FillLen(len);
+ break;
+
+ case ENCODE_BASE64STAR:
+ len = EncodeBase64(buf,buf_size,source,slen,TableEncode64star,true,0,0);
+ len = EncodeBase64FillLen(len);
+ break;
+
+ case ENCODE_BASE64XML:
+ len = EncodeBase64(buf,buf_size,source,slen,TableEncode64xml,true,0,0);
+ len = EncodeBase64FillLen(len);
+ break;
+
+ case ENCODE_JSON:
+ len = EncodeJSON(buf,buf_size,source,slen);
+ break;
+
+ default:
+ if ( slen < 0 )
+ len = StringCopyS(buf,buf_size,source) - buf;
+ else
+ len = StringCopySM(buf,buf_size,source,slen) - buf;
+ break;
+ }
+
+ //--- terminate with NULL
+
+ if ( len >= buf_size )
+ len = buf_size - 1;
+ buf[len] = 0;
+ return len;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t EncodeByModeMem
+(
+ // Returns the encoded 'source'. Result is NULL-terminated.
+ // It points either to 'buf' or is alloced (on buf==NULL or to less space)
+ // If alloced (mem.ptr!=buf) => call FreeMem(&mem)
+
+ char *buf, // NULL or destination buffer
+ uint buf_size, // size of 'buf'
+ ccp source, // string to encode
+ int slen, // length of string. if -1, str is null terminated
+ EncodeMode_t emode // encoding mode
+)
+{
+ DASSERT( source || !slen );
+ DASSERT( (uint)emode < ENCODE__N );
+
+ if ( slen < 0 )
+ slen = source ? strlen(source) : 0;
+
+ uint need;
+ switch (emode)
+ {
+ case ENCODE_STRING:
+ case ENCODE_UTF8:
+ need = slen * 4 + 5;
+ break;
+
+ case ENCODE_BASE64:
+ case ENCODE_BASE64URL:
+ case ENCODE_BASE64STAR:
+ case ENCODE_BASE64XML:
+ need = ( 4*slen ) / 3 + 10;
+ break;
+
+ case ENCODE_JSON: // [[json]]
+ {
+ need = slen + 1;
+ ccp ptr = source;
+ ccp end = ptr + slen;
+ while ( ptr < end )
+ if ( (uchar)*ptr++ < ' ' )
+ need += 5;
+ }
+ break;
+
+ default:
+ need = slen + 1;
+ break;
+ }
+
+ const bool alloced = !buf || buf_size < need;
+ if (alloced)
+ {
+ buf = MALLOC(need);
+ buf_size = need;
+ }
+
+ mem_t mem;
+ mem.len = EncodeByMode(buf,buf_size,source,slen,emode);
+ if ( alloced && mem.len+10 < buf_size )
+ buf = REALLOC( buf, mem.len+1 );
+ mem.ptr = buf;
+ return mem;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// time ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+s64 timezone_adjust_sec = -1;
+s64 timezone_adjust_usec = -1;
+int timezone_adjust_isdst = -1;
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupTimezone ( bool force )
+{
+ if ( force || timezone_adjust_sec == -1 )
+ {
+ tzset();
+ timezone_adjust_sec = timezone;
+
+ time_t tim = GetTimeSec(false);
+ struct tm *tm = localtime(&tim);
+ timezone_adjust_isdst = tm->tm_isdst;
+ if (tm->tm_isdst)
+ timezone_adjust_sec -= 3600;
+
+ timezone_adjust_usec = 1000000ll * timezone_adjust_sec;
+ TRACE("TZ: %s,%s, %ld, %d => %lld %lld\n",
+ tzname[0], tzname[1], timezone, daylight,
+ timezone_adjust_sec, timezone_adjust_usec );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct timeval GetTimeOfDay ( bool localtime )
+{
+ static uint last_hour = 0;
+ struct timeval tval;
+ gettimeofday(&tval,NULL);
+ if (localtime)
+ {
+ const uint hour = tval.tv_sec / 3600;
+ if ( last_hour != hour )
+ {
+ last_hour = hour;
+ SetupTimezone(true);
+ }
+ tval.tv_sec -= timezone_adjust_sec;
+ }
+ return tval;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+DayTime_t GetDayTime ( bool localtime )
+{
+ struct timeval tval = GetTimeOfDay(localtime);
+ div_t d;
+
+ DayTime_t dt;
+ dt.time = tval.tv_sec;
+ d = div(tval.tv_sec,86400);
+ dt.day = d.quot;
+ d = div(d.rem,3600);
+ dt.hour = d.quot;
+ d = div(d.rem,60);
+ dt.min = d.quot;
+ dt.sec = d.rem;
+ dt.usec = tval.tv_usec;
+
+ return dt;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u_sec_t GetTimeSec ( bool localtime )
+{
+ struct timeval tval = GetTimeOfDay(localtime);
+ return tval.tv_sec;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u_msec_t GetTimeMSec ( bool localtime )
+{
+ struct timeval tval = GetTimeOfDay(localtime);
+ return (u64)(tval.tv_sec) * 1000ll + tval.tv_usec/1000;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u_usec_t GetTimeUSec ( bool localtime )
+{
+ struct timeval tval = GetTimeOfDay(localtime);
+ return (u64)(tval.tv_sec) * 1000000ll + tval.tv_usec;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// timer ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static time_t time_base = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+u_msec_t GetTimerMSec()
+{
+ struct timeval tval;
+ gettimeofday(&tval,NULL);
+ if (!time_base)
+ time_base = tval.tv_sec;
+
+ return (u_msec_t)( tval.tv_sec - time_base ) * 1000 + tval.tv_usec/1000;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u_usec_t GetTimerUSec()
+{
+ struct timeval tval;
+ gettimeofday(&tval,NULL);
+ if (!time_base)
+ time_base = tval.tv_sec;
+
+ return (u_usec_t)( tval.tv_sec - time_base ) * 1000000 + tval.tv_usec;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimeByFormat
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ time_t time // seconds since epoch -> time()
+)
+{
+ char buf[100];
+ struct tm *tm = localtime(&time);
+ const uint len = strftime(buf,sizeof(buf),format,tm);
+ return CopyCircBuf(buf,len+1);
+}
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimeByFormatUTC
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ time_t time // seconds since epoch -> time()
+)
+{
+ char buf[100];
+ struct tm *tm = gmtime(&time);
+ const uint len = strftime(buf,sizeof(buf),format,tm);
+ return CopyCircBuf(buf,len+1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintUsecByFormat
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ time_t time, // seconds since epoch -> time()
+ uint usec // micro second of second
+)
+{
+ char buf[100];
+ struct tm *tm = localtime(&time);
+ const uint len = strftime(buf,sizeof(buf),format,tm);
+ char *at = strchr(buf,'@');
+ if (at)
+ {
+ char ubuf[8];
+ snprintf(ubuf,sizeof(ubuf),"%06u",usec);
+ ccp src = ubuf;
+ while ( *at == '@' && *src )
+ *at++ = *src++;
+ }
+
+ return CopyCircBuf(buf,len+1);
+}
+
+//-----------------------------------------------------------------------------
+
+ccp PrintUsecByFormatUTC
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ time_t time, // seconds since epoch -> time()
+ uint usec // micro second of second
+)
+{
+ char buf[100];
+ struct tm *tm = gmtime(&time);
+ const uint len = strftime(buf,sizeof(buf),format,tm);
+ char *at = strchr(buf,'@');
+ if (at)
+ {
+ char ubuf[8];
+ snprintf(ubuf,sizeof(ubuf),"%06u",usec);
+ ccp src = ubuf;
+ while ( *at == '@' && *src )
+ *at++ = *src++;
+ }
+
+ return CopyCircBuf(buf,len+1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimevalByFormat
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ const struct timeval *tv // time to print, if NULL use gettimeofday()
+)
+{
+ struct timeval temp;
+ if (!tv)
+ {
+ gettimeofday(&temp,0);
+ tv = &temp;
+ }
+
+ return PrintUsecByFormat(format,tv->tv_sec,tv->tv_usec);
+}
+
+//-----------------------------------------------------------------------------
+
+ccp PrintTimevalByFormatUTC
+(
+ // returns temporary buffer by GetCircBuf();
+
+ ccp format, // format string for strftime()
+ const struct timeval *tv // time to print, if NULL use gettimeofday()
+)
+{
+ struct timeval temp;
+ if (!tv)
+ {
+ gettimeofday(&temp,0);
+ tv = &temp;
+ }
+
+ return PrintUsecByFormatUTC(format,tv->tv_sec,tv->tv_usec);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimeSec
+(
+ char * buf, // result buffer (>19 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ uint sec // seconds (=time) to print
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 20 );
+
+ time_t time = sec;
+ struct tm *tm = localtime(&time);
+ strftime(buf,buf_size,"%F %T",tm);
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimeMSec
+(
+ char * buf, // result buffer (>23 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_msec_t msec, // milliseconds to print
+ uint fraction // number of digits (0-3) to print as fraction
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 24 );
+
+ time_t time = msec / 1000;
+ struct tm *tm = localtime(&time);
+ uint len = strftime(buf,buf_size,"%F %T",tm);
+
+ if ( fraction && len + 4 < buf_size )
+ {
+ len += snprintf(buf+len, buf_size-len, ".%03llu", msec % 1000 );
+ const uint pos = len - 3 + ( fraction < 3 ? fraction : 3 );
+ if ( pos < buf_size )
+ buf[pos] = 0;
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimeUSec
+(
+ char * buf, // result buffer (>26 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u_usec_t usec, // microseconds to print
+ uint fraction // number of digits (0-6) to print as fraction
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 28 );
+
+ time_t time = usec / 1000000;
+ struct tm *tm = localtime(&time);
+ uint len = strftime(buf,buf_size,"%F %T",tm);
+
+ if ( fraction && len + 7 < buf_size )
+ {
+ len += snprintf(buf+len, buf_size-len, ".%06llu", usec % 1000000 );
+ const uint pos = len - 6 + ( fraction < 6 ? fraction : 6 );
+ if ( pos < buf_size )
+ buf[pos] = 0;
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimerMSec
+(
+ char * buf, // result buffer (>12 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 msec, // milliseconds to print
+ uint fraction // number of digits (0-3) to print as fraction
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 16 );
+
+ if (fraction)
+ {
+ const uint len = snprintf(buf,buf_size,"%02d:%02d:%02d.%03d",
+ msec/3600000, msec/60000%60, msec/1000%60, msec%1000 );
+ if ( len > 3 && fraction < 3 )
+ {
+ const uint pos = len - 3 + fraction;
+ if ( pos < buf_size )
+ buf[pos] = 0;
+ }
+ }
+ else
+ snprintf(buf,buf_size,"%02d:%02d:%02d",
+ msec/3600000, msec/60000%60, msec/1000%60 );
+
+ ccp ptr = buf;
+ int colon_counter = 0;
+ while ( *ptr == '0' || *ptr == ':' && !colon_counter++ )
+ ptr++;
+ return *ptr == ':' ? ptr-1 : ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimerUSec
+(
+ char * buf, // result buffer (>16 bytes are good)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s_usec_t usec, // microseconds to print
+ uint fraction // number of digits (0-3) to print as fraction
+)
+{
+ if ( !buf || buf_size < 4 )
+ buf = GetCircBuf( buf_size = 20 );
+
+ const bool minus = usec < 0;
+ if (minus)
+ usec = -usec;
+
+ u32 sec = (u64)usec / 1000000;
+
+ if (fraction)
+ {
+ const uint len = snprintf(buf+1,buf_size-1,"%02d:%02d:%02d.%06lld",
+ sec/3600, sec/60%60, sec%60, (u64)usec%1000000 );
+ if ( len > 6 && fraction < 6 )
+ {
+ const uint pos = len - 6 + fraction;
+ if ( pos < buf_size )
+ buf[pos] = 0;
+ }
+ }
+ else
+ snprintf(buf+1,buf_size-1,"%02d:%02d:%02d",
+ sec/3600, sec/60%60, sec%60 );
+
+ char *ptr = buf+1;
+ while ( *ptr == '0' || *ptr == ':' )
+ ptr++;
+ if ( *ptr == '.' )
+ ptr--;
+ if (minus)
+ *--ptr = '-';
+ return ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimer3
+(
+ char * buf, // result buffer (>3 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 sec, // seconds to print
+ int usec, // 0...999999: usec fraction,
+ // otherwise suppress ms/us output
+ bool aligned // true: print aligned 3 character output
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 4 );
+
+ if ( !sec && usec >= 0 && usec <= 999999 )
+ {
+ if (!usec)
+ StringCopyS(buf,buf_size,aligned?" 0":"0");
+ else if ( usec < 100 )
+ snprintf(buf,buf_size,aligned?"%2uu":"%uu",usec);
+ else if ( usec < 1000 )
+ snprintf(buf,buf_size,".%ui",usec/100);
+ else if ( usec < 100000 )
+ snprintf(buf,buf_size,aligned?"%2ui":"%ui",usec/1000);
+ else
+ snprintf(buf,buf_size,".%us",usec/100000);
+ }
+ else if ( sec < 100 )
+ snprintf(buf,buf_size,aligned?"%2llus":"%llus",sec);
+ else if ( sec < 6000 )
+ snprintf(buf,buf_size,aligned?"%2llum":"%llum",sec/60);
+ else if ( sec < 360000 )
+ snprintf(buf,buf_size,aligned?"%2lluh":"%lluh",sec/3600);
+ else
+ {
+ const u64 days = sec / (24*3600);
+ if ( days < 100 )
+ snprintf(buf,buf_size,aligned?"%2llud":"%llud",days);
+ else if ( days < 700 )
+ snprintf(buf,buf_size,aligned?"%2lluw":"%lluw",days/7);
+ else
+ {
+ const u32 years = days / 365;
+ if ( years < 100 )
+ snprintf(buf,buf_size,aligned?"%2uy":"%uy",years);
+ else
+ buf = "***";
+ }
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimer4
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 sec, // seconds to print
+ int usec, // 0...999999: usec fraction,
+ // otherwise suppress ms/us output
+ bool aligned // true: print aligned 4 character output
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 5 );
+
+ if ( sec < 10 && usec >= 0 && usec <= 999999 )
+ {
+ if (sec)
+ snprintf(buf,buf_size,"%llu.%us",sec,usec/100000);
+ else if (!usec)
+ StringCopyS(buf,buf_size,aligned?" 0":"0");
+ else if ( usec < 1000 )
+ snprintf(buf,buf_size,aligned?"%3uu":"%uu",usec);
+ else if ( usec < 10000 )
+ snprintf(buf,buf_size,"%u.%ui",usec/1000,usec/100%10);
+ else
+ snprintf(buf,buf_size,aligned?"%3ui":"%ui",usec/1000);
+ }
+ else if ( sec < 600 )
+ snprintf(buf,buf_size,aligned?"%3llus":"%llus",sec);
+ else if ( sec < 36000 )
+ snprintf(buf,buf_size,aligned?"%3llum":"%llum",sec/60);
+ else if ( sec < 168*3600 )
+ snprintf(buf,buf_size,aligned?"%3lluh":"%lluh",sec/3600);
+ else
+ {
+ const u64 days = sec / (24*3600);
+ if ( days < 365 )
+ snprintf(buf,buf_size,aligned?"%3llud":"%llud",days);
+ else if ( days < 10*365 )
+ snprintf(buf,buf_size,aligned?"%3lluw":"%lluw",days/7);
+ else
+ {
+ const u32 years = days / 365;
+ if ( years < 1000 )
+ snprintf(buf,buf_size,aligned?"%3uy":"%uy",years);
+ else
+ buf = "****";
+ }
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimer6
+(
+ char * buf, // result buffer (>6 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 sec, // seconds to print
+ int usec, // 0...999999: usec fraction,
+ // otherwise suppress ms/us output
+ bool aligned // true: print aligned 6 character output
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 7 );
+
+ if (aligned)
+ {
+ if ( sec < 10 && usec >= 0 && usec <= 999999 )
+ {
+ usec += 1000000 * sec;
+ if (!usec)
+ StringCopyS(buf,buf_size," 0");
+ else if ( usec < 10000 )
+ snprintf(buf,buf_size,"%4uus",usec);
+ else
+ snprintf(buf,buf_size,"%4ums",usec/1000);
+ }
+ else if ( sec < 100 )
+ {
+ if ( usec >= 0 && usec <= 999999 )
+ snprintf(buf,buf_size,"%2llu.%02us",sec,usec/10000);
+ else
+ snprintf(buf,buf_size,"%5llus",sec);
+ }
+ else if ( sec < 100*60 )
+ snprintf(buf,buf_size,"%2llum%02llus",sec/60,sec%60);
+ else if ( sec < 100*3600 )
+ snprintf(buf,buf_size,"%2lluh%02llum",sec/3600,sec/60%60);
+ else
+ {
+ const u64 days = sec / (24*3600);
+ if ( days < 100 )
+ snprintf(buf,buf_size,"%2llud%02lluh",days,sec/3600%24);
+ else if ( days < 7000 )
+ snprintf(buf,buf_size,"%3lluw%llud",days/7,days%7);
+ else
+ {
+ const u32 years = days / 365;
+ if ( years < 100 )
+ snprintf(buf,buf_size,"%2uy%02lluw",years,days%365/7);
+ else if ( years < 100000 )
+ snprintf(buf,buf_size,"%5uy",years);
+ else
+ buf = "******";
+ }
+ }
+ }
+ else
+ {
+ if ( sec < 10 )
+ {
+ usec += 1000000 * sec;
+ if (!usec)
+ StringCopyS(buf,buf_size,"0");
+ else if ( usec < 10000 )
+ snprintf(buf,buf_size,"%uus",usec);
+ else
+ snprintf(buf,buf_size,"%ums",usec/1000);
+ }
+ else if ( sec < 100 )
+ {
+ if ( usec >= 0 && usec <= 999999 )
+ {
+ const uint val2 = usec/10000;
+ if (val2)
+ snprintf(buf,buf_size,"%llu.%02us",sec,val2);
+ else
+ snprintf(buf,buf_size,"%llus",sec);
+ }
+ else
+ snprintf(buf,buf_size,"%llus",sec);
+ }
+ else if ( sec < 100*60 )
+ {
+ const uint val2 = sec%60;
+ if (val2)
+ snprintf(buf,buf_size,"%llum%02us",sec/60,val2);
+ else
+ snprintf(buf,buf_size,"%llum",sec/60);
+ }
+ else if ( sec < 100*3600 )
+ {
+ const uint val2 = sec/60%60;
+ if (val2)
+ snprintf(buf,buf_size,"%lluh%02um",sec/3600,val2);
+ else
+ snprintf(buf,buf_size,"%lluh",sec/3600);
+ }
+ else
+ {
+ const u64 days = sec / (24*3600);
+ if ( days < 100 )
+ {
+ const uint val2 = sec/3600%24;
+ if (val2)
+ snprintf(buf,buf_size,"%llud%02uh",days,val2);
+ else
+ snprintf(buf,buf_size,"%llud",days);
+ }
+ else if ( days < 7000 )
+ {
+ const uint val2 = days%7;
+ if (val2)
+ snprintf(buf,buf_size,"%lluw%ud",days/7,val2);
+ else
+ snprintf(buf,buf_size,"%lluw",days/7);
+ }
+ else
+ {
+ const u32 years = days / 365;
+ if ( years < 100 )
+ {
+ const uint val2 = days%365/7;
+ if (val2)
+ snprintf(buf,buf_size,"%uy%02uw",years,val2);
+ else
+ snprintf(buf,buf_size,"%uy",years);
+ }
+ else if ( years < 100000 )
+ snprintf(buf,buf_size,"%uy",years);
+ else
+ buf = "******";
+ }
+ }
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimerUSec4s
+(
+ char * buf, // result buffer (>4 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s_usec_t usec, // microseconds to print
+ sizeform_mode_t mode // support of DC_SFORM_ALIGN, DC_SFORM_PLUS
+)
+{
+ if ( !buf || buf_size < 2 )
+ buf = GetCircBuf( buf_size = 5 );
+
+ if (!usec)
+ StringCopyS(buf,buf_size, mode & DC_SFORM_ALIGN ? " 0" : "0" );
+ else
+ {
+ char sign;
+ if ( usec < 0 )
+ {
+ usec = -usec;
+ sign = '-';
+ }
+ else
+ sign = mode & DC_SFORM_PLUS ? '+' : ' ';
+
+ PrintTimerUSec3(buf+1,buf_size-1,usec,mode&DC_SFORM_ALIGN);
+ *buf = ' ';
+
+ char *ptr = buf+1;
+ while ( *ptr == ' ' )
+ ptr++;
+ ptr[-1] = sign;
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintTimerUSec7s
+(
+ char * buf, // result buffer (>7 bytes)
+ // If NULL, a local circulary static buffer is used
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s_usec_t usec, // microseconds to print
+ sizeform_mode_t mode // support of DC_SFORM_ALIGN, DC_SFORM_PLUS
+)
+{
+ if ( !buf || buf_size < 2 )
+ buf = GetCircBuf( buf_size = 8 );
+
+ if (!usec)
+ StringCopyS(buf,buf_size, mode & DC_SFORM_ALIGN ? " 0" : "0" );
+ else
+ {
+ char sign;
+ if ( usec < 0 )
+ {
+ usec = -usec;
+ sign = '-';
+ }
+ else
+ sign = mode & DC_SFORM_PLUS ? '+' : ' ';
+
+ PrintTimerUSec6(buf+1,buf_size-1,usec, mode & DC_SFORM_ALIGN );
+ *buf = ' ';
+
+ char *ptr = buf+1;
+ while ( *ptr == ' ' )
+ ptr++;
+ ptr[-1] = sign;
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#if 0
+u64 GetXTime ( xtime_t * xtime )
+{
+ struct timeval tval;
+ gettimeofday(&tval,0);
+ if (!time_base)
+ time_base = tval.tv_sec;
+ if (!rel_usec_base)
+ rel_usec_base = (u64)( tval.tv_sec - time_base ) * 1000000 + tval.tv_usec;
+
+ const ull total_usec = 1000000ull * tval.tv_sec + tval.tv_usec;
+
+ if (xtime)
+ {
+ xtime->time = tval.tv_sec;
+ xtime->usec = tval.tv_usec;
+ xtime->total_usec = total_usec;
+ xtime->rel_usec = total_usec - rel_usec_base;
+ }
+ return total_usec;
+}
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan date & time ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanInterval
+(
+ time_t *res_time, // not NULL: store seconds
+ u32 *res_usec, // not NULL: store micro seconds of second
+ ccp source // source text
+)
+{
+ uint days = 0, sec = 0, usec = 0;
+ bool day_possible = true;
+ uint colon_count = 0;
+ char *src = source ? (char*)source : "";
+
+ while ( *src > 0 && *src <= ' ' )
+ src++;
+
+ for(;;)
+ {
+ if ( *src < '0' || *src >='9' )
+ break;
+ ulong num = strtoul(src,&src,10);
+ if ( *src == 'd' && day_possible )
+ {
+ src++;
+ days = num;
+ day_possible = false;
+ }
+ else if ( *src == ':' && colon_count < 2 )
+ {
+ src++;
+ colon_count++;
+ sec = ( sec + num ) * 60;
+ }
+ else
+ {
+ sec += num;
+ if ( *src == '.' )
+ {
+ src++;
+ uint i;
+ for ( i = 0; i < 6; i++ )
+ {
+ usec *= 10;
+ if ( *src >= '0' && *src <= '9' )
+ usec += *src++ - '0';
+ }
+ while ( *src >= '0' && *src <= '9' )
+ src++;
+ }
+ else if ( colon_count == 1 )
+ sec *= 60;
+ }
+ }
+
+ if (res_time)
+ *res_time = days * 86400 + sec;
+ if (res_usec)
+ *res_usec = usec;
+ return src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanInterval64
+(
+ u64 *res_usec, // not NULL: store total micro seconds
+ ccp source // source text
+)
+{
+ time_t time;
+ u32 usec;
+ char *src = ScanInterval(&time,&usec,source);
+ if (res_usec)
+ *res_usec = time * 1000000ull + usec;
+ return src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanDateTime
+(
+ time_t *res_time, // not NULL: store seconds here
+ u32 *res_usec, // not NULL: store useconds of second here
+ ccp source, // source text
+ bool allow_delta // true: allow +|- interval
+)
+{
+ //--- scan date
+
+ time_t total_time = 0;
+ int total_usec = 0;
+ ccp src = source ? source : "";
+ while ( *src > 0 && *src <= ' ' )
+ src++;
+
+ struct tm tm;
+ memset(&tm,0,sizeof(tm));
+ src = strptime(src,"%Y-%m-%d",&tm);
+
+ noPRINT(" %4u-%02u-%02u %2u:%02u:%02u |%s|\n",
+ tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, src );
+
+ if (src)
+ total_time = mktime(&tm);
+ else
+ src = source ? source : "";
+ noPRINT_TIME(total_time,"DATE");
+
+
+ //--- scan time
+
+ time_t time;
+ u32 usec;
+ src = ScanInterval(&time,&usec,src);
+ total_time += time;
+ total_usec += usec;
+ noPRINT_TIME(total_time,"TIME");
+
+
+ //--- scan timezone
+
+ ccp src2 = src;
+ while ( *src2 > 0 && *src2 <= ' ' )
+ src2++;
+ if ( ( src2[0] == '-' || src2[0] == '+' )
+ && src2[1] >= '0' && src2[1] <= '9'
+ && src2[2] >= '0' && src2[2] <= '9'
+ && src2[3] >= '0' && src2[3] <= '9'
+ && src2[4] >= '0' && src2[4] <= '9'
+ && ( src2[5] < '0' || src2[5] > '9' ))
+ {
+ int delta = 36000 * src2[1]
+ + 3600 * src2[2]
+ + 600 * src2[3]
+ + 60 * src2[4]
+ - 40260 * '0';
+ if ( src2[0] == '-' )
+ delta = -delta;
+
+ noPRINT("GOT TIMEZONE: %.5s => %d\n",src2,delta);
+ total_time -= delta + timezone;
+ src = src2 + 5;
+ }
+ else
+ {
+ PRINT("timezone=%ld\n",timezone);
+ struct tm *tm = localtime(&total_time);
+ if (tm->tm_isdst)
+ total_time -= 3600;
+ }
+ noPRINT_TIME(total_time,"ZONE");
+
+
+ //--- scan delta
+
+ if (allow_delta)
+ {
+ ccp src2 = src;
+ while ( *src2 > 0 && *src2 <= ' ' )
+ src2++;
+ bool minus = *src2 == '-';
+ if ( minus || *src2 == '+' )
+ {
+ src = ScanInterval(&time,&usec,src2+1);
+ if (minus)
+ {
+ total_time -= time;
+ total_usec -= usec;
+ }
+ else
+ {
+ total_time += time;
+ total_usec += usec;
+ }
+ }
+ }
+
+ if ( total_usec < 0 )
+ {
+ total_time -= ( 999999 - total_usec ) / 1000000;
+ total_usec = 1000000 - (-total_usec) % 1000000;
+ }
+ else if ( total_usec >= 1000000 )
+ {
+ total_time += total_usec / 1000000;
+ total_usec %= 1000000;
+ }
+
+ if (res_time)
+ *res_time = total_time;
+ if (res_usec)
+ *res_usec = total_usec;
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanDateTime64
+(
+ u64 *res_usec, // not NULL: store total micro seconds
+ ccp source, // source text
+ bool allow_delta // true: allow +|- interval
+)
+{
+ time_t time;
+ u32 usec;
+ char *src = ScanDateTime(&time,&usec,source,allow_delta);
+ if (res_usec)
+ *res_usec = time * 1000000ull + usec;
+ return src;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Since 2001 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// hour support
+
+ccp hour2text
+(
+ int hour, // hour to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use '-'
+ ccp sep_dt // NULL or separator of between date and time
+ // if NULL: use ' '
+)
+{
+ time_t tim = hour2time(hour);
+ struct tm tm;
+ gmtime_r(&tim,&tm);
+
+ if (!sep_date)
+ sep_date = "-";
+
+ if (!sep_dt)
+ sep_dt = " ";
+
+ return fw >= 13
+ ? PrintCircBuf("%04d%s%02u%s%02u%s%02u",
+ tm.tm_year + 1900, sep_date,
+ tm.tm_mon + 1, sep_date,
+ tm.tm_mday, sep_dt,
+ tm.tm_hour
+ )
+ : fw >= 11
+ ? PrintCircBuf("%02d%s%02u%s%02u%s%02u",
+ tm.tm_year % 100, sep_date,
+ tm.tm_mon + 1, sep_date,
+ tm.tm_mday, sep_dt,
+ tm.tm_hour
+ )
+ : fw >= 10
+ ? PrintCircBuf("%2u.%s%02u:xx",
+ tm.tm_mday, sep_dt,
+ tm.tm_hour
+ )
+ : PrintCircBuf("%02u%s%02u%s%02u",
+ tm.tm_mon + 1, sep_date,
+ tm.tm_mday, sep_dt,
+ tm.tm_hour
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////
+// day support
+
+ccp day2text
+(
+ int day, // day to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use '-'
+ ccp sep_dt // ignored
+)
+{
+ time_t tim = day2time(day);
+ struct tm tm;
+ gmtime_r(&tim,&tm);
+
+ if (!sep_date)
+ sep_date = "-";
+
+ return fw >= 10
+ ? PrintCircBuf("%04d%s%02u%s%02u",
+ tm.tm_year + 1900, sep_date,
+ tm.tm_mon + 1, sep_date,
+ tm.tm_mday )
+ : PrintCircBuf("%02d%s%02u%s%02u",
+ tm.tm_year % 100, sep_date,
+ tm.tm_mon + 1, sep_date,
+ tm.tm_mday );
+}
+
+///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////
+// week support
+
+ccp week2text
+(
+ int week, // week to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use 'w'
+ ccp sep_dt // ignored
+)
+{
+ time_t tim = week2time(week) + 4*86400;
+ struct tm tm;
+ gmtime_r(&tim,&tm);
+
+ return PrintCircBuf("%04d%s%02u",
+ tm.tm_year + 1900,
+ sep_date ? sep_date : "w",
+ tm.tm_yday / 7 + 1 );
+}
+
+///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////
+// month support
+
+int time2month ( time_t tim )
+{
+ struct tm tm;
+ gmtime_r(&tim,&tm);
+ return ( tm.tm_mon + 12 * tm.tm_year ) - 101*12;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+time_t month2time ( int month )
+{
+ SetupTimezone(false);
+ struct tm tm = {0};
+
+ tm.tm_isdst = -1;
+ tm.tm_year = month / 12 + 101;
+ tm.tm_mon = month % 12;
+ tm.tm_mday = 1;
+
+ int delta = 43200-timezone_adjust_sec;
+ if ( delta < 0 )
+ {
+ delta += 86400;
+ tm.tm_mday++;
+ }
+
+ tm.tm_hour = delta / 3600;
+ tm.tm_min = delta / 60 % 60;
+ return mktime(&tm) / 86400 * 86400;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+ccp month2text
+(
+ int month, // month to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use '-'
+ ccp sep_dt // ignored
+)
+{
+ month += 2001*12;
+ return PrintCircBuf("%4d%s%02u",
+ month/12,
+ sep_date ? sep_date : "-",
+ month%12 + 1 );
+}
+
+///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////
+// quarter support
+
+int time2quarter ( time_t tim )
+{
+ struct tm tm;
+ gmtime_r(&tim,&tm);
+ return ( tm.tm_mon/3 + 4 * tm.tm_year ) - 101*4;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+time_t quarter2time ( int quarter )
+{
+ SetupTimezone(false);
+ struct tm tm = {0};
+
+ tm.tm_isdst = -1;
+ tm.tm_year = quarter / 4 + 101;
+ tm.tm_mon = quarter % 4 * 3;
+ tm.tm_mday = 1;
+
+ int delta = 43200-timezone_adjust_sec;
+ if ( delta < 0 )
+ {
+ delta += 86400;
+ tm.tm_mday++;
+ }
+
+ tm.tm_hour = delta / 3600;
+ tm.tm_min = delta / 60 % 60;
+ return mktime(&tm) / 86400 * 86400;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+ccp quarter2text
+(
+ int quarter, // quarter to print
+ int fw, // wanted field width assuming separators are 1 char
+ ccp sep_date, // NULL or separator within date
+ // if NULL: use 'q'
+ ccp sep_dt // ignored
+)
+{
+ quarter += 2001*4;
+ return PrintCircBuf("%4d%s%u",
+ quarter/4,
+ sep_date ? sep_date : "q",
+ quarter%4 + 1 );
+}
+
+///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////
+// year support
+
+int time2year ( time_t tim )
+{
+ struct tm tm;
+ gmtime_r(&tim,&tm);
+ return tm.tm_year + 1900;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+time_t year2time ( int year )
+{
+ SetupTimezone(false);
+ struct tm tm = {0};
+
+ tm.tm_isdst = -1;
+ tm.tm_year = year - 1900;
+ tm.tm_mday = 1;
+
+ int delta = 43200-timezone_adjust_sec;
+ if ( delta < 0 )
+ {
+ delta += 86400;
+ tm.tm_mday++;
+ }
+
+ tm.tm_hour = delta / 3600;
+ tm.tm_min = delta / 60 % 60;
+ return mktime(&tm) / 86400 * 86400;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// urandom support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int urandom_available = 0; // <0:not, =0:not tested, >0:ok
+bool use_urandom_for_myrandom = true;
+
+uint ReadFromUrandom ( void *dest, uint size )
+{
+ static int fd = -1;
+
+ if ( fd == -1 )
+ {
+ if ( urandom_available < 0 )
+ return 0;
+ #ifdef O_CLOEXEC
+ fd = open("/dev/urandom",O_CLOEXEC|O_NONBLOCK);
+ #else
+ fd = open("/dev/urandom",O_NONBLOCK);
+ #endif
+ if ( fd == -1 )
+ {
+ urandom_available = -1;
+ return 0;
+ }
+ #ifndef O_CLOEXEC
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+ #endif
+ urandom_available = 1;
+ }
+
+ u8 *buf = dest;
+ while ( size > 0 )
+ {
+ ssize_t stat = read(fd,buf,size);
+ if ( stat < 0 )
+ {
+ urandom_available = -1;
+ close(fd);
+ return 0;
+ }
+ buf += stat;
+ size -= stat;
+ }
+
+ return buf - (u8*)dest;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// random numbers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+//------------------------------------------------------------------
+// Algorithm: The Art of Computer Programming, part II, section 3.6
+// by Donald E. Knuth
+//------------------------------------------------------------------
+
+const u32 RANDOM32_C_ADD = 2 * 197731421; // 2 Primzahlen
+const u32 RANDOM32_COUNT_BASE = 4294967; // Primzahl == ~UINT_MAX32/1000;
+
+static int random32_a_index = -1; // Index in die a-Tabelle
+static u32 random32_count = 1; // Abwärts-Zähler bis zum Wechsel von a,c
+static u32 random32_a,
+ random32_c,
+ random32_X; // Die letzten Werte
+
+static u32 random32_a_tab[] = // Init-Tabelle
+{
+ 0xbb40e62d, 0x3dc8f2f5, 0xdc024635, 0x7a5b6c8d,
+ 0x583feb95, 0x91e06dbd, 0xa7ec03f5, 0
+};
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u32 MyRandom ( u32 max )
+{
+ if (use_urandom_for_myrandom)
+ {
+ u32 res32;
+ if (ReadFromUrandom(&res32,sizeof(res32)))
+ {
+ if (!max)
+ return res32;
+
+ u64 res64 = (u64)max * res32;
+ return res64 >> 32;
+ }
+ use_urandom_for_myrandom = false;
+ }
+
+ if (!--random32_count)
+ {
+ // Neue Berechnung von random32_a und random32_c fällig
+
+ if ( random32_a_index < 0 )
+ {
+ // allererste Initialisierung auf Zeitbasis
+
+ MySeedByTime();
+ }
+ else
+ {
+ random32_c += RANDOM32_C_ADD;
+ random32_a = random32_a_tab[++random32_a_index];
+ if (!random32_a)
+ {
+ random32_a_index = 0;
+ random32_a = random32_a_tab[0];
+ }
+
+ random32_count = RANDOM32_COUNT_BASE;
+ }
+ }
+
+ // Jetzt erfolgt die eigentliche Berechnung
+
+ random32_X = random32_a * random32_X + random32_c;
+
+ if (!max)
+ return random32_X;
+
+ u64 res = (u64)max * random32_X;
+ return res >> 32;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 MySeed ( u64 base )
+{
+ uint a_tab_len = 0;
+ while (random32_a_tab[a_tab_len])
+ a_tab_len++;
+ const u32 base32 = base / a_tab_len;
+
+ random32_a_index = (int)( base % a_tab_len );
+ random32_a = random32_a_tab[random32_a_index];
+ random32_c = ( base32 & 15 ) * RANDOM32_C_ADD + 1;
+ random32_X = base32 ^ ( base >> 32 );
+ random32_count = RANDOM32_COUNT_BASE;
+
+ return base;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 MySeedByTime()
+{
+ #if WIN_SZS_LIB
+
+ u64 time;
+ QueryPerformanceCounter((LARGE_INTEGER*)&time);
+ return MySeed(time);
+
+ #else
+
+ struct timeval tval;
+ gettimeofday(&tval,NULL);
+ return MySeed( tval.tv_sec ^ tval.tv_usec );
+
+ #endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MyRandomFill ( void * buf, size_t size )
+{
+ size_t xsize = size / sizeof(u32);
+ if (xsize)
+ {
+ size -= xsize * sizeof(u32);
+ u32 * ptr = buf;
+ while ( xsize-- > 0 )
+ *ptr++ = MyRandom(0);
+ buf = ptr;
+ }
+
+ u8 * ptr = buf;
+ while ( size-- > 0 )
+ *ptr++ = MyRandom(0);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// UUID ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void CreateUUID ( uuid_buf_t dest )
+{
+ DASSERT(dest);
+ const uint stat = ReadFromUrandom(dest,sizeof(uuid_buf_t));
+ if (!stat)
+ MyRandomFill(dest,sizeof(uuid_buf_t));
+
+ dest[6] = dest[6] & 0x0f | 0x40;
+ dest[8] = dest[8] & 0x3f | 0x80;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CreateTextUUID ( char *buf, uint bufsize )
+{
+ uuid_buf_t uuid;
+ CreateUUID(uuid);
+ return PrintUUID(buf,bufsize,uuid);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintUUID ( char *buf, uint bufsize, uuid_buf_t uuid )
+{
+ return snprintf(buf,bufsize,
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ uuid[0],uuid[1],uuid[2],uuid[3],
+ uuid[4],uuid[5], uuid[6],uuid[7], uuid[8],uuid[9],
+ uuid[10],uuid[11],uuid[12],uuid[13],uuid[14],uuid[15] );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanUUID ( uuid_buf_t uuid, ccp source )
+{
+ memset(uuid,0,sizeof(uuid_buf_t));
+
+ uint i;
+ ccp src = source;
+ for ( i = 0; i < sizeof(uuid_buf_t); i++ )
+ {
+ if ( *src == '-' )
+ src++;
+
+ uint num;
+ ccp end = ScanNumber(&num,src,0,16,2);
+ if ( end != src+2 )
+ return (char*)source;
+ uuid[i] = num;
+ src = end;
+ }
+ return (char*)src;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// gcd(), lcm() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint gcd ( uint n1, uint n2 ) // greatest common divisor, german: ggt
+{
+ if ( !n1 || !n2 )
+ return 0;
+
+ if ( n1 > n2 )
+ {
+ uint temp = n1;
+ n1 = n2;
+ n2 = temp;
+ }
+
+ while ( n1 != 1 )
+ {
+ DASSERT ( n1 > 1 );
+ DASSERT ( n2 >= n1 );
+
+ uint temp = n2 % n1;
+ if ( !temp )
+ break;
+ n2 = n1;
+ n1 = temp;
+ }
+ return n1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 gcd32 ( u32 n1, u32 n2 ) // greatest common divisor, german: ggt
+{
+ if ( !n1 || !n2 )
+ return 0;
+
+ if ( n1 > n2 )
+ {
+ u32 temp = n1;
+ n1 = n2;
+ n2 = temp;
+ }
+
+ while ( n1 != 1 )
+ {
+ DASSERT ( n1 > 1 );
+ DASSERT ( n2 > n1 );
+
+ u32 temp = n2 % n1;
+ if ( !temp )
+ break;
+ n2 = n1;
+ n1 = temp;
+ }
+ return n1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 gcd64 ( u64 n1, u64 n2 ) // greatest common divisor, german: ggt
+{
+ if ( !n1 || !n2 )
+ return 0;
+
+ if ( n1 > n2 )
+ {
+ u64 temp = n1;
+ n1 = n2;
+ n2 = temp;
+ }
+
+ while ( n1 != 1 )
+ {
+ DASSERT ( n1 > 1 );
+ DASSERT ( n2 > n1 );
+
+ u64 temp = n2 % n1;
+ if ( !temp )
+ break;
+ n2 = n1;
+ n1 = temp;
+ }
+ return n1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint lcm ( uint n1, uint n2 ) // lowest common multiple, german: kgv
+{
+ uint g = gcd(n1,n2);
+ if ( g > 0 )
+ {
+ n1 /= g;
+ g = n1 * n2;
+ if ( g < n1 || g < n2 ) // overflow?
+ g = 0;
+ }
+ return g;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 lcm32 ( u32 n1, u32 n2 ) // lowest common multiple, german: kgv
+{
+ u32 g = gcd32(n1,n2);
+ if ( g > 0 )
+ {
+ n1 /= g;
+ g = n1 * n2;
+ if ( g < n1 || g < n2 ) // overflow?
+ g = 0;
+ }
+ return g;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 lcm64 ( u64 n1, u64 n2 ) // lowest common multiple, german: kgv
+{
+ u64 g = gcd64(n1,n2);
+ if ( g > 0 )
+ {
+ n1 /= g;
+ g = n1 * n2;
+ if ( g < n1 || g < n2 ) // overflow?
+ g = 0;
+ }
+ return g;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan number ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define NN NUMBER_NULL
+#define NC NUMBER_CONTROL
+#define NL NUMBER_LINE
+#define NS NUMBER_SPACE
+#define NT NUMBER_TIE
+#define NP NUMBER_SEPARATE
+#define NX NUMBER_OTHER
+
+const char DigitTable[] =
+{
+ NN, NC, NC, NC, NC, NC, NC, NC, NC, NS, NL, NC, NS, NL, NC, NC,
+ NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC,
+ NS, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NP, NT, NT, NX, // !"#$%&' ()*+,-./
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, NT, NP, NX, NX, NX, NX, // 01234567 89:;<=>?
+
+ NX, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // @ABCDEFG HIJKLMNO
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, NX, NX, NX, NX, NX, // PQRSTUVW XYZ[\]^_
+ NX, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // `abcdefg hijklmno
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, NX, NX, NX, NX, NX, // pqrstuvw xyz{|}~
+
+ NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX,
+ NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX,
+ NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX,
+ NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX,
+
+ NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX,
+ NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX,
+ NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX,
+ NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX, NX,
+};
+
+#undef NN
+#undef NC
+#undef NL
+#undef NS
+#undef NT
+#undef NP
+#undef NX
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 ScanDigits
+(
+ // same as ScanNumber(), but other interface
+ // returns the scanned number
+
+ ccp *source, // pointer to source string, modified
+ ccp end_source, // NULL or end of 'source'
+ uint intbase, // integer base, 2..36
+ int maxchar, // max number of digits to read
+ uint *count // not NULL: store number of scanned digits here
+)
+{
+ DASSERT(source);
+ DASSERT( intbase >= 2 && intbase <= 36 );
+
+ ccp src = *source;
+ u64 num = 0;
+ while ( maxchar-- && ( !end_source || src < end_source ) )
+ {
+ const u8 digit = (u8)DigitTable[(u8)*src];
+ if ( digit >= intbase )
+ break;
+ num = num * intbase + digit;
+ src++;
+ }
+ if (count)
+ *count = src - *source;
+ *source = src;
+ return num;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanNumber
+(
+ // same as ScanDigits(), but other interface
+ // returns a pointer to the first not used character
+
+ uint *dest_num, // store result here, never NULL
+ ccp src, // pointer to source string, modified
+ ccp src_end, // NULL or end of 'src'
+ uint intbase, // integer base, 2..36
+ int maxchar // max number of digits to read
+)
+{
+ DASSERT(dest_num);
+ uint num = 0;
+ if (src)
+ {
+ while ( maxchar-- && ( !src_end || src < src_end ) )
+ {
+ const uint n = (uint)DigitTable[*(uchar*)src];
+ if ( n >= (uint)intbase )
+ break;
+ num = num * intbase + n;
+ src++;
+ }
+ }
+ *dest_num = num;
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanEscape
+(
+ // returns a pointer to the first not used character
+
+ uint *dest_code, // store result here, never NULL
+ ccp src, // pointer to source string (behind escape char)
+ ccp src_end // NULL or end of 'src'
+)
+{
+ DASSERT(dest_code);
+ DASSERT(src);
+ if ( !src || ( src_end ? src >= src_end : !*src ) )
+ {
+ *dest_code = 0;
+ return (char*)src;
+ }
+
+ uint code = 0;
+ switch (*src++)
+ {
+ case '\\': code = '\\'; break;
+ case 'a': code = '\a'; break;
+ case 'b': code = '\b'; break;
+ case 'E':
+ case 'e': code = '\e'; break;
+ case 'f': code = '\f'; break;
+ case 'n': code = '\n'; break;
+ case 'r': code = '\r'; break;
+ case 't': code = '\t'; break;
+ case 'v': code = '\v'; break;
+
+ case 'x':
+ src = ScanNumber(&code,src,src_end,16,2);
+ break;
+
+ case 'u':
+ src = ScanNumber(&code,src,src_end,16,4);
+ break;
+
+ case 'U':
+ src = ScanNumber(&code,src,src_end,16,8);
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ src = ScanNumber(&code,src-1,src_end,8,3);
+ break;
+
+ default:
+ code = src[-1];
+ break;
+ }
+
+ *dest_code = code;
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ScanHexString
+(
+ // return the number of written bytes
+
+ void *buf, // write scanned data here
+ uint buf_size, // size of buf
+ ccp *source, // pointer to source string, modified
+ ccp end_source, // NULL or end of 'source'
+ bool allow_tie // allow chars ' .:-' and TAB,VT as byte separator
+)
+{
+ DASSERT(buf);
+ DASSERT(source);
+
+ u8 *dest = buf;
+ uint count = 0;
+ while ( count < buf_size )
+ {
+ ccp src = *source;
+ if (allow_tie)
+ {
+ while ( !end_source || src < end_source )
+ {
+ const char res = DigitTable[(u8)*src];
+ if ( res != NUMBER_SPACE && res != NUMBER_TIE )
+ break;
+ src++;
+ }
+ *source = src;
+ }
+
+ while ( !end_source || src < end_source )
+ {
+ const char res = DigitTable[(u8)*src];
+ if ( res < 0 || res >= 16 )
+ break;
+ src++;
+ }
+
+ uint n_digits = src - *source;
+ if (!n_digits)
+ break;
+
+ while ( n_digits > 0 )
+ {
+ uint scanned;
+ uint num = ScanDigits( source, end_source, 16,
+ n_digits & 1 ? 1 : 2, &scanned );
+ if (!scanned)
+ return count;
+
+ n_digits -= scanned;
+ *dest++ = num;
+ count++;
+ }
+ }
+
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanS32
+(
+ // return 'source' on error
+
+ s32 *res_num, // not NULL: store result (only on success)
+ ccp source, // NULL or source text
+ uint default_base // base for numbers without '0x' prefix
+ // 0: C like with octal support
+ // 10: standard value for decimal numbers
+ // 16: standard value for hex numbers
+)
+{
+ if (!source)
+ return 0;
+
+ ccp src = source;
+ while ( *src > 0 && *src <= ' ' )
+ src++;
+
+ const bool minus = *src == '-';
+ if ( minus || *src == '+' )
+ {
+ src++;
+ while ( *src > 0 && *src <= ' ' )
+ src++;
+ }
+ const uint base = src[0] == '0' && ( src[1] == 'x' || src[1] == 'X' )
+ ? 16 : default_base;
+ char *end;
+ const s32 num = strtoul( src, &end, base );
+ if ( (ccp)end > src )
+ {
+ if (res_num)
+ *res_num = minus ? -num : num;
+ return end;
+ }
+
+ return (char*)source;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanS64
+(
+ // return 'source' on error
+
+ s64 *res_num, // not NULL: store result (only on success)
+ ccp source, // NULL or source text
+ uint default_base // base for numbers without '0x' prefix
+ // 0: C like with octal support
+ // 10: standard value for decimal numbers
+ // 16: standard value for hex numbers
+)
+{
+ if (!source)
+ return 0;
+
+ ccp src = source;
+ while ( *src > 0 && *src <= ' ' )
+ src++;
+
+ const bool minus = *src == '-';
+ if ( minus || *src == '+' )
+ {
+ src++;
+ while ( *src > 0 && *src <= ' ' )
+ src++;
+ }
+ const uint base = src[0] == '0' && ( src[1] == 'x' || src[1] == 'X' )
+ ? 16 : default_base;
+ char *end;
+ const s64 num = strtoull( src, &end, base );
+ if ( (ccp)end > src )
+ {
+ if (res_num)
+ *res_num = minus ? -num : num;
+ return end;
+ }
+
+ return (char*)source;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// strto*() replacements, base ion Scan*() with better base support
+
+long int str2l ( const char *nptr, char **endptr, int base )
+{
+ s32 res = 0;
+ char *end = ScanS32(&res,nptr,base);
+ if (endptr)
+ *endptr = end;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+long long int str2ll ( const char *nptr, char **endptr, int base )
+{
+ s64 res = 0;
+ char *end = ScanS64(&res,nptr,base);
+ if (endptr)
+ *endptr = end;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned long int str2ul ( const char *nptr, char **endptr, int base )
+{
+ u32 res = 0;
+ char *end = ScanU32(&res,nptr,base);
+ if (endptr)
+ *endptr = end;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned long long int str2ull ( const char *nptr, char **endptr, int base )
+{
+ u64 res = 0;
+ char *end = ScanU64(&res,nptr,base);
+ if (endptr)
+ *endptr = end;
+ return res;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// round float/double (zero LSB) ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+float RoundF2bytes ( float f )
+{
+ union { float f; u32 u; } x;
+ x.f = f;
+ if ( x.u & 0x8000 )
+ {
+ x.u &= 0xffff0000;
+ int exp;
+ const float frac = frexpf(x.f,&exp);
+ x.f = ldexpf(frac+0.00585938,exp);
+ }
+
+ x.u &= 0xffff0000;
+ return x.f;
+}
+
+//-----------------------------------------------------------------------------
+
+float RoundF3bytes ( float f )
+{
+ union { float f; u32 u; } x;
+ x.f = f;
+ if ( x.u & 0x80 )
+ {
+ x.u &= 0xffffff00;
+ int exp;
+ const float frac = frexpf(x.f,&exp);
+ x.f = ldexpf(frac+0.00002289,exp);
+ }
+
+ x.u &= 0xffffff00;
+ return x.f;
+}
+
+//-----------------------------------------------------------------------------
+
+double RoundD6bytes ( double d )
+{
+ union { double d; u64 u; } x;
+ x.d = d;
+ if ( x.u & 0x8000 )
+ {
+ x.u &= 0xffffffffffff0000ull;
+ int exp;
+ const double frac = frexp(x.d,&exp);
+ x.d = ldexp(frac+0.000000000010913936,exp);
+ }
+
+ x.u &= 0xffffffffffff0000ull;
+ return x.d;
+}
+
+//-----------------------------------------------------------------------------
+
+double RoundD7bytes ( double d )
+{
+ union { double d; u64 u; } x;
+ x.d = d;
+ if ( x.u & 0x80 )
+ {
+ x.u &= 0xffffffffffffff00ull;
+ int exp;
+ const double frac = frexp(x.d,&exp);
+ x.d = ldexp(frac+0.000000000000042633,exp);
+ }
+
+ x.u &= 0xffffffffffffff00ull;
+ return x.d;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print number ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberU4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN, DC_SFORM_DASH
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 5 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ if ( num < 1000000000 ) // 1e9
+ {
+ u32 n32 = num;
+ if ( !n32 && mode & DC_SFORM_DASH )
+ StringCopyS(buf,buf_size, aligned ? " -" : "-" );
+ else if ( n32 < 10000 )
+ snprintf(buf,buf_size, aligned ? "%4u" : "%u", n32 );
+ else if ( n32 < 1000000 )
+ snprintf(buf,buf_size, aligned ? "%3uk" : "%uk", n32/1000 );
+ else if ( n32 < 10000000 )
+ {
+ n32 /= 100000;
+ snprintf(buf,buf_size,"%u.%uM", n32/10, n32%10 );
+ }
+ else
+ snprintf(buf,buf_size, aligned ? "%3uM" : "%uM", n32/1000000 );
+ }
+ else
+ {
+ u32 n32;
+ ccp factor = "GTPE";
+ if ( num < 1000000000000000ull ) // 1e15
+ n32 = num / 100000000; // 1e8
+ else
+ {
+ factor += 2;
+ n32 = num / 100000000000000ull; // 1e14
+ }
+
+ while (*factor)
+ {
+ if ( n32 < 100 )
+ {
+ snprintf(buf,buf_size,"%u.%u%c", n32/10, n32%10, *factor );
+ break;
+ }
+ else if ( n32 < 10000 )
+ {
+ snprintf(buf,buf_size, aligned ? "%3u%c" : "%u%c", n32/10, *factor );
+ break;
+ }
+ n32 /= 1000;
+ factor++;
+ }
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberU5
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN, DC_SFORM_DASH
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 6 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ if ( num < 1000000000 ) // 1e9
+ {
+ u32 n32 = num;
+ if ( !n32 && mode & DC_SFORM_DASH )
+ StringCopyS(buf,buf_size, aligned ? " -" : "-" );
+ else if ( n32 < 100000 )
+ snprintf(buf,buf_size, aligned ? "%5u" : "%u", n32 );
+ else if ( n32 < 10000000 )
+ snprintf(buf,buf_size, aligned ? "%4uk" : "%uk", n32/1000 );
+ else
+ snprintf(buf,buf_size, aligned ? "%4uM" : "%uM", n32/1000000 );
+ }
+ else
+ {
+ u32 n32;
+ ccp factor = "MGTPE";
+ if ( num < 1000000000000000ull ) // 1e15
+ n32 = num / 1000000; // 1e6
+ else
+ {
+ factor += 2;
+ n32 = num / 1000000000000ull; // 1e12
+ }
+
+ while (*factor)
+ {
+ if ( n32 < 10000 )
+ {
+ snprintf(buf,buf_size, aligned ? "%4u%c" : "%u%c", n32, *factor );
+ break;
+ }
+ n32 /= 1000;
+ factor++;
+ }
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberU6
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN, DC_SFORM_DASH
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 7 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ if ( num < 1000000000 ) // 1e9
+ {
+ u32 n32 = num;
+ if ( !n32 && mode & DC_SFORM_DASH )
+ StringCopyS(buf,buf_size, aligned ? " -" : "-" );
+ else if ( n32 < 1000000 )
+ snprintf(buf,buf_size, aligned ? "%6u" : "%u", n32 );
+ else if ( n32 < 100000000 )
+ snprintf(buf,buf_size, aligned ? "%5uk" : "%uk", n32/1000 );
+ else
+ snprintf(buf,buf_size, aligned ? "%5uM" : "%uM", n32/1000000 );
+ }
+ else
+ {
+ u32 n32;
+ ccp factor = "MGTPE";
+ if ( num < 1000000000000000ull ) // 1e15
+ n32 = num / 1000000; // 1e6
+ else
+ {
+ factor += 2;
+ n32 = num / 1000000000000ull; // 1e12
+ }
+
+ while (*factor)
+ {
+ if ( n32 < 100000 )
+ {
+ snprintf(buf,buf_size, aligned ? "%5u%c" : "%u%c", n32, *factor );
+ break;
+ }
+ n32 /= 1000;
+ factor++;
+ }
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberU7
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN, DC_SFORM_DASH
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 8 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ if ( num < 1000000000 ) // 1e9
+ {
+ u32 n32 = num;
+ if ( !n32 && mode & DC_SFORM_DASH )
+ StringCopyS(buf,buf_size, aligned ? " -" : "-" );
+ else if ( n32 < 10000000 )
+ snprintf(buf,buf_size, aligned ? "%7u" : "%u", n32 );
+ else
+ snprintf(buf,buf_size, aligned ? "%6uk" : "%uk", n32/1000 );
+ }
+ else
+ {
+ u32 n32;
+ ccp factor = "MGTPE";
+ if ( num < 1000000000000000ull ) // 1e15
+ n32 = num / 1000000; // 1e6
+ else
+ {
+ factor += 2;
+ n32 = num / 1000000000000ull; // 1e12
+ }
+
+ while (*factor)
+ {
+ if ( n32 < 1000000 )
+ {
+ snprintf(buf,buf_size, aligned ? "%6u%c" : "%u%c", n32, *factor );
+ break;
+ }
+ n32 /= 1000;
+ factor++;
+ }
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberS5
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s64 num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN, DC_SFORM_DASH
+)
+{
+ if ( num >= 0 )
+ return PrintNumberU5(buf,buf_size,num,mode);
+
+ if (!buf)
+ buf = GetCircBuf( buf_size = 6 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ if ( num > -1000000000 ) // -1e9
+ {
+ s32 n32 = num;
+ if ( n32 > -10000 )
+ snprintf(buf,buf_size, aligned ? "%5d" : "%d", n32 );
+ else if ( n32 > -1000000 )
+ snprintf(buf,buf_size, aligned ? "%4dk" : "%dk", n32/1000 );
+ else if ( n32 > -10000000 )
+ {
+ n32 /= -100000;
+ snprintf(buf,buf_size,"-%u.%uM", n32/10, n32%10 );
+ }
+ else
+ snprintf(buf,buf_size, aligned ? "%4dM" : "%dM", n32/1000000 );
+ }
+ else
+ {
+ s32 n32;
+ ccp factor = "GTPE";
+ if ( num > -1000000000000000ull ) // -1e15
+ n32 = num / 100000000; // -1e8
+ else
+ {
+ factor += 2;
+ n32 = num / 100000000000000ull; // -1e14
+ }
+
+ while (*factor)
+ {
+ if ( n32 > -100 )
+ {
+ snprintf(buf,buf_size,"-%u.%u%c", -n32/10, -n32%10, *factor );
+ break;
+ }
+ else if ( n32 > -10000 )
+ {
+ snprintf(buf,buf_size, aligned ? "%4d%c" : "%d%c", n32/10, *factor );
+ break;
+ }
+ n32 /= 1000;
+ factor++;
+ }
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberS6
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s64 num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN, DC_SFORM_DASH
+)
+{
+ if ( num >= 0 )
+ return PrintNumberU6(buf,buf_size,num,mode);
+
+ if (!buf)
+ buf = GetCircBuf( buf_size = 7 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ if ( num > -1000000000 ) // -1e9
+ {
+ s32 n32 = num;
+ if ( n32 > -100000 )
+ snprintf(buf,buf_size, aligned ? "%6d" : "%d", n32 );
+ else if ( n32 > -10000000 )
+ snprintf(buf,buf_size, aligned ? "%5dk" : "%dk", n32/1000 );
+ else
+ snprintf(buf,buf_size, aligned ? "%5dM" : "%dM", n32/1000000 );
+ }
+ else
+ {
+ s32 n32;
+ ccp factor = "MGTPE";
+ if ( num > -1000000000000000ull ) // -1e15
+ n32 = num / 1000000; // -1e6
+ else
+ {
+ factor += 2;
+ n32 = num / 1000000000000ull; // -1e12
+ }
+
+ while (*factor)
+ {
+ if ( n32 > -10000 )
+ {
+ snprintf(buf,buf_size, aligned ? "%5d%c" : "%d%c", n32, *factor );
+ break;
+ }
+ n32 /= 1000;
+ factor++;
+ }
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberS7
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ s64 num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN, DC_SFORM_DASH
+)
+{
+ if ( num >= 0 )
+ return PrintNumberU7(buf,buf_size,num,mode);
+
+ if (!buf)
+ buf = GetCircBuf( buf_size = 8 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ if ( num > -100000000 ) // -1e8
+ {
+ s32 n32 = num;
+ if ( n32 > -1000000 )
+ snprintf(buf,buf_size, aligned ? "%7d" : "%d", n32 );
+ else
+ snprintf(buf,buf_size, aligned ? "%6dk" : "%dk", n32/1000 );
+ }
+ else
+ {
+ s32 n32;
+ ccp factor = "MGTPE";
+ if ( num > -1000000000000000ull ) // -1e15
+ n32 = num / 1000000; // -1e6
+ else
+ {
+ factor += 2;
+ n32 = num / 1000000000000ull; // -1e12
+ }
+
+ while (*factor)
+ {
+ if ( n32 > -100000 )
+ {
+ snprintf(buf,buf_size, aligned ? "%6d%c" : "%d%c", n32, *factor );
+ break;
+ }
+ n32 /= 1000;
+ factor++;
+ }
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberD5
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ double num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 16 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ double abs = fabs(num);
+ if ( abs >= 1e2 )
+ {
+ if ( num <= 0 )
+ {
+ if ( abs <= S64_MAX )
+ return PrintNumberS5(buf,buf_size,double2int64(num),mode);
+ return "-****";
+ }
+ else
+ {
+ if ( abs <= U64_MAX )
+ return PrintNumberU5(buf,buf_size,double2int64(num),mode);
+ }
+ snprintf(buf,buf_size,"%5.0e",num);
+ return buf;
+ }
+
+ if ( num < 0.0 )
+ {
+ if ( abs < 1e-2 )
+ return aligned ? " -0" : "-0";
+
+ if ( abs < 10.0 )
+ snprintf(buf,buf_size,"%5.2f",num);
+ else
+ snprintf(buf,buf_size,"%5.1f",num);
+ }
+ else
+ {
+ if ( num < 1e-3 )
+ return aligned ? " 0" : "0";
+
+ if ( num < 10.0 )
+ snprintf(buf,buf_size,"%5.3f",num);
+ else
+ snprintf(buf,buf_size,"%5.2f",num);
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberD6
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ double num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 7 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ double abs = fabs(num);
+ if ( abs >= 1e3 )
+ {
+ if ( num <= 0 )
+ {
+ if ( abs <= S64_MAX )
+ return PrintNumberS6(buf,buf_size,double2int64(num),mode);
+ }
+ else
+ {
+ if ( abs <= U64_MAX )
+ return PrintNumberU6(buf,buf_size,double2int64(num),mode);
+ }
+ snprintf(buf,buf_size,"%6.0e",num);
+ return buf;
+ }
+
+ if ( num < 0.0 )
+ {
+ if ( abs < 1e-3 )
+ return aligned ? " -0" : "-0";
+
+ if ( abs < 10.0 )
+ snprintf(buf,buf_size,"%6.3f",num);
+ else if ( abs < 100.0 )
+ snprintf(buf,buf_size,"%6.2f",num);
+ else
+ snprintf(buf,buf_size,"%6.1f",num);
+ }
+ else
+ {
+ if ( num < 1e-4 )
+ return aligned ? " 0" : "0";
+
+ if ( num < 10.0 )
+ snprintf(buf,buf_size,"%6.4f",num);
+ else if ( num < 100.0 )
+ snprintf(buf,buf_size,"%6.3f",num);
+ else
+ snprintf(buf,buf_size,"%6.2f",num);
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintNumberD7
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ double num, // number to print
+ sizeform_mode_t mode // any of DC_SFORM_ALIGN
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 18 );
+ const sizeform_mode_t aligned = mode & DC_SFORM_ALIGN;
+
+ double abs = fabs(num);
+ if ( abs >= 1e4 )
+ {
+ if ( num <= 0 )
+ {
+ if ( abs <= S64_MAX )
+ return PrintNumberS7(buf,buf_size,double2int64(num),mode);
+ snprintf(buf,buf_size,"%7.0e",num);
+ return buf;
+ }
+ else
+ {
+ if ( abs <= U64_MAX )
+ return PrintNumberU7(buf,buf_size,double2int64(num),mode);
+ }
+ snprintf(buf,buf_size,"%7.1e",num);
+ return buf;
+ }
+
+ if ( num < 0.0 )
+ {
+ if ( abs < 1e-4 )
+ return aligned ? " -0" : "-0";
+
+ if ( abs < 10.0 )
+ snprintf(buf,buf_size,"%7.4f",num);
+ else if ( abs < 100.0 )
+ snprintf(buf,buf_size,"%7.3f",num);
+ else if ( abs < 1000.0 )
+ snprintf(buf,buf_size,"%7.2f",num);
+ else
+ snprintf(buf,buf_size,"%7.1f",num);
+ }
+ else
+ {
+ if ( num < 1e-5 )
+ return aligned ? " 0" : "0";
+
+ if ( num < 10.0 )
+ snprintf(buf,buf_size,"%7.5f",num);
+ else if ( num < 100.0 )
+ snprintf(buf,buf_size,"%7.4f",num);
+ else if ( num < 1000.0 )
+ snprintf(buf,buf_size,"%7.3f",num);
+ else
+ snprintf(buf,buf_size,"%7.2f",num);
+ }
+
+ return buf;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print size ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp dc_size_tab_1000[DC_SIZE_N_MODES+1] =
+{
+ 0, // DC_SIZE_DEFAULT
+ 0, // DC_SIZE_AUTO
+
+ "B", // DC_SIZE_BYTES
+ "kB", // DC_SIZE_K
+ "MB", // DC_SIZE_M
+ "GB", // DC_SIZE_G
+ "TB", // DC_SIZE_T
+ "PB", // DC_SIZE_P
+ "EB", // DC_SIZE_E
+
+ 0 // all others
+};
+
+//-----------------------------------------------------------------------------
+
+ccp dc_size_tab_1024[DC_SIZE_N_MODES+1] =
+{
+ 0, // DC_SIZE_DEFAULT
+ 0, // DC_SIZE_AUTO
+
+ "B", // DC_SIZE_BYTES
+ "KiB", // DC_SIZE_K
+ "MiB", // DC_SIZE_M
+ "GiB", // DC_SIZE_G
+ "TiB", // DC_SIZE_T
+ "PiB", // DC_SIZE_P
+ "EiB", // DC_SIZE_E
+
+ 0 // all others
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetSizeUnit // get a unit for column headers
+(
+ size_mode_t mode, // print mode
+ ccp if_invalid // output for invalid modes
+)
+{
+ const bool force_1000 = ( mode & DC_SIZE_M_BASE ) == DC_SIZE_F_1000;
+
+ switch ( mode & DC_SIZE_M_MODE )
+ {
+ //---- SI and IEC units
+
+ case DC_SIZE_DEFAULT:
+ case DC_SIZE_AUTO: return "size";
+ case DC_SIZE_BYTES: return "bytes";
+
+ case DC_SIZE_K: return force_1000 ? "kB" : "KiB";
+ case DC_SIZE_M: return force_1000 ? "MB" : "MiB";
+ case DC_SIZE_G: return force_1000 ? "GB" : "GiB";
+ case DC_SIZE_T: return force_1000 ? "TB" : "TiB";
+ case DC_SIZE_P: return force_1000 ? "PB" : "PiB";
+ case DC_SIZE_E: return force_1000 ? "EB" : "EiB";
+ }
+
+ return if_invalid;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetSizeFW // get a good value field width
+(
+ size_mode_t mode, // print mode
+ int min_fw // minimum fw => return max(calc_fw,min_fw);
+ // this value is also returned for invalid modes
+)
+{
+ int fw = mode & (DC_SIZE_F_AUTO_UNIT|DC_SIZE_F_NO_UNIT) ? 0 : 4;
+
+ switch ( mode & DC_SIZE_M_MODE )
+ {
+ case DC_SIZE_DEFAULT:
+ case DC_SIZE_AUTO:
+ if ( !(mode & DC_SIZE_F_NO_UNIT) )
+ fw = 4;
+ fw += 4;
+ break;
+
+ case DC_SIZE_BYTES: fw += 10; break;
+ case DC_SIZE_K: fw += 7; break;
+ case DC_SIZE_M: fw += 4; break;
+ case DC_SIZE_G: fw += 3; break;
+ case DC_SIZE_T: fw += 3; break;
+ case DC_SIZE_P: fw += 3; break;
+ case DC_SIZE_E: fw += 3; break;
+
+ default: fw = min_fw;
+ }
+
+ return fw > min_fw ? fw : min_fw;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// [[PrintSize]]
+
+char * PrintSize
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 size, // size to print
+ sizeform_mode_t sform_mode, // output format, bit field
+ size_mode_t mode // print mode
+)
+{
+ DASSERT( DC_SIZE_N_MODES <= DC_SIZE_M_MODE + 1 );
+
+
+ if (!buf)
+ buf = GetCircBuf( buf_size = 20 );
+
+ const bool force_1000 = ( mode & DC_SIZE_M_BASE ) == DC_SIZE_F_1000;
+
+ // ??? [[2do]] follow 'sform_mode'
+ const sizeform_mode_t aligned = sform_mode & DC_SFORM_ALIGN;
+
+ switch ( mode & DC_SIZE_M_MODE )
+ {
+ //---- SI and IEC units
+
+ case DC_SIZE_BYTES:
+ snprintf(buf,buf_size, aligned ? "%6llu B" : "%llu B", size );
+ break;
+
+ case DC_SIZE_K:
+ if (force_1000)
+ snprintf(buf,buf_size, aligned ? "%5llu kB" : "%llu kB",
+ ( size + KB_SI/2 ) / KB_SI );
+ else
+ snprintf(buf,buf_size, aligned ? "%4llu KiB" : "%llu KiB",
+ ( size + KiB/2 ) / KiB );
+ break;
+
+ case DC_SIZE_M:
+ if (force_1000)
+ snprintf(buf,buf_size, aligned ? "%5llu MB" : "%llu MB",
+ ( size + MB_SI /2 ) / MB_SI );
+ else
+ snprintf(buf,buf_size, aligned ? "%4llu MiB" : "%llu MiB",
+ ( size + MiB /2 ) / MiB );
+ break;
+
+ case DC_SIZE_G:
+ if (force_1000)
+ snprintf(buf,buf_size, aligned ? "%5llu GB" : "%llu GB",
+ ( size + GB_SI/2 ) / GB_SI );
+ else
+ snprintf(buf,buf_size, aligned ? "%4llu GiB" : "%llu GiB",
+ ( size + GiB/2 ) / GiB );
+ break;
+
+ case DC_SIZE_T:
+ if (force_1000)
+ snprintf(buf,buf_size, aligned ? "%5llu TB" : "%llu TB",
+ ( size + TB_SI/2 ) / TB_SI );
+ else
+ snprintf(buf,buf_size, aligned ? "%4llu TiB" : "%llu TiB",
+ ( size + TiB/2 ) / TiB );
+ break;
+
+ case DC_SIZE_P:
+ if (force_1000)
+ snprintf(buf,buf_size, aligned ? "%5llu PB" : "%llu PB",
+ ( size + PB_SI/2 ) / PB_SI );
+ else
+ snprintf(buf,buf_size, aligned ? "%4llu PiB" : "%llu PiB",
+ ( size + PiB/2 ) / PiB );
+ break;
+
+ case DC_SIZE_E:
+ if (force_1000)
+ snprintf(buf,buf_size, aligned ? "%5llu EB" : "%llu EB",
+ ( size + EB_SI/2 ) / EB_SI );
+ else
+ snprintf(buf,buf_size, aligned ? "%4llu EiB" : "%llu EiB",
+ ( size + EiB/2 ) / EiB );
+ break;
+
+
+ //----- default == auto
+
+ default:
+ buf = force_1000
+ ? PrintSize1000(buf,buf_size,size,sform_mode)
+ : PrintSize1024(buf,buf_size,size,sform_mode);
+ if ( !(mode&DC_SIZE_F_NO_UNIT) )
+ return buf;
+ }
+
+ if ( mode & (DC_SIZE_F_AUTO_UNIT|DC_SIZE_F_NO_UNIT) )
+ {
+ char * ptr = buf;
+ while ( *ptr == ' ' )
+ ptr++;
+ while ( *ptr && *ptr != ' ' )
+ ptr++;
+ *ptr = 0;
+ }
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// [[PrintSize1000]]
+
+char * PrintSize1000
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 size, // size to print
+ sizeform_mode_t sform_mode // output format, bit field
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 20 );
+
+ u64 num;
+ size_mode_t unit;
+
+ u64 mb = (size+MB_SI/2)/MB_SI; // maybe an overflow => extra if
+ if ( mb < 10000 && size < EB_SI )
+ {
+ u64 kb = (size+KB_SI/2)/KB_SI;
+ if ( kb < 10 )
+ {
+ num = size;
+ unit = DC_SIZE_BYTES;
+ }
+ else if ( kb < 10000 )
+ {
+ num = kb;
+ unit = DC_SIZE_K;
+ }
+ else
+ {
+ num = mb;
+ unit = DC_SIZE_M;
+ }
+ }
+ else
+ {
+ mb = size / MB_SI; // recalc because of possible overflow
+ u64 tb = (mb+MB_SI/2)/MB_SI;
+ if ( tb < 10000 )
+ {
+ if ( tb < 10 )
+ {
+ num = (mb+KB_SI/2)/KB_SI;
+ unit = DC_SIZE_G;
+ }
+ else
+ {
+ num = tb;
+ unit = DC_SIZE_T;
+ }
+ }
+ else
+ {
+ u64 pb = (mb+GB_SI/2)/GB_SI;
+ if ( pb < 10000 )
+ {
+ num = pb;
+ unit = DC_SIZE_P;
+ }
+ else
+ {
+ num = (mb+TB_SI/2)/TB_SI;
+ unit = DC_SIZE_E;
+ }
+ }
+ }
+
+ if ( sform_mode & DC_SFORM_INC
+ && num
+ && !( num % 1000 )
+ && dc_size_tab_1000[unit+1]
+ )
+ {
+ unit++;
+ num /= 1000;
+ }
+
+ char unit_buf[8];
+ char *dest = unit_buf;
+ if ( !(sform_mode & DC_SFORM_NARROW) )
+ *dest++ = ' ';
+
+ if ( sform_mode & DC_SFORM_UNIT1 )
+ {
+ if ( unit != DC_SIZE_BYTES )
+ *dest++ = dc_size_tab_1000[unit][0];
+ else if ( sform_mode & DC_SFORM_ALIGN )
+ *dest++ = ' ';
+ else
+ dest = unit_buf; // not any char
+ *dest = 0;
+ }
+ else if ( sform_mode & DC_SFORM_ALIGN )
+ snprintf(dest,sizeof(unit_buf)-1,"%-3s",dc_size_tab_1000[unit]);
+ else
+ StringCopyS(dest,sizeof(unit_buf)-1,dc_size_tab_1000[unit]);
+
+ if ( !num && unit == DC_SIZE_BYTES && sform_mode & DC_SFORM_DASH )
+ {
+ if ( sform_mode & DC_SFORM_ALIGN )
+ snprintf(buf,buf_size," -%s",unit_buf);
+ else
+ StringCopyS(buf,buf_size,"-");
+ }
+ if ( sform_mode & DC_SFORM_ALIGN )
+ snprintf(buf,buf_size,"%4llu%s",num,unit_buf);
+ else
+ snprintf(buf,buf_size,"%llu%s",num,unit_buf);
+
+ return buf;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// [[PrintSize1024]]
+
+char * PrintSize1024
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u64 size, // size to print
+ sizeform_mode_t sform_mode // output format, bit field
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 20 );
+
+ u64 num;
+ size_mode_t unit;
+
+ u64 mib = (size+MiB/2)/MiB; // maybe an overflow => extra if
+ if ( mib < 10000 && size < EiB )
+ {
+ u64 kib = (size+KiB/2)/KiB;
+ if ( kib < 10 )
+ {
+ num = size;
+ unit = DC_SIZE_BYTES;
+ }
+ else if ( kib < 10000 )
+ {
+ num = kib;
+ unit = DC_SIZE_K;
+ }
+ else
+ {
+ num = mib;
+ unit = DC_SIZE_M;
+ }
+ }
+ else
+ {
+ mib = size / MiB; // recalc because of possible overflow
+ u64 tib = (mib+MiB/2)/MiB;
+ if ( tib < 10000 )
+ {
+ if ( tib < 10 )
+ {
+ num = (mib+KiB/2)/KiB;
+ unit = DC_SIZE_G;
+ }
+ else
+ {
+ num = tib;
+ unit = DC_SIZE_T;
+ }
+ }
+ else
+ {
+ u64 pib = (mib+GiB/2)/GiB;
+ if ( pib < 10000 )
+ {
+ num = pib;
+ unit = DC_SIZE_P;
+ }
+ else
+ {
+ num = (mib+TiB/2)/TiB;
+ unit = DC_SIZE_E;
+ }
+ }
+ }
+
+ if ( sform_mode & DC_SFORM_INC
+ && num
+ && !( num & 0x3ff )
+ && dc_size_tab_1024[unit+1]
+ )
+ {
+ unit++;
+ num /= 0x400;
+ }
+
+ char unit_buf[8];
+ char *dest = unit_buf;
+ if ( !(sform_mode & DC_SFORM_NARROW) )
+ *dest++ = ' ';
+
+ if ( sform_mode & DC_SFORM_UNIT1 )
+ {
+ if ( unit != DC_SIZE_BYTES )
+ *dest++ = dc_size_tab_1000[unit][0];
+ else if ( sform_mode & DC_SFORM_ALIGN )
+ *dest++ = ' ';
+ else
+ dest = unit_buf; // not any char
+ *dest = 0;
+ }
+ else if ( sform_mode & DC_SFORM_ALIGN )
+ snprintf(dest,sizeof(unit_buf)-1,"%-3s",dc_size_tab_1024[unit]);
+ else
+ StringCopyS(dest,sizeof(unit_buf)-1,dc_size_tab_1024[unit]);
+
+ if ( !num && unit == DC_SIZE_BYTES && sform_mode & DC_SFORM_DASH )
+ {
+ if ( sform_mode & DC_SFORM_ALIGN )
+ snprintf(buf,buf_size," -%s",unit_buf);
+ else
+ StringCopyS(buf,buf_size,"-");
+ }
+ else if ( sform_mode & DC_SFORM_ALIGN )
+ snprintf(buf,buf_size,"%4llu%s",num,unit_buf);
+ else
+ snprintf(buf,buf_size,"%llu%s",num,unit_buf);
+
+ return buf;
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan size ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u64 (*ScanSizeFactorHook)
+(
+ char ch_factor, // char to analyze
+ int force_base // if 1000|1024: force multiple of this
+) = 0;
+
+//-----------------------------------------------------------------------------
+
+u64 ScanSizeFactor
+(
+ char ch_factor, // char to analyze
+ int force_base // if 1000|1024: force multiple of this
+)
+{
+ if (ScanSizeFactorHook)
+ {
+ u64 res = ScanSizeFactorHook(ch_factor,force_base);
+ if (res)
+ return res;
+ }
+
+ if ( force_base == 1000 )
+ {
+ switch (ch_factor)
+ {
+ case 'b': case 'c': return 1;
+ case 'k': case 'K': return KB_SI;
+ case 'm': case 'M': return MB_SI;
+ case 'g': case 'G': return GB_SI;
+ case 't': case 'T': return TB_SI;
+ case 'p': case 'P': return PB_SI;
+ case 'e': case 'E': return EB_SI;
+ }
+ }
+ else if ( force_base == 1024 )
+ {
+ switch (ch_factor)
+ {
+ case 'b': case 'c': return 1;
+ case 'k': case 'K': return KiB;
+ case 'm': case 'M': return MiB;
+ case 'g': case 'G': return GiB;
+ case 't': case 'T': return TiB;
+ case 'p': case 'P': return PiB;
+ case 'e': case 'E': return EiB;
+ }
+ }
+ else
+ {
+ switch (ch_factor)
+ {
+ case 'b':
+ case 'c': return 1;
+
+ case 'k': return KB_SI;
+ case 'm': return MB_SI;
+ case 'g': return GB_SI;
+ case 't': return TB_SI;
+ case 'p': return PB_SI;
+ case 'e': return EB_SI;
+
+ case 'K': return KiB;
+ case 'M': return MiB;
+ case 'G': return GiB;
+ case 'T': return TiB;
+ case 'P': return PiB;
+ case 'E': return EiB;
+ }
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeTerm
+(
+ double *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ int force_base // if 1000|1024: force multiple of this
+)
+{
+ ASSERT(source);
+
+ char * end;
+ double d = strtod(source,&end);
+ if ( end > source )
+ {
+ // something was read
+
+ if ( *end == '/' )
+ {
+ const double div = strtod(end+1,&end);
+ if ( div > 0.0 )
+ d /= div;
+ }
+
+ u64 factor = ScanSizeFactor(*end,force_base);
+ if (factor)
+ end++;
+ else
+ factor = default_factor;
+
+ if (factor)
+ d *= factor;
+ else
+ end = (char*)source;
+ }
+
+ if (num)
+ *num = d;
+
+ return end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSize
+(
+ double *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base // if 1000|1024: force multiple of this
+)
+{
+ DASSERT(source);
+ TRACE("ScanSize(df=%llx,%llx, base=%u)\n",
+ default_factor, default_factor_add, force_base );
+
+ double sum = 0.0;
+ bool add = true;
+ char * end = 0;
+ for(;;)
+ {
+ double term;
+ end = ScanSizeTerm(&term,source,default_factor,force_base);
+ if ( end == source )
+ break;
+
+ if (add)
+ sum += term;
+ else
+ sum -= term;
+
+ while ( *end > 0 && *end <= ' ' )
+ end++;
+
+ if ( *end == '+' )
+ add = true;
+ else if ( *end == '-' )
+ add = false;
+ else
+ break;
+
+ source = end+1;
+ while ( *source > 0 && *source <= ' ' )
+ source++;
+
+ if ( !*source && default_factor_add )
+ {
+ if (add)
+ sum += default_factor_add;
+ else
+ sum -= default_factor_add;
+ end = (char*)source;
+ break;
+ }
+
+ default_factor = default_factor_add;
+ }
+
+ if (num)
+ *num = sum;
+
+ return end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeU32
+(
+ u32 *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base // if 1000|1024: force multiple of this
+)
+{
+ double d;
+ char * end = ScanSize(&d,source,default_factor,default_factor_add,force_base);
+ if ( d < 0 || d > ~(u32)0 )
+ end = (char*)source;
+ else if (num)
+ *num = (u32)d;
+
+ return end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeU64
+(
+ u64 *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base // if 1000|1024: force multiple of this
+)
+{
+ double d;
+ char * end = ScanSize(&d,source,default_factor,default_factor_add,force_base);
+ if ( d < 0 || d > ~(u64)0 )
+ end = (char*)source;
+ else if (num)
+ *num = (u64)d;
+
+ return end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeRange
+(
+ int *stat, // if not NULL: store result
+ // 0:none, 1:single, 2:range
+ double *num1, // not NULL: store 'from' result
+ double *num2, // not NULL: store 'to' result
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ double max_value // >0: max value for open ranges
+)
+{
+ int count = 0;
+ double d1, d2;
+
+ char *end = ScanSize(&d1,source,default_factor,default_factor_add,force_base);
+ if ( end == source )
+ {
+ d1 = d2 = 0.0;
+ count = 0;
+ }
+ else
+ {
+ count++;
+ d2 = d1;
+ if ( *end == '#' || *end == ':' )
+ {
+ source = end;
+ end = ScanSize(&d2,source+1,default_factor,default_factor_add,force_base);
+ if ( end == source+1 )
+ {
+ if ( *source == ':' && max_value > 0.0 )
+ {
+ count++;
+ d2 = max_value;
+ if ( *end == '*' )
+ end++;
+ }
+ else
+ {
+ end--;
+ d2 = d1;
+ }
+ }
+ else
+ {
+ count++;
+ if ( *source == '#' )
+ d2 += d1-1;
+ }
+ if ( d2 < d1 )
+ d2 = d1;
+ }
+ }
+
+ if (stat)
+ *stat = count;
+ if (num1)
+ *num1 = d1;
+ if (num2)
+ *num2 = d2;
+ return end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeRangeU32
+(
+ int *stat, // if not NULL: store result
+ // 0:none, 1:single, 2:range
+ u32 *num1, // not NULL: store 'from' result
+ u32 *num2, // not NULL: store 'to' result
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ u32 max_value // >0: max value for open ranges
+)
+{
+ double d1, d2;
+ char * end = ScanSizeRange(stat,&d1,&d2,source,
+ default_factor,default_factor_add,force_base,
+ max_value>0 ? max_value : U32_MAX );
+
+ if ( d1 < 0 || d1 > U32_MAX || d2 < 0 || d2 > U32_MAX )
+ end = (char*)source;
+ else
+ {
+ if (num1)
+ *num1 = (u32)d1;
+ if (num2)
+ *num2 = (u32)d2;
+ }
+
+ return end;
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeRangeU64
+(
+ int *stat, // if not NULL: store result
+ // 0:none, 1:single, 2:range
+ u64 *num1, // not NULL: store 'from' result
+ u64 *num2, // not NULL: store 'to' result
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ u64 max_value // >0: max value for open ranges
+)
+{
+ double d1, d2;
+ char * end = ScanSizeRange(stat,&d1,&d2,source,
+ default_factor,default_factor_add,force_base,
+ max_value>0 ? max_value : U64_MAX );
+
+ if ( d1 < 0 || d1 > U64_MAX || d2 < 0 || d2 > U64_MAX )
+ end = (char*)source;
+ else
+ {
+ if (num1)
+ *num1 = (u64)d1;
+ if (num2)
+ *num2 = (u64)d2;
+ }
+
+ return end;
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeRangeList
+(
+ uint *n_range, // not NULL: store number of scanned ranges
+ double *num, // array with '2*max_range' elements
+ // unused elements are filled with 0.0
+ uint max_range, // max number of allowed ranges
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ double max_value // >0: max value for open ranges
+)
+{
+ DASSERT(num);
+
+ uint i = 0;
+ char *ptr = (char*)source;
+
+ while ( i < max_range )
+ {
+ while ( *ptr > 0 && *ptr <= ' ' || *ptr == ',' )
+ ptr++;
+
+ int stat;
+ ptr = ScanSizeRange( &stat, num, num+1, ptr,
+ default_factor, default_factor_add, force_base, max_value );
+ if (!stat)
+ break;
+ num += 2;
+ i++;
+
+ char *ptr2 = ptr;
+ while ( *ptr2 > 0 && *ptr2 <= ' ' )
+ ptr2++;
+ if ( *ptr2 != ',' )
+ break;
+
+ ptr = ptr2 + 1;
+ }
+
+ if (n_range)
+ *n_range = i;
+
+ while ( i < max_range )
+ {
+ *num++ = 0.0;
+ *num++ = 0.0;
+ i++;
+ }
+
+ return ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeRangeListU32
+(
+ uint *n_range, // not NULL: store number of scanned ranges
+ u32 *num, // array with '2*max_range' elements
+ // unused elements are filled with 0
+ uint max_range, // max number of allowed ranges
+
+ ccp source, // source text
+ u32 default_factor, // use this factor if number hasn't one
+ u32 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ u32 max_value // >0: max value for open ranges
+)
+{
+ DASSERT(num);
+
+ const uint max = 100;
+ double d_buf[2*max];
+ double *d = max_range < max ? d_buf : MALLOC(max_range*2*sizeof(double));
+
+ char *end = ScanSizeRangeList( n_range, d, max_range,
+ source, default_factor, default_factor_add,
+ force_base, max_value );
+
+ uint i;
+ for ( i = 0; i < 2*max_range; i++ )
+ num[i] = d[i] < 0.0 ? 0.0 : d[i] < U32_MAX ? d[i] : U32_MAX;
+
+ if ( d != d_buf )
+ FREE(d);
+ return end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSizeRangeListU64
+(
+ uint *n_range, // not NULL: store number of scanned ranges
+ u64 *num, // array with '2*max_range' elements
+ // unused elements are filled with 0
+ uint max_range, // max number of allowed ranges
+
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+ u64 max_value // >0: max value for open ranges
+)
+{
+ DASSERT(num);
+
+ const uint max = 100;
+ double d_buf[2*max];
+ double *d = max_range < max ? d_buf : MALLOC(max_range*2*sizeof(double));
+
+ char *end = ScanSizeRangeList( n_range, d, max_range,
+ source, default_factor, default_factor_add,
+ force_base, max_value );
+
+ uint i;
+ for ( i = 0; i < 2*max_range; i++ )
+ num[i] = d[i] < 0.0 ? 0.0 : d[i] < U64_MAX ? d[i] : U64_MAX;
+
+ if ( d != d_buf )
+ FREE(d);
+ return end;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanSizeOpt
+(
+ double *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ u64 default_factor_add, // use this factor for summands
+ int force_base, // if 1000|1024: force multiple of this
+
+ ccp opt_name, // NULL or name of option for error messages
+ u64 min, // minimum allowed value
+ u64 max, // maximum allowed value
+ bool print_err // true: print error messages
+)
+{
+ double d;
+ char * end = ScanSize(&d,source,default_factor,default_factor_add,force_base);
+
+ #ifdef DEBUG
+ {
+ u64 size = d;
+ TRACE("--%s %8.6g ~ %llu ~ %llu GiB ~ %llu GB\n",
+ opt_name, d, size, (size+GiB/2)/GiB, (size+500000000)/1000000000 );
+ }
+ #endif
+
+ enumError err = ERR_OK;
+
+ if ( source == end || *end )
+ {
+ err = ERR_SYNTAX;
+ if (print_err)
+ ERROR0(ERR_SYNTAX,
+ "Illegal number for option --%s: %s\n",
+ opt_name, source );
+ }
+ else if ( min > 0 && d < min )
+ {
+ err = ERR_SYNTAX;
+ if (print_err)
+ ERROR0(ERR_SEMANTIC,
+ "Value of --%s too small (must not <%llu): %s\n",
+ opt_name, min, source );
+ }
+ else if ( max > 0 && d > max )
+ {
+ err = ERR_SYNTAX;
+ if (print_err)
+ ERROR0(ERR_SEMANTIC,
+ "Value of --%s too large (must not >%llu): %s\n",
+ opt_name, max, source );
+ }
+
+ if ( num && !err )
+ *num = d;
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanSizeOptU64
+(
+ u64 *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ int force_base, // if 1000|1024: force multiple of this
+
+ ccp opt_name, // NULL or name of option for error messages
+ u64 min, // >0: minimum allowed value
+ u64 max, // >0: maximum allowed value
+ u32 multiple, // >0: result must be multiple
+ u32 pow2, // >0: result must power of '1<<pow2'
+ bool print_err // true: print error messages
+)
+{
+ if (!max)
+ max = ~(u64)0;
+
+ if ( pow2 && !force_base )
+ {
+ // try base 1024 first without error messages
+ u64 val;
+ if (!ScanSizeOptU64( &val, source, default_factor, 1024,
+ opt_name, min,max, multiple, pow2, false ))
+ {
+ if (num)
+ *num = val;
+ return ERR_OK;
+ }
+ }
+
+ double d = 0.0;
+ enumError err = ScanSizeOpt(&d,source,default_factor,
+ multiple ? multiple : 1,
+ force_base,opt_name,min,max,print_err);
+
+ u64 val;
+ if ( d < 0.0 )
+ {
+ val = 0;
+ err = ERR_SEMANTIC;
+ if (print_err)
+ ERROR0(ERR_SEMANTIC, "--%s: negative values not allowed: %s\n",
+ opt_name, source );
+ }
+ else
+ val = d;
+
+ if ( err == ERR_OK && pow2 > 0 )
+ {
+ int shift_count = 0;
+ u64 shift_val = val;
+ if (val)
+ {
+ while (!(shift_val&1))
+ {
+ shift_count++;
+ shift_val >>= 1;
+ }
+ }
+
+ if ( shift_val != 1 || shift_count/pow2*pow2 != shift_count )
+ {
+ err = ERR_SEMANTIC;
+ if (print_err)
+ ERROR0(ERR_SYNTAX,
+ "--%s: value must be a power of %d but not %llu\n",
+ opt_name, 1<<pow2, val );
+ }
+ }
+
+ if ( err == ERR_OK && multiple > 1 )
+ {
+ u64 xval = val / multiple * multiple;
+ if ( xval != val )
+ {
+ if ( min > 0 && xval < min )
+ xval += multiple;
+
+ if (print_err)
+ ERROR0(ERR_WARNING,
+ "--%s: value must be a multiple of %u -> use %llu instead of %llu.\n",
+ opt_name, multiple, xval, val );
+ val = xval;
+ }
+ }
+
+ if ( num && !err )
+ *num = val;
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanSizeOptU32
+(
+ u32 *num, // not NULL: store result
+ ccp source, // source text
+ u64 default_factor, // use this factor if number hasn't one
+ int force_base, // if 1000|1024: force multiple of this
+
+ ccp opt_name, // NULL or name of option for error messages
+ u64 min, // >0: minimum allowed value
+ u64 max, // >0: maximum allowed value
+ u32 multiple, // >0: result must be multiple
+ u32 pow2, // >0: result must power of '1<<pow2'
+ bool print_err // true: print error messages
+)
+{
+ if ( !max || max > ~(u32)0 )
+ max = ~(u32)0;
+
+ u64 val = 0;
+ enumError err = ScanSizeOptU64( &val, source, default_factor, force_base,
+ opt_name, min, max, multiple, pow2, print_err );
+
+ if ( num && !err )
+ *num = (u32)val;
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// strto*() like wrappers
+
+u32 GetSizeU32 ( ccp src, char **end, int force_base )
+{
+ u32 num = 0;
+ char *ptr = ScanSizeU32(&num,src,1,1,force_base);
+ if (end)
+ *end = ptr;
+ return num;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetSizeU64 ( ccp src, char **end, int force_base )
+{
+ u64 num = 0;
+ char *ptr = ScanSizeU64(&num,src,1,1,force_base);
+ if (end)
+ *end = ptr;
+ return num;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+double GetSizeD ( ccp src, char **end, int force_base )
+{
+ double num = 0.0;
+ char *ptr = ScanSize(&num,src,1,1,force_base);
+ if (end)
+ *end = ptr;
+ return num;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Scan Duration ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanSIFactor
+(
+ // returns end of scanned string
+
+ double *num, // not NULL: store result
+ ccp source, // source text
+ double default_factor // return this if no factor found
+)
+{
+ DASSERT(source);
+ switch (*(uchar*)source++)
+ {
+ case 'y': default_factor = 1e-24; break;
+ case 'z': default_factor = 1e-21; break;
+ case 'a': default_factor = 1e-18; break;
+ case 'f': default_factor = 1e-15; break;
+ case 'p': default_factor = 1e-12; break;
+ case 'n': default_factor = 1e-9; break;
+ case 0xb5: // µ as ANSI
+ case 'u': default_factor = 1e-6; break;
+ case 'm': default_factor = 1e-3; break;
+ case 'c': default_factor = 1e-2; break;
+ case 'd': default_factor = 1e-1; break;
+
+ case 'h': default_factor = 1e2; break;
+ case 'k': default_factor = 1e3; break;
+ case 'M': default_factor = 1e6; break;
+ case 'G': default_factor = 1e9; break;
+ case 'T': default_factor = 1e12; break;
+ case 'P': default_factor = 1e15; break;
+ case 'E': default_factor = 1e18; break;
+ case 'Z': default_factor = 1e21; break;
+ case 'Y': default_factor = 1e24; break;
+
+ case 0xc2:
+ if ( (uchar)*source == 0xb5 )
+ {
+ // µ as UTF-8
+ source++;
+ default_factor = 1e-6;
+ break;
+ }
+ else
+ source--;
+ break;
+
+ default:
+ source--;
+ }
+ if (num)
+ *num = default_factor;
+ return (char*)source;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+double GetDurationFactor ( char ch )
+{
+ switch (ch)
+ {
+ case 's': return 1.0; break;
+ case 'm': return 60.0; break;
+ case 'h': return 3600.0; break;
+ case 't':
+ case 'd': return 86400.0; break;
+ case 'w': return 7*86400.0; break;
+ case 'j':
+ case 'y': return 365*86400.0; break;
+ default: return 0.0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanDuration
+(
+ double *num, // not NULL: store result
+ ccp source, // source text
+ double default_factor, // default factor if no SI unit found
+ ScanDuration_t mode
+)
+{
+ if (!source)
+ return 0;
+
+ ccp src = source;
+ if ( mode & SDUMD_SKIP_BLANKS )
+ while ( *src == ' ' || *src == '\t' )
+ src++;
+
+ if ( mode & SDUMD_ALLOW_LIST )
+ {
+ bool valid = false;
+ mode &= ~(SDUMD_SKIP_BLANKS|SDUMD_ALLOW_LIST);
+ double sum = 0.0;
+ bool sub = false;
+ for(;;)
+ {
+ double val;
+ ccp end = ScanDuration(&val,src,default_factor,mode);
+ if ( src == end )
+ break;
+ if (sub)
+ {
+ sub = false;
+ sum -= val;
+ }
+ else
+ sum += val;
+ src = end;
+ valid = true;
+
+ if ( mode & SDUMD_ALLOW_SPACE_SEP )
+ while ( *src == ' ' || *src == '\t' )
+ src++;
+ if ( *src == '+' && mode & SDUMD_ALLOW_PLUS_SEP )
+ src++;
+ else if ( *src == '-' && mode & SDUMD_ALLOW_MINUS_SEP )
+ {
+ src++;
+ sub = true;
+ }
+ mode |= SDUMD_SKIP_BLANKS;
+ }
+ if (num)
+ *num = sum;
+ return (char*)( valid ? src : source );
+ }
+
+
+ //--- single scan
+
+ char *end;
+ double val = strtod(src,&end);
+ if ( end > src )
+ {
+ if ( *end == ':' && mode & SDUMD_ALLOW_COLON )
+ {
+ // 'H:M' or 'H:M:S'
+ src = end + 1;
+ val *= 3600;
+ double min = strtod(src,&end);
+ if ( end > src )
+ {
+ val += 60*min;
+ src = end;
+ if ( *src == ':' )
+ {
+ // 'H:M:S'
+ double sec = strtod(++src,&end);
+ if ( end > src )
+ {
+ src = end;
+ val += sec;
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( *end == '/' && mode & SDUMD_ALLOW_DIV )
+ {
+ const double div = strtod(end+1,&end);
+ if ( div > 0.0 )
+ val /= div;
+ }
+ src = end;
+
+ double si_factor;
+ end = ScanSIFactor(&si_factor,src,1.0);
+
+ double unit = GetDurationFactor(*end);
+ if (unit)
+ {
+ val *= unit * si_factor;
+ src = end+1;
+ }
+ else
+ {
+ unit = GetDurationFactor(*src);
+ if (unit)
+ {
+ val *= unit;
+ src++;
+ }
+ else
+ {
+ val *= (end>src) ? si_factor : default_factor;
+ src = end;
+ }
+ }
+ }
+ if (num)
+ *num = val;
+ return (char*)src;
+ }
+
+ if (num)
+ *num = 0.0;
+ return (char*)source;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// PrintIP4*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintIP4A
+(
+ // print in A-notation
+
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = FW_IPV4_A_PORT+1 );
+
+ u8 addr[4];
+ write_be32(addr,ip4);
+
+ if ( port >= 0 && port <= 0xffff )
+ snprintf(buf,buf_size,"%u.%u:%u",
+ addr[0], ip4 & 0xffffff, port );
+ else
+ snprintf(buf,buf_size,"%u.%u",
+ addr[0], ip4 & 0xffffff );
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintIP4B
+(
+ // print in A-notation
+
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = FW_IPV4_B_PORT+1 );
+
+ u8 addr[4];
+ write_be32(addr,ip4);
+
+ if ( port >= 0 && port <= 0xffff )
+ snprintf(buf,buf_size,"%u.%u.%u:%u",
+ addr[0], addr[1], ip4 & 0xffff, port );
+ else
+ snprintf(buf,buf_size,"%u.%u.%u",
+ addr[0], addr[1], ip4 & 0xffff );
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintIP4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = FW_IPV4_C_PORT+1 );
+
+ u8 addr[4];
+ write_be32(addr,ip4);
+
+ if ( port >= 0 && port <= 0xffff )
+ snprintf(buf,buf_size,"%u.%u.%u.%u:%u",
+ addr[0], addr[1], addr[2], addr[3], port );
+ else
+ snprintf(buf,buf_size,"%u.%u.%u.%u",
+ addr[0], addr[1], addr[2], addr[3] );
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintLeftIP4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = FW_IPV4_C_PORT+1 );
+
+ u8 addr[4];
+ write_be32(addr,ip4);
+ uint len, fw;
+
+ if ( port >= 0 && port <= 0xffff )
+ {
+ fw = 21;
+ len = snprintf(buf,buf_size,"%u.%u.%u.%u:%u",
+ addr[0], addr[1], addr[2], addr[3], port );
+ }
+ else
+ {
+ fw = 15;
+ len = snprintf(buf,buf_size,"%u.%u.%u.%u",
+ addr[0], addr[1], addr[2], addr[3] );
+ }
+
+ char *dest = buf + len;
+ char *end = buf + ( fw < buf_size ? fw : buf_size );
+ while ( dest < end )
+ *dest++ = ' ';
+ *dest = 0;
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintRightIP4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port, // 0..0xffff: add ':port'
+ uint port_mode // 0: 'ip:port', 1: 'ip :port', 2: 'ip : port'
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = 24 );
+
+ u8 addr[4];
+ write_be32(addr,ip4);
+
+ char tempbuf[16];
+ snprintf(tempbuf,sizeof(tempbuf),"%u.%u.%u.%u",
+ addr[0], addr[1], addr[2], addr[3] );
+
+ if ( port >= 0 && port <= 0xffff )
+ {
+ if (!port_mode)
+ snprintf(buf,buf_size,"%15s:%-5u",tempbuf,port);
+ else if ( port_mode == 1 )
+ snprintf(buf,buf_size,"%15s :%-5u",tempbuf,port);
+ else
+ snprintf(buf,buf_size,"%15s : %-5u",tempbuf,port);
+ }
+ else
+ snprintf(buf,buf_size,"%15s",tempbuf);
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintAlignedIP4
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ u32 ip4, // IP4 to print
+ s32 port // 0..0xffff: add ':port'
+)
+{
+ if (!buf)
+ buf = GetCircBuf( buf_size = FW_IPV4_C_PORT+1 );
+
+ u8 addr[4];
+ write_be32(addr,ip4);
+
+ if ( port >= 0 && port <= 0xffff )
+ snprintf(buf,buf_size,"%3u.%3u.%3u.%3u:%5u",
+ addr[0], addr[1], addr[2], addr[3], port );
+ else
+ snprintf(buf,buf_size,"%3u.%3u.%3u.%3u",
+ addr[0], addr[1], addr[2], addr[3] );
+ return buf;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CRC16 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void CreateCRC16Table ( u16 table[0x100], u16 polynom )
+{
+ int i, j;
+ for ( i = 0; i < 256; i++ )
+ {
+ u16 val = i << 8;
+ for ( j = 0; j < 8; j++ )
+ {
+ if ( val & 0x8000 )
+ val = val << 1 ^ polynom;
+ else
+ val <<= 1;
+ }
+ table[i] = val;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+const u16 * GetCRC16Table ( u16 polynom )
+{
+ static u16 *table = 0;
+ static u16 active_polynom = 0;
+
+ if ( !table || active_polynom != polynom )
+ {
+ if (!table)
+ table = MALLOC(sizeof(u16)*0x100);
+ CreateCRC16Table(table,polynom);
+ active_polynom = polynom;
+ }
+ return table;
+}
+
+//-----------------------------------------------------------------------------
+
+u16 CalcCRC16 ( cvp data, uint data_size, u16 polynom, u16 preset )
+{
+ DASSERT( data || !data_size );
+
+ register u16 crc = preset;
+ if ( data_size > 0 )
+ {
+ const u16 *table = GetCRC16Table(polynom);
+ const u8 *ptr = data;
+ while ( data_size-- > 0 )
+ crc = table[ ( *ptr++ ^ crc >> 8 ) & 0xff ] ^ crc << 8;
+ }
+ return crc;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// misc ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+float double2float ( double d )
+{
+ // reduce precision
+ return d;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintNiceID4 ( uint id )
+{
+ if (!id)
+ return "----";
+
+ char *buf = GetCircBuf(5);
+ DASSERT(buf);
+
+ #if 0 // the second solution is much faster!
+
+ buf[4] = 0;
+ div_t d = div( (int)( id % (26*26*100) ), 10 );
+ buf[3] = '0' + d.rem;
+ d = div(d.quot,10);
+ buf[2] = '0' + d.rem;
+ d = div(d.quot,26);
+ buf[1] = 'A' + d.rem;
+ buf[0] = 'A' + d.quot;
+
+ #else
+
+ buf[0] = 'A' + id / 2600 % 26;
+ buf[1] = 'A' + id / 100 % 26;
+ buf[2] = '0' + id / 10 % 10;
+ buf[3] = '0' + id % 10;
+ buf[4] = 0;
+
+ #endif
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanNiceID4 ( int * res, ccp arg )
+{
+ DASSERT(res);
+ *res = -1;
+
+ if (!arg)
+ return 0;
+
+ char ch = tolower((int)arg[0]);
+ if ( ch >= 'a' && ch <= 'z' )
+ {
+ int num = ch - 'a';
+ ch = tolower((int)arg[1]);
+ if ( ch >= 'a' && ch <= 'z' )
+ {
+ num = 26 * num + ch - 'a';
+ ch = arg[2];
+ if ( ch >= '0' && ch <= '9' )
+ {
+ num = 10 * num + ch - '0';
+ ch = arg[3];
+ if ( ch >= '0' && ch <= '9' )
+ {
+ *res = 10 * num + ch - '0';
+ return (char*)arg+4;
+ }
+ }
+ }
+ }
+ return (char*)arg;
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-numeric.h b/project/dclib/dclib-numeric.h
new file mode 100644
index 0000000..19fefda
--- /dev/null
+++ b/project/dclib/dclib-numeric.h
@@ -0,0 +1,839 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_NUMERIC_H
+#define DCLIB_NUMERIC_H 1
+
+#include "dclib-types.h"
+#include "dclib-debug.h"
+#include <math.h>
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// float vector ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[float3]]
+
+typedef struct float3
+{
+ union
+ {
+ u32 u[3];
+ float v[3];
+ struct
+ {
+ float x;
+ float y;
+ float z;
+ }
+ __attribute__ ((packed));
+ }
+ __attribute__ ((packed));
+}
+__attribute__ ((packed)) float3;
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[float3List_t]]
+
+typedef struct float3List_t
+{
+ float3 *list; // list of data
+ uint *sort; // NULL or 'size' alloced elements
+ uint used; // used elements of 'list'
+ uint size; // alloced elements of 'list'
+
+} float3List_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeF3L
+(
+ float3List_t *f3l, // valid data
+ uint bin_search // >0: enable binary search
+ // and allocate 'bin_search' elements
+);
+
+void ResetF3L ( float3List_t * f3l );
+float3 * AppendF3L ( float3List_t * f3l );
+uint FindInsertFloatF3L ( float3List_t * f3l, float3 *val, bool fast );
+float3 * GrowF3L ( float3List_t * f3l, uint n );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// double vector ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[double3]]
+
+typedef struct double3
+{
+ union
+ {
+ u64 u[3];
+ double v[3];
+ struct
+ {
+ double x;
+ double y;
+ double z;
+ };
+ };
+
+} double3;
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[double3List_t]]
+
+typedef struct double3List_t
+{
+ double3 *list; // list of data
+ uint used; // used elements of 'list'
+ uint size; // alloced elements of 'list'
+
+} double3List_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeD3L ( double3List_t * d3l );
+void ResetD3L ( double3List_t * d3l );
+double3 * AppendD3L ( double3List_t * d3l );
+uint FindInsertFloatD3L ( double3List_t * d3l, double3 *val, bool fast );
+double3 * GrowD3L ( double3List_t * d3l, uint n );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// 2D vector macros ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define SideOfLine(ax,ay,bx,by,px,py) \
+ ( ((bx)-(ax)) * ((py)-(ay)) - ((by)-(ay)) * ((px)-(ax)) )
+#define SideOfLineXY(a,b,p) \
+ ( ((b).x-(a).x) * ((p).y-(a).y) - ((b).y-(a).y) * ((p).x-(a).x) )
+#define SideOfLineXZ(a,b,p) \
+ ( ((b).x-(a).x) * ((p).z-(a).z) - ((b).z-(a).z) * ((p).x-(a).x) )
+#define SideOfLineYZ(a,b,p) \
+ ( ((b).y-(a).y) * ((p).z-(a).z) - ((b).z-(a).z) * ((p).y-(a).y) )
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// 3D vector macros ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define NULL_EPSILON 1e-9 // 'fabs(value) < NULL_EPSILON' is 0.0
+#define MIN_DEGREE 1e-4 // 'fabs(value) < MIN_DEGREE' is 0.0
+
+#define Assign3(d,s) do { (d).x = (s).x; (d).y = (s).y; (d).z = (s).z; } while (0)
+
+#define IsNull3(d) ( (d).x == 0.0 && (d).y == 0.0 && (d).z == 0.0 )
+#define IsNull3e(d,e) ( fabs((d).x) < (e) \
+ && fabs((d).y) < (e) \
+ && fabs((d).z) < (e) )
+
+#define IsOne3(d) ( (d).x == 1.0 && (d).y == 1.0 && (d).z == 1.0 )
+#define IsOne3e(d,e) ( fabs((d).x-1.0) < (e) \
+ && fabs((d).y-1.0) < (e) \
+ && fabs((d).z-1.0) < (e) )
+
+#define IsVal3(d,v) ( (d).x == (v) && (d).y == (v) && (d).z == (v) )
+#define IsVal3e(d,v,e) ( fabs( (d).x - (v) ) < (e) \
+ && fabs( (d).y - (v) ) < (e) \
+ && fabs( (d).z - (v) ) < (e) )
+
+#define IsEQ3(a,b) ( (a).x == (b).x && (a).y == (b).y && (a).z == (b).z )
+#define IsEQ3e(a,b,e) ( fabs( (a).x - (b).x ) < (e) \
+ && fabs( (a).y - (b).y ) < (e) \
+ && fabs( (a).z - (b).z ) < (e) )
+
+#define Add3(d,a,b) do { (d).x = (a).x + (b).x; \
+ (d).y = (a).y + (b).y; \
+ (d).z = (a).z + (b).z; \
+ } while (0)
+
+#define Sub3(d,a,b) do { (d).x = (a).x - (b).x; \
+ (d).y = (a).y - (b).y; \
+ (d).z = (a).z - (b).z; \
+ } while (0)
+
+#define Neg3(v) do { (v).x = -(v).x; (v).y = -(v).y; (v).z = -(v).z; } while (0)
+
+#define Div3f(d,a,f) do { (d).x = (a).x / (f); \
+ (d).y = (a).y / (f); \
+ (d).z = (a).z / (f); \
+ } while (0)
+
+#define Mult3f(d,a,f) do { (d).x = (a).x * (f); \
+ (d).y = (a).y * (f); \
+ (d).z = (a).z * (f); \
+ } while (0)
+
+#define LengthSqare3(d) ( (d).x*(d).x + (d).y*(d).y + (d).z*(d).z )
+#define Length3(d) sqrt( (d).x*(d).x + (d).y*(d).y + (d).z*(d).z )
+#define DotProd3(a,b) ( (a).x*(b).x + (a).y*(b).y + (a).z*(b).z )
+
+#define CrossProd3(d,a,b) do { (d).x = (a).y*(b).z - (a).z*(b).y; \
+ (d).y = (a).z*(b).x - (a).x*(b).z; \
+ (d).z = (a).x*(b).y - (a).y*(b).x; \
+ } while (0)
+
+#define CrossProd3x(d,a,b) do { typeof((d).x) xx = (a).y*(b).z - (a).z*(b).y; \
+ typeof((d).y) yy = (a).z*(b).x - (a).x*(b).z; \
+ (d).z = (a).x*(b).y - (a).y*(b).x; \
+ (d).x = xx; (d).y = yy; \
+ } while (0)
+
+#define Unit3(v) do { double l = Length3(v); \
+ if (l) { (v).x /= l; (v).y /= l; (v).z /= l; }} while (0)
+#define Unit3f(v,f) do { double l = Length3(v); \
+ if (l) { l = (f)/l; \
+ (v).x *= l; (v).y *= l; (v).z *= l; }} while (0)
+
+#define MinMax3p(min,max,a,b,c) do { \
+ if (a<b) { min = a < c ? a : c; max = b > c ? b : c; } \
+ else { min = b < c ? b : c; max = a > c ? a : c; } } while (0)
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// check numbers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static inline bool IsNormalF ( float f )
+{
+ return isfinite(f);
+// const int fpclass = fpclassify(f);
+// return fpclass == FP_NORMAL || fpclass == FP_ZERO;
+}
+
+static inline bool IsNormalD ( double d )
+{
+ return isfinite(d);
+// const int fpclass = fpclassify(d);
+// return fpclass == FP_NORMAL || fpclass == FP_ZERO;
+}
+
+bool IsNormalF3 ( float *f3 );
+bool IsNormalF3be ( float *f3 ); // big endian!
+bool IsNormalD3 ( double *d3 );
+
+bool IsEqualD3 ( const double3 *a, const double3 *b,
+ double null_epsilon, double diff_epsilon );
+
+//--- new functions
+bool IsSameF ( float a, float b, int bit_diff );
+bool IsSameD ( double a, double b, int bit_diff );
+bool IsSameF3 ( const float *a, const float *b, int bit_diff );
+bool IsSameD3 ( const double *a, const double *b, int bit_diff );
+
+//--- radiant and gegree
+#define rad2deg(n) ( (n)*(180.0/M_PI) )
+#define deg2rad(n) ( (n)*(M_PI/180.0) )
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// 2D vector functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint PointsInConvexPolygonF
+(
+ // return, how many points are inside the polygon
+
+ float *pts, // points: pointer to a array with '2*n_pts' elements
+ uint n_pts, // Number of (x,y) pairs in 'pts'
+
+ float *pol, // polygon: pointer to a array with '2*n_pol' elements
+ uint n_pol, // number of (x,y) pairs in 'pol'
+
+ bool all, // false: count the numbers of points inside
+ // true: return '1' for all points inside and otherwise '0'
+
+ int *r_dir // If not NULL then return:
+ // -1: polygon is defined counterclockwise
+ // 0: unknown
+ // +1: polygon is defined clockwise
+ // If at least one point is inside, the result is known.
+);
+
+//-----------------------------------------------------------------------------
+
+uint PointsInConvexPolygonD
+(
+ // return, how many points are inside the polygon
+
+ double *pts, // points: pointer to a array with '2*n_pts' elements
+ uint n_pts, // Number of (x,y) pairs in 'pts'
+
+ double *pol, // polygon: pointer to a array with '2*n_pol' elements
+ uint n_pol, // number of (x,y) pairs in 'pol'
+
+ bool all, // false: count the numbers of points inside
+ // true: return '1' for all points inside and otherwise '0'
+
+ int *r_dir // If not NULL then return:
+ // -1: polygon is defined counterclockwise
+ // 0: unknown
+ // +1: polygon is defined clockwise
+ // If at least one point is inside, the result is known.
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// float34 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[float34]]
+
+typedef struct float34 // transformation matrix
+{
+ union
+ {
+ float v[12];
+ float m[3][4];
+ struct
+ {
+ float x[4];
+ float y[4];
+ float z[4];
+ }
+ __attribute__ ((packed));
+ }
+ __attribute__ ((packed));
+}
+__attribute__ ((packed)) float34;
+
+//-----------------------------------------------------------------------------
+
+void ClearFloat34 ( float34 *f34 );
+void SetupFloat34 ( float34 *f34 );
+
+void SetupMatrixF34
+(
+ float34 *mat, // data to setup
+ float3 *scale, // NULL or scaling vector
+ float3 *rotate, // NULL or rotation vector in *radiant*
+ float3 *translate // NULL or translation vector
+);
+
+void CalcInverseMatrixF34
+(
+ float34 *i, // store inverse matrix here
+ const float34 *t // transformation matrix
+);
+
+void MultiplyF34
+(
+ float34 *dest, // valid, store result here, may be 'src1' or 'src2'
+ const float34 *src1, // first valid source, if NULL use dest
+ const float34 *src2 // second valid source, if NULL use dest
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// double34 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[double34]]
+
+typedef struct double34 // transformation matrix
+{
+ union
+ {
+ double v[12];
+ double m[3][4];
+ struct
+ {
+ double x[4];
+ double y[4];
+ double z[4];
+ }
+ __attribute__ ((packed));
+ }
+ __attribute__ ((packed));
+}
+__attribute__ ((packed)) double34;
+
+//-----------------------------------------------------------------------------
+
+void ClearDouble34 ( double34 *d34 );
+void SetupDouble34 ( double34 *d34 );
+
+void SetupMatrixD34
+(
+ double34 *mat, // data to setup
+ double3 *scale, // NULL or scaling vector
+ double3 *rotate, // NULL or rotation vector in *radiant*
+ double3 *translate // NULL or translation vector
+);
+
+void CalcInverseMatrixD34
+(
+ double34 *i, // store inverse matrix here
+ const double34 *t // transformation matrix
+);
+
+void MultiplyD34
+(
+ double34 *dest, // valid, store result here, may be 'src1' or 'src2'
+ const double34 *src1, // first valid source, if NULL use dest
+ const double34 *src2 // second valid source, if NULL use dest
+);
+
+//-----------------------------------------------------------------------------
+
+void CopyF34toD34
+(
+ double34 *dest, // destination matrix
+ const float34 *src // source matrix
+);
+
+void CopyD34toF34
+(
+ float34 *dest, // destination matrix
+ const double34 *src // source matrix
+);
+
+//-----------------------------------------------------------------------------
+
+static inline float3 TransformF3D34 ( const double34 *d34, const float3 *val )
+{
+ DASSERT(d34);
+ DASSERT(val);
+ const double *m = d34->v;
+ float3 res;
+ res.x = m[ 0] * val->x + m[ 1] * val->y + m[ 2] * val->z + m[ 3];
+ res.y = m[4+0] * val->x + m[4+1] * val->y + m[4+2] * val->z + m[4+3];
+ res.z = m[8+0] * val->x + m[8+1] * val->y + m[8+2] * val->z + m[8+3];
+ return res;
+}
+
+static inline double3 TransformD3D34 ( const double34 *d34, const double3 *val )
+{
+ DASSERT(d34);
+ DASSERT(val);
+ const double *m = d34->v;
+ double3 res;
+ res.x = m[ 0] * val->x + m[ 1] * val->y + m[ 2] * val->z + m[ 3];
+ res.y = m[4+0] * val->x + m[4+1] * val->y + m[4+2] * val->z + m[4+3];
+ res.z = m[8+0] * val->x + m[8+1] * val->y + m[8+2] * val->z + m[8+3];
+ return res;
+}
+
+static inline float3 TransformF3F34 ( const float34 *d34, const float3 *val )
+{
+ DASSERT(d34);
+ DASSERT(val);
+ const float *m = d34->v;
+ float3 res;
+ res.x = m[ 0] * val->x + m[ 1] * val->y + m[ 2] * val->z + m[ 3];
+ res.y = m[4+0] * val->x + m[4+1] * val->y + m[4+2] * val->z + m[4+3];
+ res.z = m[8+0] * val->x + m[8+1] * val->y + m[8+2] * val->z + m[8+3];
+ return res;
+}
+
+static inline double3 TransformD3F34 ( const float34 *d34, const double3 *val )
+{
+ DASSERT(d34);
+ DASSERT(val);
+ const float *m = d34->v;
+ double3 res;
+ res.x = m[ 0] * val->x + m[ 1] * val->y + m[ 2] * val->z + m[ 3];
+ res.y = m[4+0] * val->x + m[4+1] * val->y + m[4+2] * val->z + m[4+3];
+ res.z = m[8+0] * val->x + m[8+1] * val->y + m[8+2] * val->z + m[8+3];
+ return res;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatrixD_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[MatrixD_t]]
+
+typedef struct MatrixD_t
+{
+ //--- status
+
+ bool valid; // false: data structure needs initialization
+ bool norm_valid; // false: normed values are invalid
+ bool tmatrix_valid; // false: transformation matrix is invalid
+ bool imatrix_valid; // false: inverse matrix is invalid
+ uint sequence_number; // Used for tracking. It is incremented
+ // on every new calculation by CalcNormMatrixD()
+
+ //--- matrix usage status
+
+ u8 use_matrix; // 0: matrix not needed for transformation
+ // 1: matrix needed for transformation
+ // 2: matrix is the only valid reference
+
+ //--- status, only valid, if 'norm_valid'
+
+ u8 scale_enabled; // scaling is enabled: 1=x, 2=y, 4=z
+ u8 rotate_enabled; // rotation is enabled: 1=x, 2=y, 4=z
+ u8 translate_enabled; // translation is enabled: 1=x, 2=y, 4=z
+ u8 transform_enabled; // 'OR' result of the 3 enabled values above
+ // OR 'use_matrix<<3'
+
+ //--- base values: clear 'norm_valid' and '*matrix_valid' on change
+
+ double3 scale; // scale vector
+ double3 scale_origin; // origin for scaling
+ double3 shift; // shift vector
+ double3 rotate_deg; // rotation in degree and radiant.
+ double3 rotate_rad; // both values are used as sum.
+ double3 rotate_origin[3]; // origin for rotation
+ double3 translate; // translation vector
+
+ //--- normed values, calculated by CalcNormMatrixD()
+
+ double3 norm_scale; // scale vector
+ double3 norm_rotate_deg; // rotation vector in degree
+ double3 norm_rotate_rad; // rotation vector in radiant
+ double3 norm_translate; // translation vector
+ double3 norm_pos2d; // recommended positions for 2d transformations
+
+ //--- matrices, calculated by CalcMatrixD()
+
+ double34 trans_matrix; // transformation matrix
+ double34 inv_matrix; // inverse transformation matrix
+}
+MatrixD_t;
+
+//-----------------------------------------------------------------------------
+
+// statistics
+extern u64 N_MatrixD_forward; // total number of forward transformations
+extern u64 N_MatrixD_inverse; // total number of inverse transformations
+
+//-----------------------------------------------------------------------------
+
+void InitializeMatrixD ( MatrixD_t * mat );
+
+void CopyMatrixD
+(
+ MatrixD_t *dest, // valid destination
+ const
+ MatrixD_t *src // NULL or source
+);
+
+void TermAssignMatrixD
+(
+ // called after dirext matrix manipulations
+ MatrixD_t * mat // valid data structure
+);
+
+//-----------------------------------------------------------------------------
+
+MatrixD_t * SetScaleMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * scale, // NULL: clear scale, not NULL: set scale
+ double3 * origin // not NULL: Origin for scaling
+);
+
+MatrixD_t * SetShiftMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * shift // NULL: clear Shift, not NULL: set Shift
+);
+
+MatrixD_t * SetScaleShiftMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ uint xyz, // 0..2: x/y/z coordinate to set
+ double old1, // old value of first point
+ double new1, // new value of first point
+ double old2, // old value of second point
+ double new2 // new value of second point
+);
+
+MatrixD_t * SetRotateMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ uint xyz, // 0..2: x/y/z coordinate to set
+ double degree, // angle in degree
+ double radiant, // angle in radiant
+ double3 * origin // not NULL: Origin for the rotation
+);
+
+MatrixD_t * SetTranslateMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * translate // NULL: clear translate, not NULL: set translate
+);
+
+MatrixD_t * SetMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ double34 * src // NULL or matrix to set
+);
+
+//-----------------------------------------------------------------------------
+
+MatrixD_t * SetAScaleMatrixD
+(
+ // calculate the matrix as rotation around a specified axis
+
+ MatrixD_t * mat, // valid data structure
+ double scale, // scale factor
+ double3 * dir // NULL or scaling direction as vector
+);
+
+MatrixD_t * SetARotateMatrixD
+(
+ // calculate the matrix as rotation around a specified axis
+
+ MatrixD_t * mat, // valid data structure
+ double degree, // angle in degree
+ double radiant, // angle in radiant, add to degree with factor
+ double3 * pt1, // point #1 to define the axis; if NULL use (0,0,0)
+ double3 * pt2 // point #2 to define the axis; if NULL use (0,0,0)
+);
+
+//-----------------------------------------------------------------------------
+
+MatrixD_t * CheckStatusMatrixD
+(
+ MatrixD_t * mat // valid data structure
+);
+
+MatrixD_t * CalcNormMatrixD
+(
+ MatrixD_t * mat // valid data structure
+);
+
+MatrixD_t * CalcTransMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ bool always // false: calc transformation matrix only
+ // if needed for transformation
+ // true: ignore 'rotate_enabled'
+);
+
+MatrixD_t * CalcMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ bool always // false: calc both matrices only
+ // if needed for transformation
+ // true: calc matrices always
+);
+
+MatrixD_t * MultiplyMatrixD
+(
+ MatrixD_t *dest, // valid, store result here, may be 'src1' or 'src2'
+ MatrixD_t *src1, // first valid source, if NULL use dest
+ MatrixD_t *src2, // second valid source, if NULL use dest
+ uint inverse_mode // bit field:
+ // 1: use inverse matrix of src1
+ // 2: use inverse matrix of src2
+);
+
+MatrixD_t * CalcVectorsMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ bool always // false: calc vectors only if matrix is valid
+ // true: calc matrix if invalid & vectors always
+);
+
+static inline bool TransformationEnabledMatrixD ( MatrixD_t * mat )
+{
+ return CalcNormMatrixD(mat)->transform_enabled != 0;
+}
+
+//-----------------------------------------------------------------------------
+
+double3 TransformD3MatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ double3 * val // value to transform
+);
+
+double3 InvTransformD3MatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ double3 * val // value to transform
+);
+
+void TransformD3NMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * val, // value array to transform
+ int n, // number of 3D vectors in 'val'
+ uint off // if n>1: offset from one to next 'val' vector
+);
+
+void InvTransformD3NMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * val, // value array to transform
+ int n, // number of 3D vectors in 'val'
+ uint off // if n>1: offset from one to next 'val' vector
+);
+
+//-----------------------------------------------------------------------------
+
+float3 TransformF3MatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ float3 * val // value to transform
+);
+
+float3 InvTransformF3MatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ float3 * val // value to transform
+);
+
+void TransformF3NMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ float3 * val, // value array to transform
+ int n, // number of 3D vectors in 'val'
+ uint off // if n>1: offset from one to next 'val' vector
+);
+
+void InvTransformF3NMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ float3 * val, // value array to transform
+ int n, // number of 3D vectors in 'val'
+ uint off // if n>1: offset from one to next 'val' vector
+);
+
+//-----------------------------------------------------------------------------
+
+double3 BaseTransformD3MatrixD
+(
+ // transform using the base parameters
+
+ MatrixD_t * mat, // valid data structure
+ const
+ double3 * val // value to transform
+);
+
+
+double3 NormTransformD3MatrixD
+(
+ // transform using the norm parameters
+
+ MatrixD_t * mat, // valid data structure
+ const
+ double3 * val // value to transform
+);
+
+double3 TransformHelperD3MatrixD
+(
+ // transform using vectors
+
+ double3 *scale, // NULL or scale vector
+ double3 *scale_origin, // NULL or origin for scaling
+ double3 *shift, // NULL or shift vector
+ double3 *rotate_deg, // NULL or rotation in degree
+ double3 *xrot_origin, // NULL or origin for x-rotation
+ double3 *yrot_origin, // NULL or origin for y-rotation
+ double3 *zrot_origin, // NULL or origin for z-rotation
+ double3 *translate, // NULL or translation vector
+
+ const
+ double3 * val // value to transform
+);
+
+//-----------------------------------------------------------------------------
+
+void PrintMatrixD
+(
+ FILE * f, // file to print to
+ uint indent, // indention
+ ccp eol, // not NULL: print as 'end of line'
+
+ MatrixD_t * mat, // valid data structure
+ uint create, // bitfield for setup:
+ // 1: create normals if invalid
+ // 2: create transform matrix if invalid
+ // 4: create inverse matrix if invalid
+ uint print // bitfield print selection:
+ // 1: print base vectors
+ // 2: print normalized values, if valid
+ // 4: print transformation matrix, if valid
+ // 8: print inverse matrix, if valid
+ // 0x10: print also header, if only 1 section is print
+ // 0x20: print all, don't suppress NULL vectors
+ // 0x40: print an additional EOL behind each section
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CRC16 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define CRC16_CCITT_POLYNOM 0x1021
+
+void CreateCRC16Table ( u16 table[0x100], u16 polynom );
+const u16 * GetCRC16Table ( u16 polynom );
+
+u16 CalcCRC16 ( cvp data, uint data_size, u16 polynom, u16 preset );
+static inline u16 CalcCRC16CCITT ( cvp data, uint data_size )
+ { return CalcCRC16(data,data_size,CRC16_CCITT_POLYNOM,0); }
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// misc ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static inline int float2int ( float f ) { return (int) truncf(f+0.5); }
+static inline int double2int ( double d ) { return (int) trunc (d+0.5); }
+static inline s64 float2int64 ( float f ) { return (s64) truncf(f+0.5); }
+static inline s64 double2int64 ( double d ) { return (s64) trunc (d+0.5); }
+
+///////////////////////////////////////////////////////////////////////////////
+
+// all values are multiple of 26*26*100 = 67600 = 0x10810
+#define GOOD_MAX_NICE4_5 67600 // 1 * 67600
+#define GOOD_MAX_NICE4_6 946400 // 14 * 67600
+#define GOOD_MAX_NICE4_7 9937200 // 147 * 67600
+#define GOOD_MAX_NICE4_8 99980400 // 1479 * 67600
+#define GOOD_MAX_NICE4_9 999939200 // 14792 * 67600
+#define GOOD_MAX_NICE4 4294966000 // 63535 * 67600 = 0xfffffaf0 = -1296
+
+ccp PrintNiceID4 ( uint nice_gid );
+char * ScanNiceID4 ( int * res, ccp arg );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_NUMERIC_H
+
diff --git a/project/dclib/dclib-option.c b/project/dclib/dclib-option.c
new file mode 100644
index 0000000..5a2044b
--- /dev/null
+++ b/project/dclib/dclib-option.c
@@ -0,0 +1,1012 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include "dclib-option.h"
+#include <getopt.h>
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// GenericOpt_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeGenericOpt ( GenericOpt_t *go, char first_char )
+{
+ DASSERT(go);
+ memset(go,0,sizeof(*go));
+ InitializeMemPool(&go->str_pool,2000);
+
+ char *dest = go->sopt;
+ if (first_char)
+ *dest++ = first_char;
+ *dest++ = ':';
+ *dest = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetGenericOpt ( GenericOpt_t *go )
+{
+ DASSERT(go);
+ ResetMemPool(&go->str_pool);
+ FREE(go->lopt);
+ InitializeGenericOpt(go,0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AddGenericOpt
+(
+ GenericOpt_t *go, // data structure
+ const GenericOptParm_t *gp, // list to add, list end on name==NULL
+ bool rm_minus // remove '-' on options for alternatives
+)
+{
+ DASSERT(go);
+ DASSERT(gp);
+
+ char * sopt_ptr = go->sopt + strlen(go->sopt);
+ char * sopt_end = go->sopt + sizeof(go->sopt) - 3;
+
+ for ( ; gp->name; gp++ )
+ {
+ ccp ptr = gp->name;
+ for(;;)
+ {
+ while ( *ptr == '|' )
+ ptr++;
+
+ uint have_minus = 0;
+ char name_buf[50], *name_ptr = name_buf;
+ while ( *ptr && *ptr != '|' )
+ {
+ char ch = *ptr++;
+ if ( ch == '-' )
+ have_minus++;
+ if ( name_ptr < name_buf + sizeof(name_buf) - 1 )
+ *name_ptr++ = ch;
+ }
+ *name_ptr = 0;
+ uint len = name_ptr - name_buf;
+ if (!len)
+ break;
+
+ //--- short option
+
+ if ( len == 1 )
+ {
+ uchar ch = *name_buf;
+ if ( ch > ' ' && ch < 0x80 && ch != ':'
+ && !go->sopt_id[ch] && sopt_ptr < sopt_end )
+ {
+ go->sopt_id[ch] = gp->id;
+ *sopt_ptr++ = ch;
+ if ( gp->arg )
+ {
+ *sopt_ptr++ = ':';
+ if ( gp->arg > 1 )
+ *sopt_ptr++ = ':';
+ }
+ }
+ }
+
+
+ //--- long option
+
+ for(;;)
+ {
+ #if 0 // avoid duplicates, needed?
+ uint i;
+ for ( i = 0; i < go->lopt_used; i++ )
+ if (!strcmp(name_buf,go->lopt[i].name))
+ goto skip;
+ #endif
+
+ if ( go->lopt_used == go->lopt_size )
+ {
+ go->lopt_size += 100;
+ go->lopt = REALLOC( go->lopt, go->lopt_size * sizeof(*go->lopt) );
+ }
+
+ struct option *opt = go->lopt + go->lopt_used++;
+ opt->name = StrDupMemPool(&go->str_pool,name_buf);
+ opt->has_arg = gp->arg;
+ opt->flag = 0;
+ opt->val = gp->id;
+
+ //skip:;
+ if ( !have_minus || !rm_minus )
+ break;
+ have_minus = 0;
+
+ char *src = name_buf, *dest = name_buf;
+ while ( *src )
+ {
+ if ( *src != '-' )
+ *dest++ = *src;
+ src++;
+ }
+ *dest = 0;
+ }
+ }
+ }
+
+ *sopt_ptr = 0;
+
+ if ( go->lopt_used == go->lopt_size )
+ {
+ go->lopt_size += 100;
+ go->lopt = REALLOC( go->lopt, go->lopt_size * sizeof(*go->lopt) );
+ }
+ struct option *opt = go->lopt + go->lopt_used;
+ memset(opt,0,sizeof(*opt));
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// global options: parameters ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+GParamValueType_t GetGParamValueType ( GOPT_t gt )
+{
+ if ( gt & GOPT_RESERVED_BEG )
+ gt = GetGOPType(gt);
+
+ switch (gt)
+ {
+ case GOPT_IDX_INC0:
+ case GOPT_IDX_INC:
+ case GOPT_IDX_DEC0:
+ case GOPT_IDX_DEC:
+ case GOPT_IDX_SETP:
+ case GOPT_IDX_SETM:
+ case GOPT_IDX_SINT:
+ return GPT_SN;
+
+ case GOPT_IDX_UINT:
+ case GOPT_IDX_OFF_ON:
+ case GOPT_IDX_OR:
+ case GOPT_IDX_SETBIT:
+ return GPT_UN;
+
+ case GOPT_IDX_SETP64:
+ case GOPT_IDX_SETM64:
+ case GOPT_IDX_S64:
+ return GPT_SN64;
+
+ case GOPT_IDX_U64:
+ case GOPT_IDX_OR64:
+ case GOPT_IDX_SETBIT64:
+ case GOPT_IDX_HEX:
+ case GOPT_IDX_SIZE:
+ return GPT_UN64;
+
+ case GOPT_IDX_DOUBLE:
+ case GOPT_IDX_DURATION:
+ return GPT_D;
+
+ case GOPT_IDX_STRING:
+ return GPT_STR;
+
+ case GOPT_IDX_BIT:
+ return GPT_BIT;
+
+ default:
+ return GPT_NONE;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+GetGParam_t GetGParamINT ( const GParam_t *par, int *return_val )
+{
+ if (par)
+ {
+ DASSERT( par->vtype == GetGParamValueType(par->ptype) );
+ switch (par->vtype)
+ {
+ case GPT_NONE:
+ case GPT_STR:
+ case GPT_BIT:
+ return GGP_NONE;
+
+ case GPT_SN:
+ if (return_val)
+ *return_val = par->sn;
+ return GGP_OK;
+
+ case GPT_UN:
+ if (return_val)
+ *return_val = par->un;
+ return GGP_SIGN;
+
+ case GPT_SN64:
+ if (return_val)
+ *return_val = par->sn64;
+ return GGP_CONVERT;
+
+ case GPT_UN64:
+ if (return_val)
+ *return_val = par->un64;
+ return GGP_CONVERT;
+
+ case GPT_D:
+ if (return_val)
+ *return_val = par->d;
+ return GGP_CONVERT;
+ }
+ }
+ return GGP_NONE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GetGParam_t GetGParamUINT ( const GParam_t *par, uint *return_val )
+{
+ if (par)
+ {
+ DASSERT( par->vtype == GetGParamValueType(par->ptype) );
+ switch (par->vtype)
+ {
+ case GPT_NONE:
+ case GPT_STR:
+ case GPT_BIT:
+ return GGP_NONE;
+
+ case GPT_SN:
+ if (return_val)
+ *return_val = par->sn;
+ return GGP_SIGN;
+
+ case GPT_UN:
+ if (return_val)
+ *return_val = par->un;
+ return GGP_OK;
+
+ case GPT_SN64:
+ if (return_val)
+ *return_val = par->sn64;
+ return GGP_CONVERT;
+
+ case GPT_UN64:
+ if (return_val)
+ *return_val = par->un64;
+ return GGP_CONVERT;
+
+ case GPT_D:
+ if (return_val)
+ *return_val = par->d;
+ return GGP_CONVERT;
+ }
+ }
+ return GGP_NONE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GetGParam_t GetGParamS64 ( const GParam_t *par, s64 *return_val )
+{
+ if (par)
+ {
+ DASSERT( par->vtype == GetGParamValueType(par->ptype) );
+ switch (par->vtype)
+ {
+ case GPT_NONE:
+ case GPT_STR:
+ case GPT_BIT:
+ return GGP_NONE;
+
+ case GPT_SN:
+ if (return_val)
+ *return_val = par->sn;
+ return GGP_OK;
+
+ case GPT_UN:
+ if (return_val)
+ *return_val = par->un;
+ return GGP_SIGN;
+
+ case GPT_SN64:
+ if (return_val)
+ *return_val = par->sn64;
+ return GGP_OK;
+
+ case GPT_UN64:
+ if (return_val)
+ *return_val = par->un64;
+ return GGP_SIGN;
+
+ case GPT_D:
+ if (return_val)
+ *return_val = par->d;
+ return GGP_CONVERT;
+ }
+ }
+ return GGP_NONE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GetGParam_t GetGParamU64 ( const GParam_t *par, u64 *return_val )
+{
+ if (par)
+ {
+ DASSERT( par->vtype == GetGParamValueType(par->ptype) );
+ switch (par->vtype)
+ {
+ case GPT_NONE:
+ case GPT_STR:
+ case GPT_BIT:
+ return GGP_NONE;
+
+ case GPT_SN:
+ if (return_val)
+ *return_val = par->sn;
+ return GGP_SIGN;
+
+ case GPT_UN:
+ if (return_val)
+ *return_val = par->un;
+ return GGP_OK;
+
+ case GPT_SN64:
+ if (return_val)
+ *return_val = par->sn64;
+ return GGP_SIGN;
+
+ case GPT_UN64:
+ if (return_val)
+ *return_val = par->un64;
+ return GGP_OK;
+
+ case GPT_D:
+ if (return_val)
+ *return_val = par->d;
+ return GGP_CONVERT;
+ }
+ }
+ return GGP_NONE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GetGParam_t GetGParamDBL ( const GParam_t *par, double *return_val )
+{
+ if (par)
+ {
+ DASSERT( par->vtype == GetGParamValueType(par->ptype) );
+ switch (par->vtype)
+ {
+ case GPT_NONE:
+ case GPT_STR:
+ case GPT_BIT:
+ return GGP_NONE;
+
+ case GPT_SN:
+ if (return_val)
+ *return_val = par->sn;
+ return GGP_CONVERT;
+
+ case GPT_UN:
+ if (return_val)
+ *return_val = par->un;
+ return GGP_CONVERT;
+
+ case GPT_SN64:
+ if (return_val)
+ *return_val = par->sn64;
+ return GGP_CONVERT;
+
+ case GPT_UN64:
+ if (return_val)
+ *return_val = par->un64;
+ return GGP_CONVERT;
+
+ case GPT_D:
+ if (return_val)
+ *return_val = par->d;
+ return GGP_OK;
+ }
+ }
+ return GGP_NONE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GetGParam_t GetGParamSTR ( const GParam_t *par, ccp *return_val )
+{
+ DASSERT( !par || par->vtype == GetGParamValueType(par->ptype) );
+ if ( par && par->vtype == GPT_STR )
+ {
+ if (return_val)
+ *return_val = par->str;
+ return GGP_OK;
+ }
+ return GGP_NONE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GetGParam_t GetGParamBIT ( const GParam_t *par, u32 *return_val, u32 *return_mask )
+{
+ DASSERT( !par || par->vtype == GetGParamValueType(par->ptype) );
+ if ( par && par->vtype == GPT_BIT )
+ {
+ if (return_val)
+ *return_val = par->bit.val;
+ if (return_mask)
+ *return_mask = par->bit.mask;
+ return GGP_OK;
+ }
+ return GGP_NONE;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintGParam ( const GParam_t *par )
+{
+ DASSERT(par);
+ DASSERT( par->vtype == GetGParamValueType(par->ptype) );
+ char buf[100];
+ ccp extra = "";
+
+ switch (par->ptype)
+ {
+ case GOPT_IDX_NULL:
+ snprintf(buf,sizeof(buf),"[-]");
+ break;
+
+ case GOPT_IDX_INC0:
+ case GOPT_IDX_DEC0:
+ case GOPT_IDX_DEC:
+ extra = "?";
+ case GOPT_IDX_INC:
+ snprintf(buf,sizeof(buf),"[%sINC] %d",extra,par->sn);
+ break;
+
+ case GOPT_IDX_SETP:
+ case GOPT_IDX_SETM:
+ extra = "?";
+ case GOPT_IDX_SINT:
+ snprintf(buf,sizeof(buf),"[%sSINT] %d",extra,par->sn);
+ break;
+
+ case GOPT_IDX_OR:
+ case GOPT_IDX_SETBIT:
+ case GOPT_IDX_OFF_ON:
+ extra = "?";
+ case GOPT_IDX_UINT:
+ snprintf(buf,sizeof(buf),"[%sUINT] %u",extra,par->un);
+ break;
+
+ case GOPT_IDX_BIT:
+ snprintf(buf,sizeof(buf),"[BIT] set=%x, clr=%x",
+ par->bit.val, par->bit.mask & ~par->bit.val );
+ break;
+
+ case GOPT_IDX_SETP64:
+ case GOPT_IDX_SETM64:
+ extra = "?";
+ case GOPT_IDX_S64:
+ snprintf(buf,sizeof(buf),"[%sS64] %lld",extra,par->sn64);
+ break;
+
+ case GOPT_IDX_OR64:
+ case GOPT_IDX_SETBIT64:
+ extra = "?";
+ case GOPT_IDX_U64:
+ snprintf(buf,sizeof(buf),"[%sU64] %llu",extra,par->un64);
+ break;
+
+ case GOPT_IDX_HEX:
+ snprintf(buf,sizeof(buf),"[HEX] 0x%llx",par->un64);
+ break;
+
+ case GOPT_IDX_SIZE:
+ snprintf(buf,sizeof(buf),"[SIZE] %llu = %s",
+ par->un64, PrintSize1024(0,0,par->un64,false) );
+ break;
+
+ case GOPT_IDX_DOUBLE:
+ snprintf(buf,sizeof(buf),"[DBL] %12g",par->d);
+ break;
+
+ case GOPT_IDX_DURATION:
+ snprintf(buf,sizeof(buf),"[DUR] %12g = %s",
+ par->d, PrintTimerUSec6(0,0,double2usec(par->d),false) );
+ break;
+
+ case GOPT_IDX_STRING:
+ snprintf(buf,sizeof(buf),"[STR] \"%s\"",par->str);
+ break;
+
+
+ default:
+ snprintf(buf,sizeof(buf),"[?%u]",par->ptype);
+ break;
+ }
+
+ const uint size = strlen(buf) + 1;
+ char *res = GetCircBuf(size);
+ memcpy(res,buf,size);
+ return res;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ScanGOptionsHelper() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const GenericOptParm_t StandardGOptions[] =
+{
+ { GOPT_O_TEST_OPT, 0, "_opt" },
+ { GOPT_O_HELP, 0, "h|help" },
+ { GOPT_O_CLEAR, 0, "c|clear" },
+ { GOPT_O_PAGER, 0, "p|pager" },
+ { GOPT_O_QUIT, 0, "Q|quit" },
+ { GOPT_O_QUIET, 0, "q|quiet" },
+ { GOPT_O_VERBOSE, 0, "v|verbose" },
+ {0,0,0}
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int ScanGOffOn ( GenericOpt_t *go, int opt_stat, bool use_max, ccp optarg )
+{
+ ccp object = "'off/on' value";
+
+ char buf[100];
+ if (go)
+ {
+ uint i;
+ for ( i = 0; i < go->lopt_used; i++ )
+ {
+ struct option *lp = go->lopt + i;
+ if ( lp->val == opt_stat && lp->has_arg && lp->name )
+ {
+ snprintf(buf,sizeof(buf),"'off/on' value of --%s",lp->name);
+ object = buf;
+ break;
+ }
+ }
+ }
+
+ const uint max_num = use_max ? ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE : 0;
+ return ScanKeywordOffOn(optarg,max_num,object);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanGOptionsHelper
+(
+ GOptions_t *gopt, // valid pointer
+ GenericOpt_t *go, // NULL or list with options
+ // => ResetGenericOpt() is called at finish
+ int argc, // argument counter
+ char **argv, // list of arguments
+ int max_param, // >=0: max allowed parameters
+ ScanGOpt_t scan_opt, // scan options
+
+ //--- call back functions
+ ScanOptionFunc OnAddOptions, // not NULL: called once to add options
+ ScanOptionFunc OnOption, // not NULL: called for each option
+ ScanOptionFunc OnArgument, // not NULL: called for each argument
+ void *any_ptr // NULL or user specific pointer for call back
+)
+{
+ DASSERT(gopt);
+ DASSERT(argc>1);
+ DASSERT(argv);
+
+ //--- setup options
+
+ GenericOpt_t go0;
+ if (!go)
+ {
+ go = &go0;
+ InitializeGenericOpt( go, scan_opt & SGO_KNOWN ? '+' : 0 );
+ }
+
+ if (OnAddOptions)
+ OnAddOptions(go,gopt,any_ptr,scan_opt,0);
+ AddGenericOpt(go,StandardGOptions,true);
+
+ if ( argc > 1 )
+ {
+ // skip command
+ argc--;
+ argv++;
+ }
+
+ optind = 0; // '0' is correct for GNU to reset scanning
+ #if !defined(TEST) && !defined(DEBUG)
+ opterr = 0; // suppress error messages to stderr
+ #endif
+
+
+ //--- check options
+
+ InitializeGOptions(gopt);
+
+ int err = 0, test_opt = 0;
+ enumError stat = ERR_OK;
+ while ( stat == ERR_OK )
+ {
+ int opt_stat = getopt_long(argc,argv,go->sopt,go->lopt,0);
+ if ( opt_stat == -1 )
+ break;
+
+ // translate short option names
+ if ( (uint)opt_stat < sizeof(go->sopt_id)/sizeof(*go->sopt_id) && go->sopt_id[opt_stat] )
+ opt_stat = go->sopt_id[opt_stat];
+
+ if ( opt_stat >= GOPT_RESERVED_BEG && opt_stat < GOPT_RESERVED_END )
+ {
+ const uint pidx = opt_stat & GOPT_M_INDEX;
+ if ( pidx >= GOPT_N_PARAM )
+ {
+ printf("%s!!! %s: Parameter index %u must be <%u: %s%s\n",
+ colout->warn, *argv,
+ pidx, GOPT_N_PARAM, argv[optind-1],
+ colout->reset );
+ stat = ERR_SYNTAX;
+ break;
+ }
+ GParam_t *par = gopt->param + pidx;
+
+ const uint ptype = GetGOPType(opt_stat);
+ switch(ptype)
+ {
+ //--- increment & decrement: indicator 'ptype' is always GOPT_IDX_INC
+
+ case GOPT_IDX_INC0:
+ if ( par->ptype != GOPT_IDX_INC )
+ {
+ par->ptype = GOPT_IDX_INC;
+ par->sn = 1;
+ }
+ else
+ par->sn = par->sn >= 0 ? par->sn+1 : 0;
+ break;
+
+ case GOPT_IDX_INC:
+ if ( par->ptype != GOPT_IDX_INC )
+ {
+ par->ptype = GOPT_IDX_INC;
+ par->sn = 1;
+ }
+ else
+ par->sn = par->sn > 0 ? par->sn+1 : 1;
+ break;
+
+ case GOPT_IDX_DEC0:
+ if ( par->ptype != GOPT_IDX_INC )
+ {
+ par->ptype = GOPT_IDX_INC;
+ par->sn = -1;
+ }
+ else
+ par->sn = par->sn <= 0 ? par->sn-1 : 0;
+ break;
+
+ case GOPT_IDX_DEC:
+ if ( par->ptype != GOPT_IDX_INC )
+ {
+ par->ptype = GOPT_IDX_INC;
+ par->sn = -1;
+ }
+ else
+ par->sn = par->sn < 0 ? par->sn-1 : -1;
+ break;
+
+
+ //--- set values: indicator 'ptype' is GOPT_IDX_SINT or GOPT_IDX_S64
+
+ case GOPT_IDX_SETP:
+ par->ptype = GOPT_IDX_SINT;
+ par->un = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
+ break;
+
+ case GOPT_IDX_SETM:
+ par->ptype = GOPT_IDX_SINT;
+ par->sn = - ( ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE );
+ break;
+
+ case GOPT_IDX_SETP64:
+ par->ptype = GOPT_IDX_S64;
+ par->un64 = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
+ break;
+
+ case GOPT_IDX_SETM64:
+ par->ptype = GOPT_IDX_S64;
+ par->sn64 = - ( ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE );
+ break;
+
+
+ //--- set bits: indicator 'ptype' is GOPT_IDX_UINT or GOPT_IDX_U64
+
+ case GOPT_IDX_OR:
+ if ( par->ptype != GOPT_IDX_UINT )
+ {
+ par->ptype = GOPT_IDX_UINT;
+ par->un = 0;
+ }
+ par->un |= ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
+ break;
+
+ case GOPT_IDX_SETBIT:
+ {
+ if ( par->ptype != GOPT_IDX_UINT )
+ {
+ par->ptype = GOPT_IDX_UINT;
+ par->un = 0;
+ }
+ const uint bitnum = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
+ if ( bitnum < 8*sizeof(par->un) )
+ par->un |= 1u << bitnum;
+ }
+ break;
+
+ case GOPT_IDX_OR64:
+ if ( par->ptype != GOPT_IDX_U64 )
+ {
+ par->ptype = GOPT_IDX_U64;
+ par->un64 = 0;
+ }
+ par->un64 |= ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
+ break;
+
+ case GOPT_IDX_SETBIT64:
+ if ( par->ptype != GOPT_IDX_U64 )
+ {
+ par->ptype = GOPT_IDX_U64;
+ par->un64 = 0;
+ }
+ {
+ const uint bitnum = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
+ if ( bitnum < 64 )
+ par->un64 |= 1llu << bitnum;
+ }
+ break;
+
+ case GOPT_IDX_BIT:
+ {
+ if ( par->ptype != GOPT_IDX_BIT )
+ {
+ par->ptype = GOPT_IDX_BIT;
+ par->bit.val = par->bit.mask = 0;
+ }
+
+ const int stat = ScanGOffOn(go,opt_stat,false,optarg);
+ if ( stat >= 0 )
+ {
+ const uint bitnum = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
+ if ( bitnum < 32 )
+ {
+ const u32 m = 1u << bitnum;
+ par->bit.mask |= m;
+ if ( stat > 0 )
+ par->un |= m;
+ else
+ par->un &= ~m;
+ }
+ }
+ }
+ break;
+
+
+ //--- scan parameters
+
+ case GOPT_IDX_SINT:
+ par->ptype = GOPT_IDX_SINT;
+ par->sn = str2l(optarg,0,10);
+ break;
+
+ case GOPT_IDX_UINT:
+ par->ptype = GOPT_IDX_UINT;
+ par->un = str2ul(optarg,0,10);
+ break;
+
+ case GOPT_IDX_OFF_ON:
+ {
+ const int stat = ScanGOffOn(go,opt_stat,true,optarg);
+ par->un = stat >= 0 ? stat : 0;
+ par->ptype = GOPT_IDX_UINT;
+ }
+ break;
+
+ case GOPT_IDX_S64:
+ par->ptype = GOPT_IDX_S64;
+ par->sn64 = str2ll(optarg,0,10);
+ break;
+
+ case GOPT_IDX_U64:
+ par->ptype = GOPT_IDX_U64;
+ par->un64 = str2ull(optarg,0,10);
+ break;
+
+ case GOPT_IDX_HEX:
+ par->ptype = GOPT_IDX_HEX;
+ par->un64 = str2ull(optarg,0,16);
+ break;
+
+ case GOPT_IDX_SIZE:
+ par->ptype = GOPT_IDX_SIZE;
+ ScanSizeU64(&par->un64,optarg,1,1,0); break;
+ break;
+
+ case GOPT_IDX_DOUBLE:
+ par->ptype = GOPT_IDX_DOUBLE;
+ par->d = strtod(optarg,0);
+ break;
+
+ case GOPT_IDX_DURATION:
+ par->ptype = GOPT_IDX_DURATION;
+ ScanDuration(&par->d,optarg,1,SDUMD_M_ALL); break;
+ break;
+
+ case GOPT_IDX_STRING:
+ par->ptype = GOPT_IDX_STRING;
+ par->str = optarg;
+ break;
+
+ default:
+ printf("!!! %s: Undefinef GOPT_* type 0x%02x [0x%02x]: %s\n",
+ *argv, GetGOPType(opt_stat), opt_stat, argv[optind-1] );
+ stat = ERR_ERROR;
+ }
+ par->vtype = GetGParamValueType(par->ptype);
+ continue;
+ }
+
+ switch (opt_stat)
+ {
+ case '?':
+ if ( optopt && (uint)optopt < 0x7f )
+ printf("%s!!! %s: Invalid option: %s-%c [0x%02x]%s\n",
+ colout->warn, *argv,
+ colout->value, optopt, optopt,
+ colout->reset );
+ else
+ printf("%s!!! %s: Invalid option: %s%s%s\n",
+ colout->warn, *argv,
+ colout->value, argv[optind-1],
+ colout->reset );
+ stat = ERR_SYNTAX;
+ break;
+
+ case ':':
+ printf("%s!!! %s: Missing parameter for %s%s%s\n",
+ colout->warn, *argv,
+ colout->value, argv[optind-1],
+ colout->reset );
+ stat = ERR_SYNTAX;
+ break;
+
+
+ //--- standard options
+
+ case GOPT_O_QUIET:
+ gopt->verbose = gopt->verbose > -1 ? -1 : gopt->verbose - 1;
+ break;
+ case GOPT_O_VERBOSE:
+ gopt->verbose = gopt->verbose < 0 ? 0 : gopt->verbose + 1;
+ break;
+
+ case GOPT_O_HELP: gopt->help++; scan_opt |= SGO_PAGER; break;
+ case GOPT_O_CLEAR: gopt->clear++; break;
+ case GOPT_O_PAGER: gopt->pager++; break;
+ case GOPT_O_QUIT: gopt->quit++; break;
+ case GOPT_O_TEST_OPT: test_opt++; break;
+
+
+ //--- misc
+
+ default:
+ if (OnOption)
+ {
+ const int ostat = OnOption(go,gopt,any_ptr,opt_stat,optarg);
+ if ( ostat <= 0 )
+ {
+ if (!ostat)
+ printf("!!! %s: Internal error while scanning options [0x%02x]!\n",
+ *argv, opt_stat );
+ stat = ERR_ERROR;
+ }
+ }
+ break;
+ }
+ }
+
+ if (test_opt)
+ {
+ printf("\nOPTIONS [N=%u/%u]: %s\n",go->lopt_used,go->lopt_size,go->sopt);
+ const struct option *opt;
+ for ( opt = go->lopt; opt->name; opt++ )
+ printf(" %04x %u %s\n", opt->val, opt->has_arg, opt->name );
+ }
+
+ char **dest = argv + optind;
+ gopt->argv = dest;
+
+ uint i;
+ for ( i = optind; i < argc; i++ )
+ {
+ char *arg = argv[i];
+ if ( !OnArgument || !OnArgument(go,gopt,any_ptr,dest-gopt->argv,arg) )
+ *dest++ = arg;
+ }
+ gopt->argc = dest - gopt->argv;
+
+ if (err)
+ {
+ printf("%s!!! %s: Syntax error!%s\n",colout->warn,*argv,colout->reset);
+ stat = ERR_SYNTAX;
+ goto abort;
+ }
+
+ if ( max_param >= 0 && gopt->argc > max_param )
+ {
+ if (!max_param)
+ printf(
+ "%s!!! %s: No parameters allowed, but %u found!%s\n",
+ colout->warn, *argv, gopt->argc, colout->reset );
+ else
+ printf(
+ "%s!!! %s: Maximal %u parameter%s allowed, but %u found!%s\n",
+ colout->warn, *argv,
+ max_param, max_param == 1 ? "" : "s",
+ gopt->argc, colout->reset );
+ stat = ERR_SYNTAX;
+ goto abort;
+ }
+
+ if ( scan_opt & SGO_PAGER && !gopt->pager )
+ gopt->pager++;
+
+ abort:
+ ResetGenericOpt(go);
+ if (stat)
+ InitializeGOptions(gopt);
+ return stat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-option.h b/project/dclib/dclib-option.h
new file mode 100644
index 0000000..f1bf8f0
--- /dev/null
+++ b/project/dclib/dclib-option.h
@@ -0,0 +1,383 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_OPTION_H
+#define DCLIB_OPTION_H 1
+
+#include "dclib-basics.h"
+struct TCPStream_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// GenericOpt_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[GenericOptParm_t]]
+
+typedef struct GenericOptParm_t
+{
+ u32 id; // id of the option,
+ u8 arg; // 0:no arg, 1:arg, 2:optional arg
+ ccp name; // pipe separated option names
+}
+GenericOptParm_t;
+
+//-----------------------------------------------------------------------------
+// [[GenericOpt_t]]
+
+struct option;
+
+typedef struct GenericOpt_t
+{
+ //--- short options
+
+ char sopt[0x100]; // short options string
+ u32 sopt_id[0x80];
+
+ //--- long options
+
+ struct option *lopt; // long options list
+ uint lopt_used; // number of used 'lopt' elements
+ uint lopt_size; // number of alloced 'lopt' elements
+
+ //--- string pool
+
+ MemPool_t str_pool; // pool for option names
+}
+GenericOpt_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeGenericOpt ( GenericOpt_t *go, char first_char );
+void ResetGenericOpt ( GenericOpt_t *go );
+
+void AddGenericOpt
+(
+ GenericOpt_t *go, // data structure
+ const GenericOptParm_t *gp, // list to add, list end on name==NULL
+ bool rm_minus // remove '-' on options for alternatives
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// GOPT_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[GOPT_t]]
+
+typedef enum GOPT_t
+{
+ GOPT_RESERVED_BEG = 0x01000000, // begin of reserved range
+ GOPT_RESERVED_END = 0x02000000, // end of reserved range
+
+ GOPT_M_INDEX = 0x000000ff, // mask to get the index
+ GOPT_N_PARAM = 25, // max used index+1 = num of params
+ GOPT_M_VALUE = 0x0003ff00, // mask for GOPT_T_PLUS|GOPT_T_MINUS
+ GOPT_S_VALUE = 8, // value is shifted by this num
+ GOPT_M_TYPE = 0x00fc0000, // mask to isolate option type
+ GOPT_I_TYPE = 0x00040000, // increment between types
+ GOPT_S_TYPE = 18, // type is shifted by this num
+
+
+ //--- parameters by index
+
+ GOPT_IDX_NULL = 0, // type undefined
+
+ GOPT_IDX_INC, // increment 'sn', ptype := GOPT_T_INC
+ GOPT_IDX_INC0, // increment 'sn', ptype := GOPT_T_INC
+ GOPT_IDX_DEC, // decrement 'sn', ptype := GOPT_T_INC
+ GOPT_IDX_DEC0, // decrement 'sn', ptype := GOPT_T_INC
+
+ GOPT_IDX_SETP, // set 'sn' to value 0..0x3ff, ptype := GOPT_IDX_SINT
+ GOPT_IDX_SETM, // set 'sn' to value -0..-0x3ff, ptype := GOPT_IDX_SINT
+ GOPT_IDX_SETP64, // set 'sn64' to value 0..0x3ff, ptype := GOPT_IDX_S64
+ GOPT_IDX_SETM64, // set 'sn64' to value -0..-0x3ff, ptype := GOPT_IDX_S64
+
+ GOPT_IDX_OR, // set 'un' |= value 0..0x3ff, ptype := GOPT_IDX_UINT
+ GOPT_IDX_SETBIT, // set bit VALUE in 'un', ptype := GOPT_IDX_UINT
+ GOPT_IDX_BIT, // ana param and set/clear bit VALUE in 'un', ptype := GOPT_IDX_UINT
+ GOPT_IDX_OR64, // set 'un64' |= value 0..0x3ff, ptype := GOPT_IDX_U64
+ GOPT_IDX_SETBIT64, // set bit VALUE in 'un64', ptype := GOPT_IDX_UINT64
+
+ GOPT_IDX_SINT, // scan signed integer
+ GOPT_IDX_UINT, // scan unsigned integer
+ GOPT_IDX_OFF_ON, // scan 'off|onn|0|1' and otional a number, ptype := GOPT_IDX_UINT
+ GOPT_IDX_S64, // scan signed 64-bit integer
+ GOPT_IDX_U64, // scan unsigned 64-bit integer
+ GOPT_IDX_HEX, // scan unsigned 64-bit integer, assume hex
+ GOPT_IDX_SIZE, // scan size (double with optional SI) and store as 'un64'
+ GOPT_IDX_DOUBLE, // scan double float
+ GOPT_IDX_DURATION, // scan duration (double with optional SI) and store as 'd'
+ GOPT_IDX_STRING, // store ccp pointer to text
+
+ GOPT_IDX_END, // end of parameters
+
+
+ //--- parameters types
+
+ GOPT_T_NULL = 0, // type undefined
+ GOPT_T_INC = GOPT_RESERVED_BEG | ( GOPT_IDX_INC << GOPT_S_TYPE ),
+ GOPT_T_INC0 = GOPT_RESERVED_BEG | ( GOPT_IDX_INC0 << GOPT_S_TYPE ),
+ GOPT_T_DEC = GOPT_RESERVED_BEG | ( GOPT_IDX_DEC << GOPT_S_TYPE ),
+ GOPT_T_DEC0 = GOPT_RESERVED_BEG | ( GOPT_IDX_DEC0 << GOPT_S_TYPE ),
+ GOPT_T_SETP = GOPT_RESERVED_BEG | ( GOPT_IDX_SETP << GOPT_S_TYPE ),
+ GOPT_T_SETM = GOPT_RESERVED_BEG | ( GOPT_IDX_SETM << GOPT_S_TYPE ),
+ GOPT_T_SETP64 = GOPT_RESERVED_BEG | ( GOPT_IDX_SETP64 << GOPT_S_TYPE ),
+ GOPT_T_SETM64 = GOPT_RESERVED_BEG | ( GOPT_IDX_SETM64 << GOPT_S_TYPE ),
+ GOPT_T_OR = GOPT_RESERVED_BEG | ( GOPT_IDX_OR << GOPT_S_TYPE ),
+ GOPT_T_SETBIT = GOPT_RESERVED_BEG | ( GOPT_IDX_SETBIT << GOPT_S_TYPE ),
+ GOPT_T_BIT = GOPT_RESERVED_BEG | ( GOPT_IDX_BIT << GOPT_S_TYPE ),
+ GOPT_T_OR64 = GOPT_RESERVED_BEG | ( GOPT_IDX_OR64 << GOPT_S_TYPE ),
+ GOPT_T_SETBIT64 = GOPT_RESERVED_BEG | ( GOPT_IDX_SETBIT64 << GOPT_S_TYPE ),
+ GOPT_T_SINT = GOPT_RESERVED_BEG | ( GOPT_IDX_SINT << GOPT_S_TYPE ),
+ GOPT_T_UINT = GOPT_RESERVED_BEG | ( GOPT_IDX_UINT << GOPT_S_TYPE ),
+ GOPT_T_OFF_ON = GOPT_RESERVED_BEG | ( GOPT_IDX_OFF_ON << GOPT_S_TYPE ),
+ GOPT_T_S64 = GOPT_RESERVED_BEG | ( GOPT_IDX_S64 << GOPT_S_TYPE ),
+ GOPT_T_U64 = GOPT_RESERVED_BEG | ( GOPT_IDX_U64 << GOPT_S_TYPE ),
+ GOPT_T_HEX = GOPT_RESERVED_BEG | ( GOPT_IDX_HEX << GOPT_S_TYPE ),
+ GOPT_T_SIZE = GOPT_RESERVED_BEG | ( GOPT_IDX_SIZE << GOPT_S_TYPE ),
+ GOPT_T_DOUBLE = GOPT_RESERVED_BEG | ( GOPT_IDX_DOUBLE << GOPT_S_TYPE ),
+ GOPT_T_DURATION = GOPT_RESERVED_BEG | ( GOPT_IDX_DURATION << GOPT_S_TYPE ),
+ GOPT_T_STRING = GOPT_RESERVED_BEG | ( GOPT_IDX_STRING << GOPT_S_TYPE ),
+ GOPT_T_END = GOPT_RESERVED_BEG | ( GOPT_IDX_END << GOPT_S_TYPE ),
+
+
+ //--- named options
+
+ GOPT_O__BEGIN = 0x100,
+ GOPT_O_TEST_OPT, // test: print all defined options
+
+ GOPT_O_HELP, // force help
+ GOPT_O_CLEAR, // clear screen -> ScanGOptionsHelperTN()
+ GOPT_O_PAGER, // activate pager -> ScanGOptionsHelperTN()
+ GOPT_O_QUIT, // quit if command done -> ScanGOptionsHelperTN()
+
+ GOPT_O_QUIET, // be quiet
+ GOPT_O_VERBOSE, // be verbose
+
+ GOPT_O__CONTINUE // continue with this value on more options
+}
+GOPT_t;
+
+static inline GOPT_t GetGOPType ( GOPT_t gt )
+ { return ( gt & GOPT_M_TYPE ) >> GOPT_S_TYPE; }
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// global options: parameters ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[GParamValueType_t]]
+
+typedef enum GParamValueType_t
+{
+ GPT_NONE, // no param set
+ GPT_SN, // value stored as 'sn'
+ GPT_UN, // value stored as 'un'
+ GPT_SN64, // value stored as 'sn64'
+ GPT_UN64, // value stored as 'un64'
+ GPT_D, // value stored as 'd'
+ GPT_STR, // value stored as 'str'
+ GPT_BIT, // value stored as 'bit.val' & 'bit.mask'
+}
+GParamValueType_t;
+
+GParamValueType_t GetGParamValueType ( GOPT_t gt );
+
+///////////////////////////////////////////////////////////////////////////////
+// [[GParam_t]]
+
+typedef struct GParam_t
+{
+ u16 ptype; // GOPT_IDX_*
+ u16 vtype; // GParamValueType_t
+
+ union
+ {
+ int sn; // used by GOPT_T_INC*, GOPT_T_DEC*, GOPT_T_SINT
+ uint un; // used by GOPT_T_UINT
+ s64 sn64; // used by GOPT_T_S64, GOPT_T_SETM
+ u64 un64; // used by GOPT_T_U64, GOPT_T_HEX, GOPT_T_SIZE, GOPT_T_SETP
+ double d; // used by GOPT_T_DOUBLE, GOPT_T_DURATION
+ ccp str; // used by GOPT_T_STRING
+ struct { u32 val; u32 mask; } bit;
+ // used by GOPT_T_BIT
+ };
+}
+GParam_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[GetGParam_t]]
+
+typedef enum GetGParam_t
+{
+ GGP_NONE, // no param set or param unusable, 'return_val' untouched
+ GGP_CONVERT, // param set, but converted (different numeric type)
+ GGP_SIGN, // param returned, but sign maybe converted
+ GGP_OK // original or extended param returned
+}
+GetGParam_t;
+
+// If gp==NULL: return GGP_NONE
+// If return_val==NULL: analyse only
+GetGParam_t GetGParamINT ( const GParam_t *par, int *return_val );
+GetGParam_t GetGParamUINT ( const GParam_t *par, uint *return_val );
+GetGParam_t GetGParamS64 ( const GParam_t *par, s64 *return_val );
+GetGParam_t GetGParamU64 ( const GParam_t *par, u64 *return_val );
+GetGParam_t GetGParamDBL ( const GParam_t *par, double *return_val );
+GetGParam_t GetGParamSTR ( const GParam_t *par, ccp *return_val );
+GetGParam_t GetGParamBIT ( const GParam_t *par, u32 *return_val, u32 *return_mask );
+
+ccp PrintGParam ( const GParam_t *par );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// global options ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[GOptions_t]]
+
+typedef struct GOptions_t
+{
+ //--- parameters
+
+ int argc; // number of parameters
+ char **argv; // paramters without options
+
+
+ //--- named options
+
+ int help; // number of GOPT_O_HELP
+ int clear; // number of GOPT_O_CLEAR
+ int pager; // number of GOPT_O_PAGER,
+ int quit; // number of GOPT_O_QUIT,
+ int verbose; // combi of GOPT_O_QUIET & GOPT_O_VERBOSE,
+
+
+ //--- generic parameter support
+
+ GParam_t param[GOPT_N_PARAM];
+}
+GOptions_t;
+
+//-----------------------------------------------------------------------------
+
+static inline void InitializeGOptions ( GOptions_t *go )
+{
+ DASSERT(go);
+ memset(go,0,sizeof(*go));
+}
+
+static inline const GParam_t * GetGParam ( const GOptions_t *go, uint par_index )
+ { return go && par_index < GOPT_N_PARAM ? go->param+par_index : 0; }
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern const GenericOptParm_t StandardGOptions[];
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef int (*ScanOptionFunc)
+(
+ // returns: 0 on not procecced / <0 on error / >0 on success
+
+ GenericOpt_t *go, // valid pointer to generic options
+ GOptions_t *gopt, // valid pointer to options data
+ void *any_ptr, // user specific pointer
+ int num, // numeric param:
+ // OnAddOptions(): 'scan_opt'
+ // OnOption(): option id
+ // OnArgument(): argument index
+ ccp arg // NULL or text argument
+);
+
+///////////////////////////////////////////////////////////////////////////////
+// [[ScanGOpt_t]]
+
+typedef enum ScanGOpt_t
+{
+ SGO_KNOWN = 0x0001, // scan only until first unknown parameter
+ SGO_PAGER = 0x0002, // auto enable pager
+
+ SGO__CONTINUE = 0x0004 // define more flags based on this value
+}
+ScanGOpt_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanGOptionsHelper
+(
+ GOptions_t *gopt, // valid pointer
+ GenericOpt_t *go, // NULL or list with options
+ // => ResetGenericOpt() is called at finish
+ int argc, // argument counter
+ char **argv, // list of arguments
+ int max_param, // >=0: max allowed parameters
+ ScanGOpt_t scan_opt, // scan options
+
+ //--- call back functions
+ ScanOptionFunc OnAddOptions, // not NULL: called once to add options
+ ScanOptionFunc OnOption, // not NULL: called for each option
+ ScanOptionFunc OnArgument, // not NULL: called for each argument
+ void *any_ptr // NULL or user specific pointer for call back
+);
+
+//-----------------------------------------------------------------------------
+
+enumError ScanGOptionsHelperTN
+(
+ // like ScanGOptionsHelper(), but with TELNET support
+ // for 'gopt->clear', 'gopt->pager' and 'gopt->quit',
+ // if 'ts' is not NULL and telnet is active.
+
+ GOptions_t *gopt, // valid pointer
+ GenericOpt_t *go, // NULL or list with options
+ // => ResetGenericOpt() is called at finish
+ int argc, // argument counter
+ char **argv, // list of arguments
+ int max_param, // >=0: max allowed parameters
+ ScanGOpt_t scan_opt, // scan options
+ struct TCPStream_t *ts, // NULL or current stream with TELNET support
+
+ //--- call back functions
+ ScanOptionFunc OnAddOptions, // not NULL: called once to add options
+ ScanOptionFunc OnOption, // not NULL: called for each option
+ ScanOptionFunc OnArgument, // not NULL: called for each argument
+ void *any_ptr // NULL or user specific pointer for call back
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_OPTION_H
+
diff --git a/project/dclib/dclib-regex.c b/project/dclib/dclib-regex.c
new file mode 100644
index 0000000..d9bdf99
--- /dev/null
+++ b/project/dclib/dclib-regex.c
@@ -0,0 +1,465 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include "dclib-regex.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// some macro definitions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#undef REGEX_FREE
+#undef REOPT_ICASE
+#undef MATCH_BEG
+#undef MATCH_END
+
+#if DCLIB_USE_PCRE
+ #define REGEX_FREE pcre_free
+ #define REOPT_ICASE PCRE_CASELESS
+ #define MATCH_BEG(i) match[2*i]
+ #define MATCH_END(i) match[2*i+1]
+#else
+ #define REGEX_FREE regfree
+ #define REOPT_ICASE REG_ICASE
+ #define MATCH_BEG(i) match[i].rm_so
+ #define MATCH_END(i) match[i].rm_eo
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct Regex_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeRegex ( Regex_t *re )
+{
+ DASSERT(re);
+ memset(re,0,sizeof(*re));
+ re->re_list = re->re_pool;
+ re->re_size = sizeof(re->re_pool)/sizeof(*re->re_pool);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetRegex ( Regex_t *re )
+{
+ DASSERT(re);
+
+ RegexElem_t *e, *eend = re->re_list + re->re_used;
+ for ( e = re->re_list; e < eend; e++ )
+ {
+ #if DCLIB_USE_PCRE
+ if (e->regex)
+ pcre_free(e->regex);
+ #else
+ regfree(&e->regex);
+ #endif
+
+ FreeString(e->pattern);
+ FreeString(e->replace.ptr);
+ FREE(e->repl);
+ }
+
+ if ( re->re_list != re->re_pool )
+ FREE(re->re_list);
+
+ InitializeRegex(re);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static RegexElem_t * GetNextRegexElem ( Regex_t *re )
+{
+ DASSERT(re);
+ if ( re->re_used >= re->re_size )
+ {
+ re->re_size = 3*re->re_size/2 + 10;
+ if ( re->re_list == re->re_pool )
+ {
+ re->re_list = MALLOC( sizeof(*re->re_list) * re->re_size );
+ memcpy( re->re_list, re->re_pool, sizeof(*re->re_list) * re->re_used );
+ }
+ else
+ re->re_list = REALLOC( re->re_list, sizeof(*re->re_list) * re->re_size );
+ }
+
+ RegexElem_t *e = re->re_list + re->re_used;
+ memset(e,0,sizeof(*e));
+ return e;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static RegexReplace_t * GetNextRegexRepl
+ ( RegexElem_t *elem, char *ptr, int len, int ref )
+{
+ DASSERT(elem);
+ if ( !len && ref < 0 )
+ return 0;
+
+ if ( elem->repl_used >= elem->repl_size )
+ {
+ elem->repl_size = 3*elem->repl_size/2 + 10;
+ elem->repl = REALLOC( elem->repl, sizeof(*elem->repl) * elem->repl_size );
+ }
+
+ RegexReplace_t *repl = elem->repl + elem->repl_used++;
+ repl->str.ptr = ptr;
+ repl->str.len = len;
+ repl->ref = ref;
+ PRINT("REPL[%d]: |%.*s| %d\n",elem->repl_used-1,len,ptr,ref);
+ return repl;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanRegex ( Regex_t *re, bool init_re, ccp regex )
+{
+ DASSERT(re);
+ if (init_re)
+ InitializeRegex(re);
+ else
+ ResetRegex(re);
+
+ if ( !regex || !*regex )
+ return ERR_NOTHING_TO_DO;
+
+ struct { FastBuf_t b; char space[500]; } temp;
+ InitializeFastBuf(&temp,sizeof(temp));
+
+ //--- scanning
+
+ enumError err = ERR_OK;
+ for(;;)
+ {
+ while ( isspace(*regex) || *regex == ';' )
+ regex++;
+ if (!*regex)
+ break;
+
+ noPRINT("RE: ANALYZE #%u/%u: %s\n",re->re_used, re->re_size,regex);
+
+ char sep = *regex++;
+ while ( *regex && *regex != sep )
+ {
+ char ch = *regex++;
+ if ( ch == '\\' && *regex == sep )
+ ch = *regex++;
+ AppendCharFastBuf(&temp.b,ch);
+ }
+
+ if ( *regex != sep )
+ {
+ err = ERR_SYNTAX;
+ goto exit;
+ }
+
+ ccp repl = ++regex;
+ while ( *regex && *regex != sep )
+ regex++;
+ const uint repl_len = regex - repl;
+
+ RegexElem_t *elem = GetNextRegexElem(re);
+
+ if ( *regex == sep )
+ {
+ regex++;
+ while ( *regex && *regex != ';' )
+ switch (*regex++)
+ {
+ case 'g': elem->global = true; break;
+ case 'i': elem->icase = true; elem->opt |= REOPT_ICASE; break;
+ }
+ }
+
+ PRINT("RE: PATTERN #%u/%u: [%s] %s -> %.*s : %s\n",
+ re->re_used, re->re_size,
+ GetFastBufStatus(&temp.b),
+ GetFastBufString(&temp.b),
+ repl_len, repl, regex );
+
+ // store data as strings
+ elem->pattern = MEMDUP(temp.b.buf,temp.b.ptr-temp.b.buf);
+ elem->replace.ptr = repl_len ? MEMDUP(repl,repl_len) : EmptyString;
+ elem->replace.len = repl_len;
+ re->re_used++;
+ ClearFastBuf(&temp.b);
+ }
+
+
+ //--- finalize
+
+ re->valid = true;
+
+ uint e;
+ RegexElem_t *elem;
+ for ( e = 0, elem = re->re_list; e < re->re_used; e++, elem++ )
+ {
+ #if DCLIB_USE_PCRE
+
+ ccp errptr;
+ int erroffset;
+ re->regex = pcre_compile(elem->pattern,opt,&errptr,&erroffset,0);
+ if (!re->regex)
+ {
+ PRINT("PCRE error: %s: %s\n",errptr,pattern+erroffset);
+ return ERR_SEMANTIC;
+ err = ERR_SEMANTIC;
+ goto exit;
+ }
+
+ #else
+
+ int stat = regcomp(&elem->regex,elem->pattern,elem->opt|REG_EXTENDED);
+ if (stat)
+ {
+ #if HAVE_PRINT0
+ char error[100];
+ regerror(stat,&re->regex,errormsizeof(error));
+ PRINT("PCRE error: %s: %s\n",errptr,pattern+erroffset);
+ #endif
+ re->valid = false;
+ err = ERR_SEMANTIC;
+ }
+ else
+ elem->valid = true;
+ #endif
+
+
+ //--- analyse replace
+
+ char *ptr = (char*)elem->replace.ptr;
+ char *dest = ptr;
+ char *start = dest;
+ char *end = ptr + elem->replace.len;
+
+ while ( ptr < end )
+ {
+ while ( ptr < end && *ptr != '\\' && *ptr != '$' )
+ *dest++ = *ptr++;
+ if ( ptr == end )
+ {
+ GetNextRegexRepl(elem,start,dest-start,-1);
+ break;
+ }
+
+ if ( *ptr == ptr[1] )
+ {
+ ptr++;
+ *dest++ = *ptr++;
+ }
+ else if ( ptr[1] >= '0' && ptr[1] <= '9' )
+ {
+ GetNextRegexRepl(elem,start,dest-start,ptr[1]-'0');
+ start = dest;
+ ptr += 2;
+ }
+ else if ( ptr[1] == '{' )
+ {
+ char *p = ptr+2;
+ if ( *p >= '0' && *p <= '9' )
+ {
+ uint num = *p++ - '0';
+ if ( *p >= '0' && *p <= '9' )
+ num = 10*num + *p++ - '0';
+ if ( p < end && *p == '}' )
+ {
+ GetNextRegexRepl(elem,start,dest-start,num);
+ start = dest;
+ ptr = p+1;
+ continue;
+ }
+ }
+ *dest++ = *ptr++;
+ }
+ else
+ *dest++ = *ptr++;
+ }
+
+ if ( dest != EmptyString )
+ *dest = 0;
+ elem->replace.len = dest - elem->replace.ptr;
+ }
+
+ PRINT("RE: n=%d, valid=%d\n",re->re_used,re->valid);
+
+ exit:
+ ResetFastBuf(&temp.b);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ReplaceRegex
+(
+ Regex_t *re, // valid Regex_t
+ FastBuf_t *res, // return buffer, cleared
+ ccp src,
+ int src_len // -1: use strlen()
+)
+{
+ DASSERT(re);
+ DASSERT(res);
+
+
+ //--- sanity checks
+
+ if (!res)
+ return -ERR_INVALID_DATA;
+
+ ClearFastBuf(res);
+
+ if ( !re || !re->valid )
+ return -ERR_INVALID_DATA;
+
+ if ( !src || !*src )
+ return 0;
+
+ if ( src_len < 0 )
+ src_len = strlen(src);
+
+
+ //--- prepare data
+
+ enumError err = ERR_OK;
+ uint count = 0;
+
+ struct { FastBuf_t b; char space[500]; } temp;
+ InitializeFastBuf(&temp,sizeof(temp));
+ FastBuf_t *dest = res;
+
+ #if DCLIB_USE_PCRE
+ DASSERT(re->regex);
+ enum { N_MATCH = 100 };
+ int match[N_MATCH];
+ #else
+ enum { N_MATCH = 100 };
+ regmatch_t match[N_MATCH];
+ #endif
+
+
+ //--- exec RE
+
+ uint e;
+ RegexElem_t *elem;
+ for ( e = 0, elem = re->re_list; e < re->re_used; e++, elem++ )
+ {
+ uint pos = 0, copied = 0;
+ while( pos < src_len )
+ {
+ memset(match,0,sizeof(match));
+
+ #if DCLIB_USE_PCRE
+ int stat = pcre_exec(re->regex,0,src,src_len,pos,0,match,N_MATCH);
+ noPRINT("RE: stat=%2d : %2d %2d : %2d %2d : %2d %2d\n",
+ stat, match[0],match[1], match[2],match[3], match[4],match[5] );
+ if ( stat < 0 )
+ {
+ if ( stat == PCRE_ERROR_NOMATCH )
+ break;
+ err = -ERR_ERROR;
+ goto exit;
+ }
+ const uint match_beg = match[0];
+ const uint match_end = match[1];
+
+ #else
+
+ int stat = regexec(&elem->regex,src+pos,N_MATCH,match,0);
+ PRINT("RE: pos=%2d, stat=%2d : %2d %2d : %2d %2d : %2d %2d\n",
+ pos, stat, match[0].rm_so,match[0].rm_eo,
+ match[1].rm_so,match[1].rm_eo, match[2].rm_so,match[2].rm_eo );
+ if (stat)
+ {
+ if ( stat == REG_NOMATCH )
+ break;
+ err = -ERR_ERROR;
+ goto exit;
+ }
+ const uint match_beg = match[0].rm_so + pos;
+ const uint match_end = match[0].rm_eo + pos;
+
+ #endif
+
+ if ( match_beg > copied )
+ AppendFastBuf(dest,src+copied,match_beg-copied);
+ copied = match_end;
+
+ RegexReplace_t *repl = elem->repl;
+ RegexReplace_t *repl_end = repl + elem->repl_used;
+ for ( ; repl < repl_end; repl++ )
+ {
+ AppendMemFastBuf(dest,repl->str);
+ #if DCLIB_USE_PCRE
+ #error
+ #else
+ if ( repl->ref >= 0 && repl->ref < N_MATCH )
+ {
+ const regmatch_t *m = match + repl->ref;
+ if ( m->rm_so >= 0 )
+ AppendFastBuf( dest,
+ src + m->rm_so + pos,
+ m->rm_eo - m->rm_so );
+ }
+ #endif
+ }
+
+ pos = match_end + ( match_beg == match_end );
+ count++;
+
+ if (!elem->global)
+ break;
+ }
+
+ if ( src_len > copied )
+ AppendFastBuf(dest,src+copied,src_len-copied);
+
+ PRINT("DEST: %s\n",GetFastBufString(dest));
+ src = GetFastBufString(dest);
+ src_len = GetFastBufLen(dest);
+ dest = dest == res ? &temp.b : res;
+ ClearFastBuf(dest);
+ }
+
+ exit:;
+ AssignFastBuf(res,src,src_len);
+ return err < 0 ? err : count;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-regex.h b/project/dclib/dclib-regex.h
new file mode 100644
index 0000000..9c8484d
--- /dev/null
+++ b/project/dclib/dclib-regex.h
@@ -0,0 +1,131 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_REGEX_H
+#define DCLIB_REGEX_H 1
+
+#if DCLIB_USE_PCRE
+ #include <pcre.h>
+ #define DC_REGEX_TYPE pcre
+#else
+ #ifndef DCLIB_USE_REGEX
+ #define DCLIB_USE_REGEX 1
+ #endif
+ #if DCLIB_USE_REGEX
+ #include <regex.h>
+ #define DC_REGEX_TYPE regex_t
+ #else
+ #error Nn Regex Support!
+ #endif
+#endif
+
+#include "dclib-types.h"
+#include "dclib-debug.h"
+#include "dclib-basics.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct Regex_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[RegexReplace_t]]
+
+typedef struct RegexReplace_t
+{
+ mem_t str; // first: string to replace (maybe 0 bytes)
+ int ref; // second: back reference to copy (if >=0)
+}
+RegexReplace_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[RegexElem_t]]
+
+typedef struct RegexElem_t
+{
+ bool valid; // true: successfull compiled
+ bool global; // true: replace all ('g')
+ bool icase; // true: ignore case ('i')
+
+ #if DCLIB_USE_PCRE
+ DC_REGEX_TYPE *regex; // not NULL: compiled regular expression
+ #else
+ DC_REGEX_TYPE regex; // compiled regular expression
+ #endif
+
+ int opt; // compile options
+ ccp pattern; // pattern string, alloced
+ mem_t replace; // replace string, alloced
+
+ RegexReplace_t *repl; // replace data, use 'replace' as reference
+ uint repl_used; // number of used elements in 'repl'
+ uint repl_size; // number of available elements in 'repl'
+}
+RegexElem_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[RegexMain_t]]
+
+typedef struct Regex_t
+{
+ bool valid; // true: succesfull initialized
+
+ RegexElem_t *re_list; // list; either 're_pool' or alloced
+ RegexElem_t re_pool[3]; // pool for fast access
+ uint re_used; // number of used elements in 're_list'
+ uint re_size; // number of available elements in 're_list'
+}
+Regex_t;
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeRegex ( Regex_t *re );
+void ResetRegex ( Regex_t *re );
+
+enumError ScanRegex ( Regex_t *re, bool init_re, ccp regex );
+
+int ReplaceRegex
+(
+ Regex_t *re, // valid Regex_t
+ FastBuf_t *res, // return buffer, cleared
+ ccp src,
+ int src_len // -1: use strlen()
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_REGEX_H
+
diff --git a/project/dclib/dclib-shift-jis.c b/project/dclib/dclib-shift-jis.c
new file mode 100644
index 0000000..13c5a93
--- /dev/null
+++ b/project/dclib/dclib-shift-jis.c
@@ -0,0 +1,1408 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+//#include <string.h>
+//#include <limits.h>
+
+#include "dclib-basics.h"
+//#include "dclib-debug.h"
+//#include "dclib-utf8.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// shift_jis_single[] ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// all UTF-16 values by 'iconv'
+
+#define X SJIS_TAB_MAPPING_BEG
+#define INV SJIS_INVALID_CODE
+
+static const u16 shift_jis_single[0x100] =
+{
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, // 0x00..0x07
+ 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // 0x08..0x0f
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, // 0x10..0x17
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, // 0x18..0x1f
+
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // 0x20..0x27
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, // 0x28..0x2f
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0x30..0x37
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, // 0x38..0x3f
+
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // 0x40..0x47
+ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, // 0x48..0x4f
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // 0x50..0x57
+ 0x58, 0x59, 0x5a, 0x5b, 0xa5, 0x5d, 0x5e, 0x5f, // 0x58..0x5f
+
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // 0x60..0x67
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, // 0x68..0x6f
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // 0x70..0x77
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d,0x203e, 0x7f, // 0x78..0x7f
+
+ INV, X+0, X+1, X+2, X+3, INV, INV, INV, // 0x80..0x87
+ X+4, X+5, X+6, X+7, X+8, X+9, X+10, X+11, // 0x88..0x8f
+ X+12, X+13, X+14, X+15, X+16, X+17, X+18, X+19, // 0x90..0x97
+ X+20, X+21, X+22, X+23, X+24, X+25, X+26, X+27, // 0x98..0x9f
+
+ INV,0xff61,0xff62,0xff63, 0xff64,0xff65,0xff66,0xff67, // 0xa0..0xa7
+ 0xff68,0xff69,0xff6a,0xff6b, 0xff6c,0xff6d,0xff6e,0xff6f, // 0xa8..0xaf
+ 0xff70,0xff71,0xff72,0xff73, 0xff74,0xff75,0xff76,0xff77, // 0xb0..0xb7
+ 0xff78,0xff79,0xff7a,0xff7b, 0xff7c,0xff7d,0xff7e,0xff7f, // 0xb8..0xbf
+
+ 0xff80,0xff81,0xff82,0xff83, 0xff84,0xff85,0xff86,0xff87, // 0xc0..0xc7
+ 0xff88,0xff89,0xff8a,0xff8b, 0xff8c,0xff8d,0xff8e,0xff8f, // 0xc8..0xcf
+ 0xff90,0xff91,0xff92,0xff93, 0xff94,0xff95,0xff96,0xff97, // 0xd0..0xd7
+ 0xff98,0xff99,0xff9a,0xff9b, 0xff9c,0xff9d,0xff9e,0xff9f, // 0xd8..0xdf
+
+ X+28, X+29, X+30, X+31, X+32, X+33, X+34, X+35, // 0xe0..0xe7
+ X+36, X+37, X+38, INV, INV, INV, INV, INV, // 0xe8..0xef
+ INV, INV, INV, INV, INV, INV, INV, INV, // 0xf0..0xf7
+ INV, INV, INV, INV, INV, INV, INV, X+39, // 0xf8..0xff
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// shift_jis_double_*[] ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+
+static const u16 shift_jis_double_81[] =
+{
+ 0x3000,0x3001,0x3002,0xff0c, 0xff0e,0x30fb,0xff1a,0xff1b, // 81 40..47
+ 0xff1f,0xff01,0x309b,0x309c, 0xb4,0xff40, 0xa8,0xff3e, // 81 48..4f
+ 0xffe3,0xff3f,0x30fd,0x30fe, 0x309d,0x309e,0x3003,0x4edd, // 81 50..57
+ 0x3005,0x3006,0x3007,0x30fc, 0x2015,0x2010,0xff0f,0xff3c, // 81 58..5f
+ 0x301c,0x2016,0xff5c,0x2026, 0x2025,0x2018,0x2019,0x201c, // 81 60..67
+ 0x201d,0xff08,0xff09,0x3014, 0x3015,0xff3b,0xff3d,0xff5b, // 81 68..6f
+ 0xff5d,0x3008,0x3009,0x300a, 0x300b,0x300c,0x300d,0x300e, // 81 70..77
+ 0x300f,0x3010,0x3011,0xff0b, 0x2212, 0xb1, 0xd7, INV, // 81 78..7f
+ 0xf7,0xff1d,0x2260,0xff1c, 0xff1e,0x2266,0x2267,0x221e, // 81 80..87
+ 0x2234,0x2642,0x2640, 0xb0, 0x2032,0x2033,0x2103,0xffe5, // 81 88..8f
+ 0xff04, 0xa2, 0xa3,0xff05, 0xff03,0xff06,0xff0a,0xff20, // 81 90..97
+ 0xa7,0x2606,0x2605,0x25cb, 0x25cf,0x25ce,0x25c7,0x25c6, // 81 98..9f
+ 0x25a1,0x25a0,0x25b3,0x25b2, 0x25bd,0x25bc,0x203b,0x3012, // 81 a0..a7
+ 0x2192,0x2190,0x2191,0x2193, 0x3013, INV, INV, INV, // 81 a8..af
+ INV, INV, INV, INV, INV, INV, INV, INV, // 81 b0..b7
+ 0x2208,0x220b,0x2286,0x2287, 0x2282,0x2283,0x222a,0x2229, // 81 b8..bf
+ INV, INV, INV, INV, INV, INV, INV, INV, // 81 c0..c7
+ 0x2227,0x2228, 0xac,0x21d2, 0x21d4,0x2200,0x2203, INV, // 81 c8..cf
+ INV, INV, INV, INV, INV, INV, INV, INV, // 81 d0..d7
+ INV, INV,0x2220,0x22a5, 0x2312,0x2202,0x2207,0x2261, // 81 d8..df
+ 0x2252,0x226a,0x226b,0x221a, 0x223d,0x221d,0x2235,0x222b, // 81 e0..e7
+ 0x222c, INV, INV, INV, INV, INV, INV, INV, // 81 e8..ef
+ 0x212b,0x2030,0x266f,0x266d, 0x266a,0x2020,0x2021, 0xb6, // 81 f0..f7
+ INV, INV, INV, INV, 0x25ef, // 81 f8..fc
+};
+
+static const u16 shift_jis_double_82[] =
+{
+ 0xff10,0xff11,0xff12,0xff13, 0xff14,0xff15,0xff16,0xff17, // 82 4f..56
+ 0xff18,0xff19, INV, INV, INV, INV, INV, INV, // 82 57..5e
+ INV,0xff21,0xff22,0xff23, 0xff24,0xff25,0xff26,0xff27, // 82 5f..66
+ 0xff28,0xff29,0xff2a,0xff2b, 0xff2c,0xff2d,0xff2e,0xff2f, // 82 67..6e
+ 0xff30,0xff31,0xff32,0xff33, 0xff34,0xff35,0xff36,0xff37, // 82 6f..76
+ 0xff38,0xff39,0xff3a, INV, INV, INV, INV, INV, // 82 77..7e
+ INV, INV,0xff41,0xff42, 0xff43,0xff44,0xff45,0xff46, // 82 7f..86
+ 0xff47,0xff48,0xff49,0xff4a, 0xff4b,0xff4c,0xff4d,0xff4e, // 82 87..8e
+ 0xff4f,0xff50,0xff51,0xff52, 0xff53,0xff54,0xff55,0xff56, // 82 8f..96
+ 0xff57,0xff58,0xff59,0xff5a, INV, INV, INV, INV, // 82 97..9e
+ 0x3041,0x3042,0x3043,0x3044, 0x3045,0x3046,0x3047,0x3048, // 82 9f..a6
+ 0x3049,0x304a,0x304b,0x304c, 0x304d,0x304e,0x304f,0x3050, // 82 a7..ae
+ 0x3051,0x3052,0x3053,0x3054, 0x3055,0x3056,0x3057,0x3058, // 82 af..b6
+ 0x3059,0x305a,0x305b,0x305c, 0x305d,0x305e,0x305f,0x3060, // 82 b7..be
+ 0x3061,0x3062,0x3063,0x3064, 0x3065,0x3066,0x3067,0x3068, // 82 bf..c6
+ 0x3069,0x306a,0x306b,0x306c, 0x306d,0x306e,0x306f,0x3070, // 82 c7..ce
+ 0x3071,0x3072,0x3073,0x3074, 0x3075,0x3076,0x3077,0x3078, // 82 cf..d6
+ 0x3079,0x307a,0x307b,0x307c, 0x307d,0x307e,0x307f,0x3080, // 82 d7..de
+ 0x3081,0x3082,0x3083,0x3084, 0x3085,0x3086,0x3087,0x3088, // 82 df..e6
+ 0x3089,0x308a,0x308b,0x308c, 0x308d,0x308e,0x308f,0x3090, // 82 e7..ee
+ 0x3091,0x3092,0x3093, // 82 ef..f1
+};
+
+static const u16 shift_jis_double_83[] =
+{
+ 0x30a1,0x30a2,0x30a3,0x30a4, 0x30a5,0x30a6,0x30a7,0x30a8, // 83 40..47
+ 0x30a9,0x30aa,0x30ab,0x30ac, 0x30ad,0x30ae,0x30af,0x30b0, // 83 48..4f
+ 0x30b1,0x30b2,0x30b3,0x30b4, 0x30b5,0x30b6,0x30b7,0x30b8, // 83 50..57
+ 0x30b9,0x30ba,0x30bb,0x30bc, 0x30bd,0x30be,0x30bf,0x30c0, // 83 58..5f
+ 0x30c1,0x30c2,0x30c3,0x30c4, 0x30c5,0x30c6,0x30c7,0x30c8, // 83 60..67
+ 0x30c9,0x30ca,0x30cb,0x30cc, 0x30cd,0x30ce,0x30cf,0x30d0, // 83 68..6f
+ 0x30d1,0x30d2,0x30d3,0x30d4, 0x30d5,0x30d6,0x30d7,0x30d8, // 83 70..77
+ 0x30d9,0x30da,0x30db,0x30dc, 0x30dd,0x30de,0x30df, INV, // 83 78..7f
+ 0x30e0,0x30e1,0x30e2,0x30e3, 0x30e4,0x30e5,0x30e6,0x30e7, // 83 80..87
+ 0x30e8,0x30e9,0x30ea,0x30eb, 0x30ec,0x30ed,0x30ee,0x30ef, // 83 88..8f
+ 0x30f0,0x30f1,0x30f2,0x30f3, 0x30f4,0x30f5,0x30f6, INV, // 83 90..97
+ INV, INV, INV, INV, INV, INV, INV, 0x391, // 83 98..9f
+ 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, // 83 a0..a7
+ 0x39a, 0x39b, 0x39c, 0x39d, 0x39e, 0x39f, 0x3a0, 0x3a1, // 83 a8..af
+ 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, 0x3a9, INV, // 83 b0..b7
+ INV, INV, INV, INV, INV, INV, INV, 0x3b1, // 83 b8..bf
+ 0x3b2, 0x3b3, 0x3b4, 0x3b5, 0x3b6, 0x3b7, 0x3b8, 0x3b9, // 83 c0..c7
+ 0x3ba, 0x3bb, 0x3bc, 0x3bd, 0x3be, 0x3bf, 0x3c0, 0x3c1, // 83 c8..cf
+ 0x3c3, 0x3c4, 0x3c5, 0x3c6, 0x3c7, 0x3c8, 0x3c9, // 83 d0..d6
+};
+
+static const u16 shift_jis_double_84[] =
+{
+ 0x410, 0x411, 0x412, 0x413, 0x414, 0x415, 0x401, 0x416, // 84 40..47
+ 0x417, 0x418, 0x419, 0x41a, 0x41b, 0x41c, 0x41d, 0x41e, // 84 48..4f
+ 0x41f, 0x420, 0x421, 0x422, 0x423, 0x424, 0x425, 0x426, // 84 50..57
+ 0x427, 0x428, 0x429, 0x42a, 0x42b, 0x42c, 0x42d, 0x42e, // 84 58..5f
+ 0x42f, INV, INV, INV, INV, INV, INV, INV, // 84 60..67
+ INV, INV, INV, INV, INV, INV, INV, INV, // 84 68..6f
+ 0x430, 0x431, 0x432, 0x433, 0x434, 0x435, 0x451, 0x436, // 84 70..77
+ 0x437, 0x438, 0x439, 0x43a, 0x43b, 0x43c, 0x43d, INV, // 84 78..7f
+ 0x43e, 0x43f, 0x440, 0x441, 0x442, 0x443, 0x444, 0x445, // 84 80..87
+ 0x446, 0x447, 0x448, 0x449, 0x44a, 0x44b, 0x44c, 0x44d, // 84 88..8f
+ 0x44e, 0x44f, INV, INV, INV, INV, INV, INV, // 84 90..97
+ INV, INV, INV, INV, INV, INV, INV,0x2500, // 84 98..9f
+ 0x2502,0x250c,0x2510,0x2518, 0x2514,0x251c,0x252c,0x2524, // 84 a0..a7
+ 0x2534,0x253c,0x2501,0x2503, 0x250f,0x2513,0x251b,0x2517, // 84 a8..af
+ 0x2523,0x2533,0x252b,0x253b, 0x254b,0x2520,0x252f,0x2528, // 84 b0..b7
+ 0x2537,0x253f,0x251d,0x2530, 0x2525,0x2538,0x2542, // 84 b8..be
+};
+
+static const u16 shift_jis_double_88[] =
+{
+ 0x4e9c,0x5516,0x5a03,0x963f, 0x54c0,0x611b,0x6328,0x59f6, // 88 9f..a6
+ 0x9022,0x8475,0x831c,0x7a50, 0x60aa,0x63e1,0x6e25,0x65ed, // 88 a7..ae
+ 0x8466,0x82a6,0x9bf5,0x6893, 0x5727,0x65a1,0x6271,0x5b9b, // 88 af..b6
+ 0x59d0,0x867b,0x98f4,0x7d62, 0x7dbe,0x9b8e,0x6216,0x7c9f, // 88 b7..be
+ 0x88b7,0x5b89,0x5eb5,0x6309, 0x6697,0x6848,0x95c7,0x978d, // 88 bf..c6
+ 0x674f,0x4ee5,0x4f0a,0x4f4d, 0x4f9d,0x5049,0x56f2,0x5937, // 88 c7..ce
+ 0x59d4,0x5a01,0x5c09,0x60df, 0x610f,0x6170,0x6613,0x6905, // 88 cf..d6
+ 0x70ba,0x754f,0x7570,0x79fb, 0x7dad,0x7def,0x80c3,0x840e, // 88 d7..de
+ 0x8863,0x8b02,0x9055,0x907a, 0x533b,0x4e95,0x4ea5,0x57df, // 88 df..e6
+ 0x80b2,0x90c1,0x78ef,0x4e00, 0x58f1,0x6ea2,0x9038,0x7a32, // 88 e7..ee
+ 0x8328,0x828b,0x9c2f,0x5141, 0x5370,0x54bd,0x54e1,0x56e0, // 88 ef..f6
+ 0x59fb,0x5f15,0x98f2,0x6deb, 0x80e4,0x852d, // 88 f7..fc
+};
+
+static const u16 shift_jis_double_89[] =
+{
+ 0x9662,0x9670,0x96a0,0x97fb, 0x540b,0x53f3,0x5b87,0x70cf, // 89 40..47
+ 0x7fbd,0x8fc2,0x96e8,0x536f, 0x9d5c,0x7aba,0x4e11,0x7893, // 89 48..4f
+ 0x81fc,0x6e26,0x5618,0x5504, 0x6b1d,0x851a,0x9c3b,0x59e5, // 89 50..57
+ 0x53a9,0x6d66,0x74dc,0x958f, 0x5642,0x4e91,0x904b,0x96f2, // 89 58..5f
+ 0x834f,0x990c,0x53e1,0x55b6, 0x5b30,0x5f71,0x6620,0x66f3, // 89 60..67
+ 0x6804,0x6c38,0x6cf3,0x6d29, 0x745b,0x76c8,0x7a4e,0x9834, // 89 68..6f
+ 0x82f1,0x885b,0x8a60,0x92ed, 0x6db2,0x75ab,0x76ca,0x99c5, // 89 70..77
+ 0x60a6,0x8b01,0x8d8a,0x95b2, 0x698e,0x53ad,0x5186, INV, // 89 78..7f
+ 0x5712,0x5830,0x5944,0x5bb4, 0x5ef6,0x6028,0x63a9,0x63f4, // 89 80..87
+ 0x6cbf,0x6f14,0x708e,0x7114, 0x7159,0x71d5,0x733f,0x7e01, // 89 88..8f
+ 0x8276,0x82d1,0x8597,0x9060, 0x925b,0x9d1b,0x5869,0x65bc, // 89 90..97
+ 0x6c5a,0x7525,0x51f9,0x592e, 0x5965,0x5f80,0x5fdc,0x62bc, // 89 98..9f
+ 0x65fa,0x6a2a,0x6b27,0x6bb4, 0x738b,0x7fc1,0x8956,0x9d2c, // 89 a0..a7
+ 0x9d0e,0x9ec4,0x5ca1,0x6c96, 0x837b,0x5104,0x5c4b,0x61b6, // 89 a8..af
+ 0x81c6,0x6876,0x7261,0x4e59, 0x4ffa,0x5378,0x6069,0x6e29, // 89 b0..b7
+ 0x7a4f,0x97f3,0x4e0b,0x5316, 0x4eee,0x4f55,0x4f3d,0x4fa1, // 89 b8..bf
+ 0x4f73,0x52a0,0x53ef,0x5609, 0x590f,0x5ac1,0x5bb6,0x5be1, // 89 c0..c7
+ 0x79d1,0x6687,0x679c,0x67b6, 0x6b4c,0x6cb3,0x706b,0x73c2, // 89 c8..cf
+ 0x798d,0x79be,0x7a3c,0x7b87, 0x82b1,0x82db,0x8304,0x8377, // 89 d0..d7
+ 0x83ef,0x83d3,0x8766,0x8ab2, 0x5629,0x8ca8,0x8fe6,0x904e, // 89 d8..df
+ 0x971e,0x868a,0x4fc4,0x5ce8, 0x6211,0x7259,0x753b,0x81e5, // 89 e0..e7
+ 0x82bd,0x86fe,0x8cc0,0x96c5, 0x9913,0x99d5,0x4ecb,0x4f1a, // 89 e8..ef
+ 0x89e3,0x56de,0x584a,0x58ca, 0x5efb,0x5feb,0x602a,0x6094, // 89 f0..f7
+ 0x6062,0x61d0,0x6212,0x62d0, 0x6539, // 89 f8..fc
+};
+
+static const u16 shift_jis_double_8a[] =
+{
+ 0x9b41,0x6666,0x68b0,0x6d77, 0x7070,0x754c,0x7686,0x7d75, // 8a 40..47
+ 0x82a5,0x87f9,0x958b,0x968e, 0x8c9d,0x51f1,0x52be,0x5916, // 8a 48..4f
+ 0x54b3,0x5bb3,0x5d16,0x6168, 0x6982,0x6daf,0x788d,0x84cb, // 8a 50..57
+ 0x8857,0x8a72,0x93a7,0x9ab8, 0x6d6c,0x99a8,0x86d9,0x57a3, // 8a 58..5f
+ 0x67ff,0x86ce,0x920e,0x5283, 0x5687,0x5404,0x5ed3,0x62e1, // 8a 60..67
+ 0x64b9,0x683c,0x6838,0x6bbb, 0x7372,0x78ba,0x7a6b,0x899a, // 8a 68..6f
+ 0x89d2,0x8d6b,0x8f03,0x90ed, 0x95a3,0x9694,0x9769,0x5b66, // 8a 70..77
+ 0x5cb3,0x697d,0x984d,0x984e, 0x639b,0x7b20,0x6a2b, INV, // 8a 78..7f
+ 0x6a7f,0x68b6,0x9c0d,0x6f5f, 0x5272,0x559d,0x6070,0x62ec, // 8a 80..87
+ 0x6d3b,0x6e07,0x6ed1,0x845b, 0x8910,0x8f44,0x4e14,0x9c39, // 8a 88..8f
+ 0x53f6,0x691b,0x6a3a,0x9784, 0x682a,0x515c,0x7ac3,0x84b2, // 8a 90..97
+ 0x91dc,0x938c,0x565b,0x9d28, 0x6822,0x8305,0x8431,0x7ca5, // 8a 98..9f
+ 0x5208,0x82c5,0x74e6,0x4e7e, 0x4f83,0x51a0,0x5bd2,0x520a, // 8a a0..a7
+ 0x52d8,0x52e7,0x5dfb,0x559a, 0x582a,0x59e6,0x5b8c,0x5b98, // 8a a8..af
+ 0x5bdb,0x5e72,0x5e79,0x60a3, 0x611f,0x6163,0x61be,0x63db, // 8a b0..b7
+ 0x6562,0x67d1,0x6853,0x68fa, 0x6b3e,0x6b53,0x6c57,0x6f22, // 8a b8..bf
+ 0x6f97,0x6f45,0x74b0,0x7518, 0x76e3,0x770b,0x7aff,0x7ba1, // 8a c0..c7
+ 0x7c21,0x7de9,0x7f36,0x7ff0, 0x809d,0x8266,0x839e,0x89b3, // 8a c8..cf
+ 0x8acc,0x8cab,0x9084,0x9451, 0x9593,0x9591,0x95a2,0x9665, // 8a d0..d7
+ 0x97d3,0x9928,0x8218,0x4e38, 0x542b,0x5cb8,0x5dcc,0x73a9, // 8a d8..df
+ 0x764c,0x773c,0x5ca9,0x7feb, 0x8d0b,0x96c1,0x9811,0x9854, // 8a e0..e7
+ 0x9858,0x4f01,0x4f0e,0x5371, 0x559c,0x5668,0x57fa,0x5947, // 8a e8..ef
+ 0x5b09,0x5bc4,0x5c90,0x5e0c, 0x5e7e,0x5fcc,0x63ee,0x673a, // 8a f0..f7
+ 0x65d7,0x65e2,0x671f,0x68cb, 0x68c4, // 8a f8..fc
+};
+
+static const u16 shift_jis_double_8b[] =
+{
+ 0x6a5f,0x5e30,0x6bc5,0x6c17, 0x6c7d,0x757f,0x7948,0x5b63, // 8b 40..47
+ 0x7a00,0x7d00,0x5fbd,0x898f, 0x8a18,0x8cb4,0x8d77,0x8ecc, // 8b 48..4f
+ 0x8f1d,0x98e2,0x9a0e,0x9b3c, 0x4e80,0x507d,0x5100,0x5993, // 8b 50..57
+ 0x5b9c,0x622f,0x6280,0x64ec, 0x6b3a,0x72a0,0x7591,0x7947, // 8b 58..5f
+ 0x7fa9,0x87fb,0x8abc,0x8b70, 0x63ac,0x83ca,0x97a0,0x5409, // 8b 60..67
+ 0x5403,0x55ab,0x6854,0x6a58, 0x8a70,0x7827,0x6775,0x9ecd, // 8b 68..6f
+ 0x5374,0x5ba2,0x811a,0x8650, 0x9006,0x4e18,0x4e45,0x4ec7, // 8b 70..77
+ 0x4f11,0x53ca,0x5438,0x5bae, 0x5f13,0x6025,0x6551, INV, // 8b 78..7f
+ 0x673d,0x6c42,0x6c72,0x6ce3, 0x7078,0x7403,0x7a76,0x7aae, // 8b 80..87
+ 0x7b08,0x7d1a,0x7cfe,0x7d66, 0x65e7,0x725b,0x53bb,0x5c45, // 8b 88..8f
+ 0x5de8,0x62d2,0x62e0,0x6319, 0x6e20,0x865a,0x8a31,0x8ddd, // 8b 90..97
+ 0x92f8,0x6f01,0x79a6,0x9b5a, 0x4ea8,0x4eab,0x4eac,0x4f9b, // 8b 98..9f
+ 0x4fa0,0x50d1,0x5147,0x7af6, 0x5171,0x51f6,0x5354,0x5321, // 8b a0..a7
+ 0x537f,0x53eb,0x55ac,0x5883, 0x5ce1,0x5f37,0x5f4a,0x602f, // 8b a8..af
+ 0x6050,0x606d,0x631f,0x6559, 0x6a4b,0x6cc1,0x72c2,0x72ed, // 8b b0..b7
+ 0x77ef,0x80f8,0x8105,0x8208, 0x854e,0x90f7,0x93e1,0x97ff, // 8b b8..bf
+ 0x9957,0x9a5a,0x4ef0,0x51dd, 0x5c2d,0x6681,0x696d,0x5c40, // 8b c0..c7
+ 0x66f2,0x6975,0x7389,0x6850, 0x7c81,0x50c5,0x52e4,0x5747, // 8b c8..cf
+ 0x5dfe,0x9326,0x65a4,0x6b23, 0x6b3d,0x7434,0x7981,0x79bd, // 8b d0..d7
+ 0x7b4b,0x7dca,0x82b9,0x83cc, 0x887f,0x895f,0x8b39,0x8fd1, // 8b d8..df
+ 0x91d1,0x541f,0x9280,0x4e5d, 0x5036,0x53e5,0x533a,0x72d7, // 8b e0..e7
+ 0x7396,0x77e9,0x82e6,0x8eaf, 0x99c6,0x99c8,0x99d2,0x5177, // 8b e8..ef
+ 0x611a,0x865e,0x55b0,0x7a7a, 0x5076,0x5bd3,0x9047,0x9685, // 8b f0..f7
+ 0x4e32,0x6adb,0x91e7,0x5c51, 0x5c48, // 8b f8..fc
+};
+
+static const u16 shift_jis_double_8c[] =
+{
+ 0x6398,0x7a9f,0x6c93,0x9774, 0x8f61,0x7aaa,0x718a,0x9688, // 8c 40..47
+ 0x7c82,0x6817,0x7e70,0x6851, 0x936c,0x52f2,0x541b,0x85ab, // 8c 48..4f
+ 0x8a13,0x7fa4,0x8ecd,0x90e1, 0x5366,0x8888,0x7941,0x4fc2, // 8c 50..57
+ 0x50be,0x5211,0x5144,0x5553, 0x572d,0x73ea,0x578b,0x5951, // 8c 58..5f
+ 0x5f62,0x5f84,0x6075,0x6176, 0x6167,0x61a9,0x63b2,0x643a, // 8c 60..67
+ 0x656c,0x666f,0x6842,0x6e13, 0x7566,0x7a3d,0x7cfb,0x7d4c, // 8c 68..6f
+ 0x7d99,0x7e4b,0x7f6b,0x830e, 0x834a,0x86cd,0x8a08,0x8a63, // 8c 70..77
+ 0x8b66,0x8efd,0x981a,0x9d8f, 0x82b8,0x8fce,0x9be8, INV, // 8c 78..7f
+ 0x5287,0x621f,0x6483,0x6fc0, 0x9699,0x6841,0x5091,0x6b20, // 8c 80..87
+ 0x6c7a,0x6f54,0x7a74,0x7d50, 0x8840,0x8a23,0x6708,0x4ef6, // 8c 88..8f
+ 0x5039,0x5026,0x5065,0x517c, 0x5238,0x5263,0x55a7,0x570f, // 8c 90..97
+ 0x5805,0x5acc,0x5efa,0x61b2, 0x61f8,0x62f3,0x6372,0x691c, // 8c 98..9f
+ 0x6a29,0x727d,0x72ac,0x732e, 0x7814,0x786f,0x7d79,0x770c, // 8c a0..a7
+ 0x80a9,0x898b,0x8b19,0x8ce2, 0x8ed2,0x9063,0x9375,0x967a, // 8c a8..af
+ 0x9855,0x9a13,0x9e78,0x5143, 0x539f,0x53b3,0x5e7b,0x5f26, // 8c b0..b7
+ 0x6e1b,0x6e90,0x7384,0x73fe, 0x7d43,0x8237,0x8a00,0x8afa, // 8c b8..bf
+ 0x9650,0x4e4e,0x500b,0x53e4, 0x547c,0x56fa,0x59d1,0x5b64, // 8c c0..c7
+ 0x5df1,0x5eab,0x5f27,0x6238, 0x6545,0x67af,0x6e56,0x72d0, // 8c c8..cf
+ 0x7cca,0x88b4,0x80a1,0x80e1, 0x83f0,0x864e,0x8a87,0x8de8, // 8c d0..d7
+ 0x9237,0x96c7,0x9867,0x9f13, 0x4e94,0x4e92,0x4f0d,0x5348, // 8c d8..df
+ 0x5449,0x543e,0x5a2f,0x5f8c, 0x5fa1,0x609f,0x68a7,0x6a8e, // 8c e0..e7
+ 0x745a,0x7881,0x8a9e,0x8aa4, 0x8b77,0x9190,0x4e5e,0x9bc9, // 8c e8..ef
+ 0x4ea4,0x4f7c,0x4faf,0x5019, 0x5016,0x5149,0x516c,0x529f, // 8c f0..f7
+ 0x52b9,0x52fe,0x539a,0x53e3, 0x5411, // 8c f8..fc
+};
+
+static const u16 shift_jis_double_8d[] =
+{
+ 0x540e,0x5589,0x5751,0x57a2, 0x597d,0x5b54,0x5b5d,0x5b8f, // 8d 40..47
+ 0x5de5,0x5de7,0x5df7,0x5e78, 0x5e83,0x5e9a,0x5eb7,0x5f18, // 8d 48..4f
+ 0x6052,0x614c,0x6297,0x62d8, 0x63a7,0x653b,0x6602,0x6643, // 8d 50..57
+ 0x66f4,0x676d,0x6821,0x6897, 0x69cb,0x6c5f,0x6d2a,0x6d69, // 8d 58..5f
+ 0x6e2f,0x6e9d,0x7532,0x7687, 0x786c,0x7a3f,0x7ce0,0x7d05, // 8d 60..67
+ 0x7d18,0x7d5e,0x7db1,0x8015, 0x8003,0x80af,0x80b1,0x8154, // 8d 68..6f
+ 0x818f,0x822a,0x8352,0x884c, 0x8861,0x8b1b,0x8ca2,0x8cfc, // 8d 70..77
+ 0x90ca,0x9175,0x9271,0x783f, 0x92fc,0x95a4,0x964d, INV, // 8d 78..7f
+ 0x9805,0x9999,0x9ad8,0x9d3b, 0x525b,0x52ab,0x53f7,0x5408, // 8d 80..87
+ 0x58d5,0x62f7,0x6fe0,0x8c6a, 0x8f5f,0x9eb9,0x514b,0x523b, // 8d 88..8f
+ 0x544a,0x56fd,0x7a40,0x9177, 0x9d60,0x9ed2,0x7344,0x6f09, // 8d 90..97
+ 0x8170,0x7511,0x5ffd,0x60da, 0x9aa8,0x72db,0x8fbc,0x6b64, // 8d 98..9f
+ 0x9803,0x4eca,0x56f0,0x5764, 0x58be,0x5a5a,0x6068,0x61c7, // 8d a0..a7
+ 0x660f,0x6606,0x6839,0x68b1, 0x6df7,0x75d5,0x7d3a,0x826e, // 8d a8..af
+ 0x9b42,0x4e9b,0x4f50,0x53c9, 0x5506,0x5d6f,0x5de6,0x5dee, // 8d b0..b7
+ 0x67fb,0x6c99,0x7473,0x7802, 0x8a50,0x9396,0x88df,0x5750, // 8d b8..bf
+ 0x5ea7,0x632b,0x50b5,0x50ac, 0x518d,0x6700,0x54c9,0x585e, // 8d c0..c7
+ 0x59bb,0x5bb0,0x5f69,0x624d, 0x63a1,0x683d,0x6b73,0x6e08, // 8d c8..cf
+ 0x707d,0x91c7,0x7280,0x7815, 0x7826,0x796d,0x658e,0x7d30, // 8d d0..d7
+ 0x83dc,0x88c1,0x8f09,0x969b, 0x5264,0x5728,0x6750,0x7f6a, // 8d d8..df
+ 0x8ca1,0x51b4,0x5742,0x962a, 0x583a,0x698a,0x80b4,0x54b2, // 8d e0..e7
+ 0x5d0e,0x57fc,0x7895,0x9dfa, 0x4f5c,0x524a,0x548b,0x643e, // 8d e8..ef
+ 0x6628,0x6714,0x67f5,0x7a84, 0x7b56,0x7d22,0x932f,0x685c, // 8d f0..f7
+ 0x9bad,0x7b39,0x5319,0x518a, 0x5237, // 8d f8..fc
+};
+
+static const u16 shift_jis_double_8e[] =
+{
+ 0x5bdf,0x62f6,0x64ae,0x64e6, 0x672d,0x6bba,0x85a9,0x96d1, // 8e 40..47
+ 0x7690,0x9bd6,0x634c,0x9306, 0x9bab,0x76bf,0x6652,0x4e09, // 8e 48..4f
+ 0x5098,0x53c2,0x5c71,0x60e8, 0x6492,0x6563,0x685f,0x71e6, // 8e 50..57
+ 0x73ca,0x7523,0x7b97,0x7e82, 0x8695,0x8b83,0x8cdb,0x9178, // 8e 58..5f
+ 0x9910,0x65ac,0x66ab,0x6b8b, 0x4ed5,0x4ed4,0x4f3a,0x4f7f, // 8e 60..67
+ 0x523a,0x53f8,0x53f2,0x55e3, 0x56db,0x58eb,0x59cb,0x59c9, // 8e 68..6f
+ 0x59ff,0x5b50,0x5c4d,0x5e02, 0x5e2b,0x5fd7,0x601d,0x6307, // 8e 70..77
+ 0x652f,0x5b5c,0x65af,0x65bd, 0x65e8,0x679d,0x6b62, INV, // 8e 78..7f
+ 0x6b7b,0x6c0f,0x7345,0x7949, 0x79c1,0x7cf8,0x7d19,0x7d2b, // 8e 80..87
+ 0x80a2,0x8102,0x81f3,0x8996, 0x8a5e,0x8a69,0x8a66,0x8a8c, // 8e 88..8f
+ 0x8aee,0x8cc7,0x8cdc,0x96cc, 0x98fc,0x6b6f,0x4e8b,0x4f3c, // 8e 90..97
+ 0x4f8d,0x5150,0x5b57,0x5bfa, 0x6148,0x6301,0x6642,0x6b21, // 8e 98..9f
+ 0x6ecb,0x6cbb,0x723e,0x74bd, 0x75d4,0x78c1,0x793a,0x800c, // 8e a0..a7
+ 0x8033,0x81ea,0x8494,0x8f9e, 0x6c50,0x9e7f,0x5f0f,0x8b58, // 8e a8..af
+ 0x9d2b,0x7afa,0x8ef8,0x5b8d, 0x96eb,0x4e03,0x53f1,0x57f7, // 8e b0..b7
+ 0x5931,0x5ac9,0x5ba4,0x6089, 0x6e7f,0x6f06,0x75be,0x8cea, // 8e b8..bf
+ 0x5b9f,0x8500,0x7be0,0x5072, 0x67f4,0x829d,0x5c61,0x854a, // 8e c0..c7
+ 0x7e1e,0x820e,0x5199,0x5c04, 0x6368,0x8d66,0x659c,0x716e, // 8e c8..cf
+ 0x793e,0x7d17,0x8005,0x8b1d, 0x8eca,0x906e,0x86c7,0x90aa, // 8e d0..d7
+ 0x501f,0x52fa,0x5c3a,0x6753, 0x707c,0x7235,0x914c,0x91c8, // 8e d8..df
+ 0x932b,0x82e5,0x5bc2,0x5f31, 0x60f9,0x4e3b,0x53d6,0x5b88, // 8e e0..e7
+ 0x624b,0x6731,0x6b8a,0x72e9, 0x73e0,0x7a2e,0x816b,0x8da3, // 8e e8..ef
+ 0x9152,0x9996,0x5112,0x53d7, 0x546a,0x5bff,0x6388,0x6a39, // 8e f0..f7
+ 0x7dac,0x9700,0x56da,0x53ce, 0x5468, // 8e f8..fc
+};
+
+static const u16 shift_jis_double_8f[] =
+{
+ 0x5b97,0x5c31,0x5dde,0x4fee, 0x6101,0x62fe,0x6d32,0x79c0, // 8f 40..47
+ 0x79cb,0x7d42,0x7e4d,0x7fd2, 0x81ed,0x821f,0x8490,0x8846, // 8f 48..4f
+ 0x8972,0x8b90,0x8e74,0x8f2f, 0x9031,0x914b,0x916c,0x96c6, // 8f 50..57
+ 0x919c,0x4ec0,0x4f4f,0x5145, 0x5341,0x5f93,0x620e,0x67d4, // 8f 58..5f
+ 0x6c41,0x6e0b,0x7363,0x7e26, 0x91cd,0x9283,0x53d4,0x5919, // 8f 60..67
+ 0x5bbf,0x6dd1,0x795d,0x7e2e, 0x7c9b,0x587e,0x719f,0x51fa, // 8f 68..6f
+ 0x8853,0x8ff0,0x4fca,0x5cfb, 0x6625,0x77ac,0x7ae3,0x821c, // 8f 70..77
+ 0x99ff,0x51c6,0x5faa,0x65ec, 0x696f,0x6b89,0x6df3, INV, // 8f 78..7f
+ 0x6e96,0x6f64,0x76fe,0x7d14, 0x5de1,0x9075,0x9187,0x9806, // 8f 80..87
+ 0x51e6,0x521d,0x6240,0x6691, 0x66d9,0x6e1a,0x5eb6,0x7dd2, // 8f 88..8f
+ 0x7f72,0x66f8,0x85af,0x85f7, 0x8af8,0x52a9,0x53d9,0x5973, // 8f 90..97
+ 0x5e8f,0x5f90,0x6055,0x92e4, 0x9664,0x50b7,0x511f,0x52dd, // 8f 98..9f
+ 0x5320,0x5347,0x53ec,0x54e8, 0x5546,0x5531,0x5617,0x5968, // 8f a0..a7
+ 0x59be,0x5a3c,0x5bb5,0x5c06, 0x5c0f,0x5c11,0x5c1a,0x5e84, // 8f a8..af
+ 0x5e8a,0x5ee0,0x5f70,0x627f, 0x6284,0x62db,0x638c,0x6377, // 8f b0..b7
+ 0x6607,0x660c,0x662d,0x6676, 0x677e,0x68a2,0x6a1f,0x6a35, // 8f b8..bf
+ 0x6cbc,0x6d88,0x6e09,0x6e58, 0x713c,0x7126,0x7167,0x75c7, // 8f c0..c7
+ 0x7701,0x785d,0x7901,0x7965, 0x79f0,0x7ae0,0x7b11,0x7ca7, // 8f c8..cf
+ 0x7d39,0x8096,0x83d6,0x848b, 0x8549,0x885d,0x88f3,0x8a1f, // 8f d0..d7
+ 0x8a3c,0x8a54,0x8a73,0x8c61, 0x8cde,0x91a4,0x9266,0x937e, // 8f d8..df
+ 0x9418,0x969c,0x9798,0x4e0a, 0x4e08,0x4e1e,0x4e57,0x5197, // 8f e0..e7
+ 0x5270,0x57ce,0x5834,0x58cc, 0x5b22,0x5e38,0x60c5,0x64fe, // 8f e8..ef
+ 0x6761,0x6756,0x6d44,0x72b6, 0x7573,0x7a63,0x84b8,0x8b72, // 8f f0..f7
+ 0x91b8,0x9320,0x5631,0x57f4, 0x98fe, // 8f f8..fc
+};
+
+static const u16 shift_jis_double_90[] =
+{
+ 0x62ed,0x690d,0x6b96,0x71ed, 0x7e54,0x8077,0x8272,0x89e6, // 90 40..47
+ 0x98df,0x8755,0x8fb1,0x5c3b, 0x4f38,0x4fe1,0x4fb5,0x5507, // 90 48..4f
+ 0x5a20,0x5bdd,0x5be9,0x5fc3, 0x614e,0x632f,0x65b0,0x664b, // 90 50..57
+ 0x68ee,0x699b,0x6d78,0x6df1, 0x7533,0x75b9,0x771f,0x795e, // 90 58..5f
+ 0x79e6,0x7d33,0x81e3,0x82af, 0x85aa,0x89aa,0x8a3a,0x8eab, // 90 60..67
+ 0x8f9b,0x9032,0x91dd,0x9707, 0x4eba,0x4ec1,0x5203,0x5875, // 90 68..6f
+ 0x58ec,0x5c0b,0x751a,0x5c3d, 0x814e,0x8a0a,0x8fc5,0x9663, // 90 70..77
+ 0x976d,0x7b25,0x8acf,0x9808, 0x9162,0x56f3,0x53a8, INV, // 90 78..7f
+ 0x9017,0x5439,0x5782,0x5e25, 0x63a8,0x6c34,0x708a,0x7761, // 90 80..87
+ 0x7c8b,0x7fe0,0x8870,0x9042, 0x9154,0x9310,0x9318,0x968f, // 90 88..8f
+ 0x745e,0x9ac4,0x5d07,0x5d69, 0x6570,0x67a2,0x8da8,0x96db, // 90 90..97
+ 0x636e,0x6749,0x6919,0x83c5, 0x9817,0x96c0,0x88fe,0x6f84, // 90 98..9f
+ 0x647a,0x5bf8,0x4e16,0x702c, 0x755d,0x662f,0x51c4,0x5236, // 90 a0..a7
+ 0x52e2,0x59d3,0x5f81,0x6027, 0x6210,0x653f,0x6574,0x661f, // 90 a8..af
+ 0x6674,0x68f2,0x6816,0x6b63, 0x6e05,0x7272,0x751f,0x76db, // 90 b0..b7
+ 0x7cbe,0x8056,0x58f0,0x88fd, 0x897f,0x8aa0,0x8a93,0x8acb, // 90 b8..bf
+ 0x901d,0x9192,0x9752,0x9759, 0x6589,0x7a0e,0x8106,0x96bb, // 90 c0..c7
+ 0x5e2d,0x60dc,0x621a,0x65a5, 0x6614,0x6790,0x77f3,0x7a4d, // 90 c8..cf
+ 0x7c4d,0x7e3e,0x810a,0x8cac, 0x8d64,0x8de1,0x8e5f,0x78a9, // 90 d0..d7
+ 0x5207,0x62d9,0x63a5,0x6442, 0x6298,0x8a2d,0x7a83,0x7bc0, // 90 d8..df
+ 0x8aac,0x96ea,0x7d76,0x820c, 0x8749,0x4ed9,0x5148,0x5343, // 90 e0..e7
+ 0x5360,0x5ba3,0x5c02,0x5c16, 0x5ddd,0x6226,0x6247,0x64b0, // 90 e8..ef
+ 0x6813,0x6834,0x6cc9,0x6d45, 0x6d17,0x67d3,0x6f5c,0x714e, // 90 f0..f7
+ 0x717d,0x65cb,0x7a7f,0x7bad, 0x7dda, // 90 f8..fc
+};
+
+static const u16 shift_jis_double_91[] =
+{
+ 0x7e4a,0x7fa8,0x817a,0x821b, 0x8239,0x85a6,0x8a6e,0x8cce, // 91 40..47
+ 0x8df5,0x9078,0x9077,0x92ad, 0x9291,0x9583,0x9bae,0x524d, // 91 48..4f
+ 0x5584,0x6f38,0x7136,0x5168, 0x7985,0x7e55,0x81b3,0x7cce, // 91 50..57
+ 0x564c,0x5851,0x5ca8,0x63aa, 0x66fe,0x66fd,0x695a,0x72d9, // 91 58..5f
+ 0x758f,0x758e,0x790e,0x7956, 0x79df,0x7c97,0x7d20,0x7d44, // 91 60..67
+ 0x8607,0x8a34,0x963b,0x9061, 0x9f20,0x50e7,0x5275,0x53cc, // 91 68..6f
+ 0x53e2,0x5009,0x55aa,0x58ee, 0x594f,0x723d,0x5b8b,0x5c64, // 91 70..77
+ 0x531d,0x60e3,0x60f3,0x635c, 0x6383,0x633f,0x63bb, INV, // 91 78..7f
+ 0x64cd,0x65e9,0x66f9,0x5de3, 0x69cd,0x69fd,0x6f15,0x71e5, // 91 80..87
+ 0x4e89,0x75e9,0x76f8,0x7a93, 0x7cdf,0x7dcf,0x7d9c,0x8061, // 91 88..8f
+ 0x8349,0x8358,0x846c,0x84bc, 0x85fb,0x88c5,0x8d70,0x9001, // 91 90..97
+ 0x906d,0x9397,0x971c,0x9a12, 0x50cf,0x5897,0x618e,0x81d3, // 91 98..9f
+ 0x8535,0x8d08,0x9020,0x4fc3, 0x5074,0x5247,0x5373,0x606f, // 91 a0..a7
+ 0x6349,0x675f,0x6e2c,0x8db3, 0x901f,0x4fd7,0x5c5e,0x8cca, // 91 a8..af
+ 0x65cf,0x7d9a,0x5352,0x8896, 0x5176,0x63c3,0x5b58,0x5b6b, // 91 b0..b7
+ 0x5c0a,0x640d,0x6751,0x905c, 0x4ed6,0x591a,0x592a,0x6c70, // 91 b8..bf
+ 0x8a51,0x553e,0x5815,0x59a5, 0x60f0,0x6253,0x67c1,0x8235, // 91 c0..c7
+ 0x6955,0x9640,0x99c4,0x9a28, 0x4f53,0x5806,0x5bfe,0x8010, // 91 c8..cf
+ 0x5cb1,0x5e2f,0x5f85,0x6020, 0x614b,0x6234,0x66ff,0x6cf0, // 91 d0..d7
+ 0x6ede,0x80ce,0x817f,0x82d4, 0x888b,0x8cb8,0x9000,0x902e, // 91 d8..df
+ 0x968a,0x9edb,0x9bdb,0x4ee3, 0x53f0,0x5927,0x7b2c,0x918d, // 91 e0..e7
+ 0x984c,0x9df9,0x6edd,0x7027, 0x5353,0x5544,0x5b85,0x6258, // 91 e8..ef
+ 0x629e,0x62d3,0x6ca2,0x6fef, 0x7422,0x8a17,0x9438,0x6fc1, // 91 f0..f7
+ 0x8afe,0x8338,0x51e7,0x86f8, 0x53ea, // 91 f8..fc
+};
+
+static const u16 shift_jis_double_92[] =
+{
+ 0x53e9,0x4f46,0x9054,0x8fb0, 0x596a,0x8131,0x5dfd,0x7aea, // 92 40..47
+ 0x8fbf,0x68da,0x8c37,0x72f8, 0x9c48,0x6a3d,0x8ab0,0x4e39, // 92 48..4f
+ 0x5358,0x5606,0x5766,0x62c5, 0x63a2,0x65e6,0x6b4e,0x6de1, // 92 50..57
+ 0x6e5b,0x70ad,0x77ed,0x7aef, 0x7baa,0x7dbb,0x803d,0x80c6, // 92 58..5f
+ 0x86cb,0x8a95,0x935b,0x56e3, 0x58c7,0x5f3e,0x65ad,0x6696, // 92 60..67
+ 0x6a80,0x6bb5,0x7537,0x8ac7, 0x5024,0x77e5,0x5730,0x5f1b, // 92 68..6f
+ 0x6065,0x667a,0x6c60,0x75f4, 0x7a1a,0x7f6e,0x81f4,0x8718, // 92 70..77
+ 0x9045,0x99b3,0x7bc9,0x755c, 0x7af9,0x7b51,0x84c4, INV, // 92 78..7f
+ 0x9010,0x79e9,0x7a92,0x8336, 0x5ae1,0x7740,0x4e2d,0x4ef2, // 92 80..87
+ 0x5b99,0x5fe0,0x62bd,0x663c, 0x67f1,0x6ce8,0x866b,0x8877, // 92 88..8f
+ 0x8a3b,0x914e,0x92f3,0x99d0, 0x6a17,0x7026,0x732a,0x82e7, // 92 90..97
+ 0x8457,0x8caf,0x4e01,0x5146, 0x51cb,0x558b,0x5bf5,0x5e16, // 92 98..9f
+ 0x5e33,0x5e81,0x5f14,0x5f35, 0x5f6b,0x5fb4,0x61f2,0x6311, // 92 a0..a7
+ 0x66a2,0x671d,0x6f6e,0x7252, 0x753a,0x773a,0x8074,0x8139, // 92 a8..af
+ 0x8178,0x8776,0x8abf,0x8adc, 0x8d85,0x8df3,0x929a,0x9577, // 92 b0..b7
+ 0x9802,0x9ce5,0x52c5,0x6357, 0x76f4,0x6715,0x6c88,0x73cd, // 92 b8..bf
+ 0x8cc3,0x93ae,0x9673,0x6d25, 0x589c,0x690e,0x69cc,0x8ffd, // 92 c0..c7
+ 0x939a,0x75db,0x901a,0x585a, 0x6802,0x63b4,0x69fb,0x4f43, // 92 c8..cf
+ 0x6f2c,0x67d8,0x8fbb,0x8526, 0x7db4,0x9354,0x693f,0x6f70, // 92 d0..d7
+ 0x576a,0x58f7,0x5b2c,0x7d2c, 0x722a,0x540a,0x91e3,0x9db4, // 92 d8..df
+ 0x4ead,0x4f4e,0x505c,0x5075, 0x5243,0x8c9e,0x5448,0x5824, // 92 e0..e7
+ 0x5b9a,0x5e1d,0x5e95,0x5ead, 0x5ef7,0x5f1f,0x608c,0x62b5, // 92 e8..ef
+ 0x633a,0x63d0,0x68af,0x6c40, 0x7887,0x798e,0x7a0b,0x7de0, // 92 f0..f7
+ 0x8247,0x8a02,0x8ae6,0x8e44, 0x9013, // 92 f8..fc
+};
+
+static const u16 shift_jis_double_93[] =
+{
+ 0x90b8,0x912d,0x91d8,0x9f0e, 0x6ce5,0x6458,0x64e2,0x6575, // 93 40..47
+ 0x6ef4,0x7684,0x7b1b,0x9069, 0x93d1,0x6eba,0x54f2,0x5fb9, // 93 48..4f
+ 0x64a4,0x8f4d,0x8fed,0x9244, 0x5178,0x586b,0x5929,0x5c55, // 93 50..57
+ 0x5e97,0x6dfb,0x7e8f,0x751c, 0x8cbc,0x8ee2,0x985b,0x70b9, // 93 58..5f
+ 0x4f1d,0x6bbf,0x6fb1,0x7530, 0x96fb,0x514e,0x5410,0x5835, // 93 60..67
+ 0x5857,0x59ac,0x5c60,0x5f92, 0x6597,0x675c,0x6e21,0x767b, // 93 68..6f
+ 0x83df,0x8ced,0x9014,0x90fd, 0x934d,0x7825,0x783a,0x52aa, // 93 70..77
+ 0x5ea6,0x571f,0x5974,0x6012, 0x5012,0x515a,0x51ac, INV, // 93 78..7f
+ 0x51cd,0x5200,0x5510,0x5854, 0x5858,0x5957,0x5b95,0x5cf6, // 93 80..87
+ 0x5d8b,0x60bc,0x6295,0x642d, 0x6771,0x6843,0x68bc,0x68df, // 93 88..8f
+ 0x76d7,0x6dd8,0x6e6f,0x6d9b, 0x706f,0x71c8,0x5f53,0x75d8, // 93 90..97
+ 0x7977,0x7b49,0x7b54,0x7b52, 0x7cd6,0x7d71,0x5230,0x8463, // 93 98..9f
+ 0x8569,0x85e4,0x8a0e,0x8b04, 0x8c46,0x8e0f,0x9003,0x900f, // 93 a0..a7
+ 0x9419,0x9676,0x982d,0x9a30, 0x95d8,0x50cd,0x52d5,0x540c, // 93 a8..af
+ 0x5802,0x5c0e,0x61a7,0x649e, 0x6d1e,0x77b3,0x7ae5,0x80f4, // 93 b0..b7
+ 0x8404,0x9053,0x9285,0x5ce0, 0x9d07,0x533f,0x5f97,0x5fb3, // 93 b8..bf
+ 0x6d9c,0x7279,0x7763,0x79bf, 0x7be4,0x6bd2,0x72ec,0x8aad, // 93 c0..c7
+ 0x6803,0x6a61,0x51f8,0x7a81, 0x6934,0x5c4a,0x9cf6,0x82eb, // 93 c8..cf
+ 0x5bc5,0x9149,0x701e,0x5678, 0x5c6f,0x60c7,0x6566,0x6c8c, // 93 d0..d7
+ 0x8c5a,0x9041,0x9813,0x5451, 0x66c7,0x920d,0x5948,0x90a3, // 93 d8..df
+ 0x5185,0x4e4d,0x51ea,0x8599, 0x8b0e,0x7058,0x637a,0x934b, // 93 e0..e7
+ 0x6962,0x99b4,0x7e04,0x7577, 0x5357,0x6960,0x8edf,0x96e3, // 93 e8..ef
+ 0x6c5d,0x4e8c,0x5c3c,0x5f10, 0x8fe9,0x5302,0x8cd1,0x8089, // 93 f0..f7
+ 0x8679,0x5eff,0x65e5,0x4e73, 0x5165, // 93 f8..fc
+};
+
+static const u16 shift_jis_double_94[] =
+{
+ 0x5982,0x5c3f,0x97ee,0x4efb, 0x598a,0x5fcd,0x8a8d,0x6fe1, // 94 40..47
+ 0x79b0,0x7962,0x5be7,0x8471, 0x732b,0x71b1,0x5e74,0x5ff5, // 94 48..4f
+ 0x637b,0x649a,0x71c3,0x7c98, 0x4e43,0x5efc,0x4e4b,0x57dc, // 94 50..57
+ 0x56a2,0x60a9,0x6fc3,0x7d0d, 0x80fd,0x8133,0x81bf,0x8fb2, // 94 58..5f
+ 0x8997,0x86a4,0x5df4,0x628a, 0x64ad,0x8987,0x6777,0x6ce2, // 94 60..67
+ 0x6d3e,0x7436,0x7834,0x5a46, 0x7f75,0x82ad,0x99ac,0x4ff3, // 94 68..6f
+ 0x5ec3,0x62dd,0x6392,0x6557, 0x676f,0x76c3,0x724c,0x80cc, // 94 70..77
+ 0x80ba,0x8f29,0x914d,0x500d, 0x57f9,0x5a92,0x6885, INV, // 94 78..7f
+ 0x6973,0x7164,0x72fd,0x8cb7, 0x58f2,0x8ce0,0x966a,0x9019, // 94 80..87
+ 0x877f,0x79e4,0x77e7,0x8429, 0x4f2f,0x5265,0x535a,0x62cd, // 94 88..8f
+ 0x67cf,0x6cca,0x767d,0x7b94, 0x7c95,0x8236,0x8584,0x8feb, // 94 90..97
+ 0x66dd,0x6f20,0x7206,0x7e1b, 0x83ab,0x99c1,0x9ea6,0x51fd, // 94 98..9f
+ 0x7bb1,0x7872,0x7bb8,0x8087, 0x7b48,0x6ae8,0x5e61,0x808c, // 94 a0..a7
+ 0x7551,0x7560,0x516b,0x9262, 0x6e8c,0x767a,0x9197,0x9aea, // 94 a8..af
+ 0x4f10,0x7f70,0x629c,0x7b4f, 0x95a5,0x9ce9,0x567a,0x5859, // 94 b0..b7
+ 0x86e4,0x96bc,0x4f34,0x5224, 0x534a,0x53cd,0x53db,0x5e06, // 94 b8..bf
+ 0x642c,0x6591,0x677f,0x6c3e, 0x6c4e,0x7248,0x72af,0x73ed, // 94 c0..c7
+ 0x7554,0x7e41,0x822c,0x85e9, 0x8ca9,0x7bc4,0x91c6,0x7169, // 94 c8..cf
+ 0x9812,0x98ef,0x633d,0x6669, 0x756a,0x76e4,0x78d0,0x8543, // 94 d0..d7
+ 0x86ee,0x532a,0x5351,0x5426, 0x5983,0x5e87,0x5f7c,0x60b2, // 94 d8..df
+ 0x6249,0x6279,0x62ab,0x6590, 0x6bd4,0x6ccc,0x75b2,0x76ae, // 94 e0..e7
+ 0x7891,0x79d8,0x7dcb,0x7f77, 0x80a5,0x88ab,0x8ab9,0x8cbb, // 94 e8..ef
+ 0x907f,0x975e,0x98db,0x6a0b, 0x7c38,0x5099,0x5c3e,0x5fae, // 94 f0..f7
+ 0x6787,0x6bd8,0x7435,0x7709, 0x7f8e, // 94 f8..fc
+};
+
+static const u16 shift_jis_double_95[] =
+{
+ 0x9f3b,0x67ca,0x7a17,0x5339, 0x758b,0x9aed,0x5f66,0x819d, // 95 40..47
+ 0x83f1,0x8098,0x5f3c,0x5fc5, 0x7562,0x7b46,0x903c,0x6867, // 95 48..4f
+ 0x59eb,0x5a9b,0x7d10,0x767e, 0x8b2c,0x4ff5,0x5f6a,0x6a19, // 95 50..57
+ 0x6c37,0x6f02,0x74e2,0x7968, 0x8868,0x8a55,0x8c79,0x5edf, // 95 58..5f
+ 0x63cf,0x75c5,0x79d2,0x82d7, 0x9328,0x92f2,0x849c,0x86ed, // 95 60..67
+ 0x9c2d,0x54c1,0x5f6c,0x658c, 0x6d5c,0x7015,0x8ca7,0x8cd3, // 95 68..6f
+ 0x983b,0x654f,0x74f6,0x4e0d, 0x4ed8,0x57e0,0x592b,0x5a66, // 95 70..77
+ 0x5bcc,0x51a8,0x5e03,0x5e9c, 0x6016,0x6276,0x6577, INV, // 95 78..7f
+ 0x65a7,0x666e,0x6d6e,0x7236, 0x7b26,0x8150,0x819a,0x8299, // 95 80..87
+ 0x8b5c,0x8ca0,0x8ce6,0x8d74, 0x961c,0x9644,0x4fae,0x64ab, // 95 88..8f
+ 0x6b66,0x821e,0x8461,0x856a, 0x90e8,0x5c01,0x6953,0x98a8, // 95 90..97
+ 0x847a,0x8557,0x4f0f,0x526f, 0x5fa9,0x5e45,0x670d,0x798f, // 95 98..9f
+ 0x8179,0x8907,0x8986,0x6df5, 0x5f17,0x6255,0x6cb8,0x4ecf, // 95 a0..a7
+ 0x7269,0x9b92,0x5206,0x543b, 0x5674,0x58b3,0x61a4,0x626e, // 95 a8..af
+ 0x711a,0x596e,0x7c89,0x7cde, 0x7d1b,0x96f0,0x6587,0x805e, // 95 b0..b7
+ 0x4e19,0x4f75,0x5175,0x5840, 0x5e63,0x5e73,0x5f0a,0x67c4, // 95 b8..bf
+ 0x4e26,0x853d,0x9589,0x965b, 0x7c73,0x9801,0x50fb,0x58c1, // 95 c0..c7
+ 0x7656,0x78a7,0x5225,0x77a5, 0x8511,0x7b86,0x504f,0x5909, // 95 c8..cf
+ 0x7247,0x7bc7,0x7de8,0x8fba, 0x8fd4,0x904d,0x4fbf,0x52c9, // 95 d0..d7
+ 0x5a29,0x5f01,0x97ad,0x4fdd, 0x8217,0x92ea,0x5703,0x6355, // 95 d8..df
+ 0x6b69,0x752b,0x88dc,0x8f14, 0x7a42,0x52df,0x5893,0x6155, // 95 e0..e7
+ 0x620a,0x66ae,0x6bcd,0x7c3f, 0x83e9,0x5023,0x4ff8,0x5305, // 95 e8..ef
+ 0x5446,0x5831,0x5949,0x5b9d, 0x5cf0,0x5cef,0x5d29,0x5e96, // 95 f0..f7
+ 0x62b1,0x6367,0x653e,0x65b9, 0x670b, // 95 f8..fc
+};
+
+static const u16 shift_jis_double_96[] =
+{
+ 0x6cd5,0x6ce1,0x70f9,0x7832, 0x7e2b,0x80de,0x82b3,0x840c, // 96 40..47
+ 0x84ec,0x8702,0x8912,0x8a2a, 0x8c4a,0x90a6,0x92d2,0x98fd, // 96 48..4f
+ 0x9cf3,0x9d6c,0x4e4f,0x4ea1, 0x508d,0x5256,0x574a,0x59a8, // 96 50..57
+ 0x5e3d,0x5fd8,0x5fd9,0x623f, 0x66b4,0x671b,0x67d0,0x68d2, // 96 58..5f
+ 0x5192,0x7d21,0x80aa,0x81a8, 0x8b00,0x8c8c,0x8cbf,0x927e, // 96 60..67
+ 0x9632,0x5420,0x982c,0x5317, 0x50d5,0x535c,0x58a8,0x64b2, // 96 68..6f
+ 0x6734,0x7267,0x7766,0x7a46, 0x91e6,0x52c3,0x6ca1,0x6b86, // 96 70..77
+ 0x5800,0x5e4c,0x5954,0x672c, 0x7ffb,0x51e1,0x76c6, INV, // 96 78..7f
+ 0x6469,0x78e8,0x9b54,0x9ebb, 0x57cb,0x59b9,0x6627,0x679a, // 96 80..87
+ 0x6bce,0x54e9,0x69d9,0x5e55, 0x819c,0x6795,0x9baa,0x67fe, // 96 88..8f
+ 0x9c52,0x685d,0x4ea6,0x4fe3, 0x53c8,0x62b9,0x672b,0x6cab, // 96 90..97
+ 0x8fc4,0x4fad,0x7e6d,0x9ebf, 0x4e07,0x6162,0x6e80,0x6f2b, // 96 98..9f
+ 0x8513,0x5473,0x672a,0x9b45, 0x5df3,0x7b95,0x5cac,0x5bc6, // 96 a0..a7
+ 0x871c,0x6e4a,0x84d1,0x7a14, 0x8108,0x5999,0x7c8d,0x6c11, // 96 a8..af
+ 0x7720,0x52d9,0x5922,0x7121, 0x725f,0x77db,0x9727,0x9d61, // 96 b0..b7
+ 0x690b,0x5a7f,0x5a18,0x51a5, 0x540d,0x547d,0x660e,0x76df, // 96 b8..bf
+ 0x8ff7,0x9298,0x9cf4,0x59ea, 0x725d,0x6ec5,0x514d,0x68c9, // 96 c0..c7
+ 0x7dbf,0x7dec,0x9762,0x9eba, 0x6478,0x6a21,0x8302,0x5984, // 96 c8..cf
+ 0x5b5f,0x6bdb,0x731b,0x76f2, 0x7db2,0x8017,0x8499,0x5132, // 96 d0..d7
+ 0x6728,0x9ed9,0x76ee,0x6762, 0x52ff,0x9905,0x5c24,0x623b, // 96 d8..df
+ 0x7c7e,0x8cb0,0x554f,0x60b6, 0x7d0b,0x9580,0x5301,0x4e5f, // 96 e0..e7
+ 0x51b6,0x591c,0x723a,0x8036, 0x91ce,0x5f25,0x77e2,0x5384, // 96 e8..ef
+ 0x5f79,0x7d04,0x85ac,0x8a33, 0x8e8d,0x9756,0x67f3,0x85ae, // 96 f0..f7
+ 0x9453,0x6109,0x6108,0x6cb9, 0x7652, // 96 f8..fc
+};
+
+static const u16 shift_jis_double_97[] =
+{
+ 0x8aed,0x8f38,0x552f,0x4f51, 0x512a,0x52c7,0x53cb,0x5ba5, // 97 40..47
+ 0x5e7d,0x60a0,0x6182,0x63d6, 0x6709,0x67da,0x6e67,0x6d8c, // 97 48..4f
+ 0x7336,0x7337,0x7531,0x7950, 0x88d5,0x8a98,0x904a,0x9091, // 97 50..57
+ 0x90f5,0x96c4,0x878d,0x5915, 0x4e88,0x4f59,0x4e0e,0x8a89, // 97 58..5f
+ 0x8f3f,0x9810,0x50ad,0x5e7c, 0x5996,0x5bb9,0x5eb8,0x63da, // 97 60..67
+ 0x63fa,0x64c1,0x66dc,0x694a, 0x69d8,0x6d0b,0x6eb6,0x7194, // 97 68..6f
+ 0x7528,0x7aaf,0x7f8a,0x8000, 0x8449,0x84c9,0x8981,0x8b21, // 97 70..77
+ 0x8e0a,0x9065,0x967d,0x990a, 0x617e,0x6291,0x6b32, INV, // 97 78..7f
+ 0x6c83,0x6d74,0x7fcc,0x7ffc, 0x6dc0,0x7f85,0x87ba,0x88f8, // 97 80..87
+ 0x6765,0x83b1,0x983c,0x96f7, 0x6d1b,0x7d61,0x843d,0x916a, // 97 88..8f
+ 0x4e71,0x5375,0x5d50,0x6b04, 0x6feb,0x85cd,0x862d,0x89a7, // 97 90..97
+ 0x5229,0x540f,0x5c65,0x674e, 0x68a8,0x7406,0x7483,0x75e2, // 97 98..9f
+ 0x88cf,0x88e1,0x91cc,0x96e2, 0x9678,0x5f8b,0x7387,0x7acb, // 97 a0..a7
+ 0x844e,0x63a0,0x7565,0x5289, 0x6d41,0x6e9c,0x7409,0x7559, // 97 a8..af
+ 0x786b,0x7c92,0x9686,0x7adc, 0x9f8d,0x4fb6,0x616e,0x65c5, // 97 b0..b7
+ 0x865c,0x4e86,0x4eae,0x50da, 0x4e21,0x51cc,0x5bee,0x6599, // 97 b8..bf
+ 0x6881,0x6dbc,0x731f,0x7642, 0x77ad,0x7a1c,0x7ce7,0x826f, // 97 c0..c7
+ 0x8ad2,0x907c,0x91cf,0x9675, 0x9818,0x529b,0x7dd1,0x502b, // 97 c8..cf
+ 0x5398,0x6797,0x6dcb,0x71d0, 0x7433,0x81e8,0x8f2a,0x96a3, // 97 d0..d7
+ 0x9c57,0x9e9f,0x7460,0x5841, 0x6d99,0x7d2f,0x985e,0x4ee4, // 97 d8..df
+ 0x4f36,0x4f8b,0x51b7,0x52b1, 0x5dba,0x601c,0x73b2,0x793c, // 97 e0..e7
+ 0x82d3,0x9234,0x96b7,0x96f6, 0x970a,0x9e97,0x9f62,0x66a6, // 97 e8..ef
+ 0x6b74,0x5217,0x52a3,0x70c8, 0x88c2,0x5ec9,0x604b,0x6190, // 97 f0..f7
+ 0x6f23,0x7149,0x7c3e,0x7df4, 0x806f, // 97 f8..fc
+};
+
+static const u16 shift_jis_double_98[] =
+{
+ 0x84ee,0x9023,0x932c,0x5442, 0x9b6f,0x6ad3,0x7089,0x8cc2, // 98 40..47
+ 0x8def,0x9732,0x52b4,0x5a41, 0x5eca,0x5f04,0x6717,0x697c, // 98 48..4f
+ 0x6994,0x6d6a,0x6f0f,0x7262, 0x72fc,0x7bed,0x8001,0x807e, // 98 50..57
+ 0x874b,0x90ce,0x516d,0x9e93, 0x7984,0x808b,0x9332,0x8ad6, // 98 58..5f
+ 0x502d,0x548c,0x8a71,0x6b6a, 0x8cc4,0x8107,0x60d1,0x67a0, // 98 60..67
+ 0x9df2,0x4e99,0x4e98,0x9c10, 0x8a6b,0x85c1,0x8568,0x6900, // 98 68..6f
+ 0x6e7e,0x7897,0x8155, INV, INV, INV, INV, INV, // 98 70..77
+ INV, INV, INV, INV, INV, INV, INV, INV, // 98 78..7f
+ INV, INV, INV, INV, INV, INV, INV, INV, // 98 80..87
+ INV, INV, INV, INV, INV, INV, INV, INV, // 98 88..8f
+ INV, INV, INV, INV, INV, INV, INV, INV, // 98 90..97
+ INV, INV, INV, INV, INV, INV, INV,0x5f0c, // 98 98..9f
+ 0x4e10,0x4e15,0x4e2a,0x4e31, 0x4e36,0x4e3c,0x4e3f,0x4e42, // 98 a0..a7
+ 0x4e56,0x4e58,0x4e82,0x4e85, 0x8c6b,0x4e8a,0x8212,0x5f0d, // 98 a8..af
+ 0x4e8e,0x4e9e,0x4e9f,0x4ea0, 0x4ea2,0x4eb0,0x4eb3,0x4eb6, // 98 b0..b7
+ 0x4ece,0x4ecd,0x4ec4,0x4ec6, 0x4ec2,0x4ed7,0x4ede,0x4eed, // 98 b8..bf
+ 0x4edf,0x4ef7,0x4f09,0x4f5a, 0x4f30,0x4f5b,0x4f5d,0x4f57, // 98 c0..c7
+ 0x4f47,0x4f76,0x4f88,0x4f8f, 0x4f98,0x4f7b,0x4f69,0x4f70, // 98 c8..cf
+ 0x4f91,0x4f6f,0x4f86,0x4f96, 0x5118,0x4fd4,0x4fdf,0x4fce, // 98 d0..d7
+ 0x4fd8,0x4fdb,0x4fd1,0x4fda, 0x4fd0,0x4fe4,0x4fe5,0x501a, // 98 d8..df
+ 0x5028,0x5014,0x502a,0x5025, 0x5005,0x4f1c,0x4ff6,0x5021, // 98 e0..e7
+ 0x5029,0x502c,0x4ffe,0x4fef, 0x5011,0x5006,0x5043,0x5047, // 98 e8..ef
+ 0x6703,0x5055,0x5050,0x5048, 0x505a,0x5056,0x506c,0x5078, // 98 f0..f7
+ 0x5080,0x509a,0x5085,0x50b4, 0x50b2, // 98 f8..fc
+};
+
+static const u16 shift_jis_double_99[] =
+{
+ 0x50c9,0x50ca,0x50b3,0x50c2, 0x50d6,0x50de,0x50e5,0x50ed, // 99 40..47
+ 0x50e3,0x50ee,0x50f9,0x50f5, 0x5109,0x5101,0x5102,0x5116, // 99 48..4f
+ 0x5115,0x5114,0x511a,0x5121, 0x513a,0x5137,0x513c,0x513b, // 99 50..57
+ 0x513f,0x5140,0x5152,0x514c, 0x5154,0x5162,0x7af8,0x5169, // 99 58..5f
+ 0x516a,0x516e,0x5180,0x5182, 0x56d8,0x518c,0x5189,0x518f, // 99 60..67
+ 0x5191,0x5193,0x5195,0x5196, 0x51a4,0x51a6,0x51a2,0x51a9, // 99 68..6f
+ 0x51aa,0x51ab,0x51b3,0x51b1, 0x51b2,0x51b0,0x51b5,0x51bd, // 99 70..77
+ 0x51c5,0x51c9,0x51db,0x51e0, 0x8655,0x51e9,0x51ed, INV, // 99 78..7f
+ 0x51f0,0x51f5,0x51fe,0x5204, 0x520b,0x5214,0x520e,0x5227, // 99 80..87
+ 0x522a,0x522e,0x5233,0x5239, 0x524f,0x5244,0x524b,0x524c, // 99 88..8f
+ 0x525e,0x5254,0x526a,0x5274, 0x5269,0x5273,0x527f,0x527d, // 99 90..97
+ 0x528d,0x5294,0x5292,0x5271, 0x5288,0x5291,0x8fa8,0x8fa7, // 99 98..9f
+ 0x52ac,0x52ad,0x52bc,0x52b5, 0x52c1,0x52cd,0x52d7,0x52de, // 99 a0..a7
+ 0x52e3,0x52e6,0x98ed,0x52e0, 0x52f3,0x52f5,0x52f8,0x52f9, // 99 a8..af
+ 0x5306,0x5308,0x7538,0x530d, 0x5310,0x530f,0x5315,0x531a, // 99 b0..b7
+ 0x5323,0x532f,0x5331,0x5333, 0x5338,0x5340,0x5346,0x5345, // 99 b8..bf
+ 0x4e17,0x5349,0x534d,0x51d6, 0x535e,0x5369,0x536e,0x5918, // 99 c0..c7
+ 0x537b,0x5377,0x5382,0x5396, 0x53a0,0x53a6,0x53a5,0x53ae, // 99 c8..cf
+ 0x53b0,0x53b6,0x53c3,0x7c12, 0x96d9,0x53df,0x66fc,0x71ee, // 99 d0..d7
+ 0x53ee,0x53e8,0x53ed,0x53fa, 0x5401,0x543d,0x5440,0x542c, // 99 d8..df
+ 0x542d,0x543c,0x542e,0x5436, 0x5429,0x541d,0x544e,0x548f, // 99 e0..e7
+ 0x5475,0x548e,0x545f,0x5471, 0x5477,0x5470,0x5492,0x547b, // 99 e8..ef
+ 0x5480,0x5476,0x5484,0x5490, 0x5486,0x54c7,0x54a2,0x54b8, // 99 f0..f7
+ 0x54a5,0x54ac,0x54c4,0x54c8, 0x54a8, // 99 f8..fc
+};
+
+static const u16 shift_jis_double_9a[] =
+{
+ 0x54ab,0x54c2,0x54a4,0x54be, 0x54bc,0x54d8,0x54e5,0x54e6, // 9a 40..47
+ 0x550f,0x5514,0x54fd,0x54ee, 0x54ed,0x54fa,0x54e2,0x5539, // 9a 48..4f
+ 0x5540,0x5563,0x554c,0x552e, 0x555c,0x5545,0x5556,0x5557, // 9a 50..57
+ 0x5538,0x5533,0x555d,0x5599, 0x5580,0x54af,0x558a,0x559f, // 9a 58..5f
+ 0x557b,0x557e,0x5598,0x559e, 0x55ae,0x557c,0x5583,0x55a9, // 9a 60..67
+ 0x5587,0x55a8,0x55da,0x55c5, 0x55df,0x55c4,0x55dc,0x55e4, // 9a 68..6f
+ 0x55d4,0x5614,0x55f7,0x5616, 0x55fe,0x55fd,0x561b,0x55f9, // 9a 70..77
+ 0x564e,0x5650,0x71df,0x5634, 0x5636,0x5632,0x5638, INV, // 9a 78..7f
+ 0x566b,0x5664,0x562f,0x566c, 0x566a,0x5686,0x5680,0x568a, // 9a 80..87
+ 0x56a0,0x5694,0x568f,0x56a5, 0x56ae,0x56b6,0x56b4,0x56c2, // 9a 88..8f
+ 0x56bc,0x56c1,0x56c3,0x56c0, 0x56c8,0x56ce,0x56d1,0x56d3, // 9a 90..97
+ 0x56d7,0x56ee,0x56f9,0x5700, 0x56ff,0x5704,0x5709,0x5708, // 9a 98..9f
+ 0x570b,0x570d,0x5713,0x5718, 0x5716,0x55c7,0x571c,0x5726, // 9a a0..a7
+ 0x5737,0x5738,0x574e,0x573b, 0x5740,0x574f,0x5769,0x57c0, // 9a a8..af
+ 0x5788,0x5761,0x577f,0x5789, 0x5793,0x57a0,0x57b3,0x57a4, // 9a b0..b7
+ 0x57aa,0x57b0,0x57c3,0x57c6, 0x57d4,0x57d2,0x57d3,0x580a, // 9a b8..bf
+ 0x57d6,0x57e3,0x580b,0x5819, 0x581d,0x5872,0x5821,0x5862, // 9a c0..c7
+ 0x584b,0x5870,0x6bc0,0x5852, 0x583d,0x5879,0x5885,0x58b9, // 9a c8..cf
+ 0x589f,0x58ab,0x58ba,0x58de, 0x58bb,0x58b8,0x58ae,0x58c5, // 9a d0..d7
+ 0x58d3,0x58d1,0x58d7,0x58d9, 0x58d8,0x58e5,0x58dc,0x58e4, // 9a d8..df
+ 0x58df,0x58ef,0x58fa,0x58f9, 0x58fb,0x58fc,0x58fd,0x5902, // 9a e0..e7
+ 0x590a,0x5910,0x591b,0x68a6, 0x5925,0x592c,0x592d,0x5932, // 9a e8..ef
+ 0x5938,0x593e,0x7ad2,0x5955, 0x5950,0x594e,0x595a,0x5958, // 9a f0..f7
+ 0x5962,0x5960,0x5967,0x596c, 0x5969, // 9a f8..fc
+};
+
+static const u16 shift_jis_double_9b[] =
+{
+ 0x5978,0x5981,0x599d,0x4f5e, 0x4fab,0x59a3,0x59b2,0x59c6, // 9b 40..47
+ 0x59e8,0x59dc,0x598d,0x59d9, 0x59da,0x5a25,0x5a1f,0x5a11, // 9b 48..4f
+ 0x5a1c,0x5a09,0x5a1a,0x5a40, 0x5a6c,0x5a49,0x5a35,0x5a36, // 9b 50..57
+ 0x5a62,0x5a6a,0x5a9a,0x5abc, 0x5abe,0x5acb,0x5ac2,0x5abd, // 9b 58..5f
+ 0x5ae3,0x5ad7,0x5ae6,0x5ae9, 0x5ad6,0x5afa,0x5afb,0x5b0c, // 9b 60..67
+ 0x5b0b,0x5b16,0x5b32,0x5ad0, 0x5b2a,0x5b36,0x5b3e,0x5b43, // 9b 68..6f
+ 0x5b45,0x5b40,0x5b51,0x5b55, 0x5b5a,0x5b5b,0x5b65,0x5b69, // 9b 70..77
+ 0x5b70,0x5b73,0x5b75,0x5b78, 0x6588,0x5b7a,0x5b80, INV, // 9b 78..7f
+ 0x5b83,0x5ba6,0x5bb8,0x5bc3, 0x5bc7,0x5bc9,0x5bd4,0x5bd0, // 9b 80..87
+ 0x5be4,0x5be6,0x5be2,0x5bde, 0x5be5,0x5beb,0x5bf0,0x5bf6, // 9b 88..8f
+ 0x5bf3,0x5c05,0x5c07,0x5c08, 0x5c0d,0x5c13,0x5c20,0x5c22, // 9b 90..97
+ 0x5c28,0x5c38,0x5c39,0x5c41, 0x5c46,0x5c4e,0x5c53,0x5c50, // 9b 98..9f
+ 0x5c4f,0x5b71,0x5c6c,0x5c6e, 0x4e62,0x5c76,0x5c79,0x5c8c, // 9b a0..a7
+ 0x5c91,0x5c94,0x599b,0x5cab, 0x5cbb,0x5cb6,0x5cbc,0x5cb7, // 9b a8..af
+ 0x5cc5,0x5cbe,0x5cc7,0x5cd9, 0x5ce9,0x5cfd,0x5cfa,0x5ced, // 9b b0..b7
+ 0x5d8c,0x5cea,0x5d0b,0x5d15, 0x5d17,0x5d5c,0x5d1f,0x5d1b, // 9b b8..bf
+ 0x5d11,0x5d14,0x5d22,0x5d1a, 0x5d19,0x5d18,0x5d4c,0x5d52, // 9b c0..c7
+ 0x5d4e,0x5d4b,0x5d6c,0x5d73, 0x5d76,0x5d87,0x5d84,0x5d82, // 9b c8..cf
+ 0x5da2,0x5d9d,0x5dac,0x5dae, 0x5dbd,0x5d90,0x5db7,0x5dbc, // 9b d0..d7
+ 0x5dc9,0x5dcd,0x5dd3,0x5dd2, 0x5dd6,0x5ddb,0x5deb,0x5df2, // 9b d8..df
+ 0x5df5,0x5e0b,0x5e1a,0x5e19, 0x5e11,0x5e1b,0x5e36,0x5e37, // 9b e0..e7
+ 0x5e44,0x5e43,0x5e40,0x5e4e, 0x5e57,0x5e54,0x5e5f,0x5e62, // 9b e8..ef
+ 0x5e64,0x5e47,0x5e75,0x5e76, 0x5e7a,0x9ebc,0x5e7f,0x5ea0, // 9b f0..f7
+ 0x5ec1,0x5ec2,0x5ec8,0x5ed0, 0x5ecf, // 9b f8..fc
+};
+
+static const u16 shift_jis_double_9c[] =
+{
+ 0x5ed6,0x5ee3,0x5edd,0x5eda, 0x5edb,0x5ee2,0x5ee1,0x5ee8, // 9c 40..47
+ 0x5ee9,0x5eec,0x5ef1,0x5ef3, 0x5ef0,0x5ef4,0x5ef8,0x5efe, // 9c 48..4f
+ 0x5f03,0x5f09,0x5f5d,0x5f5c, 0x5f0b,0x5f11,0x5f16,0x5f29, // 9c 50..57
+ 0x5f2d,0x5f38,0x5f41,0x5f48, 0x5f4c,0x5f4e,0x5f2f,0x5f51, // 9c 58..5f
+ 0x5f56,0x5f57,0x5f59,0x5f61, 0x5f6d,0x5f73,0x5f77,0x5f83, // 9c 60..67
+ 0x5f82,0x5f7f,0x5f8a,0x5f88, 0x5f91,0x5f87,0x5f9e,0x5f99, // 9c 68..6f
+ 0x5f98,0x5fa0,0x5fa8,0x5fad, 0x5fbc,0x5fd6,0x5ffb,0x5fe4, // 9c 70..77
+ 0x5ff8,0x5ff1,0x5fdd,0x60b3, 0x5fff,0x6021,0x6060, INV, // 9c 78..7f
+ 0x6019,0x6010,0x6029,0x600e, 0x6031,0x601b,0x6015,0x602b, // 9c 80..87
+ 0x6026,0x600f,0x603a,0x605a, 0x6041,0x606a,0x6077,0x605f, // 9c 88..8f
+ 0x604a,0x6046,0x604d,0x6063, 0x6043,0x6064,0x6042,0x606c, // 9c 90..97
+ 0x606b,0x6059,0x6081,0x608d, 0x60e7,0x6083,0x609a,0x6084, // 9c 98..9f
+ 0x609b,0x6096,0x6097,0x6092, 0x60a7,0x608b,0x60e1,0x60b8, // 9c a0..a7
+ 0x60e0,0x60d3,0x60b4,0x5ff0, 0x60bd,0x60c6,0x60b5,0x60d8, // 9c a8..af
+ 0x614d,0x6115,0x6106,0x60f6, 0x60f7,0x6100,0x60f4,0x60fa, // 9c b0..b7
+ 0x6103,0x6121,0x60fb,0x60f1, 0x610d,0x610e,0x6147,0x613e, // 9c b8..bf
+ 0x6128,0x6127,0x614a,0x613f, 0x613c,0x612c,0x6134,0x613d, // 9c c0..c7
+ 0x6142,0x6144,0x6173,0x6177, 0x6158,0x6159,0x615a,0x616b, // 9c c8..cf
+ 0x6174,0x616f,0x6165,0x6171, 0x615f,0x615d,0x6153,0x6175, // 9c d0..d7
+ 0x6199,0x6196,0x6187,0x61ac, 0x6194,0x619a,0x618a,0x6191, // 9c d8..df
+ 0x61ab,0x61ae,0x61cc,0x61ca, 0x61c9,0x61f7,0x61c8,0x61c3, // 9c e0..e7
+ 0x61c6,0x61ba,0x61cb,0x7f79, 0x61cd,0x61e6,0x61e3,0x61f6, // 9c e8..ef
+ 0x61fa,0x61f4,0x61ff,0x61fd, 0x61fc,0x61fe,0x6200,0x6208, // 9c f0..f7
+ 0x6209,0x620d,0x620c,0x6214, 0x621b, // 9c f8..fc
+};
+
+static const u16 shift_jis_double_9d[] =
+{
+ 0x621e,0x6221,0x622a,0x622e, 0x6230,0x6232,0x6233,0x6241, // 9d 40..47
+ 0x624e,0x625e,0x6263,0x625b, 0x6260,0x6268,0x627c,0x6282, // 9d 48..4f
+ 0x6289,0x627e,0x6292,0x6293, 0x6296,0x62d4,0x6283,0x6294, // 9d 50..57
+ 0x62d7,0x62d1,0x62bb,0x62cf, 0x62ff,0x62c6,0x64d4,0x62c8, // 9d 58..5f
+ 0x62dc,0x62cc,0x62ca,0x62c2, 0x62c7,0x629b,0x62c9,0x630c, // 9d 60..67
+ 0x62ee,0x62f1,0x6327,0x6302, 0x6308,0x62ef,0x62f5,0x6350, // 9d 68..6f
+ 0x633e,0x634d,0x641c,0x634f, 0x6396,0x638e,0x6380,0x63ab, // 9d 70..77
+ 0x6376,0x63a3,0x638f,0x6389, 0x639f,0x63b5,0x636b, INV, // 9d 78..7f
+ 0x6369,0x63be,0x63e9,0x63c0, 0x63c6,0x63e3,0x63c9,0x63d2, // 9d 80..87
+ 0x63f6,0x63c4,0x6416,0x6434, 0x6406,0x6413,0x6426,0x6436, // 9d 88..8f
+ 0x651d,0x6417,0x6428,0x640f, 0x6467,0x646f,0x6476,0x644e, // 9d 90..97
+ 0x652a,0x6495,0x6493,0x64a5, 0x64a9,0x6488,0x64bc,0x64da, // 9d 98..9f
+ 0x64d2,0x64c5,0x64c7,0x64bb, 0x64d8,0x64c2,0x64f1,0x64e7, // 9d a0..a7
+ 0x8209,0x64e0,0x64e1,0x62ac, 0x64e3,0x64ef,0x652c,0x64f6, // 9d a8..af
+ 0x64f4,0x64f2,0x64fa,0x6500, 0x64fd,0x6518,0x651c,0x6505, // 9d b0..b7
+ 0x6524,0x6523,0x652b,0x6534, 0x6535,0x6537,0x6536,0x6538, // 9d b8..bf
+ 0x754b,0x6548,0x6556,0x6555, 0x654d,0x6558,0x655e,0x655d, // 9d c0..c7
+ 0x6572,0x6578,0x6582,0x6583, 0x8b8a,0x659b,0x659f,0x65ab, // 9d c8..cf
+ 0x65b7,0x65c3,0x65c6,0x65c1, 0x65c4,0x65cc,0x65d2,0x65db, // 9d d0..d7
+ 0x65d9,0x65e0,0x65e1,0x65f1, 0x6772,0x660a,0x6603,0x65fb, // 9d d8..df
+ 0x6773,0x6635,0x6636,0x6634, 0x661c,0x664f,0x6644,0x6649, // 9d e0..e7
+ 0x6641,0x665e,0x665d,0x6664, 0x6667,0x6668,0x665f,0x6662, // 9d e8..ef
+ 0x6670,0x6683,0x6688,0x668e, 0x6689,0x6684,0x6698,0x669d, // 9d f0..f7
+ 0x66c1,0x66b9,0x66c9,0x66be, 0x66bc, // 9d f8..fc
+};
+
+static const u16 shift_jis_double_9e[] =
+{
+ 0x66c4,0x66b8,0x66d6,0x66da, 0x66e0,0x663f,0x66e6,0x66e9, // 9e 40..47
+ 0x66f0,0x66f5,0x66f7,0x670f, 0x6716,0x671e,0x6726,0x6727, // 9e 48..4f
+ 0x9738,0x672e,0x673f,0x6736, 0x6741,0x6738,0x6737,0x6746, // 9e 50..57
+ 0x675e,0x6760,0x6759,0x6763, 0x6764,0x6789,0x6770,0x67a9, // 9e 58..5f
+ 0x677c,0x676a,0x678c,0x678b, 0x67a6,0x67a1,0x6785,0x67b7, // 9e 60..67
+ 0x67ef,0x67b4,0x67ec,0x67b3, 0x67e9,0x67b8,0x67e4,0x67de, // 9e 68..6f
+ 0x67dd,0x67e2,0x67ee,0x67b9, 0x67ce,0x67c6,0x67e7,0x6a9c, // 9e 70..77
+ 0x681e,0x6846,0x6829,0x6840, 0x684d,0x6832,0x684e, INV, // 9e 78..7f
+ 0x68b3,0x682b,0x6859,0x6863, 0x6877,0x687f,0x689f,0x688f, // 9e 80..87
+ 0x68ad,0x6894,0x689d,0x689b, 0x6883,0x6aae,0x68b9,0x6874, // 9e 88..8f
+ 0x68b5,0x68a0,0x68ba,0x690f, 0x688d,0x687e,0x6901,0x68ca, // 9e 90..97
+ 0x6908,0x68d8,0x6922,0x6926, 0x68e1,0x690c,0x68cd,0x68d4, // 9e 98..9f
+ 0x68e7,0x68d5,0x6936,0x6912, 0x6904,0x68d7,0x68e3,0x6925, // 9e a0..a7
+ 0x68f9,0x68e0,0x68ef,0x6928, 0x692a,0x691a,0x6923,0x6921, // 9e a8..af
+ 0x68c6,0x6979,0x6977,0x695c, 0x6978,0x696b,0x6954,0x697e, // 9e b0..b7
+ 0x696e,0x6939,0x6974,0x693d, 0x6959,0x6930,0x6961,0x695e, // 9e b8..bf
+ 0x695d,0x6981,0x696a,0x69b2, 0x69ae,0x69d0,0x69bf,0x69c1, // 9e c0..c7
+ 0x69d3,0x69be,0x69ce,0x5be8, 0x69ca,0x69dd,0x69bb,0x69c3, // 9e c8..cf
+ 0x69a7,0x6a2e,0x6991,0x69a0, 0x699c,0x6995,0x69b4,0x69de, // 9e d0..d7
+ 0x69e8,0x6a02,0x6a1b,0x69ff, 0x6b0a,0x69f9,0x69f2,0x69e7, // 9e d8..df
+ 0x6a05,0x69b1,0x6a1e,0x69ed, 0x6a14,0x69eb,0x6a0a,0x6a12, // 9e e0..e7
+ 0x6ac1,0x6a23,0x6a13,0x6a44, 0x6a0c,0x6a72,0x6a36,0x6a78, // 9e e8..ef
+ 0x6a47,0x6a62,0x6a59,0x6a66, 0x6a48,0x6a38,0x6a22,0x6a90, // 9e f0..f7
+ 0x6a8d,0x6aa0,0x6a84,0x6aa2, 0x6aa3, // 9e f8..fc
+};
+
+static const u16 shift_jis_double_9f[] =
+{
+ 0x6a97,0x8617,0x6abb,0x6ac3, 0x6ac2,0x6ab8,0x6ab3,0x6aac, // 9f 40..47
+ 0x6ade,0x6ad1,0x6adf,0x6aaa, 0x6ada,0x6aea,0x6afb,0x6b05, // 9f 48..4f
+ 0x8616,0x6afa,0x6b12,0x6b16, 0x9b31,0x6b1f,0x6b38,0x6b37, // 9f 50..57
+ 0x76dc,0x6b39,0x98ee,0x6b47, 0x6b43,0x6b49,0x6b50,0x6b59, // 9f 58..5f
+ 0x6b54,0x6b5b,0x6b5f,0x6b61, 0x6b78,0x6b79,0x6b7f,0x6b80, // 9f 60..67
+ 0x6b84,0x6b83,0x6b8d,0x6b98, 0x6b95,0x6b9e,0x6ba4,0x6baa, // 9f 68..6f
+ 0x6bab,0x6baf,0x6bb2,0x6bb1, 0x6bb3,0x6bb7,0x6bbc,0x6bc6, // 9f 70..77
+ 0x6bcb,0x6bd3,0x6bdf,0x6bec, 0x6beb,0x6bf3,0x6bef, INV, // 9f 78..7f
+ 0x9ebe,0x6c08,0x6c13,0x6c14, 0x6c1b,0x6c24,0x6c23,0x6c5e, // 9f 80..87
+ 0x6c55,0x6c62,0x6c6a,0x6c82, 0x6c8d,0x6c9a,0x6c81,0x6c9b, // 9f 88..8f
+ 0x6c7e,0x6c68,0x6c73,0x6c92, 0x6c90,0x6cc4,0x6cf1,0x6cd3, // 9f 90..97
+ 0x6cbd,0x6cd7,0x6cc5,0x6cdd, 0x6cae,0x6cb1,0x6cbe,0x6cba, // 9f 98..9f
+ 0x6cdb,0x6cef,0x6cd9,0x6cea, 0x6d1f,0x884d,0x6d36,0x6d2b, // 9f a0..a7
+ 0x6d3d,0x6d38,0x6d19,0x6d35, 0x6d33,0x6d12,0x6d0c,0x6d63, // 9f a8..af
+ 0x6d93,0x6d64,0x6d5a,0x6d79, 0x6d59,0x6d8e,0x6d95,0x6fe4, // 9f b0..b7
+ 0x6d85,0x6df9,0x6e15,0x6e0a, 0x6db5,0x6dc7,0x6de6,0x6db8, // 9f b8..bf
+ 0x6dc6,0x6dec,0x6dde,0x6dcc, 0x6de8,0x6dd2,0x6dc5,0x6dfa, // 9f c0..c7
+ 0x6dd9,0x6de4,0x6dd5,0x6dea, 0x6dee,0x6e2d,0x6e6e,0x6e2e, // 9f c8..cf
+ 0x6e19,0x6e72,0x6e5f,0x6e3e, 0x6e23,0x6e6b,0x6e2b,0x6e76, // 9f d0..d7
+ 0x6e4d,0x6e1f,0x6e43,0x6e3a, 0x6e4e,0x6e24,0x6eff,0x6e1d, // 9f d8..df
+ 0x6e38,0x6e82,0x6eaa,0x6e98, 0x6ec9,0x6eb7,0x6ed3,0x6ebd, // 9f e0..e7
+ 0x6eaf,0x6ec4,0x6eb2,0x6ed4, 0x6ed5,0x6e8f,0x6ea5,0x6ec2, // 9f e8..ef
+ 0x6e9f,0x6f41,0x6f11,0x704c, 0x6eec,0x6ef8,0x6efe,0x6f3f, // 9f f0..f7
+ 0x6ef2,0x6f31,0x6eef,0x6f32, 0x6ecc, // 9f f8..fc
+};
+
+static const u16 shift_jis_double_e0[] =
+{
+ 0x6f3e,0x6f13,0x6ef7,0x6f86, 0x6f7a,0x6f78,0x6f81,0x6f80, // e0 40..47
+ 0x6f6f,0x6f5b,0x6ff3,0x6f6d, 0x6f82,0x6f7c,0x6f58,0x6f8e, // e0 48..4f
+ 0x6f91,0x6fc2,0x6f66,0x6fb3, 0x6fa3,0x6fa1,0x6fa4,0x6fb9, // e0 50..57
+ 0x6fc6,0x6faa,0x6fdf,0x6fd5, 0x6fec,0x6fd4,0x6fd8,0x6ff1, // e0 58..5f
+ 0x6fee,0x6fdb,0x7009,0x700b, 0x6ffa,0x7011,0x7001,0x700f, // e0 60..67
+ 0x6ffe,0x701b,0x701a,0x6f74, 0x701d,0x7018,0x701f,0x7030, // e0 68..6f
+ 0x703e,0x7032,0x7051,0x7063, 0x7099,0x7092,0x70af,0x70f1, // e0 70..77
+ 0x70ac,0x70b8,0x70b3,0x70ae, 0x70df,0x70cb,0x70dd, INV, // e0 78..7f
+ 0x70d9,0x7109,0x70fd,0x711c, 0x7119,0x7165,0x7155,0x7188, // e0 80..87
+ 0x7166,0x7162,0x714c,0x7156, 0x716c,0x718f,0x71fb,0x7184, // e0 88..8f
+ 0x7195,0x71a8,0x71ac,0x71d7, 0x71b9,0x71be,0x71d2,0x71c9, // e0 90..97
+ 0x71d4,0x71ce,0x71e0,0x71ec, 0x71e7,0x71f5,0x71fc,0x71f9, // e0 98..9f
+ 0x71ff,0x720d,0x7210,0x721b, 0x7228,0x722d,0x722c,0x7230, // e0 a0..a7
+ 0x7232,0x723b,0x723c,0x723f, 0x7240,0x7246,0x724b,0x7258, // e0 a8..af
+ 0x7274,0x727e,0x7282,0x7281, 0x7287,0x7292,0x7296,0x72a2, // e0 b0..b7
+ 0x72a7,0x72b9,0x72b2,0x72c3, 0x72c6,0x72c4,0x72ce,0x72d2, // e0 b8..bf
+ 0x72e2,0x72e0,0x72e1,0x72f9, 0x72f7,0x500f,0x7317,0x730a, // e0 c0..c7
+ 0x731c,0x7316,0x731d,0x7334, 0x732f,0x7329,0x7325,0x733e, // e0 c8..cf
+ 0x734e,0x734f,0x9ed8,0x7357, 0x736a,0x7368,0x7370,0x7378, // e0 d0..d7
+ 0x7375,0x737b,0x737a,0x73c8, 0x73b3,0x73ce,0x73bb,0x73c0, // e0 d8..df
+ 0x73e5,0x73ee,0x73de,0x74a2, 0x7405,0x746f,0x7425,0x73f8, // e0 e0..e7
+ 0x7432,0x743a,0x7455,0x743f, 0x745f,0x7459,0x7441,0x745c, // e0 e8..ef
+ 0x7469,0x7470,0x7463,0x746a, 0x7476,0x747e,0x748b,0x749e, // e0 f0..f7
+ 0x74a7,0x74ca,0x74cf,0x74d4, 0x73f1, // e0 f8..fc
+};
+
+static const u16 shift_jis_double_e1[] =
+{
+ 0x74e0,0x74e3,0x74e7,0x74e9, 0x74ee,0x74f2,0x74f0,0x74f1, // e1 40..47
+ 0x74f8,0x74f7,0x7504,0x7503, 0x7505,0x750c,0x750e,0x750d, // e1 48..4f
+ 0x7515,0x7513,0x751e,0x7526, 0x752c,0x753c,0x7544,0x754d, // e1 50..57
+ 0x754a,0x7549,0x755b,0x7546, 0x755a,0x7569,0x7564,0x7567, // e1 58..5f
+ 0x756b,0x756d,0x7578,0x7576, 0x7586,0x7587,0x7574,0x758a, // e1 60..67
+ 0x7589,0x7582,0x7594,0x759a, 0x759d,0x75a5,0x75a3,0x75c2, // e1 68..6f
+ 0x75b3,0x75c3,0x75b5,0x75bd, 0x75b8,0x75bc,0x75b1,0x75cd, // e1 70..77
+ 0x75ca,0x75d2,0x75d9,0x75e3, 0x75de,0x75fe,0x75ff, INV, // e1 78..7f
+ 0x75fc,0x7601,0x75f0,0x75fa, 0x75f2,0x75f3,0x760b,0x760d, // e1 80..87
+ 0x7609,0x761f,0x7627,0x7620, 0x7621,0x7622,0x7624,0x7634, // e1 88..8f
+ 0x7630,0x763b,0x7647,0x7648, 0x7646,0x765c,0x7658,0x7661, // e1 90..97
+ 0x7662,0x7668,0x7669,0x766a, 0x7667,0x766c,0x7670,0x7672, // e1 98..9f
+ 0x7676,0x7678,0x767c,0x7680, 0x7683,0x7688,0x768b,0x768e, // e1 a0..a7
+ 0x7696,0x7693,0x7699,0x769a, 0x76b0,0x76b4,0x76b8,0x76b9, // e1 a8..af
+ 0x76ba,0x76c2,0x76cd,0x76d6, 0x76d2,0x76de,0x76e1,0x76e5, // e1 b0..b7
+ 0x76e7,0x76ea,0x862f,0x76fb, 0x7708,0x7707,0x7704,0x7729, // e1 b8..bf
+ 0x7724,0x771e,0x7725,0x7726, 0x771b,0x7737,0x7738,0x7747, // e1 c0..c7
+ 0x775a,0x7768,0x776b,0x775b, 0x7765,0x777f,0x777e,0x7779, // e1 c8..cf
+ 0x778e,0x778b,0x7791,0x77a0, 0x779e,0x77b0,0x77b6,0x77b9, // e1 d0..d7
+ 0x77bf,0x77bc,0x77bd,0x77bb, 0x77c7,0x77cd,0x77d7,0x77da, // e1 d8..df
+ 0x77dc,0x77e3,0x77ee,0x77fc, 0x780c,0x7812,0x7926,0x7820, // e1 e0..e7
+ 0x792a,0x7845,0x788e,0x7874, 0x7886,0x787c,0x789a,0x788c, // e1 e8..ef
+ 0x78a3,0x78b5,0x78aa,0x78af, 0x78d1,0x78c6,0x78cb,0x78d4, // e1 f0..f7
+ 0x78be,0x78bc,0x78c5,0x78ca, 0x78ec, // e1 f8..fc
+};
+
+static const u16 shift_jis_double_e2[] =
+{
+ 0x78e7,0x78da,0x78fd,0x78f4, 0x7907,0x7912,0x7911,0x7919, // e2 40..47
+ 0x792c,0x792b,0x7940,0x7960, 0x7957,0x795f,0x795a,0x7955, // e2 48..4f
+ 0x7953,0x797a,0x797f,0x798a, 0x799d,0x79a7,0x9f4b,0x79aa, // e2 50..57
+ 0x79ae,0x79b3,0x79b9,0x79ba, 0x79c9,0x79d5,0x79e7,0x79ec, // e2 58..5f
+ 0x79e1,0x79e3,0x7a08,0x7a0d, 0x7a18,0x7a19,0x7a20,0x7a1f, // e2 60..67
+ 0x7980,0x7a31,0x7a3b,0x7a3e, 0x7a37,0x7a43,0x7a57,0x7a49, // e2 68..6f
+ 0x7a61,0x7a62,0x7a69,0x9f9d, 0x7a70,0x7a79,0x7a7d,0x7a88, // e2 70..77
+ 0x7a97,0x7a95,0x7a98,0x7a96, 0x7aa9,0x7ac8,0x7ab0, INV, // e2 78..7f
+ 0x7ab6,0x7ac5,0x7ac4,0x7abf, 0x9083,0x7ac7,0x7aca,0x7acd, // e2 80..87
+ 0x7acf,0x7ad5,0x7ad3,0x7ad9, 0x7ada,0x7add,0x7ae1,0x7ae2, // e2 88..8f
+ 0x7ae6,0x7aed,0x7af0,0x7b02, 0x7b0f,0x7b0a,0x7b06,0x7b33, // e2 90..97
+ 0x7b18,0x7b19,0x7b1e,0x7b35, 0x7b28,0x7b36,0x7b50,0x7b7a, // e2 98..9f
+ 0x7b04,0x7b4d,0x7b0b,0x7b4c, 0x7b45,0x7b75,0x7b65,0x7b74, // e2 a0..a7
+ 0x7b67,0x7b70,0x7b71,0x7b6c, 0x7b6e,0x7b9d,0x7b98,0x7b9f, // e2 a8..af
+ 0x7b8d,0x7b9c,0x7b9a,0x7b8b, 0x7b92,0x7b8f,0x7b5d,0x7b99, // e2 b0..b7
+ 0x7bcb,0x7bc1,0x7bcc,0x7bcf, 0x7bb4,0x7bc6,0x7bdd,0x7be9, // e2 b8..bf
+ 0x7c11,0x7c14,0x7be6,0x7be5, 0x7c60,0x7c00,0x7c07,0x7c13, // e2 c0..c7
+ 0x7bf3,0x7bf7,0x7c17,0x7c0d, 0x7bf6,0x7c23,0x7c27,0x7c2a, // e2 c8..cf
+ 0x7c1f,0x7c37,0x7c2b,0x7c3d, 0x7c4c,0x7c43,0x7c54,0x7c4f, // e2 d0..d7
+ 0x7c40,0x7c50,0x7c58,0x7c5f, 0x7c64,0x7c56,0x7c65,0x7c6c, // e2 d8..df
+ 0x7c75,0x7c83,0x7c90,0x7ca4, 0x7cad,0x7ca2,0x7cab,0x7ca1, // e2 e0..e7
+ 0x7ca8,0x7cb3,0x7cb2,0x7cb1, 0x7cae,0x7cb9,0x7cbd,0x7cc0, // e2 e8..ef
+ 0x7cc5,0x7cc2,0x7cd8,0x7cd2, 0x7cdc,0x7ce2,0x9b3b,0x7cef, // e2 f0..f7
+ 0x7cf2,0x7cf4,0x7cf6,0x7cfa, 0x7d06, // e2 f8..fc
+};
+
+static const u16 shift_jis_double_e3[] =
+{
+ 0x7d02,0x7d1c,0x7d15,0x7d0a, 0x7d45,0x7d4b,0x7d2e,0x7d32, // e3 40..47
+ 0x7d3f,0x7d35,0x7d46,0x7d73, 0x7d56,0x7d4e,0x7d72,0x7d68, // e3 48..4f
+ 0x7d6e,0x7d4f,0x7d63,0x7d93, 0x7d89,0x7d5b,0x7d8f,0x7d7d, // e3 50..57
+ 0x7d9b,0x7dba,0x7dae,0x7da3, 0x7db5,0x7dc7,0x7dbd,0x7dab, // e3 58..5f
+ 0x7e3d,0x7da2,0x7daf,0x7ddc, 0x7db8,0x7d9f,0x7db0,0x7dd8, // e3 60..67
+ 0x7ddd,0x7de4,0x7dde,0x7dfb, 0x7df2,0x7de1,0x7e05,0x7e0a, // e3 68..6f
+ 0x7e23,0x7e21,0x7e12,0x7e31, 0x7e1f,0x7e09,0x7e0b,0x7e22, // e3 70..77
+ 0x7e46,0x7e66,0x7e3b,0x7e35, 0x7e39,0x7e43,0x7e37, INV, // e3 78..7f
+ 0x7e32,0x7e3a,0x7e67,0x7e5d, 0x7e56,0x7e5e,0x7e59,0x7e5a, // e3 80..87
+ 0x7e79,0x7e6a,0x7e69,0x7e7c, 0x7e7b,0x7e83,0x7dd5,0x7e7d, // e3 88..8f
+ 0x8fae,0x7e7f,0x7e88,0x7e89, 0x7e8c,0x7e92,0x7e90,0x7e93, // e3 90..97
+ 0x7e94,0x7e96,0x7e8e,0x7e9b, 0x7e9c,0x7f38,0x7f3a,0x7f45, // e3 98..9f
+ 0x7f4c,0x7f4d,0x7f4e,0x7f50, 0x7f51,0x7f55,0x7f54,0x7f58, // e3 a0..a7
+ 0x7f5f,0x7f60,0x7f68,0x7f69, 0x7f67,0x7f78,0x7f82,0x7f86, // e3 a8..af
+ 0x7f83,0x7f88,0x7f87,0x7f8c, 0x7f94,0x7f9e,0x7f9d,0x7f9a, // e3 b0..b7
+ 0x7fa3,0x7faf,0x7fb2,0x7fb9, 0x7fae,0x7fb6,0x7fb8,0x8b71, // e3 b8..bf
+ 0x7fc5,0x7fc6,0x7fca,0x7fd5, 0x7fd4,0x7fe1,0x7fe6,0x7fe9, // e3 c0..c7
+ 0x7ff3,0x7ff9,0x98dc,0x8006, 0x8004,0x800b,0x8012,0x8018, // e3 c8..cf
+ 0x8019,0x801c,0x8021,0x8028, 0x803f,0x803b,0x804a,0x8046, // e3 d0..d7
+ 0x8052,0x8058,0x805a,0x805f, 0x8062,0x8068,0x8073,0x8072, // e3 d8..df
+ 0x8070,0x8076,0x8079,0x807d, 0x807f,0x8084,0x8086,0x8085, // e3 e0..e7
+ 0x809b,0x8093,0x809a,0x80ad, 0x5190,0x80ac,0x80db,0x80e5, // e3 e8..ef
+ 0x80d9,0x80dd,0x80c4,0x80da, 0x80d6,0x8109,0x80ef,0x80f1, // e3 f0..f7
+ 0x811b,0x8129,0x8123,0x812f, 0x814b, // e3 f8..fc
+};
+
+static const u16 shift_jis_double_e4[] =
+{
+ 0x968b,0x8146,0x813e,0x8153, 0x8151,0x80fc,0x8171,0x816e, // e4 40..47
+ 0x8165,0x8166,0x8174,0x8183, 0x8188,0x818a,0x8180,0x8182, // e4 48..4f
+ 0x81a0,0x8195,0x81a4,0x81a3, 0x815f,0x8193,0x81a9,0x81b0, // e4 50..57
+ 0x81b5,0x81be,0x81b8,0x81bd, 0x81c0,0x81c2,0x81ba,0x81c9, // e4 58..5f
+ 0x81cd,0x81d1,0x81d9,0x81d8, 0x81c8,0x81da,0x81df,0x81e0, // e4 60..67
+ 0x81e7,0x81fa,0x81fb,0x81fe, 0x8201,0x8202,0x8205,0x8207, // e4 68..6f
+ 0x820a,0x820d,0x8210,0x8216, 0x8229,0x822b,0x8238,0x8233, // e4 70..77
+ 0x8240,0x8259,0x8258,0x825d, 0x825a,0x825f,0x8264, INV, // e4 78..7f
+ 0x8262,0x8268,0x826a,0x826b, 0x822e,0x8271,0x8277,0x8278, // e4 80..87
+ 0x827e,0x828d,0x8292,0x82ab, 0x829f,0x82bb,0x82ac,0x82e1, // e4 88..8f
+ 0x82e3,0x82df,0x82d2,0x82f4, 0x82f3,0x82fa,0x8393,0x8303, // e4 90..97
+ 0x82fb,0x82f9,0x82de,0x8306, 0x82dc,0x8309,0x82d9,0x8335, // e4 98..9f
+ 0x8334,0x8316,0x8332,0x8331, 0x8340,0x8339,0x8350,0x8345, // e4 a0..a7
+ 0x832f,0x832b,0x8317,0x8318, 0x8385,0x839a,0x83aa,0x839f, // e4 a8..af
+ 0x83a2,0x8396,0x8323,0x838e, 0x8387,0x838a,0x837c,0x83b5, // e4 b0..b7
+ 0x8373,0x8375,0x83a0,0x8389, 0x83a8,0x83f4,0x8413,0x83eb, // e4 b8..bf
+ 0x83ce,0x83fd,0x8403,0x83d8, 0x840b,0x83c1,0x83f7,0x8407, // e4 c0..c7
+ 0x83e0,0x83f2,0x840d,0x8422, 0x8420,0x83bd,0x8438,0x8506, // e4 c8..cf
+ 0x83fb,0x846d,0x842a,0x843c, 0x855a,0x8484,0x8477,0x846b, // e4 d0..d7
+ 0x84ad,0x846e,0x8482,0x8469, 0x8446,0x842c,0x846f,0x8479, // e4 d8..df
+ 0x8435,0x84ca,0x8462,0x84b9, 0x84bf,0x849f,0x84d9,0x84cd, // e4 e0..e7
+ 0x84bb,0x84da,0x84d0,0x84c1, 0x84c6,0x84d6,0x84a1,0x8521, // e4 e8..ef
+ 0x84ff,0x84f4,0x8517,0x8518, 0x852c,0x851f,0x8515,0x8514, // e4 f0..f7
+ 0x84fc,0x8540,0x8563,0x8558, 0x8548, // e4 f8..fc
+};
+
+static const u16 shift_jis_double_e5[] =
+{
+ 0x8541,0x8602,0x854b,0x8555, 0x8580,0x85a4,0x8588,0x8591, // e5 40..47
+ 0x858a,0x85a8,0x856d,0x8594, 0x859b,0x85ea,0x8587,0x859c, // e5 48..4f
+ 0x8577,0x857e,0x8590,0x85c9, 0x85ba,0x85cf,0x85b9,0x85d0, // e5 50..57
+ 0x85d5,0x85dd,0x85e5,0x85dc, 0x85f9,0x860a,0x8613,0x860b, // e5 58..5f
+ 0x85fe,0x85fa,0x8606,0x8622, 0x861a,0x8630,0x863f,0x864d, // e5 60..67
+ 0x4e55,0x8654,0x865f,0x8667, 0x8671,0x8693,0x86a3,0x86a9, // e5 68..6f
+ 0x86aa,0x868b,0x868c,0x86b6, 0x86af,0x86c4,0x86c6,0x86b0, // e5 70..77
+ 0x86c9,0x8823,0x86ab,0x86d4, 0x86de,0x86e9,0x86ec, INV, // e5 78..7f
+ 0x86df,0x86db,0x86ef,0x8712, 0x8706,0x8708,0x8700,0x8703, // e5 80..87
+ 0x86fb,0x8711,0x8709,0x870d, 0x86f9,0x870a,0x8734,0x873f, // e5 88..8f
+ 0x8737,0x873b,0x8725,0x8729, 0x871a,0x8760,0x875f,0x8778, // e5 90..97
+ 0x874c,0x874e,0x8774,0x8757, 0x8768,0x876e,0x8759,0x8753, // e5 98..9f
+ 0x8763,0x876a,0x8805,0x87a2, 0x879f,0x8782,0x87af,0x87cb, // e5 a0..a7
+ 0x87bd,0x87c0,0x87d0,0x96d6, 0x87ab,0x87c4,0x87b3,0x87c7, // e5 a8..af
+ 0x87c6,0x87bb,0x87ef,0x87f2, 0x87e0,0x880f,0x880d,0x87fe, // e5 b0..b7
+ 0x87f6,0x87f7,0x880e,0x87d2, 0x8811,0x8816,0x8815,0x8822, // e5 b8..bf
+ 0x8821,0x8831,0x8836,0x8839, 0x8827,0x883b,0x8844,0x8842, // e5 c0..c7
+ 0x8852,0x8859,0x885e,0x8862, 0x886b,0x8881,0x887e,0x889e, // e5 c8..cf
+ 0x8875,0x887d,0x88b5,0x8872, 0x8882,0x8897,0x8892,0x88ae, // e5 d0..d7
+ 0x8899,0x88a2,0x888d,0x88a4, 0x88b0,0x88bf,0x88b1,0x88c3, // e5 d8..df
+ 0x88c4,0x88d4,0x88d8,0x88d9, 0x88dd,0x88f9,0x8902,0x88fc, // e5 e0..e7
+ 0x88f4,0x88e8,0x88f2,0x8904, 0x890c,0x890a,0x8913,0x8943, // e5 e8..ef
+ 0x891e,0x8925,0x892a,0x892b, 0x8941,0x8944,0x893b,0x8936, // e5 f0..f7
+ 0x8938,0x894c,0x891d,0x8960, 0x895e, // e5 f8..fc
+};
+
+static const u16 shift_jis_double_e6[] =
+{
+ 0x8966,0x8964,0x896d,0x896a, 0x896f,0x8974,0x8977,0x897e, // e6 40..47
+ 0x8983,0x8988,0x898a,0x8993, 0x8998,0x89a1,0x89a9,0x89a6, // e6 48..4f
+ 0x89ac,0x89af,0x89b2,0x89ba, 0x89bd,0x89bf,0x89c0,0x89da, // e6 50..57
+ 0x89dc,0x89dd,0x89e7,0x89f4, 0x89f8,0x8a03,0x8a16,0x8a10, // e6 58..5f
+ 0x8a0c,0x8a1b,0x8a1d,0x8a25, 0x8a36,0x8a41,0x8a5b,0x8a52, // e6 60..67
+ 0x8a46,0x8a48,0x8a7c,0x8a6d, 0x8a6c,0x8a62,0x8a85,0x8a82, // e6 68..6f
+ 0x8a84,0x8aa8,0x8aa1,0x8a91, 0x8aa5,0x8aa6,0x8a9a,0x8aa3, // e6 70..77
+ 0x8ac4,0x8acd,0x8ac2,0x8ada, 0x8aeb,0x8af3,0x8ae7, INV, // e6 78..7f
+ 0x8ae4,0x8af1,0x8b14,0x8ae0, 0x8ae2,0x8af7,0x8ade,0x8adb, // e6 80..87
+ 0x8b0c,0x8b07,0x8b1a,0x8ae1, 0x8b16,0x8b10,0x8b17,0x8b20, // e6 88..8f
+ 0x8b33,0x97ab,0x8b26,0x8b2b, 0x8b3e,0x8b28,0x8b41,0x8b4c, // e6 90..97
+ 0x8b4f,0x8b4e,0x8b49,0x8b56, 0x8b5b,0x8b5a,0x8b6b,0x8b5f, // e6 98..9f
+ 0x8b6c,0x8b6f,0x8b74,0x8b7d, 0x8b80,0x8b8c,0x8b8e,0x8b92, // e6 a0..a7
+ 0x8b93,0x8b96,0x8b99,0x8b9a, 0x8c3a,0x8c41,0x8c3f,0x8c48, // e6 a8..af
+ 0x8c4c,0x8c4e,0x8c50,0x8c55, 0x8c62,0x8c6c,0x8c78,0x8c7a, // e6 b0..b7
+ 0x8c82,0x8c89,0x8c85,0x8c8a, 0x8c8d,0x8c8e,0x8c94,0x8c7c, // e6 b8..bf
+ 0x8c98,0x621d,0x8cad,0x8caa, 0x8cbd,0x8cb2,0x8cb3,0x8cae, // e6 c0..c7
+ 0x8cb6,0x8cc8,0x8cc1,0x8ce4, 0x8ce3,0x8cda,0x8cfd,0x8cfa, // e6 c8..cf
+ 0x8cfb,0x8d04,0x8d05,0x8d0a, 0x8d07,0x8d0f,0x8d0d,0x8d10, // e6 d0..d7
+ 0x9f4e,0x8d13,0x8ccd,0x8d14, 0x8d16,0x8d67,0x8d6d,0x8d71, // e6 d8..df
+ 0x8d73,0x8d81,0x8d99,0x8dc2, 0x8dbe,0x8dba,0x8dcf,0x8dda, // e6 e0..e7
+ 0x8dd6,0x8dcc,0x8ddb,0x8dcb, 0x8dea,0x8deb,0x8ddf,0x8de3, // e6 e8..ef
+ 0x8dfc,0x8e08,0x8e09,0x8dff, 0x8e1d,0x8e1e,0x8e10,0x8e1f, // e6 f0..f7
+ 0x8e42,0x8e35,0x8e30,0x8e34, 0x8e4a, // e6 f8..fc
+};
+
+static const u16 shift_jis_double_e7[] =
+{
+ 0x8e47,0x8e49,0x8e4c,0x8e50, 0x8e48,0x8e59,0x8e64,0x8e60, // e7 40..47
+ 0x8e2a,0x8e63,0x8e55,0x8e76, 0x8e72,0x8e7c,0x8e81,0x8e87, // e7 48..4f
+ 0x8e85,0x8e84,0x8e8b,0x8e8a, 0x8e93,0x8e91,0x8e94,0x8e99, // e7 50..57
+ 0x8eaa,0x8ea1,0x8eac,0x8eb0, 0x8ec6,0x8eb1,0x8ebe,0x8ec5, // e7 58..5f
+ 0x8ec8,0x8ecb,0x8edb,0x8ee3, 0x8efc,0x8efb,0x8eeb,0x8efe, // e7 60..67
+ 0x8f0a,0x8f05,0x8f15,0x8f12, 0x8f19,0x8f13,0x8f1c,0x8f1f, // e7 68..6f
+ 0x8f1b,0x8f0c,0x8f26,0x8f33, 0x8f3b,0x8f39,0x8f45,0x8f42, // e7 70..77
+ 0x8f3e,0x8f4c,0x8f49,0x8f46, 0x8f4e,0x8f57,0x8f5c, INV, // e7 78..7f
+ 0x8f62,0x8f63,0x8f64,0x8f9c, 0x8f9f,0x8fa3,0x8fad,0x8faf, // e7 80..87
+ 0x8fb7,0x8fda,0x8fe5,0x8fe2, 0x8fea,0x8fef,0x9087,0x8ff4, // e7 88..8f
+ 0x9005,0x8ff9,0x8ffa,0x9011, 0x9015,0x9021,0x900d,0x901e, // e7 90..97
+ 0x9016,0x900b,0x9027,0x9036, 0x9035,0x9039,0x8ff8,0x904f, // e7 98..9f
+ 0x9050,0x9051,0x9052,0x900e, 0x9049,0x903e,0x9056,0x9058, // e7 a0..a7
+ 0x905e,0x9068,0x906f,0x9076, 0x96a8,0x9072,0x9082,0x907d, // e7 a8..af
+ 0x9081,0x9080,0x908a,0x9089, 0x908f,0x90a8,0x90af,0x90b1, // e7 b0..b7
+ 0x90b5,0x90e2,0x90e4,0x6248, 0x90db,0x9102,0x9112,0x9119, // e7 b8..bf
+ 0x9132,0x9130,0x914a,0x9156, 0x9158,0x9163,0x9165,0x9169, // e7 c0..c7
+ 0x9173,0x9172,0x918b,0x9189, 0x9182,0x91a2,0x91ab,0x91af, // e7 c8..cf
+ 0x91aa,0x91b5,0x91b4,0x91ba, 0x91c0,0x91c1,0x91c9,0x91cb, // e7 d0..d7
+ 0x91d0,0x91d6,0x91df,0x91e1, 0x91db,0x91fc,0x91f5,0x91f6, // e7 d8..df
+ 0x921e,0x91ff,0x9214,0x922c, 0x9215,0x9211,0x925e,0x9257, // e7 e0..e7
+ 0x9245,0x9249,0x9264,0x9248, 0x9295,0x923f,0x924b,0x9250, // e7 e8..ef
+ 0x929c,0x9296,0x9293,0x929b, 0x925a,0x92cf,0x92b9,0x92b7, // e7 f0..f7
+ 0x92e9,0x930f,0x92fa,0x9344, 0x932e, // e7 f8..fc
+};
+
+static const u16 shift_jis_double_e8[] =
+{
+ 0x9319,0x9322,0x931a,0x9323, 0x933a,0x9335,0x933b,0x935c, // e8 40..47
+ 0x9360,0x937c,0x936e,0x9356, 0x93b0,0x93ac,0x93ad,0x9394, // e8 48..4f
+ 0x93b9,0x93d6,0x93d7,0x93e8, 0x93e5,0x93d8,0x93c3,0x93dd, // e8 50..57
+ 0x93d0,0x93c8,0x93e4,0x941a, 0x9414,0x9413,0x9403,0x9407, // e8 58..5f
+ 0x9410,0x9436,0x942b,0x9435, 0x9421,0x943a,0x9441,0x9452, // e8 60..67
+ 0x9444,0x945b,0x9460,0x9462, 0x945e,0x946a,0x9229,0x9470, // e8 68..6f
+ 0x9475,0x9477,0x947d,0x945a, 0x947c,0x947e,0x9481,0x947f, // e8 70..77
+ 0x9582,0x9587,0x958a,0x9594, 0x9596,0x9598,0x9599, INV, // e8 78..7f
+ 0x95a0,0x95a8,0x95a7,0x95ad, 0x95bc,0x95bb,0x95b9,0x95be, // e8 80..87
+ 0x95ca,0x6ff6,0x95c3,0x95cd, 0x95cc,0x95d5,0x95d4,0x95d6, // e8 88..8f
+ 0x95dc,0x95e1,0x95e5,0x95e2, 0x9621,0x9628,0x962e,0x962f, // e8 90..97
+ 0x9642,0x964c,0x964f,0x964b, 0x9677,0x965c,0x965e,0x965d, // e8 98..9f
+ 0x965f,0x9666,0x9672,0x966c, 0x968d,0x9698,0x9695,0x9697, // e8 a0..a7
+ 0x96aa,0x96a7,0x96b1,0x96b2, 0x96b0,0x96b4,0x96b6,0x96b8, // e8 a8..af
+ 0x96b9,0x96ce,0x96cb,0x96c9, 0x96cd,0x894d,0x96dc,0x970d, // e8 b0..b7
+ 0x96d5,0x96f9,0x9704,0x9706, 0x9708,0x9713,0x970e,0x9711, // e8 b8..bf
+ 0x970f,0x9716,0x9719,0x9724, 0x972a,0x9730,0x9739,0x973d, // e8 c0..c7
+ 0x973e,0x9744,0x9746,0x9748, 0x9742,0x9749,0x975c,0x9760, // e8 c8..cf
+ 0x9764,0x9766,0x9768,0x52d2, 0x976b,0x9771,0x9779,0x9785, // e8 d0..d7
+ 0x977c,0x9781,0x977a,0x9786, 0x978b,0x978f,0x9790,0x979c, // e8 d8..df
+ 0x97a8,0x97a6,0x97a3,0x97b3, 0x97b4,0x97c3,0x97c6,0x97c8, // e8 e0..e7
+ 0x97cb,0x97dc,0x97ed,0x9f4f, 0x97f2,0x7adf,0x97f6,0x97f5, // e8 e8..ef
+ 0x980f,0x980c,0x9838,0x9824, 0x9821,0x9837,0x983d,0x9846, // e8 f0..f7
+ 0x984f,0x984b,0x986b,0x986f, 0x9870, // e8 f8..fc
+};
+
+static const u16 shift_jis_double_e9[] =
+{
+ 0x9871,0x9874,0x9873,0x98aa, 0x98af,0x98b1,0x98b6,0x98c4, // e9 40..47
+ 0x98c3,0x98c6,0x98e9,0x98eb, 0x9903,0x9909,0x9912,0x9914, // e9 48..4f
+ 0x9918,0x9921,0x991d,0x991e, 0x9924,0x9920,0x992c,0x992e, // e9 50..57
+ 0x993d,0x993e,0x9942,0x9949, 0x9945,0x9950,0x994b,0x9951, // e9 58..5f
+ 0x9952,0x994c,0x9955,0x9997, 0x9998,0x99a5,0x99ad,0x99ae, // e9 60..67
+ 0x99bc,0x99df,0x99db,0x99dd, 0x99d8,0x99d1,0x99ed,0x99ee, // e9 68..6f
+ 0x99f1,0x99f2,0x99fb,0x99f8, 0x9a01,0x9a0f,0x9a05,0x99e2, // e9 70..77
+ 0x9a19,0x9a2b,0x9a37,0x9a45, 0x9a42,0x9a40,0x9a43, INV, // e9 78..7f
+ 0x9a3e,0x9a55,0x9a4d,0x9a5b, 0x9a57,0x9a5f,0x9a62,0x9a65, // e9 80..87
+ 0x9a64,0x9a69,0x9a6b,0x9a6a, 0x9aad,0x9ab0,0x9abc,0x9ac0, // e9 88..8f
+ 0x9acf,0x9ad1,0x9ad3,0x9ad4, 0x9ade,0x9adf,0x9ae2,0x9ae3, // e9 90..97
+ 0x9ae6,0x9aef,0x9aeb,0x9aee, 0x9af4,0x9af1,0x9af7,0x9afb, // e9 98..9f
+ 0x9b06,0x9b18,0x9b1a,0x9b1f, 0x9b22,0x9b23,0x9b25,0x9b27, // e9 a0..a7
+ 0x9b28,0x9b29,0x9b2a,0x9b2e, 0x9b2f,0x9b32,0x9b44,0x9b43, // e9 a8..af
+ 0x9b4f,0x9b4d,0x9b4e,0x9b51, 0x9b58,0x9b74,0x9b93,0x9b83, // e9 b0..b7
+ 0x9b91,0x9b96,0x9b97,0x9b9f, 0x9ba0,0x9ba8,0x9bb4,0x9bc0, // e9 b8..bf
+ 0x9bca,0x9bb9,0x9bc6,0x9bcf, 0x9bd1,0x9bd2,0x9be3,0x9be2, // e9 c0..c7
+ 0x9be4,0x9bd4,0x9be1,0x9c3a, 0x9bf2,0x9bf1,0x9bf0,0x9c15, // e9 c8..cf
+ 0x9c14,0x9c09,0x9c13,0x9c0c, 0x9c06,0x9c08,0x9c12,0x9c0a, // e9 d0..d7
+ 0x9c04,0x9c2e,0x9c1b,0x9c25, 0x9c24,0x9c21,0x9c30,0x9c47, // e9 d8..df
+ 0x9c32,0x9c46,0x9c3e,0x9c5a, 0x9c60,0x9c67,0x9c76,0x9c78, // e9 e0..e7
+ 0x9ce7,0x9cec,0x9cf0,0x9d09, 0x9d08,0x9ceb,0x9d03,0x9d06, // e9 e8..ef
+ 0x9d2a,0x9d26,0x9daf,0x9d23, 0x9d1f,0x9d44,0x9d15,0x9d12, // e9 f0..f7
+ 0x9d41,0x9d3f,0x9d3e,0x9d46, 0x9d48, // e9 f8..fc
+};
+
+static const u16 shift_jis_double_ea[] =
+{
+ 0x9d5d,0x9d5e,0x9d64,0x9d51, 0x9d50,0x9d59,0x9d72,0x9d89, // ea 40..47
+ 0x9d87,0x9dab,0x9d6f,0x9d7a, 0x9d9a,0x9da4,0x9da9,0x9db2, // ea 48..4f
+ 0x9dc4,0x9dc1,0x9dbb,0x9db8, 0x9dba,0x9dc6,0x9dcf,0x9dc2, // ea 50..57
+ 0x9dd9,0x9dd3,0x9df8,0x9de6, 0x9ded,0x9def,0x9dfd,0x9e1a, // ea 58..5f
+ 0x9e1b,0x9e1e,0x9e75,0x9e79, 0x9e7d,0x9e81,0x9e88,0x9e8b, // ea 60..67
+ 0x9e8c,0x9e92,0x9e95,0x9e91, 0x9e9d,0x9ea5,0x9ea9,0x9eb8, // ea 68..6f
+ 0x9eaa,0x9ead,0x9761,0x9ecc, 0x9ece,0x9ecf,0x9ed0,0x9ed4, // ea 70..77
+ 0x9edc,0x9ede,0x9edd,0x9ee0, 0x9ee5,0x9ee8,0x9eef, INV, // ea 78..7f
+ 0x9ef4,0x9ef6,0x9ef7,0x9ef9, 0x9efb,0x9efc,0x9efd,0x9f07, // ea 80..87
+ 0x9f08,0x76b7,0x9f15,0x9f21, 0x9f2c,0x9f3e,0x9f4a,0x9f52, // ea 88..8f
+ 0x9f54,0x9f63,0x9f5f,0x9f60, 0x9f61,0x9f66,0x9f67,0x9f6c, // ea 90..97
+ 0x9f6a,0x9f77,0x9f72,0x9f76, 0x9f95,0x9f9c,0x9fa0,0x582f, // ea 98..9f
+ 0x69c7,0x9059,0x7464,0x51dc, 0x7199, // ea a0..a4
+};
+
+static const u16 shift_jis_double_ff[] =
+{
+ 0xffff, // ff ff..ff
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// shift_jis_list[] ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[shift_jis_list_t]]
+
+typedef struct shift_jis_list_t
+{
+ u8 sect; // section
+ u8 from; // first index
+ u8 to; // last index
+ u8 count; // := to - from + 1
+ const u16 *tab; // UTF-16 table
+}
+shift_jis_list_t;
+
+//-----------------------------------------------------------------------------
+
+static const shift_jis_list_t shift_jis_list[] =
+{
+ { 0x81, 0x40, 0xfc, 189, shift_jis_double_81 }, // 0
+ { 0x82, 0x4f, 0xf1, 163, shift_jis_double_82 }, // 1
+ { 0x83, 0x40, 0xd6, 151, shift_jis_double_83 }, // 2
+ { 0x84, 0x40, 0xbe, 127, shift_jis_double_84 }, // 3
+ { 0x88, 0x9f, 0xfc, 94, shift_jis_double_88 }, // 4
+ { 0x89, 0x40, 0xfc, 189, shift_jis_double_89 }, // 5
+ { 0x8a, 0x40, 0xfc, 189, shift_jis_double_8a }, // 6
+ { 0x8b, 0x40, 0xfc, 189, shift_jis_double_8b }, // 7
+ { 0x8c, 0x40, 0xfc, 189, shift_jis_double_8c }, // 8
+ { 0x8d, 0x40, 0xfc, 189, shift_jis_double_8d }, // 9
+ { 0x8e, 0x40, 0xfc, 189, shift_jis_double_8e }, // 10
+ { 0x8f, 0x40, 0xfc, 189, shift_jis_double_8f }, // 11
+ { 0x90, 0x40, 0xfc, 189, shift_jis_double_90 }, // 12
+ { 0x91, 0x40, 0xfc, 189, shift_jis_double_91 }, // 13
+ { 0x92, 0x40, 0xfc, 189, shift_jis_double_92 }, // 14
+ { 0x93, 0x40, 0xfc, 189, shift_jis_double_93 }, // 15
+ { 0x94, 0x40, 0xfc, 189, shift_jis_double_94 }, // 16
+ { 0x95, 0x40, 0xfc, 189, shift_jis_double_95 }, // 17
+ { 0x96, 0x40, 0xfc, 189, shift_jis_double_96 }, // 18
+ { 0x97, 0x40, 0xfc, 189, shift_jis_double_97 }, // 19
+ { 0x98, 0x40, 0xfc, 189, shift_jis_double_98 }, // 20
+ { 0x99, 0x40, 0xfc, 189, shift_jis_double_99 }, // 21
+ { 0x9a, 0x40, 0xfc, 189, shift_jis_double_9a }, // 22
+ { 0x9b, 0x40, 0xfc, 189, shift_jis_double_9b }, // 23
+ { 0x9c, 0x40, 0xfc, 189, shift_jis_double_9c }, // 24
+ { 0x9d, 0x40, 0xfc, 189, shift_jis_double_9d }, // 25
+ { 0x9e, 0x40, 0xfc, 189, shift_jis_double_9e }, // 26
+ { 0x9f, 0x40, 0xfc, 189, shift_jis_double_9f }, // 27
+ { 0xe0, 0x40, 0xfc, 189, shift_jis_double_e0 }, // 28
+ { 0xe1, 0x40, 0xfc, 189, shift_jis_double_e1 }, // 29
+ { 0xe2, 0x40, 0xfc, 189, shift_jis_double_e2 }, // 30
+ { 0xe3, 0x40, 0xfc, 189, shift_jis_double_e3 }, // 31
+ { 0xe4, 0x40, 0xfc, 189, shift_jis_double_e4 }, // 32
+ { 0xe5, 0x40, 0xfc, 189, shift_jis_double_e5 }, // 33
+ { 0xe6, 0x40, 0xfc, 189, shift_jis_double_e6 }, // 34
+ { 0xe7, 0x40, 0xfc, 189, shift_jis_double_e7 }, // 35
+ { 0xe8, 0x40, 0xfc, 189, shift_jis_double_e8 }, // 36
+ { 0xe9, 0x40, 0xfc, 189, shift_jis_double_e9 }, // 37
+ { 0xea, 0x40, 0xa4, 101, shift_jis_double_ea }, // 38
+ { 0xff, 0xff, 0xff, 1, shift_jis_double_ff }, // 39
+ {0,0,0,0,0}
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanShiftJISChar ( cucp * str )
+{
+ const u8 *s = *str;
+ u16 code = *s++;
+ if (!code)
+ return 0;
+ *str = s;
+
+ code = shift_jis_single[code];
+ if ( code == SJIS_INVALID_CODE )
+ return -1;
+ if ( code < SJIS_TAB_MAPPING_BEG || code > SJIS_TAB_MAPPING_END )
+ return code;
+
+ const shift_jis_list_t *ptr = shift_jis_list + code - SJIS_TAB_MAPPING_BEG;
+ code = *s++;
+ if (!code)
+ return 0;
+ *str = s;
+
+ if ( code < ptr->from || code > ptr->to )
+ return -1;
+ code = ptr->tab[ code - ptr->from ];
+ return code == SJIS_INVALID_CODE ? -1 : code;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanShiftJISCharE ( cucp * str, cvp end )
+{
+ if (!end)
+ return ScanShiftJISChar(str);
+
+ const u8 *s = *str;
+ if ( s >= (u8*)end )
+ return 0;
+
+ u16 code = *s++;
+ code = shift_jis_single[code];
+ *str = s;
+
+ if ( code < SJIS_TAB_MAPPING_BEG || code > SJIS_TAB_MAPPING_END )
+ return code;
+ if ( code == SJIS_INVALID_CODE || s == end )
+ return -1;
+
+ const shift_jis_list_t *ptr = shift_jis_list + code - SJIS_TAB_MAPPING_BEG;
+ code = *s++;
+ *str = s;
+
+ if ( code < ptr->from || code > ptr->to )
+ return -1;
+ code = ptr->tab[ code - ptr->from ];
+ return code == SJIS_INVALID_CODE ? -1 : code;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static u16 *get_shift_jis_cache = 0;
+
+void SetupGetShiftJISCache()
+{
+ if (get_shift_jis_cache)
+ return;
+
+ const uint total_size = SJIS_CACHE_SIZE * sizeof(*get_shift_jis_cache);
+ get_shift_jis_cache = MALLOC(total_size);
+ memset(get_shift_jis_cache,~0,total_size);
+
+ const shift_jis_list_t *ptr;
+ for ( ptr = shift_jis_list; ptr->tab; ptr++ )
+ {
+ u16 code = ptr->sect << 8 | ptr->from;
+ const u16 *p = ptr->tab;
+ const u16 *e = p + ptr->count;
+ for ( ; p < e; p++, code++ )
+ {
+ DASSERT_MSG( IsValidShiftJIS(*p) || *p == SJIS_INVALID_CODE,
+ "table %02x[%zu], val=%04x\n", ptr->sect, p-ptr->tab, *p );
+ if ( *p != SJIS_INVALID_CODE )
+ get_shift_jis_cache[*p] = code;
+ }
+ }
+
+ uint i;
+ for ( i = 0; i < 0x100; i++ )
+ {
+ const u16 code = shift_jis_single[i];
+ if (IsValidShiftJIS(code))
+ get_shift_jis_cache[code] = i;
+ }
+
+ PRINT("Shift-JIS: %s\n",GetShiftJISStatistics());
+}
+
+//-----------------------------------------------------------------------------
+
+int GetShiftJISChar ( uint code )
+{
+ if (get_shift_jis_cache)
+ {
+ if ( code >= SJIS_CACHE_SIZE )
+ return -1;
+ u16 res = get_shift_jis_cache[code];
+ return res == SJIS_INVALID_CODE ? -1 : res;
+ }
+
+ if (!IsValidShiftJIS(code))
+ return -1;
+
+ uint i;
+ for ( i = 0; i < 0x100; i++ )
+ if ( shift_jis_single[i] == code )
+ return i;
+
+ const shift_jis_list_t *ptr;
+ for ( ptr = shift_jis_list; ptr->tab; ptr++ )
+ {
+ const u16 *p = ptr->tab;
+ const u16 *e = p + ptr->count;
+ for ( ; p < e; p++ )
+ if ( *p == code )
+ return ptr->sect << 8 | (p-ptr->tab) + ptr->from;
+ }
+
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetShiftJISStatistics()
+{
+ SetupGetShiftJISCache();
+
+ uint i, cache_count = 0,cache_max = 0;
+ for ( i = 0; i < SJIS_CACHE_SIZE; i++ )
+ if ( get_shift_jis_cache[i] != SJIS_INVALID_CODE )
+ {
+ cache_count++;
+ cache_max = i;
+ }
+
+
+ uint single_count = 0;
+ for ( i = 0; i < 0x100; i++ )
+ if (IsValidShiftJIS(shift_jis_single[i]))
+ single_count++;
+
+ uint dbl_count = 0, dbl_total = 0, dbl_tab = 0;
+ const shift_jis_list_t *ptr;
+ for ( ptr = shift_jis_list; ptr->tab; ptr++ )
+ {
+ dbl_tab++;
+ dbl_total += ptr->count;
+ u16 code = ptr->sect << 8 | ptr->from;
+ const u16 *p = ptr->tab;
+ const u16 *e = p + ptr->count;
+ for ( ; p < e; p++, code++ )
+ if ( *p != SJIS_INVALID_CODE )
+ dbl_count++;
+ }
+
+ char buf[100];
+ const uint len = snprintf(buf,sizeof(buf),
+ "%u singles, %u/%u doubles in %u tables, "
+ "max:%04x, cache=%u/%u"
+ ,single_count
+ ,dbl_count,dbl_total,dbl_tab
+ ,cache_max
+ ,cache_count,SJIS_CACHE_SIZE
+ );
+ return CopyCircBuf(buf,len+1);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-system.h b/project/dclib/dclib-system.h
new file mode 100644
index 0000000..64a021d
--- /dev/null
+++ b/project/dclib/dclib-system.h
@@ -0,0 +1,101 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_SYSTEM_H
+#define DCLIB_SYSTEM_H 1
+
+#include <stdio.h>
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum enumSystemID
+{
+ SYSID_UNKNOWN = 0x00000000,
+ SYSID_I386 = 0x01000000,
+ SYSID_X86_64 = 0x02000000,
+ SYSID_CYGWIN = 0x03000000,
+ SYSID_APPLE = 0x04000000,
+ SYSID_LINUX = 0x05000000,
+ SYSID_UNIX = 0x06000000,
+
+} enumSystemID;
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef SYSTEM_LINUX
+
+#ifdef __CYGWIN__
+ #define SYSTEM "cygwin"
+ #define SYSTEMID SYSID_CYGWIN
+#elif __APPLE__
+ #define SYSTEM "mac"
+ #define SYSTEMID SYSID_APPLE
+#elif __linux__
+ #define SYSTEM_LINUX 1
+ #ifdef __i386__
+ #define SYSTEM "i386"
+ #define SYSTEMID SYSID_I386
+ #elif __x86_64__
+ #define SYSTEM "x86_64"
+ #define SYSTEMID SYSID_X86_64
+ #else
+ #define SYSTEM "linux"
+ #define SYSTEMID SYSID_LINUX
+ #endif
+#elif __unix__
+ #define SYSTEM "unix"
+ #define SYSTEMID SYSID_UNIX
+#else
+ #define SYSTEM "unknown"
+ #define SYSTEMID SYSID_UNKNOWN
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline void dclibPrintSystem ( FILE * f )
+{
+ fprintf(f,
+ "SYSTEM\t\t:= %s\n"
+ "SYSTEMID\t:= 0x%x\n"
+ #ifdef SYSTEM_LINUX
+ "SYSTEM_LINUX\t:= 1\n"
+ #endif
+ ,SYSTEM
+ ,SYSTEMID );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_SYSTEM_H 1
+
diff --git a/project/dclib/dclib-tables.c b/project/dclib/dclib-tables.c
new file mode 100644
index 0000000..7206f6a
--- /dev/null
+++ b/project/dclib/dclib-tables.c
@@ -0,0 +1,417 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+//#include <string.h>
+//#include <unistd.h>
+//#include <time.h>
+//#include <stdio.h>
+//#include <errno.h>
+//#include <sys/time.h>
+
+#include "dclib-basics.h"
+//#include "dclib-debug.h"
+//#include "dclib-utf8.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// table element abbreviations ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define DN DECODE_NULL
+#define DC DECODE_CONTROL
+#define DL DECODE_LINE
+#define DS DECODE_SPACE
+#define DP DECODE_SEPARATE
+#define DF DECODE_FILLER
+#define DX DECODE_OTHER
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// bits ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const uchar TableBitCount[0x100] =
+{
+ 0,1,1,2, 1,2,2,3, 1,2,2,3, 2,3,3,4,
+ 1,2,2,3, 2,3,3,4, 2,3,3,4, 3,4,4,5,
+ 1,2,2,3, 2,3,3,4, 2,3,3,4, 3,4,4,5,
+ 2,3,3,4, 3,4,4,5, 3,4,4,5, 4,5,5,6,
+
+ 1,2,2,3, 2,3,3,4, 2,3,3,4, 3,4,4,5,
+ 2,3,3,4, 3,4,4,5, 3,4,4,5, 4,5,5,6,
+ 2,3,3,4, 3,4,4,5, 3,4,4,5, 4,5,5,6,
+ 3,4,4,5, 4,5,5,6, 4,5,5,6, 5,6,6,7,
+
+ 1,2,2,3, 2,3,3,4, 2,3,3,4, 3,4,4,5,
+ 2,3,3,4, 3,4,4,5, 3,4,4,5, 4,5,5,6,
+ 2,3,3,4, 3,4,4,5, 3,4,4,5, 4,5,5,6,
+ 3,4,4,5, 4,5,5,6, 4,5,5,6, 5,6,6,7,
+
+ 2,3,3,4, 3,4,4,5, 3,4,4,5, 4,5,5,6,
+ 3,4,4,5, 4,5,5,6, 4,5,5,6, 5,6,6,7,
+ 3,4,4,5, 4,5,5,6, 4,5,5,6, 5,6,6,7,
+ 4,5,5,6, 5,6,6,7, 5,6,6,7, 6,7,7,8
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableLowest0Bit[0x100] =
+{
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,4,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,5,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,4,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,6,
+
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,4,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,5,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,4,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,7,
+
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,4,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,5,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,4,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,6,
+
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,4,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,5,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,4,
+ 0,1,0,2, 0,1,0,3, 0,1,0,2, 0,1,0,-1
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableLowest1Bit[0x100] =
+{
+ -1,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 4,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 5,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 4,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+
+ 6,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 4,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 5,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 4,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+
+ 7,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 4,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 5,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 4,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+
+ 6,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 4,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 5,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0,
+ 4,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableHighest0Bit[0x100] =
+{
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+
+ 6,6,6,6, 6,6,6,6, 6,6,6,6, 6,6,6,6,
+ 6,6,6,6, 6,6,6,6, 6,6,6,6, 6,6,6,6,
+ 6,6,6,6, 6,6,6,6, 6,6,6,6, 6,6,6,6,
+ 6,6,6,6, 6,6,6,6, 6,6,6,6, 6,6,6,6,
+
+ 5,5,5,5, 5,5,5,5, 5,5,5,5, 5,5,5,5,
+ 5,5,5,5, 5,5,5,5, 5,5,5,5, 5,5,5,5,
+ 4,4,4,4, 4,4,4,4, 4,4,4,4, 4,4,4,4,
+ 3,3,3,3, 3,3,3,3, 2,2,2,2, 1,1,0,-1
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableHighest1Bit[0x100] =
+{
+ -1,0,1,1, 2,2,2,2, 3,3,3,3, 3,3,3,3,
+ 4,4,4,4, 4,4,4,4, 4,4,4,4, 4,4,4,4,
+ 5,5,5,5, 5,5,5,5, 5,5,5,5, 5,5,5,5,
+ 5,5,5,5, 5,5,5,5, 5,5,5,5, 5,5,5,5,
+
+ 6,6,6,6, 6,6,6,6, 6,6,6,6, 6,6,6,6,
+ 6,6,6,6, 6,6,6,6, 6,6,6,6, 6,6,6,6,
+ 6,6,6,6, 6,6,6,6, 6,6,6,6, 6,6,6,6,
+ 6,6,6,6, 6,6,6,6, 6,6,6,6, 6,6,6,6,
+
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7,
+ 7,7,7,7, 7,7,7,7, 7,7,7,7, 7,7,7,7
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scanning numbers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableNumbers[256] =
+{
+ DN, DC, DC, DC, DC, DC, DC, DC, DC, DS, DL, DC, DS, DL, DC, DC,
+ DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC,
+ DS, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DP, DX, DX, DX,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, DX, DP, DX, DX, DX, DX,
+
+ DX, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, DX, DX, DX, DX, DX,
+ DX, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, DX, DX, DX, DX, DX,
+
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX,
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX,
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX,
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX,
+
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX,
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX,
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX,
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX,
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CRC32 support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const u32 TableCRC32[0x100] =
+{
+ 0x00000000,0x77073096,0xee0e612c,0x990951ba, 0x076dc419,0x706af48f,0xe963a535,0x9e6495a3,
+ 0x0edb8832,0x79dcb8a4,0xe0d5e91e,0x97d2d988, 0x09b64c2b,0x7eb17cbd,0xe7b82d07,0x90bf1d91,
+ 0x1db71064,0x6ab020f2,0xf3b97148,0x84be41de, 0x1adad47d,0x6ddde4eb,0xf4d4b551,0x83d385c7,
+ 0x136c9856,0x646ba8c0,0xfd62f97a,0x8a65c9ec, 0x14015c4f,0x63066cd9,0xfa0f3d63,0x8d080df5,
+ 0x3b6e20c8,0x4c69105e,0xd56041e4,0xa2677172, 0x3c03e4d1,0x4b04d447,0xd20d85fd,0xa50ab56b,
+ 0x35b5a8fa,0x42b2986c,0xdbbbc9d6,0xacbcf940, 0x32d86ce3,0x45df5c75,0xdcd60dcf,0xabd13d59,
+ 0x26d930ac,0x51de003a,0xc8d75180,0xbfd06116, 0x21b4f4b5,0x56b3c423,0xcfba9599,0xb8bda50f,
+ 0x2802b89e,0x5f058808,0xc60cd9b2,0xb10be924, 0x2f6f7c87,0x58684c11,0xc1611dab,0xb6662d3d,
+ 0x76dc4190,0x01db7106,0x98d220bc,0xefd5102a, 0x71b18589,0x06b6b51f,0x9fbfe4a5,0xe8b8d433,
+ 0x7807c9a2,0x0f00f934,0x9609a88e,0xe10e9818, 0x7f6a0dbb,0x086d3d2d,0x91646c97,0xe6635c01,
+ 0x6b6b51f4,0x1c6c6162,0x856530d8,0xf262004e, 0x6c0695ed,0x1b01a57b,0x8208f4c1,0xf50fc457,
+ 0x65b0d9c6,0x12b7e950,0x8bbeb8ea,0xfcb9887c, 0x62dd1ddf,0x15da2d49,0x8cd37cf3,0xfbd44c65,
+ 0x4db26158,0x3ab551ce,0xa3bc0074,0xd4bb30e2, 0x4adfa541,0x3dd895d7,0xa4d1c46d,0xd3d6f4fb,
+ 0x4369e96a,0x346ed9fc,0xad678846,0xda60b8d0, 0x44042d73,0x33031de5,0xaa0a4c5f,0xdd0d7cc9,
+ 0x5005713c,0x270241aa,0xbe0b1010,0xc90c2086, 0x5768b525,0x206f85b3,0xb966d409,0xce61e49f,
+ 0x5edef90e,0x29d9c998,0xb0d09822,0xc7d7a8b4, 0x59b33d17,0x2eb40d81,0xb7bd5c3b,0xc0ba6cad,
+ 0xedb88320,0x9abfb3b6,0x03b6e20c,0x74b1d29a, 0xead54739,0x9dd277af,0x04db2615,0x73dc1683,
+ 0xe3630b12,0x94643b84,0x0d6d6a3e,0x7a6a5aa8, 0xe40ecf0b,0x9309ff9d,0x0a00ae27,0x7d079eb1,
+ 0xf00f9344,0x8708a3d2,0x1e01f268,0x6906c2fe, 0xf762575d,0x806567cb,0x196c3671,0x6e6b06e7,
+ 0xfed41b76,0x89d32be0,0x10da7a5a,0x67dd4acc, 0xf9b9df6f,0x8ebeeff9,0x17b7be43,0x60b08ed5,
+ 0xd6d6a3e8,0xa1d1937e,0x38d8c2c4,0x4fdff252, 0xd1bb67f1,0xa6bc5767,0x3fb506dd,0x48b2364b,
+ 0xd80d2bda,0xaf0a1b4c,0x36034af6,0x41047a60, 0xdf60efc3,0xa867df55,0x316e8eef,0x4669be79,
+ 0xcb61b38c,0xbc66831a,0x256fd2a0,0x5268e236, 0xcc0c7795,0xbb0b4703,0x220216b9,0x5505262f,
+ 0xc5ba3bbe,0xb2bd0b28,0x2bb45a92,0x5cb36a04, 0xc2d7ffa7,0xb5d0cf31,0x2cd99e8b,0x5bdeae1d,
+ 0x9b64c2b0,0xec63f226,0x756aa39c,0x026d930a, 0x9c0906a9,0xeb0e363f,0x72076785,0x05005713,
+ 0x95bf4a82,0xe2b87a14,0x7bb12bae,0x0cb61b38, 0x92d28e9b,0xe5d5be0d,0x7cdcefb7,0x0bdbdf21,
+ 0x86d3d2d4,0xf1d4e242,0x68ddb3f8,0x1fda836e, 0x81be16cd,0xf6b9265b,0x6fb077e1,0x18b74777,
+ 0x88085ae6,0xff0f6a70,0x66063bca,0x11010b5c, 0x8f659eff,0xf862ae69,0x616bffd3,0x166ccf45,
+ 0xa00ae278,0xd70dd2ee,0x4e048354,0x3903b3c2, 0xa7672661,0xd06016f7,0x4969474d,0x3e6e77db,
+ 0xaed16a4a,0xd9d65adc,0x40df0b66,0x37d83bf0, 0xa9bcae53,0xdebb9ec5,0x47b2cf7f,0x30b5ffe9,
+ 0xbdbdf21c,0xcabac28a,0x53b39330,0x24b4a3a6, 0xbad03605,0xcdd70693,0x54de5729,0x23d967bf,
+ 0xb3667a2e,0xc4614ab8,0x5d681b02,0x2a6f2b94, 0xb40bbe37,0xc30c8ea1,0x5a05df1b,0x2d02ef8d,
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// helpers for codepages ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const u16 TableCP1252_80[0x20] =
+{
+ 0x20ac, 0,0x201a,0x0192, 0x201e,0x2026,0x2020,0x2021, // 80..87
+ 0x02c6,0x2030,0x0160,0x2039, 0x0152, 0,0x017d, 0, // 88..8f
+ 0,0x2018,0x2019,0x201c, 0x201d,0x2022,0x2013,0x2014, // 90..97
+ 0x02dc,0x2122,0x0161,0x203a, 0x0153, 0,0x017e,0x0178, // 98..9f
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// BASE64 support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// https://en.wikipedia.org/wiki/Base64
+
+char TableDecode64[256] =
+{
+ DN, DC, DC, DC, DC, DC, DC, DC, DC, DS, DS, DC, DS, DS, DC, DC, // 0x
+ DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, // 1x
+ DS, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, 62, DP, DX, DX, 63, // 2x
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, DX, DP, DX, DF, DX, DX, // 3x
+
+ DX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 4x
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, DX, DX, DX, DX, DX, // 5x
+ DX, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 6x
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, DX, DX, DX, DX, DX, // 7x
+
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // 8x
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // 9x
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Ax
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Bx
+
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Cx
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Dx
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Ex
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX // Fx
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+char TableDecode64url[256] =
+{
+ DN, DC, DC, DC, DC, DC, DC, DC, DC, DS, DS, DC, DS, DS, DC, DC, // 0x
+ DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, // 1x
+ DS, DX, DX, DX, DX, DX, DX, DX, DX, DX, DF, 62, DP, 62, DX, 63, // 2x
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, DX, DP, DX, DF, DX, DX, // 3x
+
+ DX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 4x
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, DX, DX, DX, DX, 63, // 5x
+ DX, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 6x
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, DX, DX, DX, DX, DX, // 7x
+
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // 8x
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // 9x
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Ax
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Bx
+
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Cx
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Dx
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Ex
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX // Fx
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+char TableDecode64xml[256] =
+{
+ DN, DC, DC, DC, DC, DC, DC, DC, DC, DS, DS, DC, DS, DS, DC, DC, // 0x
+ DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, DC, // 1x
+ DS, DX, DX, DX, DX, DX, DX, DX, DX, DX, DF, 62, DP, 63, 62, 63, // 2x
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, DP, DX, DF, DX, DX, // 3x
+
+ DX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 4x
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, DX, DX, DX, DX, 62, // 5x
+ DX, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 6x
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, DX, DX, DX, DX, DX, // 7x
+
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // 8x
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // 9x
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Ax
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Bx
+
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Cx
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Dx
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, // Ex
+ DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX, DX // Fx
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableEncode64[64+1] =
+{
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M',
+ 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m',
+ 'n','o','p','q','r','s','t','u','v','w','x','y','z',
+ '0','1','2','3','4','5','6','7','8','9',
+ '+','/',
+ '=' // fill char
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableEncode64url[64+1] =
+{
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M',
+ 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m',
+ 'n','o','p','q','r','s','t','u','v','w','x','y','z',
+ '0','1','2','3','4','5','6','7','8','9',
+ '-','_',
+ '=' // fill char
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableEncode64star[64+1] =
+{
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M',
+ 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m',
+ 'n','o','p','q','r','s','t','u','v','w','x','y','z',
+ '0','1','2','3','4','5','6','7','8','9',
+ '-','_',
+ '*' // fill char
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char TableEncode64xml[64+1] =
+{
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M',
+ 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
+ 'a','b','c','d','e','f','g','h','i','j','k','l','m',
+ 'n','o','p','q','r','s','t','u','v','w','x','y','z',
+ '0','1','2','3','4','5','6','7','8','9',
+ '.','-',
+ '*' // fill char
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// for tests: encode the 48 bytes to get the full BASE64 alphabet
+
+const u8 TableAlphabet64[48] =
+{
+ 0x00,0x10,0x83,0x10, 0x51,0x87,0x20,0x92, 0x8b,0x30,0xd3,0x8f, 0x41,0x14,0x93,0x51,
+ 0x55,0x97,0x61,0x96, 0x9b,0x71,0xd7,0x9f, 0x82,0x18,0xa3,0x92, 0x59,0xa7,0xa2,0x9a,
+ 0xab,0xb2,0xdb,0xaf, 0xc3,0x1c,0xb3,0xd3, 0x5d,0xb7,0xe3,0x9e, 0xbb,0xf3,0xdf,0xbf,
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-types.h b/project/dclib/dclib-types.h
new file mode 100644
index 0000000..8631d9a
--- /dev/null
+++ b/project/dclib/dclib-types.h
@@ -0,0 +1,403 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_TYPES_H
+#define DCLIB_TYPES_H 1
+
+#define _GNU_SOURCE 1
+
+#include <sys/types.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#ifndef WIN_DCLIB
+ #include <stdint.h>
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// endian ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[dcEndian_t]]
+
+typedef enum dcEndian_t
+{
+ //--- always the same
+
+ DC_DEFAULT_ENDIAN = 0, // always 0
+ DC_SECOND_ENDIAN = 1, // always >0
+
+
+ //--- define local & reverse endian, if possible
+
+ #if __BYTE_ORDER == __LITTLE_ENDIAN || __BYTE_ORDER == __BIG_ENDIAN
+
+ DC_LOCAL_ENDIAN = DC_DEFAULT_ENDIAN,
+ DC_REVERSE_ENDIAN = DC_SECOND_ENDIAN,
+
+ #endif
+
+
+ //--- define aliases for little & big endian
+
+ #if __BYTE_ORDER == __BIG_ENDIAN
+
+ DC_BIG_ENDIAN = DC_DEFAULT_ENDIAN,
+ DC_LITTLE_ENDIAN = DC_SECOND_ENDIAN,
+
+ #else
+
+ DC_LITTLE_ENDIAN = DC_DEFAULT_ENDIAN,
+ DC_BIG_ENDIAN = DC_SECOND_ENDIAN,
+
+ #endif
+}
+__attribute__ ((packed)) dcEndian_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// constants ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define KB_SI 1000
+#define MB_SI (1000*1000)
+#define GB_SI (1000*1000*1000)
+#define TB_SI (1000ull*1000*1000*1000)
+#define PB_SI (1000ull*1000*1000*1000*1000)
+#define EB_SI (1000ull*1000*1000*1000*1000*1000)
+
+#define KiB 1024
+#define MiB (1024*1024)
+#define GiB (1024*1024*1024)
+#define TiB (1024ull*1024*1024*1024)
+#define PiB (1024ull*1024*1024*1024*1024)
+#define EiB (1024ull*1024*1024*1024*1024*1024)
+
+// specal '-1' support
+#define M1(a) ( (typeof(a))~0 )
+#define IS_M1(a) ( (a) == (typeof(a))~0 )
+
+#define ALIGN32(d,a) ((d)+((a)-1)&~(u32)((a)-1))
+#define ALIGN64(d,a) ((d)+((a)-1)&~(u64)((a)-1))
+#define ALIGNOFF32(d,a) ((d)&~(u32)((a)-1))
+#define ALIGNOFF64(d,a) ((d)&~(u64)((a)-1))
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// error codes ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[enumError]]
+
+typedef enum enumError
+{
+ //-------------------------------------------------------------------
+ ERR_OK, // 100% success; below = warnings
+ //-------------------------------------------------------------------
+
+ ERU_DIFFER,
+ ERR_DIFFER,
+ ERU_NOTHING_TO_DO,
+ ERR_NOTHING_TO_DO,
+ ERU_SOURCE_FOUND,
+ ERR_SOURCE_FOUND,
+ ERU_NO_SOURCE_FOUND,
+ ERR_NO_SOURCE_FOUND,
+ ERU_JOB_IGNORED,
+ ERR_JOB_IGNORED,
+ ERU_SUBJOB_WARNING,
+ ERR_SUBJOB_WARNING,
+ ERU_NOT_EXISTS,
+ ERR_NOT_EXISTS,
+
+ ERU_WARN_00,
+ ERU_WARN_01,
+ ERU_WARN_02,
+ ERU_WARN_03,
+ ERU_WARN_04,
+ ERU_WARN_05,
+ ERU_WARN_06,
+ ERU_WARN_07,
+ ERU_WARN_08,
+ ERU_WARN_09,
+ ERU_WARN_10,
+ ERU_WARN_XX,
+
+ //-------------------------------------------------------------------
+ ERU_WARNING,
+ ERR_WARNING, // separator: below = real errors and not warnings
+ //-------------------------------------------------------------------
+
+ ERU_WRONG_FILE_TYPE,
+ ERR_WRONG_FILE_TYPE,
+ ERU_INVALID_FILE,
+ ERR_INVALID_FILE,
+ ERU_INVALID_VERSION,
+ ERR_INVALID_VERSION,
+ ERU_INVALID_DATA,
+ ERR_INVALID_DATA,
+
+ ERU_ERROR1_00,
+ ERU_ERROR1_01,
+ ERU_ERROR1_02,
+ ERU_ERROR1_03,
+ ERU_ERROR1_04,
+ ERU_ERROR1_05,
+ ERU_ERROR1_06,
+ ERU_ERROR1_07,
+ ERU_ERROR1_08,
+ ERU_ERROR1_09,
+ ERU_ERROR1_10,
+ ERU_ERROR1_11,
+ ERU_ERROR1_12,
+ ERU_ERROR1_13,
+ ERU_ERROR1_14,
+ ERU_ERROR1_15,
+ ERU_ERROR1_16,
+ ERU_ERROR1_17,
+ ERU_ERROR1_18,
+ ERU_ERROR1_19,
+ ERU_ERROR1_20,
+ ERU_ERROR1_XX,
+
+ ERU_ENCODING,
+ ERR_ENCODING,
+ ERU_DECODING,
+ ERR_DECODING,
+ ERU_ALREADY_EXISTS,
+ ERR_ALREADY_EXISTS,
+ ERU_SUBJOB_FAILED,
+ ERR_SUBJOB_FAILED,
+
+ ERR_CANT_REMOVE,
+ ERU_CANT_REMOVE,
+ ERU_CANT_RENAME,
+ ERR_CANT_RENAME,
+ ERU_CANT_CLOSE,
+ ERR_CANT_CLOSE,
+ ERU_CANT_CONNECT,
+ ERR_CANT_CONNECT,
+ ERU_CANT_OPEN,
+ ERR_CANT_OPEN,
+ ERU_CANT_APPEND,
+ ERR_CANT_APPEND,
+ ERU_CANT_CREATE,
+ ERR_CANT_CREATE,
+ ERU_CANT_CREATE_DIR,
+ ERR_CANT_CREATE_DIR,
+
+ ERU_READ_FAILED,
+ ERR_READ_FAILED,
+ ERU_REMOVE_FAILED,
+ ERR_REMOVE_FAILED,
+ ERU_WRITE_FAILED,
+ ERR_WRITE_FAILED,
+ ERU_DATABASE,
+ ERR_DATABASE,
+
+ ERU_ERROR2_00,
+ ERU_ERROR2_01,
+ ERU_ERROR2_02,
+ ERU_ERROR2_03,
+ ERU_ERROR2_04,
+ ERU_ERROR2_05,
+ ERU_ERROR2_06,
+ ERU_ERROR2_07,
+ ERU_ERROR2_08,
+ ERU_ERROR2_09,
+ ERU_ERROR2_10,
+ ERU_ERROR2_XX,
+
+ ERU_MISSING_PARAM,
+ ERR_MISSING_PARAM,
+ ERU_SEMANTIC,
+ ERR_SEMANTIC,
+ ERU_SYNTAX,
+ ERR_SYNTAX,
+
+ ERU_INTERRUPT,
+ ERR_INTERRUPT,
+
+ //-------------------------------------------------------------------
+ ERU_ERROR,
+ ERR_ERROR, // separator: below = hard/fatal errors => exit
+ //-------------------------------------------------------------------
+
+ ERU_NOT_IMPLEMENTED,
+ ERR_NOT_IMPLEMENTED,
+ ERU_INTERNAL,
+ ERR_INTERNAL,
+
+ ERU_FATAL_00,
+ ERU_FATAL_01,
+ ERU_FATAL_02,
+ ERU_FATAL_03,
+ ERU_FATAL_04,
+ ERU_FATAL_XX,
+
+ ERU_OUT_OF_MEMORY,
+ ERR_OUT_OF_MEMORY,
+
+ //-------------------------------------------------------------------
+ ERU_FATAL,
+ ERR_FATAL,
+ //-------------------------------------------------------------------
+
+ ERR__N
+
+} enumError;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// base types ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef WIN_DCLIB
+
+ typedef unsigned char u8;
+ typedef unsigned short u16;
+ typedef unsigned int u32;
+ typedef unsigned long long u64;
+
+ typedef signed char s8;
+ typedef signed short s16;
+ typedef signed int s32;
+ typedef signed long long s64;
+
+#else
+
+ typedef uint8_t u8;
+ typedef uint16_t u16;
+ typedef uint32_t u32;
+ //typedef uint64_t u64; // is 'long unsigned' in 64 bit linux
+ typedef unsigned long long u64;
+
+ typedef int8_t s8;
+ typedef int16_t s16;
+ typedef int32_t s32;
+ //typedef int64_t s64; // is 'long int' in 64 bit linux
+ typedef long long int s64;
+
+ #ifndef __cplusplus
+ typedef enum bool { false, true } __attribute__ ((packed)) bool;
+ #endif
+
+#endif
+
+typedef u16 be16_t;
+typedef u32 be32_t;
+typedef u64 be64_t;
+
+typedef float float32; // float as 32 bit binary data, endian is file dependent
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define U8_MIN (0)
+#define U8_MAX (0xff)
+#define S8_MIN (-0x80)
+#define S8_MAX (0x7f)
+
+#define U16_MIN (0)
+#define U16_MAX (0xffff)
+#define S16_MIN (-0x8000)
+#define S16_MAX (0x7fff)
+
+#define U32_MIN (0u)
+#define U32_MAX (0xffffffffu)
+#define S32_MIN (-0x80000000)
+#define S32_MAX (0x7fffffff)
+
+#define U64_MIN (0ull)
+#define U64_MAX (0xffffffffffffffffull)
+#define S64_MIN (-0x8000000000000000ll)
+#define S64_MAX (0x7fffffffffffffffll)
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef DCLIB_BASIC_TYPES
+ #define DCLIB_BASIC_TYPES 1
+ typedef unsigned char uchar;
+ typedef unsigned int uint;
+ typedef unsigned long ulong;
+ typedef const void * cvp;
+ typedef const char * ccp;
+ typedef const uchar * cucp;
+#endif
+
+// [[sha1_hash_t]] [[sha1_hex_t]] [[sha1_id_t]]
+typedef u8 sha1_hash_t[20];
+typedef char sha1_hex_t[41]; // 40 chars + NULL
+typedef u8 sha1_id_t[9]; // 8*chars + NULL
+
+// [[uuid_buf_t]] [[uuid_text_t]]
+typedef u8 uuid_buf_t[16];
+typedef char uuid_text_t[37]; // "12345678-1234-1234-1234-123456789012" + NULL
+
+typedef int (*qsort_func)( const void *, const void * );
+typedef int (*qsort_r_func)( const void *, const void *, void * );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// some structs ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[KeywordTab_t]]
+
+typedef struct KeywordTab_t
+{
+ s64 id; // id
+ ccp name1; // first name
+ ccp name2; // NULL or second name
+ s64 opt; // option
+
+} KeywordTab_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MSVC extensions (not CYGWIN) ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef WIN_DCLIB
+
+ typedef u32 mode_t;
+
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_TYPES_H
+
diff --git a/project/dclib/dclib-ui.c b/project/dclib/dclib-ui.c
new file mode 100644
index 0000000..67226dd
--- /dev/null
+++ b/project/dclib/dclib-ui.c
@@ -0,0 +1,950 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#include <ctype.h>
+#include <getopt.h>
+#include <string.h>
+
+#include "dclib-basics.h"
+#include "dclib-ui.h"
+#include "dclib-debug.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// register options ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RegisterOptionByIndex
+(
+ const InfoUI_t * iu, // valid pointer
+ int opt_index, // index of option (OPT_*)
+ int level, // the level of registration
+ bool is_env // true: register environment pre setting
+)
+{
+ ASSERT(iu);
+ DASSERT(iu->opt_info);
+ DASSERT(iu->opt_used);
+ DASSERT(iu->opt_index);
+
+ if ( level <= 0 )
+ return ERR_OK;
+
+ if ( opt_index >= 0
+ && opt_index < iu->n_opt_total
+ && opt_index < UIOPT_INDEX_SIZE )
+ {
+ TRACE("OPT: RegisterOptionByIndex(,%02x,%d,%d) opt_index=%02x,%s\n",
+ opt_index, level, is_env, opt_index,
+ iu->opt_info[opt_index].long_name );
+ u8 * obj = iu->opt_used + opt_index;
+ u32 count = *obj;
+ if (is_env)
+ {
+ if ( count < 0x7f )
+ {
+ count += level;
+ *obj = count < 0x7f ? count : 0x7f;
+ }
+ }
+ else
+ {
+ if ( count < 0x80 )
+ count = 0x80;
+ count += level;
+ *obj = count < 0xff ? count : 0xff;
+ }
+
+ return ERR_OK;
+ }
+
+ PRINT("OPT: RegisterOptionbyIndex(,%02x/%02x/%02x,%d,%d) => WARNING\n",
+ opt_index, iu->n_opt_total, UIOPT_INDEX_SIZE, level, is_env );
+ return ERR_WARNING;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RegisterOptionByName
+(
+ const InfoUI_t * iu, // valid pointer
+ int opt_name, // short name of GO_* value of option
+ int level, // the level of registration
+ bool is_env // true: register environment pre setting
+)
+{
+ ASSERT(iu);
+ DASSERT(iu->opt_index);
+
+ if ( opt_name >= 0 && opt_name < UIOPT_INDEX_SIZE )
+ return RegisterOptionByIndex(iu,iu->opt_index[opt_name],level,is_env);
+
+ PRINT("OPT: RegisterOptionbyName(,%02x,%d,%d) => WARNING\n",
+ opt_name, level, is_env );
+ return ERR_WARNING;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError VerifySpecificOptions ( const InfoUI_t * iu, const KeywordTab_t * cmd )
+{
+ ASSERT(iu);
+ ASSERT(cmd);
+
+ ASSERT( cmd->id > 0 && cmd->id < iu->n_cmd );
+ const InfoCommand_t * ic = iu->cmd_info + cmd->id;
+ ASSERT(ic->opt_allowed);
+ u8 * allow = ic->opt_allowed;
+ u8 * active = iu->opt_used;
+
+ TRACE("ALLOWED SPECIFIC OPTIONS:\n");
+ TRACE_HEXDUMP(10,0,0,-20,allow,iu->n_opt_specific);
+ TRACE("ACTIVE SPECIFIC OPTIONS:\n");
+ TRACE_HEXDUMP(10,0,0,-20,active,iu->n_opt_specific);
+
+ enumError err = ERR_OK;
+ int idx;
+ for ( idx = 0; idx < iu->n_opt_specific; idx++, allow++, active++ )
+ {
+ if (!*allow)
+ {
+ if ( *active & 0x80 )
+ {
+ const InfoOption_t * io = iu->opt_info + idx;
+ if (io->short_name)
+ ERROR0(ERR_SEMANTIC,"Command '%s' doesn't allow the option --%s (-%c).\n",
+ cmd->name1, io->long_name, io->short_name );
+ else
+ ERROR0(ERR_SEMANTIC,"Command '%s' doesn't allow the option --%s.\n",
+ cmd->name1, io->long_name );
+ err = ERR_SEMANTIC;
+
+ }
+ else
+ *active = 0; // clear environ settings
+ }
+ }
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetOptionCount ( const InfoUI_t * iu, int option )
+{
+ ASSERT(iu);
+ ASSERT(iu->opt_info);
+ ASSERT(iu->opt_used);
+
+ if ( option > 0 && option < iu->n_opt_total )
+ {
+ TRACE("GetOptionCount(,%02x) option=%s => %d\n",
+ option, iu->opt_info[option].long_name,
+ iu->opt_used[option] & UIOPT_USED_MASK );
+ return iu->opt_used[option] & UIOPT_USED_MASK;
+ }
+
+ TRACE("GetOptionCount(,%02x) => INVALID [-1]\n",option);
+ return -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpUsedOptions ( const InfoUI_t * iu, FILE * f, int indent )
+{
+ ASSERT(iu);
+ ASSERT(iu->opt_info);
+ ASSERT(iu->opt_used);
+
+ TRACE("OptionUsed[]:\n");
+ TRACE_HEXDUMP16(9,0,iu->opt_used,iu->n_opt_total+1);
+
+ if (!f)
+ return;
+
+ indent = NormalizeIndent(indent);
+
+ int i;
+ for ( i = 0; i < iu->n_opt_total; i++ )
+ {
+ const u8 opt = iu->opt_used[i];
+ if (opt)
+ fprintf(f,"%*s%s %s %2u* [%02x,%s]\n",
+ indent, "",
+ i <= iu->n_opt_specific ? "CMD" : "GLB",
+ opt & 0x80 ? "PRM" : "ENV",
+ opt & UIOPT_USED_MASK,
+ i, iu->opt_info[i].long_name );
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+void WarnDepractedOptions ( const struct InfoUI_t * iu )
+{
+ DASSERT(iu);
+
+ uint i, count = 0;
+ char buf[PATH_MAX];
+ char *dest = buf;
+
+ for ( i = 0; i < iu->n_opt_total; i++ )
+ {
+ if ( iu->opt_used[i] )
+ {
+ const InfoOption_t *io = iu->opt_info + i;
+ if ( io->deprecated )
+ {
+ if (io->long_name)
+ {
+ count++;
+ dest = snprintfE(dest,buf+sizeof(buf)," --%s",io->long_name);
+ }
+ else if (io->short_name)
+ {
+ count++;
+ dest = snprintfE(dest,buf+sizeof(buf)," -%c",io->short_name);
+ }
+ }
+ }
+ }
+
+ if (count)
+ ERROR0(ERR_WARNING,
+ "Attention: Deprecated option%s:%s\n",
+ count == 1 ? "" : "s", buf );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CheckEnvOptions ( ccp varname, check_opt_func func )
+{
+ TRACE("CheckEnvOptions(%s,%p)\n",varname,func);
+ ccp env = getenv(varname);
+ if ( !env || !*env )
+ return ERR_OK;
+
+ TRACE("env[%s] = %s\n",varname,env);
+
+ const int envlen = strlen(env);
+ char * buf = MALLOC(envlen+1);
+ char * dest = buf;
+
+ int argc = 1; // argv[0] = progname
+ ccp src = env;
+ while (*src)
+ {
+ while ( *src > 0 && *src <= ' ' ) // skip blanks & control
+ src++;
+
+ if (!*src)
+ break;
+
+ argc++;
+ while ( *(u8*)src > ' ' )
+ *dest++ = *src++;
+ *dest++ = 0;
+ ASSERT( dest <= buf+envlen+1 );
+ }
+ TRACE("argc = %d\n",argc);
+
+ char ** argv = MALLOC((argc+1)*sizeof(*argv));
+ argv[0] = (char*)progname;
+ argv[argc] = 0;
+ dest = buf;
+ int i;
+ for ( i = 1; i < argc; i++ )
+ {
+ TRACE("argv[%d] = %s\n",i,dest);
+ argv[i] = dest;
+ while (*dest)
+ dest++;
+ dest++;
+ ASSERT( dest <= buf+envlen+1 );
+ }
+
+ enumError stat = func(argc,argv,true);
+ if (stat)
+ fprintf(stderr,
+ "Error while scanning the environment variable '%s'\n",varname);
+
+ // don't free() because it's possible that there are pointers to arguments
+ //FREE(argv);
+ //FREE(buf);
+
+ return stat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// text helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpText
+(
+ FILE * f, // output file, if NULL: 'pbuf' must be set
+ char * pbuf, // output buffer, ignored if 'f' not NULL
+ char * pbuf_end, // end of output buffer
+ ccp text, // source text
+ int text_len, // >=0: length of text, <0: use strlen(text)
+ bool is_makedoc, // true: write makedoc format
+ ccp end // write this text at the end of all
+)
+{
+ ASSERT(f||pbuf);
+
+ ccp tie = is_makedoc ? " \\" : "";
+ char buf[100], *buf_end = buf + 70, *dest = buf;
+
+ bool set_apos = false;
+ bool brace_active = false;
+
+ if (!text)
+ text = "";
+ if ( text_len < 0 )
+ text_len = strlen(text) ;
+ ccp text_end = text + text_len;
+
+ while ( text < text_end )
+ {
+ char * dest_break = 0;
+ ccp text_break = 0;
+
+ bool eol = false;
+ while ( !eol && text < text_end && dest < buf_end )
+ {
+ if ( *text >= 1 && *text <= 3 )
+ {
+ // \1 : Text only for built in help
+ // \2 : Text only for ui.def
+ // \3 : Text for all (default)
+
+ const bool skip = *text == ( is_makedoc ? 1 : 2 );
+ //PRINT("CH=%d, SKIP=%d\n",*text,skip);
+ text++;
+ if (skip)
+ while ( text < text_end && (u8)*text > 3 )
+ text++;
+ continue;
+ }
+
+ switch (*text)
+ {
+ case 0:
+ // ignore
+ break;
+
+ case ' ':
+ if ( dest > buf )
+ {
+ dest_break = dest;
+ text_break = text;
+ }
+ *dest++ = *text++;
+ break;
+
+ case '\n':
+ eol = true;
+ *dest++ = '\\';
+ *dest++ = 'n';
+ text++;
+ dest_break = 0;
+ text_break = 0;
+ break;
+
+ case '\\':
+ case '"':
+ *dest++ = '\\';
+ *dest++ = *text++;
+ break;
+
+ case '{':
+ if (is_makedoc)
+ *dest++ = *text++;
+ else if ( *++text == '{' )
+ *dest++ = *text++;
+ else
+ {
+ brace_active = true;
+ ccp ptr = text;
+ while ( ptr < text_end && *ptr > ' ' && *ptr != '}' )
+ ptr++;
+ set_apos = *ptr != '}';
+ if (set_apos)
+ *dest++ = '\'';
+ }
+ break;
+
+
+ case '}':
+ if ( is_makedoc || !brace_active )
+ *dest++ = *text++;
+ else if ( *++text == '}' )
+ *dest++ = *text++;
+ else
+ {
+ brace_active = false;
+ if (set_apos)
+ {
+ set_apos = false;
+ *dest++ = '\'';
+ }
+ }
+ break;
+
+ case '@':
+ case '$':
+ case '§':
+ if (!is_makedoc)
+ {
+ if ( text[1] == *text )
+ *dest++ = *text++;
+ text++;
+ break;
+ }
+ // fall through
+
+ default:
+ *dest++ = *text++;
+ }
+ DASSERT( dest < buf_end + 10 );
+ }
+
+ if ( dest >= buf_end && dest_break )
+ {
+ dest = dest_break;
+ text = text_break;
+ }
+
+ if ( dest > buf && text < text_end )
+ {
+ *dest = 0;
+ if (f)
+ fprintf(f,"\t\"%s\"%s\n",buf,tie);
+ else
+ pbuf = snprintfE(pbuf,pbuf_end,"\t\"%s\"%s\n",buf,tie);
+ dest = buf;
+ }
+ }
+
+ *dest = 0;
+ if (f)
+ fprintf(f,"\t\"%s\"%s",buf,end);
+ else
+ snprintf(pbuf,pbuf_end-pbuf,"\t\"%s\"%s",buf,end);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print options ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void print_single_option
+(
+ FILE * f, // valid output stream
+ int indent, // normalized indent of output
+ const InfoOption_t * io, // option to print
+ uint opt_fw, // field width for options names
+ uint disp_fw // total field width of displayed output
+)
+{
+ DASSERT(f);
+ DASSERT(io);
+
+ if ( !io->short_name && !io->long_name )
+ {
+ fputc('\n',f);
+ return;
+ }
+
+ ccp col_reset, col_param;
+ if (IsFileColorized(f))
+ {
+ fputs(GetTextMode(1,TTM_COL_OPTION),f);
+ col_param = io->param ? GetTextMode(1,TTM_COL_PARAM) : "";
+ col_reset = TermTextModeReset;
+ }
+ else
+ col_reset = col_param = "";
+
+ ccp iop = io->param;
+ ccp sep = !iop ? ""
+ : !io->optional_parm ? " "
+ : iop[0] == '=' || iop[0] == '[' && iop[0] == '=' ? ""
+ : "=";
+
+ int len;
+ if (!io->short_name)
+ len = fprintf(f,"%*s --%s%s%s%s ",
+ indent, "",
+ io->long_name,
+ sep,
+ col_param, iop ? iop : "" );
+ else if (!io->long_name)
+ len = fprintf(f,"%*s -%c%s%s%s ",
+ indent, "",
+ io->short_name,
+ iop ? " " : "",
+ col_param, iop ? iop : "" );
+ else
+ len = fprintf(f,"%*s -%c --%s%s%s%s ",
+ indent, "",
+ io->short_name,
+ io->long_name,
+ sep,
+ col_param, iop ? iop : "" );
+
+ len -= strlen(col_param);
+ if ( len > opt_fw )
+ {
+ fprintf(f,"%s\n",col_reset);
+ PutLines(f,opt_fw,disp_fw,0,0,io->help,0);
+ }
+ else
+ {
+ fputs(col_reset,f);
+ PutLines(f,opt_fw,disp_fw,len,0,io->help,0);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static inline bool is_opt_selected
+(
+ const InfoUI_t * iu, // valid pointer
+ const InfoOption_t * io, // active option
+ bool use_filter // true: user filter
+)
+{
+ DASSERT(io);
+ return use_filter
+ ? ( iu->opt_used[io->id] & 0x80 ) != 0
+ : !io->hidden;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void print_help_options
+(
+ const InfoUI_t * iu, // valid pointer
+ FILE * f, // valid output stream
+ bool filter_opt, // true: filter options by 'iu->opt_used'
+ int indent, // indent of output
+ uint beg_index, // index of first option to print
+ uint end_index, // index of last option to print +1
+ ccp title // NULL or title text
+)
+{
+ DASSERT(iu);
+
+ if ( end_index > iu->n_opt_total )
+ end_index = iu->n_opt_total;
+ if ( beg_index >= end_index || !f )
+ return;
+
+ if (title)
+ {
+ const bool is_col = IsFileColorized(f);
+ fprintf(f,"%*s%s%s:%s\n\n",
+ indent,"",
+ GetTextMode(is_col,TTM_COL_HEADING), title,
+ GetTextMode(is_col,TTM_RESET) );
+ }
+
+ const int fw = GetTermWidth(80,40) - 1;
+
+ const InfoOption_t *io_beg = iu->opt_info + beg_index;
+ const InfoOption_t *io_end = iu->opt_info + end_index;
+ const InfoOption_t *io;
+
+ // calculate the base option field width
+ int base_opt_fw = 0;
+ for ( io = io_beg; io < io_end; io++ )
+ {
+ if ( io->long_name && is_opt_selected(iu,io,filter_opt) )
+ {
+ int len = strlen(io->long_name);
+ if (io->param)
+ len += 1 + strlen(io->param);
+ if ( base_opt_fw < len )
+ base_opt_fw = len;
+ }
+ }
+
+ const int max_fw = 2 + (fw+5)/8;
+ if ( base_opt_fw > max_fw )
+ base_opt_fw = max_fw;
+
+ // calculate the option field width
+ int opt_fw = 0;
+ for ( io = io_beg; io < io_end; io++ )
+ {
+ if ( io->long_name && is_opt_selected(iu,io,filter_opt) )
+ {
+ int len = strlen(io->long_name);
+ if (io->param)
+ len += 1 + strlen(io->param);
+ if ( len <= base_opt_fw && opt_fw < len )
+ opt_fw = len;
+ }
+ }
+ opt_fw += indent + 9;
+
+ for ( io = io_beg; io < io_end; io++ )
+ {
+ if ( is_opt_selected(iu,io,filter_opt) )
+ {
+ if (io->separator)
+ fputc('\n',f);
+ print_single_option(f,indent,io,opt_fw,fw);
+ }
+ }
+ fputc('\n',f);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintHelpOptions
+(
+ const InfoUI_t * iu, // valid pointer
+ FILE * f, // valid output stream
+ int indent // indent of output
+)
+{
+ DASSERT(iu);
+
+ bool filter_opt = false;
+ uint i;
+ for ( i = 1; i < iu->n_opt_total; i++ )
+ {
+ const u8 used = iu->opt_used[i];
+ if ( used & 0x80 && ( used != 0x81 || !iu->opt_info[i].ignore ) )
+ {
+ filter_opt = true;
+ break;
+ }
+ }
+
+ fputc('\n',f);
+ if (filter_opt)
+ {
+ print_help_options( iu, f, true, indent,
+ 1, iu->n_opt_total,
+ "Selected options with common description" );
+ }
+ else
+ {
+ print_help_options( iu, f, false, indent,
+ iu->n_opt_specific+1, iu->n_opt_total,
+ "Global options" );
+
+ print_help_options( iu, f, false, indent,
+ 1, iu->n_opt_specific,
+ "Command specific options with common description" );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintHelpCommands
+(
+ const InfoUI_t * iu, // valid pointer
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ ccp help_cmd // NULL or name of help command
+)
+{
+ DASSERT(iu);
+
+ if ( !iu->n_cmd )
+ return;
+
+ uint col_len;
+ ccp col_head, col_cmd, col_reset;
+ if ( IsFileColorized(f))
+ {
+ col_head = GetTextMode(1,TTM_COL_HEADING);
+ col_cmd = GetTextMode(1,TTM_COL_CMD);
+ col_reset = TermTextModeReset;
+ col_len = strlen(col_cmd) + strlen(col_reset);
+ }
+ else
+ {
+ col_head = col_cmd = col_reset = "";
+ col_len = 0;
+ }
+
+ fprintf(f,"\n%*s%sCommands:%s\n\n", indent,"", col_head, col_reset );
+ const int fw = GetTermWidth(80,40) - 1;
+
+ int fw1 = 0, fw2 = 0;
+ const InfoCommand_t * ic;
+
+ for ( ic = iu->cmd_info; ic->name1; ic++ )
+ if (!ic->hidden)
+ {
+
+ const int len = strlen(ic->name1);
+ if ( fw1 < len )
+ fw1 = len;
+ if ( ic->name2 )
+ {
+ const int len = strlen(ic->name2);
+ if ( fw2 < len )
+ fw2 = len;
+ }
+ }
+ const int fw12 = fw1 + ( fw2 ? fw2 + 3 : 0 );
+
+ for ( ic = iu->cmd_info+1; ic->name1; ic++ )
+ if (!ic->hidden)
+ {
+ if (ic->separator)
+ fputc('\n',f);
+ int len;
+ if ( ic->name2 )
+ len = fprintf(f,"%*s %s%-*s%s | %s%-*s%s : ",
+ indent, "",
+ col_cmd, fw1, ic->name1, col_reset,
+ col_cmd, fw2, ic->name2, col_reset )
+ - 2*col_len;
+ else
+ len = fprintf(f,"%*s %s%-*s%s : ",
+ indent, "",
+ col_cmd, fw12, ic->name1, col_reset )
+ - col_len;
+ PutLines(f,indent+len,fw,len,0,ic->help,0);
+ }
+
+ if (help_cmd)
+ fprintf(f,
+ "\n%*sType '%s %s command' to get command specific help.\n\n",
+ indent, "", iu->tool_name, help_cmd );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print help ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintHelp
+(
+ const InfoUI_t * iu, // valid pointer
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ ccp help_cmd, // NULL or name of help command
+ ccp info, // NULL or additional text
+ ccp base_uri, // NULL or base URI for a external help link
+ ccp first_param // NULL or first param of argv[]
+)
+{
+ ASSERT(iu);
+
+ int cmd = 0;
+ if ( iu->n_cmd && first_param && *first_param )
+ {
+ int cmd_stat;
+ const KeywordTab_t * ct = ScanKeyword(&cmd_stat,first_param,iu->cmd_tab);
+ if (ct)
+ cmd = ct->id;
+ else
+ {
+ char buf[100];
+ StringCat2S(buf,sizeof(buf),"+",first_param);
+ ct = ScanKeyword(&cmd_stat,buf,iu->cmd_tab);
+ if (ct)
+ cmd = ct->id;
+ else
+ {
+ char key_buf[12];
+ char *dest = key_buf;
+ char *end = key_buf + sizeof(key_buf) - 1;
+ while ( *first_param && dest < end )
+ *dest++ = toupper((int)*first_param++);
+ *dest = 0;
+ const int key_len = dest - key_buf;
+ if ( key_len >= 3 )
+ {
+ if (!memcmp(key_buf,"OPTIONS",key_len) )
+ {
+ PrintHelpOptions(iu,f,indent);
+ return;
+ }
+
+ if ( !memcmp(key_buf,"COMMANDS",key_len)
+ || !memcmp(key_buf,"CMD",key_len) )
+ {
+ PrintHelpCommands(iu,f,indent,help_cmd);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ PrintHelpCmd(iu,f,indent,cmd,help_cmd,info,base_uri);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintHelpCmd
+(
+ const InfoUI_t * iu, // valid pointer
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ int cmd, // index of command
+ ccp help_cmd, // NULL or name of help command
+ ccp info, // NULL or poiner to additional text
+ ccp base_uri // NULL or base URI for a external help link
+)
+{
+ ASSERT(iu);
+ if (!f)
+ return;
+ indent = NormalizeIndent(indent);
+
+ if ( cmd < 0 || cmd >= iu->n_cmd )
+ cmd = 0;
+ const InfoCommand_t * ic = iu->cmd_info + cmd;
+
+ const int fw = GetTermWidth(80,40) - 1;
+
+ uint col_len;
+ ccp col_head, col_reset;
+ if (IsFileColorized(f))
+ {
+ col_head = GetTextMode(1,TTM_COL_HEADING);
+ col_reset = GetTextMode(1,TTM_RESET);
+ col_len = strlen(col_head) + strlen(col_reset);
+ }
+ else
+ {
+ col_head = col_reset = "";
+ col_len = 0;
+ }
+
+ char iobuf[10000];
+
+ if (!cmd)
+ snprintf(iobuf,sizeof(iobuf),"%s",iu->tool_name );
+ else if (ic->name2)
+ snprintf(iobuf,sizeof(iobuf),"%s %s|%s",iu->tool_name,ic->name1,ic->name2);
+ else
+ snprintf(iobuf,sizeof(iobuf),"%s %s",iu->tool_name,ic->name1);
+
+ int len = fprintf(f,"\n%*s%s : ",indent,"",iobuf) - 1;
+ PutLines(f,indent+len,fw,len,0, ic->xhelp ? ic->xhelp : ic->help, 0);
+
+ len = fprintf(f,"\n%*s%sSyntax:%s ", indent, "", col_head, col_reset ) - 1 - col_len;
+ PutLines(f,indent+len,fw,len,0,ic->syntax,0);
+ fputc('\n',f);
+
+ if (!cmd)
+ PrintHelpCommands(iu,f,indent,help_cmd);
+
+ if ( ic->n_opt )
+ {
+ fprintf(f,"%*s%s%sptions:%s\n\n",
+ indent,"", col_head,
+ !cmd && iu->n_cmd ? "Global o" : "O",
+ col_reset );
+
+ const InfoOption_t ** iop;
+
+ // calculate the base option field width
+ int base_opt_fw = 0;
+ for ( iop = ic->opt; *iop; iop++ )
+ {
+ const InfoOption_t * io = *iop;
+ if (io->long_name)
+ {
+ int len = strlen(io->long_name);
+ if (io->param)
+ len += 1 + strlen(io->param);
+ if ( base_opt_fw < len )
+ base_opt_fw = len;
+ }
+ }
+
+ const int max_fw = 2 + (fw+5)/8;
+ if ( base_opt_fw > max_fw )
+ base_opt_fw = max_fw;
+
+ // calculate the option field width
+ int opt_fw = 0;
+ for ( iop = ic->opt; *iop; iop++ )
+ {
+ const InfoOption_t * io = *iop;
+ if (io->long_name)
+ {
+ int len = strlen(io->long_name);
+ if (io->param)
+ len += 1 + strlen(io->param);
+ if ( len <= base_opt_fw && opt_fw < len )
+ opt_fw = len;
+ }
+ }
+ opt_fw += indent + 9;
+
+ for ( iop = ic->opt; *iop; iop++ )
+ print_single_option(f,indent,*iop,opt_fw,fw);
+ fputc('\n',f);
+ }
+
+ if (info)
+ fputs(info,f);
+
+ if ( base_uri && *base_uri )
+ {
+ if (!cmd)
+ fprintf(f,"%*sMore help is available at %s%s\n\n",
+ indent, "", base_uri, iu->tool_name );
+ else if (!ic->hidden)
+ {
+ char *dest = iobuf;
+ ccp src;
+ for ( src = ic->name1; *src; src++ )
+ *dest++ = *src == '_' ? '-' : tolower((int)*src);
+ *dest = 0;
+
+ fprintf(f,"%*sMore help is available at %scmd/%s/%s\n\n",
+ indent, "", base_uri, iu->tool_name, iobuf );
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-ui.h b/project/dclib/dclib-ui.h
new file mode 100644
index 0000000..1b45d43
--- /dev/null
+++ b/project/dclib/dclib-ui.h
@@ -0,0 +1,215 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_UI_H
+#define DCLIB_UI_H
+
+#include "dclib-basics.h"
+#include <getopt.h>
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// definitions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[OptionIndex_t]] // max size of OptionIndex[]
+
+#ifndef UIOPT_INDEX_SIZE
+ #define UIOPT_INDEX_SIZE 0x100
+#endif
+
+#undef UIOPT_INDEX_FW
+
+#if UIOPT_INDEX_SIZE <= 0x100
+ #define UIOPT_INDEX_FW 2
+ typedef u8 OptionIndex_t;
+#else
+ #define UIOPT_INDEX_FW 3
+ typedef u16 OptionIndex_t;
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+enum // some const
+{
+ UIOPT_USED_MASK = 0x7f, // mask to calculate usage count
+ UIOPT_LONG_BASE = 0x80, // first index for "only long options"
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// InfoOption_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[InfoOption_t]]
+
+typedef struct InfoOption_t
+{
+ int id; // id of the option
+ bool optional_parm; // true: option has optional parameters
+ bool hidden; // true: option is hidden
+ bool deprecated; // true: option is deprecated
+ bool ignore; // true: ignore on 'HELP OPTIONS'
+ bool separator; // true: print a separator above
+ char short_name; // short option name
+ ccp long_name; // the main long option name
+ ccp param; // parameter name
+ ccp help; // help info
+
+} InfoOption_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// InfoCommand_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[InfoCommand_t]]
+
+typedef struct InfoCommand_t
+{
+ int id; // id of the command
+ bool hidden; // true: command is hidden
+ bool deprecated; // true: command is deprecated
+ bool separator; // true: print a separator above
+ ccp name1; // main name
+ ccp name2; // NULL or alternative name
+ ccp syntax; // NULL or syntax info
+ ccp help; // help text
+ ccp xhelp; // NULL or a extended help text
+ int n_opt; // number of options == elements of 'opt'
+ const InfoOption_t ** opt; // field with option info
+ u8 * opt_allowed; // field with OPT__N_SPECIFIC elements
+ // 0: option permitted, 1: option allowed
+
+} InfoCommand_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// InfoUI_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[InfoUI_t]]
+
+typedef struct InfoUI_t
+{
+ ccp tool_name; // name of the tool
+
+ //----- commands -----
+
+ int n_cmd; // == CMD__N
+ const KeywordTab_t * cmd_tab; // NULL or pointer to command table
+ const InfoCommand_t * cmd_info; // pointer to 'CommandInfo[]'
+
+ //----- options -----
+
+ int n_opt_specific; // == OPT__N_SPECIFIC
+ int n_opt_total; // == OPT__N_TOTAL
+ const InfoOption_t * opt_info; // pointer to 'OptionInfo[]'
+ u8 * opt_used; // pointer to 'OptionUsed[]'
+ const OptionIndex_t * opt_index; // pointer to 'OptionIndex[]'
+ ccp opt_short; // pointer to 'OptionShort[]'
+ const struct option * opt_long; // pointer to 'OptionLong[]'
+
+} InfoUI_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RegisterOptionByIndex
+(
+ const InfoUI_t * iu, // valid pointer
+ int opt_index, // index of option (OPT_*)
+ int level, // the level of registration
+ bool is_env // true: register environment pre setting
+);
+
+enumError RegisterOptionByName
+(
+ const InfoUI_t * iu, // valid pointer
+ int opt_name, // short name of GO_* value of option
+ int level, // the level of registration
+ bool is_env // true: register environment pre setting
+);
+
+enumError VerifySpecificOptions ( const InfoUI_t * iu, const KeywordTab_t * cmd );
+int GetOptionCount ( const InfoUI_t * iu, int option );
+void DumpUsedOptions ( const InfoUI_t * iu, FILE * f, int indent );
+void WarnDepractedOptions ( const InfoUI_t * iu );
+
+typedef enumError (*check_opt_func) ( int argc, char ** argv, bool mode );
+enumError CheckEnvOptions ( ccp varname, check_opt_func );
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpText
+(
+ FILE * f, // output file, if NULL: 'pbuf' must be set
+ char * pbuf, // output buffer, ignored if 'f' not NULL
+ char * pbuf_end, // end of output buffer
+ ccp text, // source text
+ int text_len, // >=0: length of text, <0: use strlen(text)
+ bool is_makedoc, // true: write makedoc format
+ ccp end // write this text at the end of all
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintHelp
+(
+ const InfoUI_t * iu, // valid pointer
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ ccp help_cmd, // NULL or name of help command
+ ccp info, // NULL or additional text
+ ccp base_uri, // NULL or base URI for a external help link
+ ccp first_param // NULL or first param of argv[]
+);
+
+void PrintHelpCmd
+(
+ const InfoUI_t * iu, // valid pointer
+ FILE * f, // valid output stream
+ int indent, // indent of output
+ int cmd, // index of command
+ ccp help_cmd, // NULL or name of help command
+ ccp info, // NULL or poiner to additional text
+ ccp base_uri // NULL or base URI for a external help link
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_UI_H
+
diff --git a/project/dclib/dclib-utf8.c b/project/dclib/dclib-utf8.c
new file mode 100644
index 0000000..af11a68
--- /dev/null
+++ b/project/dclib/dclib-utf8.c
@@ -0,0 +1,1910 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <string.h>
+#include <limits.h>
+#include <arpa/inet.h>
+
+#include "dclib-basics.h"
+#include "dclib-debug.h"
+#include "dclib-utf8.h"
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define u0 DC_UTF8_ILLEGAL
+#define u1 DC_UTF8_1CHAR
+#define u2 DC_UTF8_2CHAR
+#define u3 DC_UTF8_3CHAR
+#define u4 DC_UTF8_4CHAR
+#define uc DC_UTF8_CONT_ANY
+
+const unsigned short TableUTF8Mode[256] =
+{
+ u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, // 0000xxxx
+ u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, // 0001xxxx
+ u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, // 0010xxxx
+ u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, // 0011xxxx
+
+ u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, // 0100xxxx
+ u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, // 0101xxxx
+ u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, // 0110xxxx
+ u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, u1,u1,u1,u1, // 0111xxxx
+
+ uc,uc,uc,uc, uc,uc,uc,uc, uc,uc,uc,uc, uc,uc,uc,uc, // 1000xxxx
+ uc,uc,uc,uc, uc,uc,uc,uc, uc,uc,uc,uc, uc,uc,uc,uc, // 1001xxxx
+ uc,uc,uc,uc, uc,uc,uc,uc, uc,uc,uc,uc, uc,uc,uc,uc, // 1010xxxx
+ uc,uc,uc,uc, uc,uc,uc,uc, uc,uc,uc,uc, uc,uc,uc,uc, // 1011xxxx
+
+ u2,u2,u2,u2, u2,u2,u2,u2, u2,u2,u2,u2, u2,u2,u2,u2, // 1100xxxx
+ u2,u2,u2,u2, u2,u2,u2,u2, u2,u2,u2,u2, u2,u2,u2,u2, // 1101xxxx
+ u3,u3,u3,u3, u3,u3,u3,u3, u3,u3,u3,u3, u3,u3,u3,u3, // 1110xxxx
+ u4,u4,u4,u4, u4,u4,u4,u4, u0,u0,u0,u0, u0,u0,u0,u0 // 1111xxxx
+};
+
+#undef u0
+#undef u1
+#undef u2
+#undef u3
+#undef u4
+#undef uc
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define CheckUTF8Mode(ch) ((dcUTF8Mode)TableUTF8Mode[(unsigned char)(ch)])
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetUTF8CharLength ( u32 code )
+{
+ // returns the length of the char 'code'
+ // 0 : illegal code
+
+ return code <= DCLIB_UNICODE_MAX_UTF8_1
+ ? 1
+ : code <= DCLIB_UNICODE_MAX_UTF8_2
+ ? 2
+ : code <= DCLIB_UNICODE_MAX_UTF8_3
+ ? 3
+ : code <= DCLIB_UNICODE_MAX_UTF8_4
+ ? 4
+ : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * NextUTF8Char ( ccp ptr )
+{
+ // go to next next UTF8 character
+
+ switch (CheckUTF8Mode(*ptr))
+ {
+ case DC_UTF8_1CHAR:
+ return *ptr ? (char*)ptr+1 : (char*)ptr;
+
+ case DC_UTF8_4CHAR:
+ if ( CheckUTF8Mode(*++ptr) != DC_UTF8_CONT_ANY )
+ break;
+
+ // fall through
+
+ case DC_UTF8_3CHAR:
+ case DC_UTF8_CONT_ANY:
+ if ( CheckUTF8Mode(*++ptr) != DC_UTF8_CONT_ANY )
+ break;
+
+ // fall through
+
+ case DC_UTF8_2CHAR:
+ if ( CheckUTF8Mode(*++ptr) != DC_UTF8_CONT_ANY )
+ break;
+
+ // fall through
+
+ default: // DC_UTF8_ILLEGAL
+ return (char*)ptr+1;
+ }
+ return (char*)ptr;
+}
+
+//-----------------------------------------------------------------------------
+
+char * NextUTF8CharE( ccp ptr, ccp end )
+{
+ // go to next next UTF8 character
+
+ if ( ptr >= end )
+ return (char*)end;
+
+ switch (CheckUTF8Mode(*ptr))
+ {
+ case DC_UTF8_1CHAR:
+ return (char*)ptr+1;
+
+ case DC_UTF8_4CHAR:
+ if ( ++ptr >= end || CheckUTF8Mode(*ptr) != DC_UTF8_CONT_ANY )
+ break;
+
+ // fall through
+
+ case DC_UTF8_3CHAR:
+ case DC_UTF8_CONT_ANY:
+ if ( ++ptr >= end || CheckUTF8Mode(*ptr) != DC_UTF8_CONT_ANY )
+ break;
+
+ // fall through
+
+ case DC_UTF8_2CHAR:
+ if ( ++ptr >= end || CheckUTF8Mode(*ptr) != DC_UTF8_CONT_ANY )
+ break;
+
+ // fall through
+
+ default: // DC_UTF8_ILLEGAL
+ return (char*)ptr+1;
+ }
+ return (char*)ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrevUTF8Char ( ccp str )
+{
+ // go to the previous UTC character
+
+ // kleine Optimierung für den Standardfall
+ if ( (uchar)str[-1] < (uint)DCLIB_UNICODE_MAX_UTF8_1 )
+ return (char*)str-1;
+
+ ccp ptr = str;
+ int n = 0;
+ dcUTF8Mode mode = CheckUTF8Mode(*--ptr);
+ while ( n < 3 && mode == DC_UTF8_CONT_ANY )
+ {
+ n++;
+ mode = CheckUTF8Mode(*--ptr);
+ }
+
+ switch (mode)
+ {
+ case DC_UTF8_1CHAR:
+ return (char*)( n<1 ? ptr : ptr+1 );
+
+ case DC_UTF8_2CHAR:
+ return (char*)( n<2 ? ptr : ptr+2 );
+
+ case DC_UTF8_3CHAR:
+ return (char*)( n<3 ? ptr : ptr+3 );
+
+ case DC_UTF8_4CHAR:
+ return (char*)( n<4 ? ptr : ptr+4 );
+
+ case DC_UTF8_CONT_ANY:
+ return (char*)str-3;
+
+ default:
+ return (char*)str-1;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+char * PrevUTF8CharB ( ccp str, ccp begin )
+{
+ // go to the previous UTC character
+
+ ccp ptr = str;
+ if ( ptr <= begin )
+ return (char*)begin;
+
+ // kleine Optimierung für den Standardfall
+ if ( (uchar)ptr[-1] < (uint)DCLIB_UNICODE_MAX_UTF8_1 )
+ return (char*)ptr-1;
+
+ int n = 0;
+ dcUTF8Mode mode = CheckUTF8Mode(*--ptr);
+ while ( ptr > begin && n < 3 && mode == DC_UTF8_CONT_ANY )
+ {
+ n++;
+ mode = CheckUTF8Mode(*--ptr);
+ }
+ switch (mode)
+ {
+ case DC_UTF8_1CHAR:
+ return (char*)( n<1 ? ptr : ptr+1 );
+
+ case DC_UTF8_2CHAR:
+ return (char*)( n<2 ? ptr : ptr+2 );
+
+ case DC_UTF8_3CHAR:
+ return (char*)( n<3 ? ptr : ptr+3 );
+
+ case DC_UTF8_4CHAR:
+ return (char*)( n<4 ? ptr : ptr+4 );
+
+ case DC_UTF8_CONT_ANY:
+ return (char*)str-3;
+
+ default:
+ return (char*)str-1;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 GetUTF8Char ( ccp str )
+{
+ // fast scan of a UTF8 char -> ignore errors in continuation bytes
+
+ const u32 result = (uchar)*str;
+ switch (CheckUTF8Mode(result))
+ {
+ case DC_UTF8_1CHAR:
+ return result;
+
+ case DC_UTF8_2CHAR:
+ return ( result & 0x1f ) << 6
+ | ( str[1] & 0x3f );
+
+ case DC_UTF8_3CHAR:
+ return ( result & 0x0f ) << 12
+ | ( str[1] & 0x3f ) << 6
+ | ( str[2] & 0x3f );
+
+ case DC_UTF8_4CHAR:
+ return ( result & 0x07 ) << 18
+ | ( str[1] & 0x3f ) << 12
+ | ( str[2] & 0x3f ) << 6
+ | ( str[3] & 0x3f );
+
+ case DC_UTF8_CONT_ANY:
+ return ( result & 0x3f ) << 12
+ | ( str[1] & 0x3f ) << 6
+ | ( str[2] & 0x3f );
+
+ default: // DC_UTF8_ILLEGAL
+ return result & 0x7f | S32_MIN;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 ScanUTF8Char ( ccp * p_str )
+{
+ // scan a UTF8 char. Set bit LONG_MAX if an error seen.
+ // *str is set to next next char if on no error.
+
+ int n_cont;
+ ccp ptr = *p_str;
+ u32 result = (uchar)*ptr++;
+ switch (CheckUTF8Mode(result))
+ {
+ case DC_UTF8_1CHAR:
+ if (result)
+ (*p_str)++;
+ return result;
+
+ case DC_UTF8_2CHAR:
+ result &= 0x1f;
+ n_cont = 1;
+ break;
+
+ case DC_UTF8_3CHAR:
+ result &= 0x0f;
+ n_cont = 2;
+ break;
+
+ case DC_UTF8_4CHAR:
+ result &= 0x07;
+ n_cont = 3;
+ break;
+
+ case DC_UTF8_CONT_ANY:
+ if ( CheckUTF8Mode((uchar)*ptr) && CheckUTF8Mode((uchar)*++ptr) )
+ ptr++;
+ *p_str = ptr;
+ return S32_MIN;
+
+ default: // DC_UTF8_ILLEGAL
+ (*p_str)++;
+ return result & 0x7f | S32_MIN;
+ }
+
+ while ( n_cont-- > 0 )
+ {
+ if ( CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ result = result << 6 | *ptr++ & 0x3f;
+ else
+ {
+ result |= S32_MIN;
+ break;
+ }
+ }
+
+ *p_str = ptr;
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+u32 ScanUTF8CharE ( ccp * p_str, ccp end )
+{
+ // scan a UTF8 char. Set bit S32_MIN if an error seen
+ // *str is set to next next char if on no error.
+
+ ccp ptr = *p_str;
+ if ( ptr >= end )
+ return S32_MIN;
+
+ int n_cont;
+ u32 result = (uchar)*ptr++;
+ switch (CheckUTF8Mode(result))
+ {
+ case DC_UTF8_1CHAR:
+ (*p_str)++;
+ return result;
+
+ case DC_UTF8_2CHAR:
+ if ( ptr >= end )
+ return S32_MIN;
+ result &= 0x1f;
+ n_cont = 1;
+ break;
+
+ case DC_UTF8_3CHAR:
+ if ( ptr+1 >= end )
+ return S32_MIN;
+ result &= 0x0f;
+ n_cont = 2;
+ break;
+
+ case DC_UTF8_4CHAR:
+ if ( ptr+2 >= end )
+ return S32_MIN;
+ result &= 0x07;
+ n_cont = 3;
+ break;
+
+ case DC_UTF8_CONT_ANY:
+ if ( ptr < end && CheckUTF8Mode((uchar)*ptr)
+ && ++ptr < end && CheckUTF8Mode((uchar)*ptr) )
+ ptr++;
+ *p_str = ptr;
+ return S32_MIN;
+
+ default: // DC_UTF8_ILLEGAL
+ (*p_str)++;
+ return result & 0x7f | S32_MIN;
+ }
+
+ while ( n_cont-- > 0 )
+ {
+ if ( CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ result = result << 6 | *ptr++ & 0x3f;
+ else
+ {
+ result |= S32_MIN;
+ break;
+ }
+ }
+
+ *p_str = ptr;
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 ScanUTF8CharInc ( ccp * p_str )
+{
+ // scan a UTF8 char. Set bit LONG_MAX if an error seen
+ // *str is set to next next char => it is incremetned always!
+
+ int n_cont;
+ ccp ptr = *p_str;
+ u32 result = (uchar)*ptr++;
+ switch (CheckUTF8Mode(result))
+ {
+ case DC_UTF8_1CHAR:
+ (*p_str)++;
+ return result;
+
+ case DC_UTF8_2CHAR:
+ result &= 0x1f;
+ n_cont = 1;
+ break;
+
+ case DC_UTF8_3CHAR:
+ result &= 0x0f;
+ n_cont = 2;
+ break;
+
+ case DC_UTF8_4CHAR:
+ result &= 0x07;
+ n_cont = 3;
+ break;
+
+ case DC_UTF8_CONT_ANY:
+ if ( CheckUTF8Mode((uchar)*ptr) && CheckUTF8Mode((uchar)*++ptr) )
+ ptr++;
+ *p_str = ptr;
+ return S32_MIN;
+
+ default: // DC_UTF8_ILLEGAL
+ (*p_str)++;
+ return result & 0x7f | S32_MIN;
+ }
+
+ while ( n_cont-- > 0 )
+ {
+ if ( CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ result = result << 6 | *ptr++ & 0x3f;
+ else
+ {
+ result |= S32_MIN;
+ break;
+ }
+ }
+
+ *p_str = ptr;
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+u32 ScanUTF8CharIncE ( ccp * p_str, ccp end )
+{
+ // scan a UTF8 char. Set bit LONG_MAX if an error seen
+ // *str is set to next next char => it is incremetned always!
+
+ ccp ptr = *p_str;
+ if ( ptr >= end )
+ {
+ (*p_str)++;
+ return S32_MIN;
+ }
+
+ int n_cont = 0;
+ u32 result = (uchar)*ptr++;
+ switch (CheckUTF8Mode(result))
+ {
+ case DC_UTF8_1CHAR:
+ (*p_str)++;
+ return result;
+
+ case DC_UTF8_2CHAR:
+ result &= 0x1f;
+ n_cont = 1;
+ break;
+
+ case DC_UTF8_3CHAR:
+ result &= 0x0f;
+ n_cont = 2;
+ break;
+
+ case DC_UTF8_4CHAR:
+ result &= 0x07;
+ n_cont = 3;
+ break;
+
+ case DC_UTF8_CONT_ANY:
+ if ( ptr < end && CheckUTF8Mode((uchar)*ptr)
+ && ++ptr < end && CheckUTF8Mode((uchar)*ptr) )
+ ptr++;
+ *p_str = ptr;
+ return S32_MIN;
+
+ default: // DC_UTF8_ILLEGAL
+ (*p_str)++;
+ return result & 0x7f | S32_MIN;
+ }
+
+ while ( n_cont-- > 0 )
+ {
+ if ( ptr < end && CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ result = result << 6 | *ptr++ & 0x3f;
+ else
+ {
+ result |= S32_MIN;
+ break;
+ }
+ }
+
+ *p_str = ptr;
+ return result;
+}
+
+// ////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u32 GetUTF8AnsiChar ( ccp str )
+{
+ // scan an UTF8 char. On error scan an ANSI char!
+
+ const uchar * ptr = (const uchar *)str;
+ u32 result = *ptr++;
+ switch (CheckUTF8Mode(result))
+ {
+ case DC_UTF8_2CHAR:
+ if ( CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ {
+ return ( result & 0x1f ) << 6 | *ptr & 0x3f;
+ }
+ return result;
+
+ case DC_UTF8_3CHAR:
+ if ( CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY )
+ {
+ return (( result & 0x0f ) << 6
+ | ptr[0] & 0x3f ) << 6
+ | ptr[1] & 0x3f;
+ }
+ return result;
+
+ case DC_UTF8_4CHAR:
+ if ( CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[2]) == DC_UTF8_CONT_ANY )
+ {
+ return ((( result & 0x07 ) << 6
+ | ptr[0] & 0x3f ) << 6
+ | ptr[1] & 0x3f ) << 6
+ | ptr[2] & 0x3f;
+ }
+ return result;
+
+ default:
+ return result;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 ScanUTF8AnsiChar ( ccp * p_str )
+{
+ // scan an UTF8 char. On error scan an ANSI char!
+
+ const uchar * ptr = (const uchar *)*p_str;
+ u32 result = *ptr++;
+ switch (CheckUTF8Mode(result))
+ {
+ case DC_UTF8_2CHAR:
+ if ( CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ {
+ result = ( result & 0x1f ) << 6 | *ptr++ & 0x3f;
+ }
+ break;
+
+ case DC_UTF8_3CHAR:
+ if ( CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY )
+ {
+ result = (( result & 0x0f ) << 6
+ | ptr[0] & 0x3f ) << 6
+ | ptr[1] & 0x3f;
+ ptr += 2;
+ }
+ break;
+
+ case DC_UTF8_4CHAR:
+ if ( CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[2]) == DC_UTF8_CONT_ANY )
+ {
+ result = ((( result & 0x07 ) << 6
+ | ptr[0] & 0x3f ) << 6
+ | ptr[1] & 0x3f ) << 6
+ | ptr[2] & 0x3f;
+ ptr += 3;
+ }
+ break;
+
+ default:
+ break;
+ }
+ *p_str = (ccp)ptr;
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+u32 ScanUTF8AnsiCharE ( ccp * p_str, ccp end )
+{
+ // scan an UTF8 char. On error scan an ANSI char!
+
+ const uchar * ptr = (const uchar *)*p_str;
+ if ( (ccp)ptr >= end )
+ {
+ (*p_str)++;
+ return S32_MIN;
+ }
+ u32 result = *ptr++;
+ switch (CheckUTF8Mode(result))
+ {
+ case DC_UTF8_2CHAR:
+ if ( (ccp)ptr < end
+ && CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ {
+ result = ( result & 0x1f ) << 6 | *ptr++ & 0x3f;
+ }
+ break;
+
+ case DC_UTF8_3CHAR:
+ if ( (ccp)ptr+1 < end
+ && CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY )
+ {
+ result = (( result & 0x0f ) << 6
+ | ptr[0] & 0x3f ) << 6
+ | ptr[1] & 0x3f;
+ ptr += 2;
+ }
+ break;
+
+ case DC_UTF8_4CHAR:
+ if ( (ccp)ptr+2 < end
+ && CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[2]) == DC_UTF8_CONT_ANY )
+ {
+ result = ((( result & 0x07 ) << 6
+ | ptr[0] & 0x3f ) << 6
+ | ptr[1] & 0x3f ) << 6
+ | ptr[2] & 0x3f;
+ ptr += 3;
+ }
+ break;
+
+ default:
+ break;
+ }
+ *p_str = (ccp)ptr;
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanUTF8Length ( ccp str, ccp end )
+{
+ int count = 0;
+ ccp ptr = str;
+ if (end)
+ {
+ while ( ptr < end )
+ {
+ const char ch = *ptr++;
+ switch (CheckUTF8Mode(ch))
+ {
+ case DC_UTF8_2CHAR:
+ if ( CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ ptr++;
+ break;
+
+ case DC_UTF8_3CHAR:
+ if ( CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY )
+ ptr += 2;
+ break;
+
+ case DC_UTF8_4CHAR:
+ if ( CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[2]) == DC_UTF8_CONT_ANY )
+ ptr += 3;
+ break;
+
+ default:
+ break;
+ }
+ count++;
+ }
+ }
+ else
+ {
+ for(;;)
+ {
+ const char ch = *ptr++;
+ if (!ch)
+ break;
+ switch (CheckUTF8Mode(ch))
+ {
+ case DC_UTF8_2CHAR:
+ if ( CheckUTF8Mode(*ptr) == DC_UTF8_CONT_ANY )
+ ptr++;
+ break;
+
+ case DC_UTF8_3CHAR:
+ if ( CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY )
+ ptr += 2;
+ break;
+
+ case DC_UTF8_4CHAR:
+ if ( CheckUTF8Mode(ptr[0]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[1]) == DC_UTF8_CONT_ANY
+ && CheckUTF8Mode(ptr[2]) == DC_UTF8_CONT_ANY )
+ ptr += 3;
+ break;
+
+ default:
+ break;
+ }
+ count++;
+ }
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int CalcUTF8PrintFW ( ccp str, ccp end, uint wanted_fw )
+{
+ const uint bytecount = end ? end - str : strlen(str);
+ const uint utf8len = ScanUTF8Length(str,end);
+ return utf8len < wanted_fw
+ ? wanted_fw - utf8len + bytecount
+ : bytecount;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintUTF8Char ( char * buf, u32 code )
+{
+ code &= DCLIB_UNICODE_CODE_MASK;
+
+ if ( code <= DCLIB_UNICODE_MAX_UTF8_1 )
+ {
+ *buf++ = code;
+ }
+ else if ( code <= DCLIB_UNICODE_MAX_UTF8_2 )
+ {
+ *buf++ = code >> 6 | 0xc0;
+ *buf++ = code & 0x3f | 0x80;
+ }
+ else if ( code <= DCLIB_UNICODE_MAX_UTF8_3 )
+ {
+ *buf++ = code >> 12 | 0xe0;
+ *buf++ = code >> 6 & 0x3f | 0x80;
+ *buf++ = code & 0x3f | 0x80;
+ }
+ else
+ {
+ *buf++ = code >> 18 & 0x07 | 0xf0;
+ *buf++ = code >> 12 & 0x3f | 0x80;
+ *buf++ = code >> 6 & 0x3f | 0x80;
+ *buf++ = code & 0x3f | 0x80;
+ }
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintUTF8CharToCircBuf ( u32 code )
+{
+ char *buf;
+ code &= DCLIB_UNICODE_CODE_MASK;
+
+ if ( code <= DCLIB_UNICODE_MAX_UTF8_1 )
+ {
+ buf = GetCircBuf(2);
+ buf[0] = code;
+ buf[1] = 0;
+ }
+ else if ( code <= DCLIB_UNICODE_MAX_UTF8_2 )
+ {
+ buf = GetCircBuf(3);
+ buf[0] = code >> 6 | 0xc0;
+ buf[1] = code & 0x3f | 0x80;
+ buf[2] = 0;
+ }
+ else if ( code <= DCLIB_UNICODE_MAX_UTF8_3 )
+ {
+ buf = GetCircBuf(4);
+ buf[0] = code >> 12 | 0xe0;
+ buf[1] = code >> 6 & 0x3f | 0x80;
+ buf[2] = code & 0x3f | 0x80;
+ buf[3] = 0;
+ }
+ else
+ {
+ buf = GetCircBuf(5);
+ buf[0] = code >> 18 & 0x07 | 0xf0;
+ buf[1] = code >> 12 & 0x3f | 0x80;
+ buf[2] = code >> 6 & 0x3f | 0x80;
+ buf[3] = code & 0x3f | 0x80;
+ buf[4] = 0;
+ }
+ return buf;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// this table is directly generated from
+// http://unicode.org/Public/UNIDATA/UnicodeData.txt
+
+const dcUnicodeTripel TableUnicodeDecomp[] =
+{
+ { 0x0000c0, 0x000041,0x000300 },
+ { 0x0000c1, 0x000041,0x000301 },
+ { 0x0000c2, 0x000041,0x000302 },
+ { 0x0000c3, 0x000041,0x000303 },
+ { 0x0000c4, 0x000041,0x000308 },
+ { 0x0000c5, 0x000041,0x00030a },
+ { 0x0000c7, 0x000043,0x000327 },
+ { 0x0000c8, 0x000045,0x000300 },
+ { 0x0000c9, 0x000045,0x000301 },
+ { 0x0000ca, 0x000045,0x000302 },
+ { 0x0000cb, 0x000045,0x000308 },
+ { 0x0000cc, 0x000049,0x000300 },
+ { 0x0000cd, 0x000049,0x000301 },
+ { 0x0000ce, 0x000049,0x000302 },
+ { 0x0000cf, 0x000049,0x000308 },
+ { 0x0000d1, 0x00004e,0x000303 },
+ { 0x0000d2, 0x00004f,0x000300 },
+ { 0x0000d3, 0x00004f,0x000301 },
+ { 0x0000d4, 0x00004f,0x000302 },
+ { 0x0000d5, 0x00004f,0x000303 },
+ { 0x0000d6, 0x00004f,0x000308 },
+ { 0x0000d9, 0x000055,0x000300 },
+ { 0x0000da, 0x000055,0x000301 },
+ { 0x0000db, 0x000055,0x000302 },
+ { 0x0000dc, 0x000055,0x000308 },
+ { 0x0000dd, 0x000059,0x000301 },
+ { 0x0000e0, 0x000061,0x000300 },
+ { 0x0000e1, 0x000061,0x000301 },
+ { 0x0000e2, 0x000061,0x000302 },
+ { 0x0000e3, 0x000061,0x000303 },
+ { 0x0000e4, 0x000061,0x000308 },
+ { 0x0000e5, 0x000061,0x00030a },
+ { 0x0000e7, 0x000063,0x000327 },
+ { 0x0000e8, 0x000065,0x000300 },
+ { 0x0000e9, 0x000065,0x000301 },
+ { 0x0000ea, 0x000065,0x000302 },
+ { 0x0000eb, 0x000065,0x000308 },
+ { 0x0000ec, 0x000069,0x000300 },
+ { 0x0000ed, 0x000069,0x000301 },
+ { 0x0000ee, 0x000069,0x000302 },
+ { 0x0000ef, 0x000069,0x000308 },
+ { 0x0000f1, 0x00006e,0x000303 },
+ { 0x0000f2, 0x00006f,0x000300 },
+ { 0x0000f3, 0x00006f,0x000301 },
+ { 0x0000f4, 0x00006f,0x000302 },
+ { 0x0000f5, 0x00006f,0x000303 },
+ { 0x0000f6, 0x00006f,0x000308 },
+ { 0x0000f9, 0x000075,0x000300 },
+ { 0x0000fa, 0x000075,0x000301 },
+ { 0x0000fb, 0x000075,0x000302 },
+ { 0x0000fc, 0x000075,0x000308 },
+ { 0x0000fd, 0x000079,0x000301 },
+ { 0x0000ff, 0x000079,0x000308 },
+ { 0x000100, 0x000041,0x000304 },
+ { 0x000101, 0x000061,0x000304 },
+ { 0x000102, 0x000041,0x000306 },
+ { 0x000103, 0x000061,0x000306 },
+ { 0x000104, 0x000041,0x000328 },
+ { 0x000105, 0x000061,0x000328 },
+ { 0x000106, 0x000043,0x000301 },
+ { 0x000107, 0x000063,0x000301 },
+ { 0x000108, 0x000043,0x000302 },
+ { 0x000109, 0x000063,0x000302 },
+ { 0x00010a, 0x000043,0x000307 },
+ { 0x00010b, 0x000063,0x000307 },
+ { 0x00010c, 0x000043,0x00030c },
+ { 0x00010d, 0x000063,0x00030c },
+ { 0x00010e, 0x000044,0x00030c },
+ { 0x00010f, 0x000064,0x00030c },
+ { 0x000112, 0x000045,0x000304 },
+ { 0x000113, 0x000065,0x000304 },
+ { 0x000114, 0x000045,0x000306 },
+ { 0x000115, 0x000065,0x000306 },
+ { 0x000116, 0x000045,0x000307 },
+ { 0x000117, 0x000065,0x000307 },
+ { 0x000118, 0x000045,0x000328 },
+ { 0x000119, 0x000065,0x000328 },
+ { 0x00011a, 0x000045,0x00030c },
+ { 0x00011b, 0x000065,0x00030c },
+ { 0x00011c, 0x000047,0x000302 },
+ { 0x00011d, 0x000067,0x000302 },
+ { 0x00011e, 0x000047,0x000306 },
+ { 0x00011f, 0x000067,0x000306 },
+ { 0x000120, 0x000047,0x000307 },
+ { 0x000121, 0x000067,0x000307 },
+ { 0x000122, 0x000047,0x000327 },
+ { 0x000123, 0x000067,0x000327 },
+ { 0x000124, 0x000048,0x000302 },
+ { 0x000125, 0x000068,0x000302 },
+ { 0x000128, 0x000049,0x000303 },
+ { 0x000129, 0x000069,0x000303 },
+ { 0x00012a, 0x000049,0x000304 },
+ { 0x00012b, 0x000069,0x000304 },
+ { 0x00012c, 0x000049,0x000306 },
+ { 0x00012d, 0x000069,0x000306 },
+ { 0x00012e, 0x000049,0x000328 },
+ { 0x00012f, 0x000069,0x000328 },
+ { 0x000130, 0x000049,0x000307 },
+ { 0x000134, 0x00004a,0x000302 },
+ { 0x000135, 0x00006a,0x000302 },
+ { 0x000136, 0x00004b,0x000327 },
+ { 0x000137, 0x00006b,0x000327 },
+ { 0x000139, 0x00004c,0x000301 },
+ { 0x00013a, 0x00006c,0x000301 },
+ { 0x00013b, 0x00004c,0x000327 },
+ { 0x00013c, 0x00006c,0x000327 },
+ { 0x00013d, 0x00004c,0x00030c },
+ { 0x00013e, 0x00006c,0x00030c },
+ { 0x000143, 0x00004e,0x000301 },
+ { 0x000144, 0x00006e,0x000301 },
+ { 0x000145, 0x00004e,0x000327 },
+ { 0x000146, 0x00006e,0x000327 },
+ { 0x000147, 0x00004e,0x00030c },
+ { 0x000148, 0x00006e,0x00030c },
+ { 0x00014c, 0x00004f,0x000304 },
+ { 0x00014d, 0x00006f,0x000304 },
+ { 0x00014e, 0x00004f,0x000306 },
+ { 0x00014f, 0x00006f,0x000306 },
+ { 0x000150, 0x00004f,0x00030b },
+ { 0x000151, 0x00006f,0x00030b },
+ { 0x000154, 0x000052,0x000301 },
+ { 0x000155, 0x000072,0x000301 },
+ { 0x000156, 0x000052,0x000327 },
+ { 0x000157, 0x000072,0x000327 },
+ { 0x000158, 0x000052,0x00030c },
+ { 0x000159, 0x000072,0x00030c },
+ { 0x00015a, 0x000053,0x000301 },
+ { 0x00015b, 0x000073,0x000301 },
+ { 0x00015c, 0x000053,0x000302 },
+ { 0x00015d, 0x000073,0x000302 },
+ { 0x00015e, 0x000053,0x000327 },
+ { 0x00015f, 0x000073,0x000327 },
+ { 0x000160, 0x000053,0x00030c },
+ { 0x000161, 0x000073,0x00030c },
+ { 0x000162, 0x000054,0x000327 },
+ { 0x000163, 0x000074,0x000327 },
+ { 0x000164, 0x000054,0x00030c },
+ { 0x000165, 0x000074,0x00030c },
+ { 0x000168, 0x000055,0x000303 },
+ { 0x000169, 0x000075,0x000303 },
+ { 0x00016a, 0x000055,0x000304 },
+ { 0x00016b, 0x000075,0x000304 },
+ { 0x00016c, 0x000055,0x000306 },
+ { 0x00016d, 0x000075,0x000306 },
+ { 0x00016e, 0x000055,0x00030a },
+ { 0x00016f, 0x000075,0x00030a },
+ { 0x000170, 0x000055,0x00030b },
+ { 0x000171, 0x000075,0x00030b },
+ { 0x000172, 0x000055,0x000328 },
+ { 0x000173, 0x000075,0x000328 },
+ { 0x000174, 0x000057,0x000302 },
+ { 0x000175, 0x000077,0x000302 },
+ { 0x000176, 0x000059,0x000302 },
+ { 0x000177, 0x000079,0x000302 },
+ { 0x000178, 0x000059,0x000308 },
+ { 0x000179, 0x00005a,0x000301 },
+ { 0x00017a, 0x00007a,0x000301 },
+ { 0x00017b, 0x00005a,0x000307 },
+ { 0x00017c, 0x00007a,0x000307 },
+ { 0x00017d, 0x00005a,0x00030c },
+ { 0x00017e, 0x00007a,0x00030c },
+ { 0x0001a0, 0x00004f,0x00031b },
+ { 0x0001a1, 0x00006f,0x00031b },
+ { 0x0001af, 0x000055,0x00031b },
+ { 0x0001b0, 0x000075,0x00031b },
+ { 0x0001cd, 0x000041,0x00030c },
+ { 0x0001ce, 0x000061,0x00030c },
+ { 0x0001cf, 0x000049,0x00030c },
+ { 0x0001d0, 0x000069,0x00030c },
+ { 0x0001d1, 0x00004f,0x00030c },
+ { 0x0001d2, 0x00006f,0x00030c },
+ { 0x0001d3, 0x000055,0x00030c },
+ { 0x0001d4, 0x000075,0x00030c },
+ { 0x0001d5, 0x0000dc,0x000304 },
+ { 0x0001d6, 0x0000fc,0x000304 },
+ { 0x0001d7, 0x0000dc,0x000301 },
+ { 0x0001d8, 0x0000fc,0x000301 },
+ { 0x0001d9, 0x0000dc,0x00030c },
+ { 0x0001da, 0x0000fc,0x00030c },
+ { 0x0001db, 0x0000dc,0x000300 },
+ { 0x0001dc, 0x0000fc,0x000300 },
+ { 0x0001de, 0x0000c4,0x000304 },
+ { 0x0001df, 0x0000e4,0x000304 },
+ { 0x0001e0, 0x000226,0x000304 },
+ { 0x0001e1, 0x000227,0x000304 },
+ { 0x0001e2, 0x0000c6,0x000304 },
+ { 0x0001e3, 0x0000e6,0x000304 },
+ { 0x0001e6, 0x000047,0x00030c },
+ { 0x0001e7, 0x000067,0x00030c },
+ { 0x0001e8, 0x00004b,0x00030c },
+ { 0x0001e9, 0x00006b,0x00030c },
+ { 0x0001ea, 0x00004f,0x000328 },
+ { 0x0001eb, 0x00006f,0x000328 },
+ { 0x0001ec, 0x0001ea,0x000304 },
+ { 0x0001ed, 0x0001eb,0x000304 },
+ { 0x0001ee, 0x0001b7,0x00030c },
+ { 0x0001ef, 0x000292,0x00030c },
+ { 0x0001f0, 0x00006a,0x00030c },
+ { 0x0001f4, 0x000047,0x000301 },
+ { 0x0001f5, 0x000067,0x000301 },
+ { 0x0001f8, 0x00004e,0x000300 },
+ { 0x0001f9, 0x00006e,0x000300 },
+ { 0x0001fa, 0x0000c5,0x000301 },
+ { 0x0001fb, 0x0000e5,0x000301 },
+ { 0x0001fc, 0x0000c6,0x000301 },
+ { 0x0001fd, 0x0000e6,0x000301 },
+ { 0x0001fe, 0x0000d8,0x000301 },
+ { 0x0001ff, 0x0000f8,0x000301 },
+ { 0x000200, 0x000041,0x00030f },
+ { 0x000201, 0x000061,0x00030f },
+ { 0x000202, 0x000041,0x000311 },
+ { 0x000203, 0x000061,0x000311 },
+ { 0x000204, 0x000045,0x00030f },
+ { 0x000205, 0x000065,0x00030f },
+ { 0x000206, 0x000045,0x000311 },
+ { 0x000207, 0x000065,0x000311 },
+ { 0x000208, 0x000049,0x00030f },
+ { 0x000209, 0x000069,0x00030f },
+ { 0x00020a, 0x000049,0x000311 },
+ { 0x00020b, 0x000069,0x000311 },
+ { 0x00020c, 0x00004f,0x00030f },
+ { 0x00020d, 0x00006f,0x00030f },
+ { 0x00020e, 0x00004f,0x000311 },
+ { 0x00020f, 0x00006f,0x000311 },
+ { 0x000210, 0x000052,0x00030f },
+ { 0x000211, 0x000072,0x00030f },
+ { 0x000212, 0x000052,0x000311 },
+ { 0x000213, 0x000072,0x000311 },
+ { 0x000214, 0x000055,0x00030f },
+ { 0x000215, 0x000075,0x00030f },
+ { 0x000216, 0x000055,0x000311 },
+ { 0x000217, 0x000075,0x000311 },
+ { 0x000218, 0x000053,0x000326 },
+ { 0x000219, 0x000073,0x000326 },
+ { 0x00021a, 0x000054,0x000326 },
+ { 0x00021b, 0x000074,0x000326 },
+ { 0x00021e, 0x000048,0x00030c },
+ { 0x00021f, 0x000068,0x00030c },
+ { 0x000226, 0x000041,0x000307 },
+ { 0x000227, 0x000061,0x000307 },
+ { 0x000228, 0x000045,0x000327 },
+ { 0x000229, 0x000065,0x000327 },
+ { 0x00022a, 0x0000d6,0x000304 },
+ { 0x00022b, 0x0000f6,0x000304 },
+ { 0x00022c, 0x0000d5,0x000304 },
+ { 0x00022d, 0x0000f5,0x000304 },
+ { 0x00022e, 0x00004f,0x000307 },
+ { 0x00022f, 0x00006f,0x000307 },
+ { 0x000230, 0x00022e,0x000304 },
+ { 0x000231, 0x00022f,0x000304 },
+ { 0x000232, 0x000059,0x000304 },
+ { 0x000233, 0x000079,0x000304 },
+ { 0x000344, 0x000308,0x000301 },
+ { 0x000385, 0x0000a8,0x000301 },
+ { 0x000386, 0x000391,0x000301 },
+ { 0x000388, 0x000395,0x000301 },
+ { 0x000389, 0x000397,0x000301 },
+ { 0x00038a, 0x000399,0x000301 },
+ { 0x00038c, 0x00039f,0x000301 },
+ { 0x00038e, 0x0003a5,0x000301 },
+ { 0x00038f, 0x0003a9,0x000301 },
+ { 0x000390, 0x0003ca,0x000301 },
+ { 0x0003aa, 0x000399,0x000308 },
+ { 0x0003ab, 0x0003a5,0x000308 },
+ { 0x0003ac, 0x0003b1,0x000301 },
+ { 0x0003ad, 0x0003b5,0x000301 },
+ { 0x0003ae, 0x0003b7,0x000301 },
+ { 0x0003af, 0x0003b9,0x000301 },
+ { 0x0003b0, 0x0003cb,0x000301 },
+ { 0x0003ca, 0x0003b9,0x000308 },
+ { 0x0003cb, 0x0003c5,0x000308 },
+ { 0x0003cc, 0x0003bf,0x000301 },
+ { 0x0003cd, 0x0003c5,0x000301 },
+ { 0x0003ce, 0x0003c9,0x000301 },
+ { 0x0003d3, 0x0003d2,0x000301 },
+ { 0x0003d4, 0x0003d2,0x000308 },
+ { 0x000400, 0x000415,0x000300 },
+ { 0x000401, 0x000415,0x000308 },
+ { 0x000403, 0x000413,0x000301 },
+ { 0x000407, 0x000406,0x000308 },
+ { 0x00040c, 0x00041a,0x000301 },
+ { 0x00040d, 0x000418,0x000300 },
+ { 0x00040e, 0x000423,0x000306 },
+ { 0x000419, 0x000418,0x000306 },
+ { 0x000439, 0x000438,0x000306 },
+ { 0x000450, 0x000435,0x000300 },
+ { 0x000451, 0x000435,0x000308 },
+ { 0x000453, 0x000433,0x000301 },
+ { 0x000457, 0x000456,0x000308 },
+ { 0x00045c, 0x00043a,0x000301 },
+ { 0x00045d, 0x000438,0x000300 },
+ { 0x00045e, 0x000443,0x000306 },
+ { 0x000476, 0x000474,0x00030f },
+ { 0x000477, 0x000475,0x00030f },
+ { 0x0004c1, 0x000416,0x000306 },
+ { 0x0004c2, 0x000436,0x000306 },
+ { 0x0004d0, 0x000410,0x000306 },
+ { 0x0004d1, 0x000430,0x000306 },
+ { 0x0004d2, 0x000410,0x000308 },
+ { 0x0004d3, 0x000430,0x000308 },
+ { 0x0004d6, 0x000415,0x000306 },
+ { 0x0004d7, 0x000435,0x000306 },
+ { 0x0004da, 0x0004d8,0x000308 },
+ { 0x0004db, 0x0004d9,0x000308 },
+ { 0x0004dc, 0x000416,0x000308 },
+ { 0x0004dd, 0x000436,0x000308 },
+ { 0x0004de, 0x000417,0x000308 },
+ { 0x0004df, 0x000437,0x000308 },
+ { 0x0004e2, 0x000418,0x000304 },
+ { 0x0004e3, 0x000438,0x000304 },
+ { 0x0004e4, 0x000418,0x000308 },
+ { 0x0004e5, 0x000438,0x000308 },
+ { 0x0004e6, 0x00041e,0x000308 },
+ { 0x0004e7, 0x00043e,0x000308 },
+ { 0x0004ea, 0x0004e8,0x000308 },
+ { 0x0004eb, 0x0004e9,0x000308 },
+ { 0x0004ec, 0x00042d,0x000308 },
+ { 0x0004ed, 0x00044d,0x000308 },
+ { 0x0004ee, 0x000423,0x000304 },
+ { 0x0004ef, 0x000443,0x000304 },
+ { 0x0004f0, 0x000423,0x000308 },
+ { 0x0004f1, 0x000443,0x000308 },
+ { 0x0004f2, 0x000423,0x00030b },
+ { 0x0004f3, 0x000443,0x00030b },
+ { 0x0004f4, 0x000427,0x000308 },
+ { 0x0004f5, 0x000447,0x000308 },
+ { 0x0004f8, 0x00042b,0x000308 },
+ { 0x0004f9, 0x00044b,0x000308 },
+ { 0x000622, 0x000627,0x000653 },
+ { 0x000623, 0x000627,0x000654 },
+ { 0x000624, 0x000648,0x000654 },
+ { 0x000625, 0x000627,0x000655 },
+ { 0x000626, 0x00064a,0x000654 },
+ { 0x0006c0, 0x0006d5,0x000654 },
+ { 0x0006c2, 0x0006c1,0x000654 },
+ { 0x0006d3, 0x0006d2,0x000654 },
+ { 0x000929, 0x000928,0x00093c },
+ { 0x000931, 0x000930,0x00093c },
+ { 0x000934, 0x000933,0x00093c },
+ { 0x000958, 0x000915,0x00093c },
+ { 0x000959, 0x000916,0x00093c },
+ { 0x00095a, 0x000917,0x00093c },
+ { 0x00095b, 0x00091c,0x00093c },
+ { 0x00095c, 0x000921,0x00093c },
+ { 0x00095d, 0x000922,0x00093c },
+ { 0x00095e, 0x00092b,0x00093c },
+ { 0x00095f, 0x00092f,0x00093c },
+ { 0x0009cb, 0x0009c7,0x0009be },
+ { 0x0009cc, 0x0009c7,0x0009d7 },
+ { 0x0009dc, 0x0009a1,0x0009bc },
+ { 0x0009dd, 0x0009a2,0x0009bc },
+ { 0x0009df, 0x0009af,0x0009bc },
+ { 0x000a33, 0x000a32,0x000a3c },
+ { 0x000a36, 0x000a38,0x000a3c },
+ { 0x000a59, 0x000a16,0x000a3c },
+ { 0x000a5a, 0x000a17,0x000a3c },
+ { 0x000a5b, 0x000a1c,0x000a3c },
+ { 0x000a5e, 0x000a2b,0x000a3c },
+ { 0x000b48, 0x000b47,0x000b56 },
+ { 0x000b4b, 0x000b47,0x000b3e },
+ { 0x000b4c, 0x000b47,0x000b57 },
+ { 0x000b5c, 0x000b21,0x000b3c },
+ { 0x000b5d, 0x000b22,0x000b3c },
+ { 0x000b94, 0x000b92,0x000bd7 },
+ { 0x000bca, 0x000bc6,0x000bbe },
+ { 0x000bcb, 0x000bc7,0x000bbe },
+ { 0x000bcc, 0x000bc6,0x000bd7 },
+ { 0x000c48, 0x000c46,0x000c56 },
+ { 0x000cc0, 0x000cbf,0x000cd5 },
+ { 0x000cc7, 0x000cc6,0x000cd5 },
+ { 0x000cc8, 0x000cc6,0x000cd6 },
+ { 0x000cca, 0x000cc6,0x000cc2 },
+ { 0x000ccb, 0x000cca,0x000cd5 },
+ { 0x000d4a, 0x000d46,0x000d3e },
+ { 0x000d4b, 0x000d47,0x000d3e },
+ { 0x000d4c, 0x000d46,0x000d57 },
+ { 0x000dda, 0x000dd9,0x000dca },
+ { 0x000ddc, 0x000dd9,0x000dcf },
+ { 0x000ddd, 0x000ddc,0x000dca },
+ { 0x000dde, 0x000dd9,0x000ddf },
+ { 0x000f43, 0x000f42,0x000fb7 },
+ { 0x000f4d, 0x000f4c,0x000fb7 },
+ { 0x000f52, 0x000f51,0x000fb7 },
+ { 0x000f57, 0x000f56,0x000fb7 },
+ { 0x000f5c, 0x000f5b,0x000fb7 },
+ { 0x000f69, 0x000f40,0x000fb5 },
+ { 0x000f73, 0x000f71,0x000f72 },
+ { 0x000f75, 0x000f71,0x000f74 },
+ { 0x000f76, 0x000fb2,0x000f80 },
+ { 0x000f78, 0x000fb3,0x000f80 },
+ { 0x000f81, 0x000f71,0x000f80 },
+ { 0x000f93, 0x000f92,0x000fb7 },
+ { 0x000f9d, 0x000f9c,0x000fb7 },
+ { 0x000fa2, 0x000fa1,0x000fb7 },
+ { 0x000fa7, 0x000fa6,0x000fb7 },
+ { 0x000fac, 0x000fab,0x000fb7 },
+ { 0x000fb9, 0x000f90,0x000fb5 },
+ { 0x001026, 0x001025,0x00102e },
+ { 0x001b06, 0x001b05,0x001b35 },
+ { 0x001b08, 0x001b07,0x001b35 },
+ { 0x001b0a, 0x001b09,0x001b35 },
+ { 0x001b0c, 0x001b0b,0x001b35 },
+ { 0x001b0e, 0x001b0d,0x001b35 },
+ { 0x001b12, 0x001b11,0x001b35 },
+ { 0x001b3b, 0x001b3a,0x001b35 },
+ { 0x001b3d, 0x001b3c,0x001b35 },
+ { 0x001b40, 0x001b3e,0x001b35 },
+ { 0x001b41, 0x001b3f,0x001b35 },
+ { 0x001b43, 0x001b42,0x001b35 },
+ { 0x001e00, 0x000041,0x000325 },
+ { 0x001e01, 0x000061,0x000325 },
+ { 0x001e02, 0x000042,0x000307 },
+ { 0x001e03, 0x000062,0x000307 },
+ { 0x001e04, 0x000042,0x000323 },
+ { 0x001e05, 0x000062,0x000323 },
+ { 0x001e06, 0x000042,0x000331 },
+ { 0x001e07, 0x000062,0x000331 },
+ { 0x001e08, 0x0000c7,0x000301 },
+ { 0x001e09, 0x0000e7,0x000301 },
+ { 0x001e0a, 0x000044,0x000307 },
+ { 0x001e0b, 0x000064,0x000307 },
+ { 0x001e0c, 0x000044,0x000323 },
+ { 0x001e0d, 0x000064,0x000323 },
+ { 0x001e0e, 0x000044,0x000331 },
+ { 0x001e0f, 0x000064,0x000331 },
+ { 0x001e10, 0x000044,0x000327 },
+ { 0x001e11, 0x000064,0x000327 },
+ { 0x001e12, 0x000044,0x00032d },
+ { 0x001e13, 0x000064,0x00032d },
+ { 0x001e14, 0x000112,0x000300 },
+ { 0x001e15, 0x000113,0x000300 },
+ { 0x001e16, 0x000112,0x000301 },
+ { 0x001e17, 0x000113,0x000301 },
+ { 0x001e18, 0x000045,0x00032d },
+ { 0x001e19, 0x000065,0x00032d },
+ { 0x001e1a, 0x000045,0x000330 },
+ { 0x001e1b, 0x000065,0x000330 },
+ { 0x001e1c, 0x000228,0x000306 },
+ { 0x001e1d, 0x000229,0x000306 },
+ { 0x001e1e, 0x000046,0x000307 },
+ { 0x001e1f, 0x000066,0x000307 },
+ { 0x001e20, 0x000047,0x000304 },
+ { 0x001e21, 0x000067,0x000304 },
+ { 0x001e22, 0x000048,0x000307 },
+ { 0x001e23, 0x000068,0x000307 },
+ { 0x001e24, 0x000048,0x000323 },
+ { 0x001e25, 0x000068,0x000323 },
+ { 0x001e26, 0x000048,0x000308 },
+ { 0x001e27, 0x000068,0x000308 },
+ { 0x001e28, 0x000048,0x000327 },
+ { 0x001e29, 0x000068,0x000327 },
+ { 0x001e2a, 0x000048,0x00032e },
+ { 0x001e2b, 0x000068,0x00032e },
+ { 0x001e2c, 0x000049,0x000330 },
+ { 0x001e2d, 0x000069,0x000330 },
+ { 0x001e2e, 0x0000cf,0x000301 },
+ { 0x001e2f, 0x0000ef,0x000301 },
+ { 0x001e30, 0x00004b,0x000301 },
+ { 0x001e31, 0x00006b,0x000301 },
+ { 0x001e32, 0x00004b,0x000323 },
+ { 0x001e33, 0x00006b,0x000323 },
+ { 0x001e34, 0x00004b,0x000331 },
+ { 0x001e35, 0x00006b,0x000331 },
+ { 0x001e36, 0x00004c,0x000323 },
+ { 0x001e37, 0x00006c,0x000323 },
+ { 0x001e38, 0x001e36,0x000304 },
+ { 0x001e39, 0x001e37,0x000304 },
+ { 0x001e3a, 0x00004c,0x000331 },
+ { 0x001e3b, 0x00006c,0x000331 },
+ { 0x001e3c, 0x00004c,0x00032d },
+ { 0x001e3d, 0x00006c,0x00032d },
+ { 0x001e3e, 0x00004d,0x000301 },
+ { 0x001e3f, 0x00006d,0x000301 },
+ { 0x001e40, 0x00004d,0x000307 },
+ { 0x001e41, 0x00006d,0x000307 },
+ { 0x001e42, 0x00004d,0x000323 },
+ { 0x001e43, 0x00006d,0x000323 },
+ { 0x001e44, 0x00004e,0x000307 },
+ { 0x001e45, 0x00006e,0x000307 },
+ { 0x001e46, 0x00004e,0x000323 },
+ { 0x001e47, 0x00006e,0x000323 },
+ { 0x001e48, 0x00004e,0x000331 },
+ { 0x001e49, 0x00006e,0x000331 },
+ { 0x001e4a, 0x00004e,0x00032d },
+ { 0x001e4b, 0x00006e,0x00032d },
+ { 0x001e4c, 0x0000d5,0x000301 },
+ { 0x001e4d, 0x0000f5,0x000301 },
+ { 0x001e4e, 0x0000d5,0x000308 },
+ { 0x001e4f, 0x0000f5,0x000308 },
+ { 0x001e50, 0x00014c,0x000300 },
+ { 0x001e51, 0x00014d,0x000300 },
+ { 0x001e52, 0x00014c,0x000301 },
+ { 0x001e53, 0x00014d,0x000301 },
+ { 0x001e54, 0x000050,0x000301 },
+ { 0x001e55, 0x000070,0x000301 },
+ { 0x001e56, 0x000050,0x000307 },
+ { 0x001e57, 0x000070,0x000307 },
+ { 0x001e58, 0x000052,0x000307 },
+ { 0x001e59, 0x000072,0x000307 },
+ { 0x001e5a, 0x000052,0x000323 },
+ { 0x001e5b, 0x000072,0x000323 },
+ { 0x001e5c, 0x001e5a,0x000304 },
+ { 0x001e5d, 0x001e5b,0x000304 },
+ { 0x001e5e, 0x000052,0x000331 },
+ { 0x001e5f, 0x000072,0x000331 },
+ { 0x001e60, 0x000053,0x000307 },
+ { 0x001e61, 0x000073,0x000307 },
+ { 0x001e62, 0x000053,0x000323 },
+ { 0x001e63, 0x000073,0x000323 },
+ { 0x001e64, 0x00015a,0x000307 },
+ { 0x001e65, 0x00015b,0x000307 },
+ { 0x001e66, 0x000160,0x000307 },
+ { 0x001e67, 0x000161,0x000307 },
+ { 0x001e68, 0x001e62,0x000307 },
+ { 0x001e69, 0x001e63,0x000307 },
+ { 0x001e6a, 0x000054,0x000307 },
+ { 0x001e6b, 0x000074,0x000307 },
+ { 0x001e6c, 0x000054,0x000323 },
+ { 0x001e6d, 0x000074,0x000323 },
+ { 0x001e6e, 0x000054,0x000331 },
+ { 0x001e6f, 0x000074,0x000331 },
+ { 0x001e70, 0x000054,0x00032d },
+ { 0x001e71, 0x000074,0x00032d },
+ { 0x001e72, 0x000055,0x000324 },
+ { 0x001e73, 0x000075,0x000324 },
+ { 0x001e74, 0x000055,0x000330 },
+ { 0x001e75, 0x000075,0x000330 },
+ { 0x001e76, 0x000055,0x00032d },
+ { 0x001e77, 0x000075,0x00032d },
+ { 0x001e78, 0x000168,0x000301 },
+ { 0x001e79, 0x000169,0x000301 },
+ { 0x001e7a, 0x00016a,0x000308 },
+ { 0x001e7b, 0x00016b,0x000308 },
+ { 0x001e7c, 0x000056,0x000303 },
+ { 0x001e7d, 0x000076,0x000303 },
+ { 0x001e7e, 0x000056,0x000323 },
+ { 0x001e7f, 0x000076,0x000323 },
+ { 0x001e80, 0x000057,0x000300 },
+ { 0x001e81, 0x000077,0x000300 },
+ { 0x001e82, 0x000057,0x000301 },
+ { 0x001e83, 0x000077,0x000301 },
+ { 0x001e84, 0x000057,0x000308 },
+ { 0x001e85, 0x000077,0x000308 },
+ { 0x001e86, 0x000057,0x000307 },
+ { 0x001e87, 0x000077,0x000307 },
+ { 0x001e88, 0x000057,0x000323 },
+ { 0x001e89, 0x000077,0x000323 },
+ { 0x001e8a, 0x000058,0x000307 },
+ { 0x001e8b, 0x000078,0x000307 },
+ { 0x001e8c, 0x000058,0x000308 },
+ { 0x001e8d, 0x000078,0x000308 },
+ { 0x001e8e, 0x000059,0x000307 },
+ { 0x001e8f, 0x000079,0x000307 },
+ { 0x001e90, 0x00005a,0x000302 },
+ { 0x001e91, 0x00007a,0x000302 },
+ { 0x001e92, 0x00005a,0x000323 },
+ { 0x001e93, 0x00007a,0x000323 },
+ { 0x001e94, 0x00005a,0x000331 },
+ { 0x001e95, 0x00007a,0x000331 },
+ { 0x001e96, 0x000068,0x000331 },
+ { 0x001e97, 0x000074,0x000308 },
+ { 0x001e98, 0x000077,0x00030a },
+ { 0x001e99, 0x000079,0x00030a },
+ { 0x001e9b, 0x00017f,0x000307 },
+ { 0x001ea0, 0x000041,0x000323 },
+ { 0x001ea1, 0x000061,0x000323 },
+ { 0x001ea2, 0x000041,0x000309 },
+ { 0x001ea3, 0x000061,0x000309 },
+ { 0x001ea4, 0x0000c2,0x000301 },
+ { 0x001ea5, 0x0000e2,0x000301 },
+ { 0x001ea6, 0x0000c2,0x000300 },
+ { 0x001ea7, 0x0000e2,0x000300 },
+ { 0x001ea8, 0x0000c2,0x000309 },
+ { 0x001ea9, 0x0000e2,0x000309 },
+ { 0x001eaa, 0x0000c2,0x000303 },
+ { 0x001eab, 0x0000e2,0x000303 },
+ { 0x001eac, 0x001ea0,0x000302 },
+ { 0x001ead, 0x001ea1,0x000302 },
+ { 0x001eae, 0x000102,0x000301 },
+ { 0x001eaf, 0x000103,0x000301 },
+ { 0x001eb0, 0x000102,0x000300 },
+ { 0x001eb1, 0x000103,0x000300 },
+ { 0x001eb2, 0x000102,0x000309 },
+ { 0x001eb3, 0x000103,0x000309 },
+ { 0x001eb4, 0x000102,0x000303 },
+ { 0x001eb5, 0x000103,0x000303 },
+ { 0x001eb6, 0x001ea0,0x000306 },
+ { 0x001eb7, 0x001ea1,0x000306 },
+ { 0x001eb8, 0x000045,0x000323 },
+ { 0x001eb9, 0x000065,0x000323 },
+ { 0x001eba, 0x000045,0x000309 },
+ { 0x001ebb, 0x000065,0x000309 },
+ { 0x001ebc, 0x000045,0x000303 },
+ { 0x001ebd, 0x000065,0x000303 },
+ { 0x001ebe, 0x0000ca,0x000301 },
+ { 0x001ebf, 0x0000ea,0x000301 },
+ { 0x001ec0, 0x0000ca,0x000300 },
+ { 0x001ec1, 0x0000ea,0x000300 },
+ { 0x001ec2, 0x0000ca,0x000309 },
+ { 0x001ec3, 0x0000ea,0x000309 },
+ { 0x001ec4, 0x0000ca,0x000303 },
+ { 0x001ec5, 0x0000ea,0x000303 },
+ { 0x001ec6, 0x001eb8,0x000302 },
+ { 0x001ec7, 0x001eb9,0x000302 },
+ { 0x001ec8, 0x000049,0x000309 },
+ { 0x001ec9, 0x000069,0x000309 },
+ { 0x001eca, 0x000049,0x000323 },
+ { 0x001ecb, 0x000069,0x000323 },
+ { 0x001ecc, 0x00004f,0x000323 },
+ { 0x001ecd, 0x00006f,0x000323 },
+ { 0x001ece, 0x00004f,0x000309 },
+ { 0x001ecf, 0x00006f,0x000309 },
+ { 0x001ed0, 0x0000d4,0x000301 },
+ { 0x001ed1, 0x0000f4,0x000301 },
+ { 0x001ed2, 0x0000d4,0x000300 },
+ { 0x001ed3, 0x0000f4,0x000300 },
+ { 0x001ed4, 0x0000d4,0x000309 },
+ { 0x001ed5, 0x0000f4,0x000309 },
+ { 0x001ed6, 0x0000d4,0x000303 },
+ { 0x001ed7, 0x0000f4,0x000303 },
+ { 0x001ed8, 0x001ecc,0x000302 },
+ { 0x001ed9, 0x001ecd,0x000302 },
+ { 0x001eda, 0x0001a0,0x000301 },
+ { 0x001edb, 0x0001a1,0x000301 },
+ { 0x001edc, 0x0001a0,0x000300 },
+ { 0x001edd, 0x0001a1,0x000300 },
+ { 0x001ede, 0x0001a0,0x000309 },
+ { 0x001edf, 0x0001a1,0x000309 },
+ { 0x001ee0, 0x0001a0,0x000303 },
+ { 0x001ee1, 0x0001a1,0x000303 },
+ { 0x001ee2, 0x0001a0,0x000323 },
+ { 0x001ee3, 0x0001a1,0x000323 },
+ { 0x001ee4, 0x000055,0x000323 },
+ { 0x001ee5, 0x000075,0x000323 },
+ { 0x001ee6, 0x000055,0x000309 },
+ { 0x001ee7, 0x000075,0x000309 },
+ { 0x001ee8, 0x0001af,0x000301 },
+ { 0x001ee9, 0x0001b0,0x000301 },
+ { 0x001eea, 0x0001af,0x000300 },
+ { 0x001eeb, 0x0001b0,0x000300 },
+ { 0x001eec, 0x0001af,0x000309 },
+ { 0x001eed, 0x0001b0,0x000309 },
+ { 0x001eee, 0x0001af,0x000303 },
+ { 0x001eef, 0x0001b0,0x000303 },
+ { 0x001ef0, 0x0001af,0x000323 },
+ { 0x001ef1, 0x0001b0,0x000323 },
+ { 0x001ef2, 0x000059,0x000300 },
+ { 0x001ef3, 0x000079,0x000300 },
+ { 0x001ef4, 0x000059,0x000323 },
+ { 0x001ef5, 0x000079,0x000323 },
+ { 0x001ef6, 0x000059,0x000309 },
+ { 0x001ef7, 0x000079,0x000309 },
+ { 0x001ef8, 0x000059,0x000303 },
+ { 0x001ef9, 0x000079,0x000303 },
+ { 0x001f00, 0x0003b1,0x000313 },
+ { 0x001f01, 0x0003b1,0x000314 },
+ { 0x001f02, 0x001f00,0x000300 },
+ { 0x001f03, 0x001f01,0x000300 },
+ { 0x001f04, 0x001f00,0x000301 },
+ { 0x001f05, 0x001f01,0x000301 },
+ { 0x001f06, 0x001f00,0x000342 },
+ { 0x001f07, 0x001f01,0x000342 },
+ { 0x001f08, 0x000391,0x000313 },
+ { 0x001f09, 0x000391,0x000314 },
+ { 0x001f0a, 0x001f08,0x000300 },
+ { 0x001f0b, 0x001f09,0x000300 },
+ { 0x001f0c, 0x001f08,0x000301 },
+ { 0x001f0d, 0x001f09,0x000301 },
+ { 0x001f0e, 0x001f08,0x000342 },
+ { 0x001f0f, 0x001f09,0x000342 },
+ { 0x001f10, 0x0003b5,0x000313 },
+ { 0x001f11, 0x0003b5,0x000314 },
+ { 0x001f12, 0x001f10,0x000300 },
+ { 0x001f13, 0x001f11,0x000300 },
+ { 0x001f14, 0x001f10,0x000301 },
+ { 0x001f15, 0x001f11,0x000301 },
+ { 0x001f18, 0x000395,0x000313 },
+ { 0x001f19, 0x000395,0x000314 },
+ { 0x001f1a, 0x001f18,0x000300 },
+ { 0x001f1b, 0x001f19,0x000300 },
+ { 0x001f1c, 0x001f18,0x000301 },
+ { 0x001f1d, 0x001f19,0x000301 },
+ { 0x001f20, 0x0003b7,0x000313 },
+ { 0x001f21, 0x0003b7,0x000314 },
+ { 0x001f22, 0x001f20,0x000300 },
+ { 0x001f23, 0x001f21,0x000300 },
+ { 0x001f24, 0x001f20,0x000301 },
+ { 0x001f25, 0x001f21,0x000301 },
+ { 0x001f26, 0x001f20,0x000342 },
+ { 0x001f27, 0x001f21,0x000342 },
+ { 0x001f28, 0x000397,0x000313 },
+ { 0x001f29, 0x000397,0x000314 },
+ { 0x001f2a, 0x001f28,0x000300 },
+ { 0x001f2b, 0x001f29,0x000300 },
+ { 0x001f2c, 0x001f28,0x000301 },
+ { 0x001f2d, 0x001f29,0x000301 },
+ { 0x001f2e, 0x001f28,0x000342 },
+ { 0x001f2f, 0x001f29,0x000342 },
+ { 0x001f30, 0x0003b9,0x000313 },
+ { 0x001f31, 0x0003b9,0x000314 },
+ { 0x001f32, 0x001f30,0x000300 },
+ { 0x001f33, 0x001f31,0x000300 },
+ { 0x001f34, 0x001f30,0x000301 },
+ { 0x001f35, 0x001f31,0x000301 },
+ { 0x001f36, 0x001f30,0x000342 },
+ { 0x001f37, 0x001f31,0x000342 },
+ { 0x001f38, 0x000399,0x000313 },
+ { 0x001f39, 0x000399,0x000314 },
+ { 0x001f3a, 0x001f38,0x000300 },
+ { 0x001f3b, 0x001f39,0x000300 },
+ { 0x001f3c, 0x001f38,0x000301 },
+ { 0x001f3d, 0x001f39,0x000301 },
+ { 0x001f3e, 0x001f38,0x000342 },
+ { 0x001f3f, 0x001f39,0x000342 },
+ { 0x001f40, 0x0003bf,0x000313 },
+ { 0x001f41, 0x0003bf,0x000314 },
+ { 0x001f42, 0x001f40,0x000300 },
+ { 0x001f43, 0x001f41,0x000300 },
+ { 0x001f44, 0x001f40,0x000301 },
+ { 0x001f45, 0x001f41,0x000301 },
+ { 0x001f48, 0x00039f,0x000313 },
+ { 0x001f49, 0x00039f,0x000314 },
+ { 0x001f4a, 0x001f48,0x000300 },
+ { 0x001f4b, 0x001f49,0x000300 },
+ { 0x001f4c, 0x001f48,0x000301 },
+ { 0x001f4d, 0x001f49,0x000301 },
+ { 0x001f50, 0x0003c5,0x000313 },
+ { 0x001f51, 0x0003c5,0x000314 },
+ { 0x001f52, 0x001f50,0x000300 },
+ { 0x001f53, 0x001f51,0x000300 },
+ { 0x001f54, 0x001f50,0x000301 },
+ { 0x001f55, 0x001f51,0x000301 },
+ { 0x001f56, 0x001f50,0x000342 },
+ { 0x001f57, 0x001f51,0x000342 },
+ { 0x001f59, 0x0003a5,0x000314 },
+ { 0x001f5b, 0x001f59,0x000300 },
+ { 0x001f5d, 0x001f59,0x000301 },
+ { 0x001f5f, 0x001f59,0x000342 },
+ { 0x001f60, 0x0003c9,0x000313 },
+ { 0x001f61, 0x0003c9,0x000314 },
+ { 0x001f62, 0x001f60,0x000300 },
+ { 0x001f63, 0x001f61,0x000300 },
+ { 0x001f64, 0x001f60,0x000301 },
+ { 0x001f65, 0x001f61,0x000301 },
+ { 0x001f66, 0x001f60,0x000342 },
+ { 0x001f67, 0x001f61,0x000342 },
+ { 0x001f68, 0x0003a9,0x000313 },
+ { 0x001f69, 0x0003a9,0x000314 },
+ { 0x001f6a, 0x001f68,0x000300 },
+ { 0x001f6b, 0x001f69,0x000300 },
+ { 0x001f6c, 0x001f68,0x000301 },
+ { 0x001f6d, 0x001f69,0x000301 },
+ { 0x001f6e, 0x001f68,0x000342 },
+ { 0x001f6f, 0x001f69,0x000342 },
+ { 0x001f70, 0x0003b1,0x000300 },
+ { 0x001f72, 0x0003b5,0x000300 },
+ { 0x001f74, 0x0003b7,0x000300 },
+ { 0x001f76, 0x0003b9,0x000300 },
+ { 0x001f78, 0x0003bf,0x000300 },
+ { 0x001f7a, 0x0003c5,0x000300 },
+ { 0x001f7c, 0x0003c9,0x000300 },
+ { 0x001f80, 0x001f00,0x000345 },
+ { 0x001f81, 0x001f01,0x000345 },
+ { 0x001f82, 0x001f02,0x000345 },
+ { 0x001f83, 0x001f03,0x000345 },
+ { 0x001f84, 0x001f04,0x000345 },
+ { 0x001f85, 0x001f05,0x000345 },
+ { 0x001f86, 0x001f06,0x000345 },
+ { 0x001f87, 0x001f07,0x000345 },
+ { 0x001f88, 0x001f08,0x000345 },
+ { 0x001f89, 0x001f09,0x000345 },
+ { 0x001f8a, 0x001f0a,0x000345 },
+ { 0x001f8b, 0x001f0b,0x000345 },
+ { 0x001f8c, 0x001f0c,0x000345 },
+ { 0x001f8d, 0x001f0d,0x000345 },
+ { 0x001f8e, 0x001f0e,0x000345 },
+ { 0x001f8f, 0x001f0f,0x000345 },
+ { 0x001f90, 0x001f20,0x000345 },
+ { 0x001f91, 0x001f21,0x000345 },
+ { 0x001f92, 0x001f22,0x000345 },
+ { 0x001f93, 0x001f23,0x000345 },
+ { 0x001f94, 0x001f24,0x000345 },
+ { 0x001f95, 0x001f25,0x000345 },
+ { 0x001f96, 0x001f26,0x000345 },
+ { 0x001f97, 0x001f27,0x000345 },
+ { 0x001f98, 0x001f28,0x000345 },
+ { 0x001f99, 0x001f29,0x000345 },
+ { 0x001f9a, 0x001f2a,0x000345 },
+ { 0x001f9b, 0x001f2b,0x000345 },
+ { 0x001f9c, 0x001f2c,0x000345 },
+ { 0x001f9d, 0x001f2d,0x000345 },
+ { 0x001f9e, 0x001f2e,0x000345 },
+ { 0x001f9f, 0x001f2f,0x000345 },
+ { 0x001fa0, 0x001f60,0x000345 },
+ { 0x001fa1, 0x001f61,0x000345 },
+ { 0x001fa2, 0x001f62,0x000345 },
+ { 0x001fa3, 0x001f63,0x000345 },
+ { 0x001fa4, 0x001f64,0x000345 },
+ { 0x001fa5, 0x001f65,0x000345 },
+ { 0x001fa6, 0x001f66,0x000345 },
+ { 0x001fa7, 0x001f67,0x000345 },
+ { 0x001fa8, 0x001f68,0x000345 },
+ { 0x001fa9, 0x001f69,0x000345 },
+ { 0x001faa, 0x001f6a,0x000345 },
+ { 0x001fab, 0x001f6b,0x000345 },
+ { 0x001fac, 0x001f6c,0x000345 },
+ { 0x001fad, 0x001f6d,0x000345 },
+ { 0x001fae, 0x001f6e,0x000345 },
+ { 0x001faf, 0x001f6f,0x000345 },
+ { 0x001fb0, 0x0003b1,0x000306 },
+ { 0x001fb1, 0x0003b1,0x000304 },
+ { 0x001fb2, 0x001f70,0x000345 },
+ { 0x001fb3, 0x0003b1,0x000345 },
+ { 0x001fb4, 0x0003ac,0x000345 },
+ { 0x001fb6, 0x0003b1,0x000342 },
+ { 0x001fb7, 0x001fb6,0x000345 },
+ { 0x001fb8, 0x000391,0x000306 },
+ { 0x001fb9, 0x000391,0x000304 },
+ { 0x001fba, 0x000391,0x000300 },
+ { 0x001fbc, 0x000391,0x000345 },
+ { 0x001fc1, 0x0000a8,0x000342 },
+ { 0x001fc2, 0x001f74,0x000345 },
+ { 0x001fc3, 0x0003b7,0x000345 },
+ { 0x001fc4, 0x0003ae,0x000345 },
+ { 0x001fc6, 0x0003b7,0x000342 },
+ { 0x001fc7, 0x001fc6,0x000345 },
+ { 0x001fc8, 0x000395,0x000300 },
+ { 0x001fca, 0x000397,0x000300 },
+ { 0x001fcc, 0x000397,0x000345 },
+ { 0x001fcd, 0x001fbf,0x000300 },
+ { 0x001fce, 0x001fbf,0x000301 },
+ { 0x001fcf, 0x001fbf,0x000342 },
+ { 0x001fd0, 0x0003b9,0x000306 },
+ { 0x001fd1, 0x0003b9,0x000304 },
+ { 0x001fd2, 0x0003ca,0x000300 },
+ { 0x001fd6, 0x0003b9,0x000342 },
+ { 0x001fd7, 0x0003ca,0x000342 },
+ { 0x001fd8, 0x000399,0x000306 },
+ { 0x001fd9, 0x000399,0x000304 },
+ { 0x001fda, 0x000399,0x000300 },
+ { 0x001fdd, 0x001ffe,0x000300 },
+ { 0x001fde, 0x001ffe,0x000301 },
+ { 0x001fdf, 0x001ffe,0x000342 },
+ { 0x001fe0, 0x0003c5,0x000306 },
+ { 0x001fe1, 0x0003c5,0x000304 },
+ { 0x001fe2, 0x0003cb,0x000300 },
+ { 0x001fe4, 0x0003c1,0x000313 },
+ { 0x001fe5, 0x0003c1,0x000314 },
+ { 0x001fe6, 0x0003c5,0x000342 },
+ { 0x001fe7, 0x0003cb,0x000342 },
+ { 0x001fe8, 0x0003a5,0x000306 },
+ { 0x001fe9, 0x0003a5,0x000304 },
+ { 0x001fea, 0x0003a5,0x000300 },
+ { 0x001fec, 0x0003a1,0x000314 },
+ { 0x001fed, 0x0000a8,0x000300 },
+ { 0x001ff2, 0x001f7c,0x000345 },
+ { 0x001ff3, 0x0003c9,0x000345 },
+ { 0x001ff4, 0x0003ce,0x000345 },
+ { 0x001ff6, 0x0003c9,0x000342 },
+ { 0x001ff7, 0x001ff6,0x000345 },
+ { 0x001ff8, 0x00039f,0x000300 },
+ { 0x001ffa, 0x0003a9,0x000300 },
+ { 0x001ffc, 0x0003a9,0x000345 },
+ { 0x00219a, 0x002190,0x000338 },
+ { 0x00219b, 0x002192,0x000338 },
+ { 0x0021ae, 0x002194,0x000338 },
+ { 0x0021cd, 0x0021d0,0x000338 },
+ { 0x0021ce, 0x0021d4,0x000338 },
+ { 0x0021cf, 0x0021d2,0x000338 },
+ { 0x002204, 0x002203,0x000338 },
+ { 0x002209, 0x002208,0x000338 },
+ { 0x00220c, 0x00220b,0x000338 },
+ { 0x002224, 0x002223,0x000338 },
+ { 0x002226, 0x002225,0x000338 },
+ { 0x002241, 0x00223c,0x000338 },
+ { 0x002244, 0x002243,0x000338 },
+ { 0x002247, 0x002245,0x000338 },
+ { 0x002249, 0x002248,0x000338 },
+ { 0x002260, 0x00003d,0x000338 },
+ { 0x002262, 0x002261,0x000338 },
+ { 0x00226d, 0x00224d,0x000338 },
+ { 0x00226e, 0x00003c,0x000338 },
+ { 0x00226f, 0x00003e,0x000338 },
+ { 0x002270, 0x002264,0x000338 },
+ { 0x002271, 0x002265,0x000338 },
+ { 0x002274, 0x002272,0x000338 },
+ { 0x002275, 0x002273,0x000338 },
+ { 0x002278, 0x002276,0x000338 },
+ { 0x002279, 0x002277,0x000338 },
+ { 0x002280, 0x00227a,0x000338 },
+ { 0x002281, 0x00227b,0x000338 },
+ { 0x002284, 0x002282,0x000338 },
+ { 0x002285, 0x002283,0x000338 },
+ { 0x002288, 0x002286,0x000338 },
+ { 0x002289, 0x002287,0x000338 },
+ { 0x0022ac, 0x0022a2,0x000338 },
+ { 0x0022ad, 0x0022a8,0x000338 },
+ { 0x0022ae, 0x0022a9,0x000338 },
+ { 0x0022af, 0x0022ab,0x000338 },
+ { 0x0022e0, 0x00227c,0x000338 },
+ { 0x0022e1, 0x00227d,0x000338 },
+ { 0x0022e2, 0x002291,0x000338 },
+ { 0x0022e3, 0x002292,0x000338 },
+ { 0x0022ea, 0x0022b2,0x000338 },
+ { 0x0022eb, 0x0022b3,0x000338 },
+ { 0x0022ec, 0x0022b4,0x000338 },
+ { 0x0022ed, 0x0022b5,0x000338 },
+ { 0x002adc, 0x002add,0x000338 },
+ { 0x00304c, 0x00304b,0x003099 },
+ { 0x00304e, 0x00304d,0x003099 },
+ { 0x003050, 0x00304f,0x003099 },
+ { 0x003052, 0x003051,0x003099 },
+ { 0x003054, 0x003053,0x003099 },
+ { 0x003056, 0x003055,0x003099 },
+ { 0x003058, 0x003057,0x003099 },
+ { 0x00305a, 0x003059,0x003099 },
+ { 0x00305c, 0x00305b,0x003099 },
+ { 0x00305e, 0x00305d,0x003099 },
+ { 0x003060, 0x00305f,0x003099 },
+ { 0x003062, 0x003061,0x003099 },
+ { 0x003065, 0x003064,0x003099 },
+ { 0x003067, 0x003066,0x003099 },
+ { 0x003069, 0x003068,0x003099 },
+ { 0x003070, 0x00306f,0x003099 },
+ { 0x003071, 0x00306f,0x00309a },
+ { 0x003073, 0x003072,0x003099 },
+ { 0x003074, 0x003072,0x00309a },
+ { 0x003076, 0x003075,0x003099 },
+ { 0x003077, 0x003075,0x00309a },
+ { 0x003079, 0x003078,0x003099 },
+ { 0x00307a, 0x003078,0x00309a },
+ { 0x00307c, 0x00307b,0x003099 },
+ { 0x00307d, 0x00307b,0x00309a },
+ { 0x003094, 0x003046,0x003099 },
+ { 0x00309e, 0x00309d,0x003099 },
+ { 0x0030ac, 0x0030ab,0x003099 },
+ { 0x0030ae, 0x0030ad,0x003099 },
+ { 0x0030b0, 0x0030af,0x003099 },
+ { 0x0030b2, 0x0030b1,0x003099 },
+ { 0x0030b4, 0x0030b3,0x003099 },
+ { 0x0030b6, 0x0030b5,0x003099 },
+ { 0x0030b8, 0x0030b7,0x003099 },
+ { 0x0030ba, 0x0030b9,0x003099 },
+ { 0x0030bc, 0x0030bb,0x003099 },
+ { 0x0030be, 0x0030bd,0x003099 },
+ { 0x0030c0, 0x0030bf,0x003099 },
+ { 0x0030c2, 0x0030c1,0x003099 },
+ { 0x0030c5, 0x0030c4,0x003099 },
+ { 0x0030c7, 0x0030c6,0x003099 },
+ { 0x0030c9, 0x0030c8,0x003099 },
+ { 0x0030d0, 0x0030cf,0x003099 },
+ { 0x0030d1, 0x0030cf,0x00309a },
+ { 0x0030d3, 0x0030d2,0x003099 },
+ { 0x0030d4, 0x0030d2,0x00309a },
+ { 0x0030d6, 0x0030d5,0x003099 },
+ { 0x0030d7, 0x0030d5,0x00309a },
+ { 0x0030d9, 0x0030d8,0x003099 },
+ { 0x0030da, 0x0030d8,0x00309a },
+ { 0x0030dc, 0x0030db,0x003099 },
+ { 0x0030dd, 0x0030db,0x00309a },
+ { 0x0030f4, 0x0030a6,0x003099 },
+ { 0x0030f7, 0x0030ef,0x003099 },
+ { 0x0030f8, 0x0030f0,0x003099 },
+ { 0x0030f9, 0x0030f1,0x003099 },
+ { 0x0030fa, 0x0030f2,0x003099 },
+ { 0x0030fe, 0x0030fd,0x003099 },
+ { 0x00fb1d, 0x0005d9,0x0005b4 },
+ { 0x00fb1f, 0x0005f2,0x0005b7 },
+ { 0x00fb2a, 0x0005e9,0x0005c1 },
+ { 0x00fb2b, 0x0005e9,0x0005c2 },
+ { 0x00fb2c, 0x00fb49,0x0005c1 },
+ { 0x00fb2d, 0x00fb49,0x0005c2 },
+ { 0x00fb2e, 0x0005d0,0x0005b7 },
+ { 0x00fb2f, 0x0005d0,0x0005b8 },
+ { 0x00fb30, 0x0005d0,0x0005bc },
+ { 0x00fb31, 0x0005d1,0x0005bc },
+ { 0x00fb32, 0x0005d2,0x0005bc },
+ { 0x00fb33, 0x0005d3,0x0005bc },
+ { 0x00fb34, 0x0005d4,0x0005bc },
+ { 0x00fb35, 0x0005d5,0x0005bc },
+ { 0x00fb36, 0x0005d6,0x0005bc },
+ { 0x00fb38, 0x0005d8,0x0005bc },
+ { 0x00fb39, 0x0005d9,0x0005bc },
+ { 0x00fb3a, 0x0005da,0x0005bc },
+ { 0x00fb3b, 0x0005db,0x0005bc },
+ { 0x00fb3c, 0x0005dc,0x0005bc },
+ { 0x00fb3e, 0x0005de,0x0005bc },
+ { 0x00fb40, 0x0005e0,0x0005bc },
+ { 0x00fb41, 0x0005e1,0x0005bc },
+ { 0x00fb43, 0x0005e3,0x0005bc },
+ { 0x00fb44, 0x0005e4,0x0005bc },
+ { 0x00fb46, 0x0005e6,0x0005bc },
+ { 0x00fb47, 0x0005e7,0x0005bc },
+ { 0x00fb48, 0x0005e8,0x0005bc },
+ { 0x00fb49, 0x0005e9,0x0005bc },
+ { 0x00fb4a, 0x0005ea,0x0005bc },
+ { 0x00fb4b, 0x0005d5,0x0005b9 },
+ { 0x00fb4c, 0x0005d1,0x0005bf },
+ { 0x00fb4d, 0x0005db,0x0005bf },
+ { 0x00fb4e, 0x0005e4,0x0005bf },
+ { 0x01d15e, 0x01d157,0x01d165 },
+ { 0x01d15f, 0x01d158,0x01d165 },
+ { 0x01d160, 0x01d15f,0x01d16e },
+ { 0x01d161, 0x01d15f,0x01d16f },
+ { 0x01d162, 0x01d15f,0x01d170 },
+ { 0x01d163, 0x01d15f,0x01d171 },
+ { 0x01d164, 0x01d15f,0x01d172 },
+ { 0x01d1bb, 0x01d1b9,0x01d165 },
+ { 0x01d1bc, 0x01d1ba,0x01d165 },
+ { 0x01d1bd, 0x01d1bb,0x01d16e },
+ { 0x01d1be, 0x01d1bc,0x01d16e },
+ { 0x01d1bf, 0x01d1bb,0x01d16f },
+ { 0x01d1c0, 0x01d1bc,0x01d16f },
+
+ {0,0,0}
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+const dcUnicodeTripel * DecomposeUnicode ( u32 code )
+{
+ int beg = 0;
+ int end = sizeof(TableUnicodeDecomp)/sizeof(*TableUnicodeDecomp) - 2;
+ while ( beg <= end )
+ {
+ const int pos = (beg+end)/2;
+ const dcUnicodeTripel * ptr = TableUnicodeDecomp+pos;
+ if ( code < ptr->code1 )
+ end = pos-1;
+ else if ( code > ptr->code1 )
+ beg = pos+1;
+ else
+ {
+ noTRACE("DecomposeUnicode(%lx) -> %lx,%lx\n",code,ptr->code2,ptr->code3);
+ return ptr;
+ }
+ }
+ noTRACE("DecomposeUnicode(%lx) -> NONE\n",code);
+ return 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-utf8.h b/project/dclib/dclib-utf8.h
new file mode 100644
index 0000000..2e3e79b
--- /dev/null
+++ b/project/dclib/dclib-utf8.h
@@ -0,0 +1,130 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_UTF8_H
+#define DCLIB_UTF8_H 1
+#ifndef WIN_DCLIB
+
+#include "dclib-types.h"
+
+///////////////////////////////////////////////////////////////////////////////
+///// this software is taken from dcLib2 and now publiced under GPL2. /////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum dcUnicodeConsts
+{
+ DCLIB_UNICODE_MAX_UTF8_1 = 0x7f,
+ DCLIB_UNICODE_MAX_UTF8_2 = 0x7ff,
+ DCLIB_UNICODE_MAX_UTF8_3 = 0xffff,
+ DCLIB_UNICODE_MAX_UTF8_4 = 0x1fffff,
+
+ DCLIB_UNICODE_CODE_MASK = 0x1fffff,
+
+} dcUnicodeConsts;
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum dcUTF8Mode
+{
+ DC_UTF8_ILLEGAL = 0x0000, // Illegale UTF8 Zeichen-Kombination
+
+ DC_UTF8_1CHAR = 0x0001, // Das Zeichen ist ein Einzelzeichen
+
+ DC_UTF8_2CHAR = 0x0002, // Beginn einer 2-Zeichen Sequenz
+ DC_UTF8_CONT_22 = 0x0004, // ein Fortsetzungszeichen an Pos 2 einer 2-er Sequenz
+
+ DC_UTF8_3CHAR = 0x0008, // Beginn einer 3-Zeichen Sequenz
+ DC_UTF8_CONT_23 = 0x0010, // ein Fortsetzungszeichen an Pos 2 einer 3-er Sequenz
+ DC_UTF8_CONT_33 = 0x0020, // ein Fortsetzungszeichen an Pos 3 einer 3-er Sequenz
+
+ DC_UTF8_4CHAR = 0x0040, // Beginn einer 4-Zeichen Sequenz
+ DC_UTF8_CONT_24 = 0x0080, // ein Fortsetzungszeichen an Pos 2 einer 4-er Sequenz
+ DC_UTF8_CONT_34 = 0x0100, // ein Fortsetzungszeichen an Pos 3 einer 4-er Sequenz
+ DC_UTF8_CONT_44 = 0x0200, // ein Fortsetzungszeichen an Pos 4 einer 4-er Sequenz
+
+ DC_UTF8_CONT_ANY = 0x0400, // ein Fortsetzungszeichen an beliebger Stelle
+
+ DC_UTF8_1CHAR_POSSIBLE = 0x0800, // als Einzelzeichen darstellbar
+ DC_UTF8_2CHAR_POSSIBLE = 0x1000, // als 2-er Sequenz darstellbar
+ DC_UTF8_3CHAR_POSSIBLE = 0x2000, // als 3-er Sequenz darstellbar
+ DC_UTF8_4CHAR_POSSIBLE = 0x4000, // als 4-er Sequenz darstellbar
+
+} dcUTF8Mode;
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern const unsigned short TableUTF8Mode[0x100];
+static inline dcUTF8Mode CheckUTF8Mode ( unsigned char ch )
+ { return (dcUTF8Mode)TableUTF8Mode[ch]; }
+
+int GetUTF8CharLength ( u32 code );
+char * NextUTF8Char ( ccp str );
+char * NextUTF8CharE ( ccp str, ccp end );
+char * PrevUTF8Char ( ccp str );
+char * PrevUTF8CharB ( ccp str, ccp begin );
+u32 GetUTF8Char ( ccp str );
+u32 ScanUTF8Char ( ccp * str );
+u32 ScanUTF8CharE ( ccp * str, ccp end );
+u32 ScanUTF8CharInc ( ccp * str );
+u32 ScanUTF8CharIncE ( ccp * str, ccp end );
+u32 GetUTF8AnsiChar ( ccp str );
+u32 ScanUTF8AnsiChar ( ccp * str );
+u32 ScanUTF8AnsiCharE ( ccp * str, ccp end );
+int ScanUTF8Length ( ccp str, ccp end );
+int CalcUTF8PrintFW ( ccp str, ccp end, uint wanted_fw );
+
+char * PrintUTF8Char ( char * buf, u32 code );
+char * PrintUTF8CharToCircBuf ( u32 code );
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct dcUnicodeTripel
+{
+ u32 code1;
+ u32 code2;
+ u32 code3;
+
+} dcUnicodeTripel;
+
+extern const dcUnicodeTripel TableUnicodeDecomp[];
+const dcUnicodeTripel * DecomposeUnicode ( u32 code );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // WIN_DCLIB
+#endif // DCLIB_UTF8_H
+
diff --git a/project/dclib/dclib-vector.c b/project/dclib/dclib-vector.c
new file mode 100644
index 0000000..070d719
--- /dev/null
+++ b/project/dclib/dclib-vector.c
@@ -0,0 +1,2582 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <string.h>
+
+#include "dclib-basics.h"
+#include "dclib-numeric.h"
+#include "dclib-debug.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// float3 list support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeF3L
+(
+ float3List_t *f3l, // valid data
+ uint bin_search // >0: enable binary search
+ // and allocate 'bin_search' elements
+)
+{
+ DASSERT(f3l);
+ memset(f3l,0,sizeof(*f3l));
+ if (bin_search)
+ {
+ f3l->size = bin_search;
+ f3l->list = MALLOC( f3l->size * sizeof(*f3l->list) );
+ f3l->sort = MALLOC( f3l->size * sizeof(*f3l->sort) );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetF3L ( float3List_t * f3l )
+{
+ DASSERT(f3l);
+ FREE(f3l->list);
+ FREE(f3l->sort);
+ memset(f3l,0,sizeof(*f3l));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+float3 * AppendF3L ( float3List_t * f3l )
+{
+ DASSERT( f3l );
+ DASSERT( f3l->used <= f3l->size );
+ DASSERT( !f3l->list == !f3l->size );
+
+ if ( f3l->used == f3l->size )
+ {
+ f3l->size = 3*f3l->size/2 + 100;
+ f3l->list = REALLOC( f3l->list, f3l->size * sizeof(*f3l->list) );
+ if (f3l->sort)
+ f3l->sort = REALLOC( f3l->sort, f3l->size * sizeof(*f3l->sort) );
+ }
+
+ DASSERT( f3l->list );
+ DASSERT( f3l->used < f3l->size );
+
+ if (f3l->sort)
+ f3l->sort[f3l->used] = 0;
+
+ float3 * item = f3l->list + f3l->used++;
+ item->x = item->y = item->z = 0.0;
+ return item;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint FindInsertFloatF3L ( float3List_t * f3l, float3 *val, bool fast )
+{
+ DASSERT( f3l );
+ DASSERT( f3l->used <= f3l->size );
+ DASSERT( !f3l->list == !f3l->size );
+ DASSERT( val );
+
+ int beg = 0;
+ if (!fast)
+ {
+ if (f3l->sort)
+ {
+ int end = f3l->used - 1;
+ while ( beg <= end )
+ {
+ uint idx = (beg+end)/2;
+ DASSERT( idx >= 0 && idx < f3l->used );
+ DASSERT_MSG( f3l->sort[idx] < f3l->used,
+ "idx=%d, sort-idx=%d, used=%u, size=%u\n",
+ idx, f3l->sort[idx], f3l->used, f3l->size );
+ int stat = memcmp( val,f3l->list + f3l->sort[idx], sizeof(*val) );
+ if ( stat < 0 )
+ end = idx - 1 ;
+ else if ( stat > 0 )
+ beg = idx + 1;
+ else
+ return f3l->sort[idx];
+ }
+ }
+ else
+ {
+ float3 *ptr = f3l->list;
+ float3 *end = ptr + f3l->used;
+ for ( ; ptr < end; ptr++ )
+ if (!memcmp(val,ptr,sizeof(*val)))
+ return ptr - f3l->list;
+ }
+ }
+ else if (f3l->sort)
+ {
+ // 'sort' not needed
+ BINGO; // don't remove this line!
+ FREE(f3l->sort);
+ f3l->sort = 0;
+ }
+
+ float3 *res = AppendF3L(f3l);
+ DASSERT(res);
+ memcpy(res,val,sizeof(*res));
+
+ const uint idx = f3l->used - 1;
+ DASSERT( idx < f3l->used );
+ if (f3l->sort)
+ {
+ DASSERT( beg >= 0 && beg <= idx );
+ uint *dest = f3l->sort + beg;
+ memmove( dest+1, dest, (idx-beg)*sizeof(*f3l->sort) );
+ *dest = idx;
+
+ #if HAVE_PRINT0
+ if ( f3l->used < 10 )
+ {
+ uint i;
+ for ( i = 0; i < f3l->used; i++ )
+ {
+ float3 *f = f3l->list + f3l->sort[i];
+ PRINT("INS %2u: %2u -> %08x %08x %08x %11.5f %11.5f %11.5f\n",
+ i, f3l->sort[i],
+ be32(&f->x), be32(&f->y), be32(&f->z),
+ f->x, f->y, f->z );
+ }
+ PRINT("---\n");
+ }
+ #endif
+ }
+
+ return idx;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+float3 * GrowF3L ( float3List_t * f3l, uint n )
+{
+ DASSERT( f3l );
+ DASSERT( f3l->used <= f3l->size );
+ DASSERT( !f3l->list == !f3l->size );
+
+ const uint new_used = f3l->used + n;
+ if ( new_used > f3l->size || !f3l->size )
+ {
+ f3l->size = new_used ? new_used : 10;
+ f3l->list = REALLOC( f3l->list, f3l->size * sizeof(*f3l->list)) ;
+ }
+
+ DASSERT( f3l->list );
+ DASSERT( f3l->used + n <= f3l->size );
+
+ float3 * result = f3l->list + f3l->used;
+ f3l->used = new_used;
+ return result;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// double3 list support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeD3L ( double3List_t * d3l )
+{
+ DASSERT(d3l);
+ memset(d3l,0,sizeof(*d3l));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetD3L ( double3List_t * d3l )
+{
+ DASSERT(d3l);
+ FREE(d3l->list);
+ memset(d3l,0,sizeof(*d3l));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+double3 * AppendD3L ( double3List_t * d3l )
+{
+ DASSERT( d3l );
+ DASSERT( d3l->used <= d3l->size );
+ DASSERT( !d3l->list == !d3l->size );
+
+ if ( d3l->used == d3l->size )
+ {
+ d3l->size = 3*d3l->size/2 + 100;
+ d3l->list = REALLOC( d3l->list, d3l->size * sizeof(*d3l->list)) ;
+ }
+
+ DASSERT( d3l->list );
+ DASSERT( d3l->used < d3l->size );
+
+ double3 * item = d3l->list + d3l->used++;
+ item->x = item->y = item->z = 0.0;
+ return item;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint FindInsertFloatD3L ( double3List_t * d3l, double3 *val, bool fast )
+{
+ DASSERT( d3l );
+ DASSERT( d3l->used <= d3l->size );
+ DASSERT( !d3l->list == !d3l->size );
+ DASSERT( val );
+
+ // first: reduce precision
+ double3 temp;
+ temp.x = double2float(val->x);
+ temp.y = double2float(val->y);
+ temp.z = double2float(val->z);
+
+ if (!fast)
+ {
+ double3 *ptr = d3l->list;
+ double3 *end = ptr + d3l->used;
+ for ( ; ptr < end; ptr++ )
+ if (!memcmp(&temp,ptr,sizeof(temp)))
+ return ptr - d3l->list;
+ }
+
+ double3 *res = AppendD3L(d3l);
+ DASSERT(res);
+ memcpy(res,&temp,sizeof(*res));
+ return d3l->used - 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+double3 * GrowD3L ( double3List_t * d3l, uint n )
+{
+ DASSERT( d3l );
+ DASSERT( d3l->used <= d3l->size );
+ DASSERT( !d3l->list == !d3l->size );
+
+ const uint new_used = d3l->used + n;
+ if ( new_used > d3l->size || !d3l->size )
+ {
+ d3l->size = new_used ? new_used : 10;
+ d3l->list = REALLOC( d3l->list, d3l->size * sizeof(*d3l->list)) ;
+ }
+
+ DASSERT( d3l->list );
+ DASSERT( d3l->used + n <= d3l->size );
+
+ double3 * result = d3l->list + d3l->used;
+ d3l->used = new_used;
+ return result;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// check numbers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsNormalF3 ( float *f3 )
+{
+ DASSERT(f3);
+ const int fpclass = fpclassify(*f3++);
+ if ( fpclass == FP_NORMAL || fpclass == FP_ZERO )
+ {
+ const int fpclass = fpclassify(*f3++);
+ if ( fpclass == FP_NORMAL || fpclass == FP_ZERO )
+ {
+ const int fpclass = fpclassify(*f3);
+ return fpclass == FP_NORMAL || fpclass == FP_ZERO;
+ }
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsNormalF3be ( float *f3 )
+{
+ DASSERT(f3);
+ const int fpclass = fpclassify(bef4(f3));
+ if ( fpclass == FP_NORMAL || fpclass == FP_ZERO )
+ {
+ const int fpclass = fpclassify(bef4(++f3));
+ if ( fpclass == FP_NORMAL || fpclass == FP_ZERO )
+ {
+ const int fpclass = fpclassify(bef4(++f3));
+ return fpclass == FP_NORMAL || fpclass == FP_ZERO;
+ }
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsNormalD3 ( double *d3 )
+{
+ DASSERT(d3);
+ const int fpclass = fpclassify(*d3++);
+ if ( fpclass == FP_NORMAL || fpclass == FP_ZERO )
+ {
+ const int fpclass = fpclassify(*d3++);
+ if ( fpclass == FP_NORMAL || fpclass == FP_ZERO )
+ {
+ const int fpclass = fpclassify(*d3);
+ return fpclass == FP_NORMAL || fpclass == FP_ZERO;
+ }
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsEqualD3 ( const double3 *a, const double3 *b,
+ double null_epsilon, double diff_epsilon )
+{
+ DASSERT(a);
+ DASSERT(b);
+ DASSERT( null_epsilon > 0.0 );
+ DASSERT( diff_epsilon > 0.0 );
+
+ double diff;
+
+ diff = fabs( a->x - b->x );
+ if ( diff > null_epsilon )
+ {
+ const double abs_a = fabs(a->x);
+ const double abs_b = fabs(b->x);
+ if ( diff > ( abs_a > abs_b ? abs_a : abs_b ) * diff_epsilon )
+ return false;
+ }
+
+ diff = fabs( a->y - b->y );
+ if ( diff > null_epsilon )
+ {
+ const double abs_a = fabs(a->y);
+ const double abs_b = fabs(b->y);
+ if ( diff > ( abs_a > abs_b ? abs_a : abs_b ) * diff_epsilon )
+ return false;
+ }
+
+ diff = fabs( a->z - b->z );
+ if ( diff > null_epsilon )
+ {
+ const double abs_a = fabs(a->z);
+ const double abs_b = fabs(b->z);
+ if ( diff > ( abs_a > abs_b ? abs_a : abs_b ) * diff_epsilon )
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsSameF ( float a, float b, int bit_diff )
+{
+ float delta = ldexpf( fabsf(a-b), 23-bit_diff );
+ return IsNormalF(delta) && delta <= fabsf(a) && delta <= fabsf(b);
+}
+
+bool IsSameD ( double a, double b, int bit_diff )
+{
+ double delta = ldexp( fabs(a-b), 52-bit_diff );
+ return IsNormalD(delta) && delta <= fabs(a) && delta <= fabs(b);
+}
+
+bool IsSameF3 ( const float *a, const float *b, int bit_diff )
+{
+ return IsSameF(a[0],b[0],bit_diff)
+ && IsSameF(a[1],b[1],bit_diff)
+ && IsSameF(a[2],b[2],bit_diff);
+}
+
+bool IsSameD3 ( const double *a, const double *b, int bit_diff )
+{
+ return IsSameD(a[0],b[0],bit_diff)
+ && IsSameD(a[1],b[1],bit_diff)
+ && IsSameD(a[2],b[2],bit_diff);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// 2D vector functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint PointsInConvexPolygonF
+(
+ // return, how many points are inside the polygon
+
+ float *pts, // points: pointer to a array with '2*n_pts' elements
+ uint n_pts, // Number of (x,y) pairs in 'pts'
+
+ float *pol, // polygon: pointer to a array with '2*n_pol' elements
+ uint n_pol, // number of (x,y) pairs in 'pol'
+
+ bool all, // false: count the numbers of points inside
+ // true: return '1' for all points inside and otherwise '0'
+
+ int *r_dir // If not NULL then return:
+ // -1: polygon is defined counterclockwise
+ // 0: unknown
+ // +1: polygon is defined clockwise
+ // If at least one point is inside, the result is known.
+)
+{
+ if ( !n_pts || n_pol < 3 )
+ return 0;
+ DASSERT(pts);
+ DASSERT(pol);
+
+ int pol_dir = 0;
+ uint count = 0;
+ for(;;)
+ {
+ float *a = pol + 2*(n_pol-1);
+ float *b = pol;
+ int dir = pol_dir;
+ uint i = n_pol;
+ for(;;)
+ {
+ const double dstat = SideOfLine(a[0],a[1],b[0],b[1],pts[0],pts[1]);
+ const int new_dir = dstat > 0.0 ? -1 : dstat < 0;
+ noPRINT("STAT: %2d %2d %2d %4g\n", pol_dir, dir, new_dir, dstat );
+ if (!dir)
+ dir = new_dir;
+ else if ( new_dir && dir != new_dir )
+ {
+ if (!all)
+ break;
+ if (r_dir)
+ *r_dir = pol_dir;
+ return 0;
+ }
+
+ if ( --i > 0 )
+ {
+ a = b;
+ b += 2;
+ continue;
+ }
+
+ if (!pol_dir)
+ pol_dir = dir;
+ count++;
+ break;
+ }
+
+ if ( --n_pts > 0 )
+ {
+ pts += 2;
+ continue;
+ }
+
+ if (r_dir)
+ *r_dir = pol_dir;
+ noPRINT("DIR: %2d\n",pol_dir);
+ return all ? 1 : count;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PointsInConvexPolygonD
+(
+ // return, how many points are inside the polygon
+
+ double *pts, // points: pointer to a array with '2*n_pts' elements
+ uint n_pts, // Number of (x,y) pairs in 'pts'
+
+ double *pol, // polygon: pointer to a array with '2*n_pol' elements
+ uint n_pol, // number of (x,y) pairs in 'pol'
+
+ bool all, // false: count the numbers of points inside
+ // true: return '1' for all points inside and otherwise '0'
+
+ int *r_dir // If not NULL then return:
+ // -1: polygon is defined counterclockwise
+ // 0: unknown
+ // +1: polygon is defined clockwise
+ // If at least one point is inside, the result is known.
+)
+{
+ if ( !n_pts || n_pol < 3 )
+ return 0;
+ DASSERT(pts);
+ DASSERT(pol);
+
+ int pol_dir = 0;
+ uint count = 0;
+ for(;;)
+ {
+ double *a = pol + 2*(n_pol-1);
+ double *b = pol;
+ int dir = pol_dir;
+ uint i = n_pol;
+ for(;;)
+ {
+ const double dstat = SideOfLine(a[0],a[1],b[0],b[1],pts[0],pts[1]);
+ const int new_dir = dstat > 0.0 ? -1 : dstat < 0;
+ noPRINT("STAT: %2d %2d %2d %4g\n", pol_dir, dir, new_dir, dstat );
+ if (!dir)
+ dir = new_dir;
+ else if ( new_dir && dir != new_dir )
+ {
+ if (!all)
+ break;
+ if (r_dir)
+ *r_dir = pol_dir;
+ return 0;
+ }
+
+ if ( --i > 0 )
+ {
+ a = b;
+ b += 2;
+ continue;
+ }
+
+ if (!pol_dir)
+ pol_dir = dir;
+ count++;
+ break;
+ }
+
+ if ( --n_pts > 0 )
+ {
+ pts += 2;
+ continue;
+ }
+
+ if (r_dir)
+ *r_dir = pol_dir;
+ noPRINT("DIR: %2d\n",pol_dir);
+ return all ? 1 : count;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// float34 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearFloat34 ( float34 *f34 )
+{
+ DASSERT(f34);
+ float *d = f34->v;
+ uint n;
+ for ( n = 0; n < 12; n++ )
+ *d++ = 0.0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupFloat34 ( float34 *f34 )
+{
+ DASSERT(f34);
+ float *d = f34->v;
+ uint n;
+ for ( n = 0; n < 12; n++ )
+ *d++ = 0.0;
+ f34->m[0][0] = f34->m[1][1] = f34->m[2][2] = 1.0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupMatrixF34
+(
+ float34 *mat, // data to setup
+ float3 *scale, // NULL or scaling vector
+ float3 *rotate, // NULL or rotation vector in *radiant*
+ float3 *translate // NULL or translation vector
+)
+{
+ // This function use 'scale', 'rotate' and 'translate' to calculate the
+ // transformation matrix. The calculations have been verified against
+ // original MDL files of Nintendo. The rotation is done in right-handed
+ // rotation with Euler angles (in degree) and x-y-z convention:
+ // => http://en.wikipedia.org/wiki/Matrix_rotation#General_rotations
+
+ DASSERT(mat);
+ float *t = mat->v;
+
+
+ //--- setup scale
+
+ float3 myscale;
+ if (scale)
+ {
+ myscale.x = fabs(scale->x) < NULL_EPSILON ? 1.0 : scale->x;
+ myscale.y = fabs(scale->y) < NULL_EPSILON ? 1.0 : scale->y;
+ myscale.z = fabs(scale->z) < NULL_EPSILON ? 1.0 : scale->z;
+ }
+ else
+ myscale.x = myscale.y = myscale.z = 1.0;
+
+
+ //--- calc translation matrix by scale and rotation
+
+ if (rotate)
+ {
+ // be sure, that null values are exact
+ const double sin_x = fabs(rotate->x) < NULL_EPSILON ? 0.0 : sin( rotate->x );
+ const double cos_x = fabs(rotate->x) < NULL_EPSILON ? 1.0 : cos( rotate->x );
+ const double sin_y = fabs(rotate->y) < NULL_EPSILON ? 0.0 : sin( rotate->y );
+ const double cos_y = fabs(rotate->y) < NULL_EPSILON ? 1.0 : cos( rotate->y );
+ const double sin_z = fabs(rotate->z) < NULL_EPSILON ? 0.0 : sin( rotate->z );
+ const double cos_z = fabs(rotate->z) < NULL_EPSILON ? 1.0 : cos( rotate->z );
+
+ t[ 0] = myscale.x * ( cos_y * cos_z );
+ t[ 1] = myscale.y * ( - cos_x * sin_z + sin_x * sin_y * cos_z );
+ t[ 2] = myscale.z * ( sin_x * sin_z + cos_x * sin_y * cos_z );
+
+ t[4+0] = myscale.x * ( cos_y * sin_z );
+ t[4+1] = myscale.y * ( cos_x * cos_z + sin_x * sin_y * sin_z );
+ t[4+2] = myscale.z * ( - sin_x * cos_z + cos_x * sin_y * sin_z );
+
+ t[8+0] = myscale.x * ( - sin_y );
+ t[8+1] = myscale.y * ( sin_x * cos_y );
+ t[8+2] = myscale.z * ( cos_x * cos_y );
+ }
+ else
+ {
+ t[ 0] = myscale.x;
+ t[4+1] = myscale.y;
+ t[8+2] = myscale.z;
+
+ t[ 1] = 0.0;
+ t[ 2] = 0.0;
+ t[4+0] = 0.0;
+ t[4+2] = 0.0;
+ t[8+0] = 0.0;
+ t[8+1] = 0.0;
+ }
+
+
+ //--- assign translation
+
+ if (translate)
+ {
+ t[ 3] = translate->x;
+ t[4+3] = translate->y;
+ t[8+3] = translate->z;
+ }
+ else
+ {
+ t[ 3] = 0.0;
+ t[4+3] = 0.0;
+ t[8+3] = 0.0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CalcInverseMatrixF34
+(
+ float34 *i, // store inverse matrix here
+ const float34 *t // transformation matrix
+)
+{
+ DASSERT(i);
+ DASSERT(t);
+
+ //--- calc inverse matrix
+
+ const double det = t->v[ 0] * t->v[4+1] * t->v[8+2]
+ + t->v[ 1] * t->v[4+2] * t->v[8+0]
+ + t->v[ 2] * t->v[4+0] * t->v[8+1]
+ - t->v[ 2] * t->v[4+1] * t->v[8+0]
+ - t->v[ 1] * t->v[4+0] * t->v[8+2]
+ - t->v[ 0] * t->v[4+2] * t->v[8+1];
+
+ if ( fabs(det) < NULL_EPSILON )
+ {
+ // this should never happen => initialize inv_matrix with 0.0
+ ClearFloat34(i);
+ }
+ else
+ {
+ const double det1 = 1.0 / det; // multplication is faster than division
+
+ i->v[ 0] = ( t->v[4+1] * t->v[8+2] - t->v[4+2] * t->v[8+1] ) * det1;
+ i->v[ 1] = ( t->v[ 2] * t->v[8+1] - t->v[ 1] * t->v[8+2] ) * det1;
+ i->v[ 2] = ( t->v[ 1] * t->v[4+2] - t->v[ 2] * t->v[4+1] ) * det1;
+
+ i->v[4+0] = ( t->v[4+2] * t->v[8+0] - t->v[4+0] * t->v[8+2] ) * det1;
+ i->v[4+1] = ( t->v[ 0] * t->v[8+2] - t->v[ 2] * t->v[8+0] ) * det1;
+ i->v[4+2] = ( t->v[ 2] * t->v[4+0] - t->v[ 0] * t->v[4+2] ) * det1;
+
+ i->v[8+0] = ( t->v[4+0] * t->v[8+1] - t->v[4+1] * t->v[8+0] ) * det1;
+ i->v[8+1] = ( t->v[ 1] * t->v[8+0] - t->v[ 0] * t->v[8+1] ) * det1;
+ i->v[8+2] = ( t->v[ 0] * t->v[4+1] - t->v[ 1] * t->v[4+0] ) * det1;
+ }
+
+ //--- calc inverse translation
+
+ i->v[ 3] = - t->v[ 3] * i->v[ 0]
+ - t->v[4+3] * i->v[ 1]
+ - t->v[8+3] * i->v[ 2];
+
+ i->v[4+3] = - t->v[ 3] * i->v[4+0]
+ - t->v[4+3] * i->v[4+1]
+ - t->v[8+3] * i->v[4+2];
+
+ i->v[8+3] = - t->v[ 3] * i->v[8+0]
+ - t->v[4+3] * i->v[8+1]
+ - t->v[8+3] * i->v[8+2];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MultiplyF34
+(
+ float34 *dest, // valid, store result here, may be 'src1' or 'src2'
+ const float34 *src1, // first valid source, if NULL use dest
+ const float34 *src2 // second valid source, if NULL use dest
+)
+{
+ DASSERT(dest);
+
+ if ( dest == src1 || dest == src2 || !src1 || !src2 )
+ {
+ float34 temp;
+ MultiplyF34( &temp, src1 ? src1 : dest, src2 ? src2 : dest );
+ *dest = temp;
+ return;
+ }
+ DASSERT( src1 && src1 != dest );
+ DASSERT( src2 && src2 != dest );
+
+ float *res = dest->v;
+ const float *a = src1->v;
+ const float *b = src2->v;
+
+ res[0+0] = a[0+0]*b[0+0] + a[4+0]*b[0+1] + a[8+0]*b[0+2];
+ res[0+1] = a[0+1]*b[0+0] + a[4+1]*b[0+1] + a[8+1]*b[0+2];
+ res[0+2] = a[0+2]*b[0+0] + a[4+2]*b[0+1] + a[8+2]*b[0+2];
+ res[0+3] = a[0+3]*b[0+0] + a[4+3]*b[0+1] + a[8+3]*b[0+2] + b[0+3];
+
+ res[4+0] = a[0+0]*b[4+0] + a[4+0]*b[4+1] + a[8+0]*b[4+2];
+ res[4+1] = a[0+1]*b[4+0] + a[4+1]*b[4+1] + a[8+1]*b[4+2];
+ res[4+2] = a[0+2]*b[4+0] + a[4+2]*b[4+1] + a[8+2]*b[4+2];
+ res[4+3] = a[0+3]*b[4+0] + a[4+3]*b[4+1] + a[8+3]*b[4+2] + b[4+3];
+
+ res[8+0] = a[0+0]*b[8+0] + a[4+0]*b[8+1] + a[8+0]*b[8+2];
+ res[8+1] = a[0+1]*b[8+0] + a[4+1]*b[8+1] + a[8+1]*b[8+2];
+ res[8+2] = a[0+2]*b[8+0] + a[4+2]*b[8+1] + a[8+2]*b[8+2];
+ res[8+3] = a[0+3]*b[8+0] + a[4+3]*b[8+1] + a[8+3]*b[8+2] + b[8+3];
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// double34 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearDouble34 ( double34 *d34 )
+{
+ DASSERT(d34);
+ double *d = d34->v;
+ uint n;
+ for ( n = 0; n < 12; n++ )
+ *d++ = 0.0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupDouble34 ( double34 *d34 )
+{
+ DASSERT(d34);
+ double *d = d34->v;
+ uint n;
+ for ( n = 0; n < 12; n++ )
+ *d++ = 0.0;
+ d34->m[0][0] = d34->m[1][1] = d34->m[2][2] = 1.0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupMatrixD34
+(
+ double34 *mat, // data to setup
+ double3 *scale, // NULL or scaling vector
+ double3 *rotate, // NULL or rotation vector in *radiant*
+ double3 *translate // NULL or translation vector
+)
+{
+ // This function use 'scale', 'rotate' and 'translate' to calculate the
+ // transformation matrix. The calculations have been verified against
+ // original MDL files of Nintendo. The rotation is done in right-handed
+ // rotation with Euler angles (in degree) and x-y-z convention:
+ // => http://en.wikipedia.org/wiki/Matrix_rotation#General_rotations
+
+ DASSERT(mat);
+ double *t = mat->v;
+
+
+ //--- setup scale
+
+ double3 myscale;
+ if (scale)
+ {
+ myscale.x = fabs(scale->x) < NULL_EPSILON ? 1.0 : scale->x;
+ myscale.y = fabs(scale->y) < NULL_EPSILON ? 1.0 : scale->y;
+ myscale.z = fabs(scale->z) < NULL_EPSILON ? 1.0 : scale->z;
+ }
+ else
+ myscale.x = myscale.y = myscale.z = 1.0;
+
+
+ //--- calc translation matrix by scale and rotation
+
+ if (rotate)
+ {
+ // be sure, that null values are exact
+ const double sin_x = fabs(rotate->x) < NULL_EPSILON ? 0.0 : sin( rotate->x );
+ const double cos_x = fabs(rotate->x) < NULL_EPSILON ? 1.0 : cos( rotate->x );
+ const double sin_y = fabs(rotate->y) < NULL_EPSILON ? 0.0 : sin( rotate->y );
+ const double cos_y = fabs(rotate->y) < NULL_EPSILON ? 1.0 : cos( rotate->y );
+ const double sin_z = fabs(rotate->z) < NULL_EPSILON ? 0.0 : sin( rotate->z );
+ const double cos_z = fabs(rotate->z) < NULL_EPSILON ? 1.0 : cos( rotate->z );
+
+ t[ 0] = myscale.x * ( cos_y * cos_z );
+ t[ 1] = myscale.y * ( - cos_x * sin_z + sin_x * sin_y * cos_z );
+ t[ 2] = myscale.z * ( sin_x * sin_z + cos_x * sin_y * cos_z );
+
+ t[4+0] = myscale.x * ( cos_y * sin_z );
+ t[4+1] = myscale.y * ( cos_x * cos_z + sin_x * sin_y * sin_z );
+ t[4+2] = myscale.z * ( - sin_x * cos_z + cos_x * sin_y * sin_z );
+
+ t[8+0] = myscale.x * ( - sin_y );
+ t[8+1] = myscale.y * ( sin_x * cos_y );
+ t[8+2] = myscale.z * ( cos_x * cos_y );
+ }
+ else
+ {
+ t[ 0] = myscale.x;
+ t[4+1] = myscale.y;
+ t[8+2] = myscale.z;
+
+ t[ 1] = 0.0;
+ t[ 2] = 0.0;
+ t[4+0] = 0.0;
+ t[4+2] = 0.0;
+ t[8+0] = 0.0;
+ t[8+1] = 0.0;
+ }
+
+
+ //--- assign translation
+
+ if (translate)
+ {
+ t[ 3] = translate->x;
+ t[4+3] = translate->y;
+ t[8+3] = translate->z;
+ }
+ else
+ {
+ t[ 3] = 0.0;
+ t[4+3] = 0.0;
+ t[8+3] = 0.0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CalcInverseMatrixD34
+(
+ double34 *i, // store inverse matrix here
+ const double34 *t // transformation matrix
+)
+{
+ DASSERT(i);
+ DASSERT(t);
+
+ //--- calc inverse matrix
+
+ const double det = t->v[ 0] * t->v[4+1] * t->v[8+2]
+ + t->v[ 1] * t->v[4+2] * t->v[8+0]
+ + t->v[ 2] * t->v[4+0] * t->v[8+1]
+ - t->v[ 2] * t->v[4+1] * t->v[8+0]
+ - t->v[ 1] * t->v[4+0] * t->v[8+2]
+ - t->v[ 0] * t->v[4+2] * t->v[8+1];
+
+ if ( fabs(det) < NULL_EPSILON )
+ {
+ // this should never happen => initialize inv_matrix with 0.0
+ ClearDouble34(i);
+ }
+ else
+ {
+ const double det1 = 1.0 / det; // multplication is faster than division
+
+ i->v[ 0] = ( t->v[4+1] * t->v[8+2] - t->v[4+2] * t->v[8+1] ) * det1;
+ i->v[ 1] = ( t->v[ 2] * t->v[8+1] - t->v[ 1] * t->v[8+2] ) * det1;
+ i->v[ 2] = ( t->v[ 1] * t->v[4+2] - t->v[ 2] * t->v[4+1] ) * det1;
+
+ i->v[4+0] = ( t->v[4+2] * t->v[8+0] - t->v[4+0] * t->v[8+2] ) * det1;
+ i->v[4+1] = ( t->v[ 0] * t->v[8+2] - t->v[ 2] * t->v[8+0] ) * det1;
+ i->v[4+2] = ( t->v[ 2] * t->v[4+0] - t->v[ 0] * t->v[4+2] ) * det1;
+
+ i->v[8+0] = ( t->v[4+0] * t->v[8+1] - t->v[4+1] * t->v[8+0] ) * det1;
+ i->v[8+1] = ( t->v[ 1] * t->v[8+0] - t->v[ 0] * t->v[8+1] ) * det1;
+ i->v[8+2] = ( t->v[ 0] * t->v[4+1] - t->v[ 1] * t->v[4+0] ) * det1;
+ }
+
+ //--- calc inverse translation
+
+ i->v[ 3] = - t->v[ 3] * i->v[ 0]
+ - t->v[4+3] * i->v[ 1]
+ - t->v[8+3] * i->v[ 2];
+
+ i->v[4+3] = - t->v[ 3] * i->v[4+0]
+ - t->v[4+3] * i->v[4+1]
+ - t->v[8+3] * i->v[4+2];
+
+ i->v[8+3] = - t->v[ 3] * i->v[8+0]
+ - t->v[4+3] * i->v[8+1]
+ - t->v[8+3] * i->v[8+2];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void MultiplyD34
+(
+ double34 *dest, // valid, store result here, may be 'src1' or 'src2'
+ const double34 *src1, // first valid source, if NULL use dest
+ const double34 *src2 // second valid source, if NULL use dest
+)
+{
+ DASSERT(dest);
+
+ if ( dest == src1 || dest == src2 || !src1 || !src2 )
+ {
+ double34 temp;
+ MultiplyD34( &temp, src1 ? src1 : dest, src2 ? src2 : dest );
+ *dest = temp;
+ return;
+ }
+ DASSERT( src1 && src1 != dest );
+ DASSERT( src2 && src2 != dest );
+
+ double *res = dest->v;
+ const double *a = src1->v;
+ const double *b = src2->v;
+
+ res[0+0] = a[0+0]*b[0+0] + a[4+0]*b[0+1] + a[8+0]*b[0+2];
+ res[0+1] = a[0+1]*b[0+0] + a[4+1]*b[0+1] + a[8+1]*b[0+2];
+ res[0+2] = a[0+2]*b[0+0] + a[4+2]*b[0+1] + a[8+2]*b[0+2];
+ res[0+3] = a[0+3]*b[0+0] + a[4+3]*b[0+1] + a[8+3]*b[0+2] + b[0+3];
+
+ res[4+0] = a[0+0]*b[4+0] + a[4+0]*b[4+1] + a[8+0]*b[4+2];
+ res[4+1] = a[0+1]*b[4+0] + a[4+1]*b[4+1] + a[8+1]*b[4+2];
+ res[4+2] = a[0+2]*b[4+0] + a[4+2]*b[4+1] + a[8+2]*b[4+2];
+ res[4+3] = a[0+3]*b[4+0] + a[4+3]*b[4+1] + a[8+3]*b[4+2] + b[4+3];
+
+ res[8+0] = a[0+0]*b[8+0] + a[4+0]*b[8+1] + a[8+0]*b[8+2];
+ res[8+1] = a[0+1]*b[8+0] + a[4+1]*b[8+1] + a[8+1]*b[8+2];
+ res[8+2] = a[0+2]*b[8+0] + a[4+2]*b[8+1] + a[8+2]*b[8+2];
+ res[8+3] = a[0+3]*b[8+0] + a[4+3]*b[8+1] + a[8+3]*b[8+2] + b[8+3];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void CopyF34toD34
+(
+ double34 *dest, // destination matrix
+ const float34 *src // source matrix
+)
+{
+ DASSERT(dest);
+ DASSERT(src);
+
+ double *d = dest->v;
+ const float *s = src->v;
+
+ uint n;
+ for ( n = 0; n < 12; n++ )
+ *d++ = *s++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CopyD34toF34
+(
+ float34 *dest, // destination matrix
+ const double34 *src // source matrix
+)
+{
+ DASSERT(dest);
+ DASSERT(src);
+
+ float *d = dest->v;
+ const double *s = src->v;
+
+ uint n;
+ for ( n = 0; n < 12; n++ )
+ *d++ = *s++;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatrixD_t: setup ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// statistics
+u64 N_MatrixD_forward = 0; // total number of forward transformations
+u64 N_MatrixD_inverse = 0; // total number of inverse transformations
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeMatrixD ( MatrixD_t * mat )
+{
+ DASSERT(mat);
+ const uint seqnum = mat->valid ? mat->sequence_number : 0;
+ memset(mat,0,sizeof(*mat));
+ mat->sequence_number = seqnum;
+
+ mat->scale.x = mat->scale.y = mat->scale.z = 1.0;
+ mat->scale_origin.x = mat->scale_origin.y = mat->scale_origin.z = 0.0;
+
+ mat->shift = mat->scale_origin;
+ mat->rotate_deg = mat->scale_origin;
+ mat->rotate_rad = mat->scale_origin;
+ mat->translate = mat->scale_origin;
+
+ double *d = (double*)mat->rotate_origin;
+ uint n;
+ for ( n = 0; n < 9; n++ )
+ *d++ = 0.0;
+
+ mat->valid = true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CopyMatrixD
+(
+ MatrixD_t *dest, // valid destination
+ const
+ MatrixD_t *src // NULL or source
+)
+{
+ DASSERT(dest);
+
+ if ( src && src->valid )
+ {
+ const uint seqnum = dest->sequence_number;
+ *dest = *src;
+ dest->sequence_number = seqnum+1;
+ }
+ else
+ InitializeMatrixD(dest);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * SetScaleMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * scale, // NULL: clear scale, not NULL: set scale
+ double3 * origin // not NULL: Origin for scaling
+)
+{
+ DASSERT(mat);
+ if (!mat->valid)
+ InitializeMatrixD(mat);
+ mat->norm_valid = mat->tmatrix_valid = mat->imatrix_valid = false;
+
+ if (scale)
+ mat->scale = *scale;
+ else
+ {
+ mat->scale.x = mat->scale.y = mat->scale.z = 1.0;
+ origin = 0;
+ }
+
+ if (origin)
+ mat->scale_origin = *origin;
+ else
+ mat->scale_origin.x = mat->scale_origin.y = mat->scale_origin.z = 0.0;
+
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * SetShiftMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * shift // NULL: clear Shift, not NULL: set Shift
+)
+{
+ DASSERT(mat);
+ if (!mat->valid)
+ InitializeMatrixD(mat);
+ mat->norm_valid = mat->tmatrix_valid = mat->imatrix_valid = false;
+
+ if (shift)
+ mat->shift = *shift;
+ else
+ mat->shift.x = mat->shift.y = mat->shift.z = 0.0;
+
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * SetScaleShiftMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ uint xyz, // 0..2: x/y/z coordinate to set
+ double old1, // old value of first point
+ double new1, // new value of first point
+ double old2, // old value of second point
+ double new2 // new value of second point
+)
+{
+ DASSERT(mat);
+ if (!mat->valid)
+ InitializeMatrixD(mat);
+
+ if ( fabs(old1-old2) >= NULL_EPSILON ) // ignore othwerwise
+ {
+ mat->norm_valid = mat->tmatrix_valid = mat->imatrix_valid = false;
+
+ const double scale = ( new2 - new1 ) / ( old2 - old1 );
+ mat->scale.v[xyz] = scale;
+ mat->scale_origin.v[xyz] = 0.0;
+
+ // new = old * scale + shift
+ mat->shift.v[xyz] = new1 - old1 * scale;
+ }
+
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * SetRotateMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ uint xyz, // 0..2: x/y/z coordinate to set
+ double degree, // angle in degree
+ double radiant, // angle in radiant
+ double3 * origin // not NULL: Origin for the rotation
+)
+{
+ DASSERT(mat);
+ DASSERT(xyz<3);
+
+ if (!mat->valid)
+ InitializeMatrixD(mat);
+ mat->norm_valid = mat->tmatrix_valid = mat->imatrix_valid = false;
+
+ mat->rotate_deg.v[xyz] = degree;
+ mat->rotate_rad.v[xyz] = radiant;
+
+ if (origin)
+ mat->rotate_origin[xyz] = *origin;
+ else
+ {
+ origin = mat->rotate_origin + xyz;
+ origin->x = origin->y = origin->z = 0.0;
+ }
+
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * SetTranslateMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * translate // NULL: clear translate, not NULL: set translate
+)
+{
+ DASSERT(mat);
+ if (!mat->valid)
+ InitializeMatrixD(mat);
+ mat->norm_valid = mat->tmatrix_valid = mat->imatrix_valid = false;
+
+ if (translate)
+ mat->translate = *translate;
+ else
+ mat->translate.x = mat->translate.y = mat->translate.z = 0.0;
+
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * CheckStatusMatrixD
+(
+ MatrixD_t * mat // valid data structure
+)
+{
+ DASSERT(mat);
+
+ if (mat->tmatrix_valid)
+ {
+ double *t = mat->trans_matrix.v;
+
+ //--- check rotation (only disabled for all axis)
+
+ if ( mat->rotate_enabled & 1
+ && fabs( t[4+2] ) < NULL_EPSILON
+ && fabs( t[8+1] ) < NULL_EPSILON
+ )
+ {
+ mat->rotate_enabled &= ~1;
+ t[4+2] = 0.0;
+ t[8+1] = 0.0;
+ }
+
+ if ( mat->rotate_enabled & 2
+ && fabs( t[ 2] ) < NULL_EPSILON
+ && fabs( t[8+0] ) < NULL_EPSILON
+ )
+ {
+ mat->rotate_enabled &= ~2;
+ t[ 2] = 0.0;
+ t[8+0] = 0.0;
+ }
+
+ if ( mat->rotate_enabled & 4
+ && fabs( t[ 1] ) < NULL_EPSILON
+ && fabs( t[4+0] ) < NULL_EPSILON
+ )
+ {
+ mat->rotate_enabled &= ~4;
+ t[ 1] = 0.0;
+ t[4+0] = 0.0;
+ }
+
+
+ //--- check scaling (don't touch scale if roating is enabled)
+
+ if ( mat->scale_enabled & 1
+ && !( mat->rotate_enabled & ~1 )
+ && fabs( t[ 0] - 1.0 ) < NULL_EPSILON )
+ {
+ t[ 0] = 1.0;
+ mat->scale_enabled &= ~1;
+ }
+
+ if ( mat->scale_enabled & 2
+ && !( mat->rotate_enabled & ~2 )
+ && fabs( t[4+1] - 1.0 ) < NULL_EPSILON )
+ {
+ t[4+1] = 1.0;
+ mat->scale_enabled &= ~2;
+ }
+
+ if ( mat->scale_enabled & 4
+ && !( mat->rotate_enabled & ~4 )
+ && fabs( t[8+2] - 1.0 ) < NULL_EPSILON )
+ {
+ t[8+2] = 1.0;
+ mat->scale_enabled &= ~4;
+ }
+
+
+ //--- check transformation
+
+ if ( mat->translate_enabled & 1 && fabs( t[ 3] ) < NULL_EPSILON )
+ {
+ t[ 3] = 0.0;
+ mat->translate_enabled &= ~1;
+ }
+
+ if ( mat->translate_enabled & 2 && fabs( t[4+3] ) < NULL_EPSILON )
+ {
+ t[4+3] = 0.0;
+ mat->translate_enabled &= ~2;
+ }
+
+ if ( mat->translate_enabled & 4 && fabs( t[8+3] ) < NULL_EPSILON )
+ {
+ t[8+3] = 0.0;
+ mat->translate_enabled &= ~4;
+ }
+
+ mat->transform_enabled = mat->scale_enabled
+ | mat->rotate_enabled
+ | mat->translate_enabled
+ | mat->use_matrix << 3;
+ }
+
+ return mat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatrixD_t: specials ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void TermAssignMatrixD
+(
+ // called after dirext matrix manipulations
+ MatrixD_t * mat // valid data structure
+)
+{
+ DASSERT(mat);
+
+ mat->valid = true;
+ mat->norm_valid = false;
+ mat->tmatrix_valid = true;
+ mat->imatrix_valid = false;
+ mat->use_matrix = 2;
+ mat->scale_enabled = 7;
+ mat->rotate_enabled = 7;
+ mat->translate_enabled = 7;
+ mat->sequence_number++;
+
+ double34 *m = &mat->trans_matrix;
+ mat->norm_scale.x = m->x[0];
+ mat->norm_scale.y = m->y[1];
+ mat->norm_scale.z = m->z[2];
+ mat->norm_translate.x = m->x[3];
+ mat->norm_translate.y = m->y[3];
+ mat->norm_translate.z = m->z[3];
+
+ CheckStatusMatrixD(mat);
+
+ if (!mat->rotate_enabled)
+ {
+ mat->use_matrix = 0;
+ mat->norm_valid = true;
+ mat->norm_rotate_deg.x = 0;
+ mat->norm_rotate_deg.y = 0;
+ mat->norm_rotate_deg.z = 0;
+ mat->norm_rotate_rad = mat->norm_rotate_deg;
+
+ mat->scale = mat->norm_scale;
+ mat->translate = mat->norm_translate;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * SetMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ double34 * src // NULL or matrix to set
+)
+{
+ DASSERT(mat);
+ InitializeMatrixD(mat);
+ if (src)
+ {
+ mat->trans_matrix = *src;
+ TermAssignMatrixD(mat);
+ }
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * SetAScaleMatrixD
+(
+ // calculate the matrix as rotation around a specified axis
+
+ MatrixD_t * mat, // valid data structure
+ double scale, // scale factor
+ double3 * dir // NULL or scaling direction as vector
+)
+{
+ DASSERT(mat);
+ InitializeMatrixD(mat);
+
+ if (dir)
+ {
+ const double hrot = atan2(dir->x,dir->z);
+ const double hlen = sqrt( dir->x*dir->x + dir->z*dir->z );
+ const double vrot = -atan2(dir->y,hlen);
+
+ PRINT("HROT: %6.4f %7.2f / %11.3f\n",hrot,hrot*180.0/M_PI,hlen);
+ PRINT("VROT: %6.4f %7.2f\n",vrot,vrot*180.0/M_PI);
+
+ MatrixD_t mrot;
+ InitializeMatrixD(&mrot);
+ mrot.rotate_rad.x = vrot;
+ mrot.rotate_rad.y = hrot;
+ CalcMatrixD(&mrot,true);
+ DASSERT(mrot.imatrix_valid);
+ SetMatrixD(mat,&mrot.inv_matrix);
+
+ double3 scale3;
+ scale3.x = scale3.y = 0;
+ scale3.z = scale;
+ SetScaleMatrixD(&mrot,&scale3,0);
+ MultiplyMatrixD(mat,0,&mrot,0);
+ TermAssignMatrixD(mat);
+ }
+
+ //--- terminate
+
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * SetARotateMatrixD
+(
+ // calculate the matrix as rotation around a specified axis
+
+ MatrixD_t * mat, // valid data structure
+ double degree, // angle in degree
+ double radiant, // angle in radiant, add to degree with factor
+ double3 * pt1, // point #1 to define the axis; if NULL use (0,0,0)
+ double3 * pt2 // point #2 to define the axis; if NULL use (0,0,0)
+)
+{
+ DASSERT(mat);
+ InitializeMatrixD(mat);
+
+ double3 dir, base;
+ if (pt1)
+ base = *pt1;
+ else
+ base.x = base.y = base.z = 0.0;
+
+ if (pt2)
+ Sub3(dir,*pt2,base);
+ else
+ {
+ dir.x = -base.x;
+ dir.y = -base.y;
+ dir.z = -base.z;
+ }
+
+ #if 1
+ radiant += degree * (M_PI/180.0);
+ const double len = Length3(dir);
+ if ( len >= NULL_EPSILON )
+ #else
+ radiant = fmod( degree * (M_PI/180.0) + radiant + M_PI, 2*M_PI) - M_PI;
+ const double len = Length3(dir);
+ if ( len >= NULL_EPSILON && fabs(radiant) >= NULL_EPSILON )
+ #endif
+ {
+ dir.x /= len;
+ dir.y /= len;
+ dir.z /= len;
+
+ const double sin0 = sin(radiant);
+ const double cos0 = cos(radiant);
+ const double cos1 = 1.0 - cos0;
+
+ double *t = mat->trans_matrix.v;
+
+ t[ 0] = dir.x * dir.x * cos1 + cos0;
+ t[ 1] = dir.x * dir.y * cos1 - dir.z * sin0;
+ t[ 2] = dir.x * dir.z * cos1 + dir.y * sin0;
+
+ t[4+0] = dir.y * dir.x * cos1 + dir.z * sin0;
+ t[4+1] = dir.y * dir.y * cos1 + cos0;
+ t[4+2] = dir.y * dir.z * cos1 - dir.x * sin0;
+
+ t[8+0] = dir.z * dir.x * cos1 - dir.y * sin0;
+ t[8+1] = dir.z * dir.y * cos1 + dir.x * sin0;
+ t[8+2] = dir.z * dir.z * cos1 + cos0;
+
+ double3 delta = TransformD3D34(&mat->trans_matrix,&base);
+ t[ 3] = base.x - delta.x;
+ t[4+3] = base.y - delta.y;
+ t[8+3] = base.z - delta.z;
+
+ TermAssignMatrixD(mat);
+ }
+
+ //--- terminate
+
+ return mat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatrixD_t: calc ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * CalcNormMatrixD
+(
+ MatrixD_t * mat // valid data structure
+)
+{
+ DASSERT(mat);
+ if (!mat->valid)
+ InitializeMatrixD(mat);
+
+ if ( mat->norm_valid || mat->tmatrix_valid )
+ return mat;
+
+ mat->sequence_number++;
+ mat->norm_valid = true;
+ mat->scale_enabled = mat->rotate_enabled = mat->translate_enabled = 0;
+
+
+ //--- scale & shift
+
+ double3 trans;
+
+ uint xyz;
+ for ( xyz = 0; xyz < 3; xyz++ )
+ {
+ double scale = mat->scale.v[xyz];
+ if ( fabs(scale) < NULL_EPSILON || fabs(scale-1.0) < NULL_EPSILON )
+ mat->scale.v[xyz] = scale = 1.0;
+ else
+ mat->scale_enabled |= 1 << xyz;
+
+ mat->norm_scale.v[xyz] = scale;
+
+ trans.v[xyz] = mat->shift.v[xyz]
+ + ( 1.0 - scale ) * mat->scale_origin.v[xyz];
+ }
+
+
+ //--- rotate
+
+ mat->norm_pos2d.x = mat->norm_pos2d.y = mat->norm_pos2d.z = 0;
+
+ for ( xyz = 0; xyz < 3; xyz++ )
+ {
+ double deg = fmod( mat->rotate_deg.v[xyz]
+ + mat->rotate_rad.v[xyz] * (180.0/M_PI)
+ + 180.0, 360.0 ) - 180.0;
+ if ( fabs(deg) < MIN_DEGREE )
+ {
+ mat->norm_rotate_deg.v[xyz] = 0.0;
+ mat->norm_rotate_rad.v[xyz] = 0.0;
+ }
+ else
+ {
+ mat->rotate_enabled |= 1 << xyz;
+ mat->norm_rotate_deg.v[xyz] = deg;
+ double rad = deg * (M_PI/180.0);
+ mat->norm_rotate_rad.v[xyz] = rad;
+
+ // translate = rot_origin + rotated(translate-rot_origin)
+
+ Sub3(trans,trans,mat->rotate_origin[xyz]);
+
+ const uint a = (xyz+2) % 3;
+ const uint b = (xyz+1) % 3;
+ rad += atan2(trans.v[a],trans.v[b]);
+ const double len = sqrt(trans.v[a]*trans.v[a]+trans.v[b]*trans.v[b]);
+
+ trans.v[a] = sin(rad) * len;
+ trans.v[b] = cos(rad) * len;
+
+ Add3(trans,trans,mat->rotate_origin[xyz]);
+
+ mat->norm_pos2d.v[a] += mat->rotate_origin[xyz].v[a];
+ mat->norm_pos2d.v[b] += mat->rotate_origin[xyz].v[b];
+ }
+ }
+
+ if ( (mat->rotate_enabled|1) == 7 ) mat->norm_pos2d.x /= 2;
+ if ( (mat->rotate_enabled|2) == 7 ) mat->norm_pos2d.y /= 2;
+ if ( (mat->rotate_enabled|4) == 7 ) mat->norm_pos2d.z /= 2;
+
+
+ //--- translate
+
+ mat->translate_enabled = false;
+
+ for ( xyz = 0; xyz < 3; xyz++ )
+ {
+ const double temp = trans.v[xyz] + mat->translate.v[xyz];
+ if ( fabs(temp) < NULL_EPSILON )
+ mat->norm_translate.v[xyz] = 0.0;
+ else
+ {
+ mat->norm_translate.v[xyz] = temp;
+ mat->translate_enabled |= 1 << xyz;
+ }
+ }
+
+
+ //--- status
+
+ if ( mat->use_matrix < 2 )
+ {
+ mat->use_matrix = mat->rotate_enabled ? 1 : 0;
+ mat->tmatrix_valid = mat->imatrix_valid = false;
+ }
+
+ mat->transform_enabled = mat->scale_enabled
+ | mat->rotate_enabled
+ | mat->translate_enabled
+ | mat->use_matrix << 3;
+
+ TRACE("%%%%%%%%%% CalcNormMatrixD(%p) => %u|%u|%u = %02x %%%%%%%%%%\n",
+ mat, mat->scale_enabled, mat->rotate_enabled,
+ mat->translate_enabled, mat->transform_enabled );
+
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * CalcTransMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ bool always // false: calc transformation matrix only
+ // if needed for transformation
+ // true: calc matrix always
+ // if 'tmatrix_valid', the matrix is never calculated
+)
+{
+ DASSERT(mat);
+ CalcNormMatrixD(mat);
+
+ if ( mat->tmatrix_valid || !always && !mat->use_matrix )
+ return mat;
+
+ TRACE("%%%%%%%%%% CalcTransMatrixD(%p,%d) => %u|%u|%u = %02x %%%%%%%%%%\n",
+ mat, always, mat->scale_enabled, mat->rotate_enabled,
+ mat->translate_enabled, mat->transform_enabled );
+ DASSERT(mat->use_matrix<2);
+
+ SetupMatrixD34( &mat->trans_matrix,
+ &mat->norm_scale,
+ &mat->norm_rotate_rad,
+ &mat->norm_translate );
+
+ mat->tmatrix_valid = true;
+ mat->imatrix_valid = false;
+ CheckStatusMatrixD(mat);
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * CalcMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ bool always // false: calc both matrices only
+ // if needed for transformation
+ // true: calc matrices always
+)
+{
+ DASSERT(mat);
+
+ if ( mat->imatrix_valid || !always && !mat->use_matrix )
+ return mat;
+
+ CalcTransMatrixD(mat,true);
+
+ TRACE("%%%%%%%%%% CalcMatrixD (%p,%d) => %u|%u|%u = %02x %%%%%%%%%%\n",
+ mat, always, mat->scale_enabled, mat->rotate_enabled,
+ mat->translate_enabled, mat->transform_enabled );
+
+ CalcInverseMatrixD34(&mat->inv_matrix,&mat->trans_matrix);
+ mat->imatrix_valid = true;
+ return mat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * MultiplyMatrixD
+(
+ MatrixD_t *dest, // valid, store result here, may be 'src1' or 'src2'
+ MatrixD_t *src1, // first valid source, if NULL use dest
+ MatrixD_t *src2, // second valid source, if NULL use dest
+ uint inverse_mode // bit field:
+ // 1: use inverse matrix of src1
+ // 2: use inverse matrix of src2
+)
+{
+ DASSERT(dest);
+
+ if ( dest == src1 || dest == src2 || !src1 || !src2 )
+ {
+ MatrixD_t temp;
+ MultiplyMatrixD( &temp, src1 ? src1 : dest, src2 ? src2 : dest, inverse_mode );
+ *dest = temp;
+ return dest;
+ }
+ DASSERT( src1 && src1 != dest );
+ DASSERT( src2 && src2 != dest );
+
+ CalcMatrixD(src1,true);
+ CalcMatrixD(src2,true);
+ InitializeMatrixD(dest);
+ MultiplyD34( &dest->trans_matrix,
+ inverse_mode & 1 ? &src1->inv_matrix : &src1->trans_matrix,
+ inverse_mode & 2 ? &src2->inv_matrix : &src2->trans_matrix );
+
+
+ //--- setup normals, simply assumption
+
+ dest->norm_scale.x = dest->trans_matrix.m[0][0];
+ dest->norm_scale.y = dest->trans_matrix.m[1][1];
+ dest->norm_scale.z = dest->trans_matrix.m[2][2];
+
+ dest->norm_rotate_deg.x = src1->norm_rotate_deg.x + src2->norm_rotate_deg.x;
+ dest->norm_rotate_deg.y = src1->norm_rotate_deg.y + src2->norm_rotate_deg.y;
+ dest->norm_rotate_deg.z = src1->norm_rotate_deg.z + src2->norm_rotate_deg.z;
+ Mult3f(dest->norm_rotate_rad,dest->norm_rotate_deg,M_PI/180.0);
+
+ dest->norm_translate.x = dest->trans_matrix.m[0][3];
+ dest->norm_translate.y = dest->trans_matrix.m[1][3];
+ dest->norm_translate.z = dest->trans_matrix.m[2][3];
+
+
+ //--- setup base values
+
+ dest->scale = dest->norm_scale;
+ dest->rotate_deg = dest->norm_rotate_deg;
+ dest->translate = dest->norm_translate;
+
+
+ //--- terminate
+
+ TermAssignMatrixD(dest);
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MatrixD_t * CalcVectorsMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ bool always // false: calc vectors only if matrix is valid
+ // true: calc matrix if invalid & vectors always
+)
+{
+ DASSERT(mat);
+ // search: Decomposing a rotation matrix
+
+ if ( !always && !mat->tmatrix_valid )
+ return mat;
+
+ CalcMatrixD(mat,false);
+
+ // [[2do]] ??? calculate scale
+ //mat->norm_scale.x = 1.0;
+ //mat->norm_scale.y = 1.0;
+ //mat->norm_scale.z = 1.0;
+
+ double (*m)[4] = mat->trans_matrix.m;
+
+ // [[2do]] ??? works only for scale == 1 ==>
+ mat->norm_rotate_rad.x = atan2( m[2][1], m[2][2] );
+ mat->norm_rotate_rad.y = atan2( -m[2][0], sqrt( m[2][1]*m[2][1] + m[2][2]*m[2][2] ) );
+ mat->norm_rotate_rad.z = atan2( m[1][0], m[0][0] );
+
+ mat->norm_rotate_deg.x = rad2deg(mat->norm_rotate_rad.x);
+ mat->norm_rotate_deg.y = rad2deg(mat->norm_rotate_rad.y);
+ mat->norm_rotate_deg.z = rad2deg(mat->norm_rotate_rad.z);
+
+ mat->norm_translate.x = m[0][3];
+ mat->norm_translate.y = m[1][3];
+ mat->norm_translate.z = m[2][3];
+
+ return mat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatrixD_t: double transform ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+double3 TransformD3MatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ double3 * val // value to transform
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+
+ N_MatrixD_forward++;
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if (mat->use_matrix)
+ {
+ if (!mat->tmatrix_valid)
+ CalcTransMatrixD(mat,true);
+ return TransformD3D34(&mat->trans_matrix,val);
+ }
+
+ double3 res;
+ res.x = val->x * mat->norm_scale.x + mat->norm_translate.x;
+ res.y = val->y * mat->norm_scale.y + mat->norm_translate.y;
+ res.z = val->z * mat->norm_scale.z + mat->norm_translate.z;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+double3 InvTransformD3MatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ double3 * val // value to transform
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+
+ N_MatrixD_inverse++;
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if (mat->use_matrix)
+ {
+ if (!mat->imatrix_valid)
+ CalcMatrixD(mat,true);
+ return TransformD3D34(&mat->inv_matrix,val);
+ }
+
+ double3 res;
+ res.x = ( val->x - mat->norm_translate.x ) / mat->norm_scale.x;
+ res.y = ( val->y - mat->norm_translate.y ) / mat->norm_scale.y;
+ res.z = ( val->z - mat->norm_translate.z ) / mat->norm_scale.z;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void TransformD3NMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * val, // value array to transform
+ int n, // number of 3D vectors in 'val'
+ uint off // if n>1: offset from one to next 'val' vector
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+ DASSERT( n <= 1 || off >= sizeof(*val) );
+
+ N_MatrixD_forward += n;
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if (mat->use_matrix)
+ {
+ if (!mat->tmatrix_valid)
+ CalcTransMatrixD(mat,true);
+
+ while ( n-- > 0 )
+ {
+ *val = TransformD3D34(&mat->trans_matrix,val);
+ val = (double3*)( (u8*)val + off );
+ }
+ }
+ else if (mat->transform_enabled)
+ {
+ while ( n-- > 0 )
+ {
+ val->x = val->x * mat->norm_scale.x + mat->norm_translate.x;
+ val->y = val->y * mat->norm_scale.y + mat->norm_translate.y;
+ val->z = val->z * mat->norm_scale.z + mat->norm_translate.z;
+ val = (double3*)( (u8*)val + off );
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InvTransformD3NMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ double3 * val, // value array to transform
+ int n, // number of 3D vectors in 'val'
+ uint off // if n>1: offset from one to next 'val' vector
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+ DASSERT( n <= 1 || off >= sizeof(*val) );
+
+ N_MatrixD_inverse += n;
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if (mat->use_matrix)
+ {
+ if (!mat->imatrix_valid)
+ CalcMatrixD(mat,true);
+
+ while ( n-- > 0 )
+ {
+ *val = TransformD3D34(&mat->inv_matrix,val);
+ val = (double3*)( (u8*)val + off );
+ }
+ }
+ else if (mat->transform_enabled)
+ {
+ double3 f; // multiplication is faster than division
+ f.x = 1.0 / mat->norm_scale.x;
+ f.y = 1.0 / mat->norm_scale.y;
+ f.z = 1.0 / mat->norm_scale.z;
+
+ while ( n-- > 0 )
+ {
+ val->x = ( val->x - mat->norm_translate.x ) * f.x;
+ val->y = ( val->y - mat->norm_translate.y ) * f.y;
+ val->z = ( val->z - mat->norm_translate.z ) * f.z;
+ val = (double3*)( (u8*)val + off );
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatrixD_t: float transform ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+float3 TransformF3MatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ float3 * val // value to transform
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+
+ N_MatrixD_forward++;
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if (mat->use_matrix)
+ {
+ if (!mat->tmatrix_valid)
+ CalcTransMatrixD(mat,true);
+ return TransformF3D34(&mat->trans_matrix,val);
+ }
+
+ float3 res;
+ res.x = val->x * mat->norm_scale.x + mat->norm_translate.x;
+ res.y = val->y * mat->norm_scale.y + mat->norm_translate.y;
+ res.z = val->z * mat->norm_scale.z + mat->norm_translate.z;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+float3 InvTransformF3MatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ const
+ float3 * val // value to transform
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+
+ N_MatrixD_inverse++;
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if (mat->use_matrix)
+ {
+ if (!mat->imatrix_valid)
+ CalcMatrixD(mat,true);
+ return TransformF3D34(&mat->inv_matrix,val);
+ }
+
+ float3 res;
+ res.x = ( val->x - mat->norm_translate.x ) / mat->norm_scale.x;
+ res.y = ( val->y - mat->norm_translate.y ) / mat->norm_scale.y;
+ res.z = ( val->z - mat->norm_translate.z ) / mat->norm_scale.z;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void TransformF3NMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ float3 * val, // value array to transform
+ int n, // number of 3D vectors in 'val'
+ uint off // if n>1: offset from one to next 'val' vector
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+ DASSERT_MSG( n <= 1 || off >= sizeof(*val), "n=%u,off=%u\n",n,off );
+
+ N_MatrixD_forward += n;
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if (mat->use_matrix)
+ {
+ if (!mat->tmatrix_valid)
+ CalcTransMatrixD(mat,true);
+
+ while ( n-- > 0 )
+ {
+ *val = TransformF3D34(&mat->trans_matrix,val);
+ val = (float3*)( (u8*)val + off );
+ }
+ }
+ else if (mat->transform_enabled)
+ {
+ while ( n-- > 0 )
+ {
+ val->x = val->x * mat->norm_scale.x + mat->norm_translate.x;
+ val->y = val->y * mat->norm_scale.y + mat->norm_translate.y;
+ val->z = val->z * mat->norm_scale.z + mat->norm_translate.z;
+ val = (float3*)( (u8*)val + off );
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InvTransformF3NMatrixD
+(
+ MatrixD_t * mat, // valid data structure
+ float3 * val, // value array to transform
+ int n, // number of 3D vectors in 'val'
+ uint off // if n>1: offset from one to next 'val' vector
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+ DASSERT( n <= 1 || off >= sizeof(*val) );
+
+ N_MatrixD_inverse += n;
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if (mat->use_matrix)
+ {
+ if (!mat->imatrix_valid)
+ CalcMatrixD(mat,true);
+
+ while ( n-- > 0 )
+ {
+ *val = TransformF3D34(&mat->inv_matrix,val);
+ val = (float3*)( (u8*)val + off );
+ }
+ }
+ else if (mat->transform_enabled)
+ {
+ double3 f; // multiplication is faster than division
+ f.x = 1.0 / mat->norm_scale.x;
+ f.y = 1.0 / mat->norm_scale.y;
+ f.z = 1.0 / mat->norm_scale.z;
+
+ while ( n-- > 0 )
+ {
+ val->x = ( val->x - mat->norm_translate.x ) * f.x;
+ val->y = ( val->y - mat->norm_translate.y ) * f.y;
+ val->z = ( val->z - mat->norm_translate.z ) * f.z;
+ val = (float3*)( (u8*)val + off );
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatrixD_t: direct transform ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+double3 BaseTransformD3MatrixD
+(
+ // transform using the base parameters
+
+ MatrixD_t * mat, // valid data structure
+ const
+ double3 * val // value to transform
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+
+ if (!mat->valid)
+ InitializeMatrixD(mat);
+
+ if ( mat->use_matrix > 1 )
+ return TransformD3MatrixD(mat,val);
+
+ double3 rot = mat->rotate_rad;
+ Mult3f(rot,rot,180.0/M_PI);
+ Add3(rot,rot,mat->rotate_deg);
+
+ return TransformHelperD3MatrixD
+ ( &mat->scale,
+ &mat->scale_origin,
+ &mat->shift,
+ &rot,
+ mat->rotate_origin+0,
+ mat->rotate_origin+1,
+ mat->rotate_origin+2,
+ &mat->translate,
+ val
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+double3 NormTransformD3MatrixD
+(
+ // transform using the norm parameters
+
+ MatrixD_t * mat, // valid data structure
+ const
+ double3 * val // value to transform
+)
+{
+ DASSERT(mat);
+ DASSERT(val);
+
+ if ( !mat->norm_valid && !mat->tmatrix_valid )
+ CalcNormMatrixD(mat);
+
+ if ( mat->use_matrix > 1 )
+ return TransformD3MatrixD(mat,val);
+
+ return TransformHelperD3MatrixD
+ ( &mat->norm_scale,
+ 0,
+ 0,
+ &mat->norm_rotate_deg,
+ 0,
+ 0,
+ 0,
+ &mat->norm_translate,
+ val
+ );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+double3 TransformHelperD3MatrixD
+(
+ // transform using vectors
+
+ double3 *scale, // NULL or scale vector
+ double3 *scale_origin, // NULL or origin for scaling
+ double3 *shift, // NULL or shift vector
+ double3 *rotate_deg, // NULL or rotation in degree
+ double3 *xrot_origin, // NULL or origin for x-rotation
+ double3 *yrot_origin, // NULL or origin for y-rotation
+ double3 *zrot_origin, // NULL or origin for z-rotation
+ double3 *translate, // NULL or translation vector
+
+ const
+ double3 * val // value to transform
+)
+{
+ DASSERT(val);
+
+ double3 res; //, null3 = { 0.0, 0.0, 0.0 };
+
+
+ //--- scale
+
+ if (!scale)
+ res = *val;
+ else if (scale_origin)
+ {
+ res.x = ( val->x - scale_origin->x ) * scale->x + scale_origin->x;
+ res.y = ( val->y - scale_origin->y ) * scale->y + scale_origin->y;
+ res.z = ( val->z - scale_origin->z ) * scale->z + scale_origin->z;
+ }
+ else
+ {
+ res.x = val->x * scale->x;
+ res.y = val->y * scale->y;
+ res.z = val->z * scale->z;
+ }
+
+
+ //--- shift
+
+ if (shift)
+ {
+ res.x += shift->x;
+ res.y += shift->y;
+ res.z += shift->z;
+ }
+
+
+ //--- rotate
+
+ if (rotate_deg)
+ {
+ uint xyz;
+ for ( xyz = 0; xyz < 3; xyz++ )
+ {
+ const double deg = fmod(rotate_deg->v[xyz]+180.0,360.0) - 180.0;
+ if ( fabs(deg) >= MIN_DEGREE )
+ {
+ uint a = xyz + 2; if ( a >= 3 ) a -= 3;
+ uint b = xyz + 1; if ( b >= 3 ) b -= 3;
+ double rad = deg * (M_PI/180.0);
+
+ if (xrot_origin)
+ {
+ const double da = res.v[a] - xrot_origin->v[a];
+ const double db = res.v[b] - xrot_origin->v[b];
+ rad += atan2(da,db);
+ const double len = sqrt(da*da+db*db);
+
+ res.v[a] = sin(rad) * len + xrot_origin->v[a];
+ res.v[b] = cos(rad) * len + xrot_origin->v[b];
+ }
+ else
+ {
+ rad += atan2(res.v[a],res.v[b]);
+ const double len = sqrt(res.v[a]*res.v[a]+res.v[b]*res.v[b]);
+
+ res.v[a] = sin(rad) * len;
+ res.v[b] = cos(rad) * len;
+ }
+ }
+ xrot_origin = yrot_origin;
+ yrot_origin = zrot_origin;
+ }
+ }
+
+
+ //--- translate
+
+ if (translate)
+ {
+ res.x += translate->x;
+ res.y += translate->y;
+ res.z += translate->z;
+ }
+
+ return res;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MatrixD_t: printing ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static ccp print_matrix_stat
+ ( MatrixD_t * mat, char * buf, uint bufsize, bool for_inverse )
+{
+ DASSERT(buf);
+ DASSERT(bufsize>10);
+
+ char *dest = buf, *bufend = buf + bufsize;
+ if (mat->scale_enabled)
+ dest = StringCopyE(dest,bufend,"+scale");
+ if (mat->rotate_enabled)
+ dest = StringCopyE(dest,bufend,"+rotate");
+ if (mat->translate_enabled)
+ dest = StringCopyE(dest,bufend,"+translate");
+
+ if ( mat->use_matrix > 1 && !for_inverse )
+ dest = StringCopyE(dest,bufend,",source");
+ else if (mat->use_matrix)
+ dest = StringCopyE(dest,bufend,",required");
+
+ if ( dest == buf )
+ StringCopyE(dest,bufend,"+no transformation");
+
+ return buf+1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintMatrixD
+(
+ FILE * f, // file to print to
+ uint indent, // indention
+ ccp eol, // not NULL: print as 'end of line'
+
+ MatrixD_t * mat, // valid data structure
+ uint create, // bitfield for setup:
+ // 1: create normals if invalid
+ // 2: create transform matrix if invalid
+ // 4: create inverse matrix if invalid
+ uint print // bitfield print selection:
+ // 1: print base vectors
+ // 2: print normalized values, if valid
+ // 4: print transformation matrix, if valid
+ // 8: print inverse matrix, if valid
+ // 0x10: print also header, if only 1 section is print
+ // 0x20: print all, don't suppress NULL vectors
+ // 0x40: print an additional EOL behind each section
+)
+{
+ DASSERT(f);
+ DASSERT(mat);
+
+ //--- setup
+
+ indent = NormalizeIndent(indent);
+ if (!eol)
+ eol = "\n";
+ ccp sep = print & 0x40 ? eol : "";
+
+ if ( create & 4 )
+ CalcMatrixD(mat,true);
+ else if ( create & 2 )
+ CalcTransMatrixD(mat,true);
+ else if ( create & 1 )
+ CalcNormMatrixD(mat);
+ else if (!mat->valid)
+ InitializeMatrixD(mat);
+
+ if (!mat->norm_valid)
+ print &= ~2;
+ if (!mat->tmatrix_valid)
+ print &= ~4;
+ else if (mat->use_matrix>1)
+ print &= ~1;
+ if (!mat->imatrix_valid)
+ print &= ~8;
+
+ const uint print_all = print & 0x20;
+ uint print_head = print & 0x10;
+ print &= 0x0f;
+ if ( print != 1 && print != 2 && print != 4 && print != 8 )
+ print_head = 1;
+
+
+ //--- print base vectors
+
+ if ( print & 1 )
+ {
+ if (print_head)
+ fprintf(f,"%*sBase Vectors:%s", indent,"", eol );
+
+ if ( print_all || !IsOne3(mat->scale) )
+ fprintf(f,"%*s Scale: %11.3f %11.3f %11.3f%s",
+ indent,"",
+ mat->scale.x, mat->scale.y, mat->scale.z,
+ eol );
+
+ if ( print_all || !IsNull3(mat->scale_origin) )
+ fprintf(f,"%*s Origin: %11.3f %11.3f %11.3f%s",
+ indent,"",
+ mat->scale_origin.x, mat->scale_origin.y, mat->scale_origin.z,
+ eol );
+
+ if ( print_all || !IsNull3(mat->shift) )
+ fprintf(f,"%*s Shift: %11.3f %11.3f %11.3f%s",
+ indent,"",
+ mat->shift.x, mat->shift.y, mat->shift.z,
+ eol );
+
+ bool print_rot = false;
+ if ( print_all || !IsNull3(mat->rotate_deg) )
+ {
+ print_rot = true;
+ fprintf(f,"%*s Rotate/deg: %11.3f %11.3f %11.3f%s",
+ indent,"",
+ mat->rotate_deg.x, mat->rotate_deg.y, mat->rotate_deg.z,
+ eol );
+ }
+
+ if ( print_all || !IsNull3(mat->rotate_rad) )
+ {
+ print_rot = true;
+ fprintf(f,"%*s Rotate/rad: %11.3f %11.3f %11.3f%s",
+ indent,"",
+ mat->rotate_rad.x, mat->rotate_rad.y, mat->rotate_rad.z,
+ eol );
+ }
+
+ if (print_rot)
+ {
+ double3 *d = mat->rotate_origin;
+ uint xyz;
+ for ( xyz = 0; xyz < 3; xyz++, d++ )
+ if ( print_all || !IsNull3(*d) )
+ fprintf(f,"%*s %c-origin: %11.3f %11.3f %11.3f%s",
+ indent,"", 'x'+xyz,
+ d->x, d->y, d->z,
+ eol );
+ }
+
+ if ( print_all || !IsNull3(mat->translate) )
+ fprintf(f,"%*s Translate: %11.3f %11.3f %11.3f%s",
+ indent,"",
+ mat->translate.x, mat->translate.y, mat->translate.z,
+ eol );
+
+ fputs(sep,f);
+ }
+
+
+ //--- print normalized vectors
+
+ if ( print & 2 )
+ {
+ if (print_head)
+ fprintf(f,"%*sNormalized Vectors (seq=%u):%s",
+ indent,"", mat->sequence_number, eol );
+
+ fprintf(f,
+ "%*s Scale: %11.3f %11.3f %11.3f [%c%c%c]%s"
+ "%*s Rotate/deg: %11.3f %11.3f %11.3f [%c%c%c]%s"
+ #if TEST0
+ "%*s Rotate/rad: %11.3f %11.3f %11.3f%s"
+ #endif
+ "%*s Translate: %11.3f %11.3f %11.3f [%c%c%c]%s",
+
+ indent,"",
+ mat->norm_scale.x, mat->norm_scale.y, mat->norm_scale.z,
+ mat->scale_enabled & 1 ? 'x' : '-',
+ mat->scale_enabled & 2 ? 'y' : '-',
+ mat->scale_enabled & 4 ? 'z' : '-',
+ eol,
+
+ indent,"",
+ mat->norm_rotate_deg.x, mat->norm_rotate_deg.y, mat->norm_rotate_deg.z,
+ mat->rotate_enabled & 1 ? 'x' : '-',
+ mat->rotate_enabled & 2 ? 'y' : '-',
+ mat->rotate_enabled & 4 ? 'z' : '-',
+ eol,
+ #if TEST0
+ indent,"",
+ mat->norm_rotate_rad.x, mat->norm_rotate_rad.y, mat->norm_rotate_rad.z,
+ eol,
+ #endif
+ indent,"",
+ mat->norm_translate.x, mat->norm_translate.y, mat->norm_translate.z,
+ mat->translate_enabled & 1 ? 'x' : '-',
+ mat->translate_enabled & 2 ? 'y' : '-',
+ mat->translate_enabled & 4 ? 'z' : '-',
+ eol );
+
+ if ( print_all || !IsNull3(mat->norm_pos2d) )
+ fprintf(f,"%*s 2D tf-base: %11.3f %11.3f %11.3f (for 2D transform)%s",
+ indent,"",
+ mat->norm_pos2d.x, mat->norm_pos2d.y, mat->norm_pos2d.z,
+ eol );
+
+ fputs(sep,f);
+ }
+
+
+ //--- print transformation matrix
+
+ char buf[50];
+ if ( print & 4 )
+ {
+ if (print_head)
+ fprintf(f,"%*sTransformation Matrix (%s):%s",
+ indent,"", print_matrix_stat(mat,buf,sizeof(buf),false), eol );
+
+ fprintf(f,
+ "%*s x' = %11.3f * x + %11.3f * y + %11.3f * z + %11.3f%s"
+ "%*s y' = %11.3f * x + %11.3f * y + %11.3f * z + %11.3f%s"
+ "%*s z' = %11.3f * x + %11.3f * y + %11.3f * z + %11.3f%s"
+ "%s",
+
+ indent,"",
+ mat->trans_matrix.x[0],
+ mat->trans_matrix.x[1],
+ mat->trans_matrix.x[2],
+ mat->trans_matrix.x[3],
+ eol,
+
+ indent,"",
+ mat->trans_matrix.y[0],
+ mat->trans_matrix.y[1],
+ mat->trans_matrix.y[2],
+ mat->trans_matrix.y[3],
+ eol,
+
+ indent,"",
+ mat->trans_matrix.z[0],
+ mat->trans_matrix.z[1],
+ mat->trans_matrix.z[2],
+ mat->trans_matrix.z[3],
+ eol, sep );
+ }
+
+
+ //--- print inverse matrix
+
+ if ( print & 8 )
+ {
+ if (print_head)
+ fprintf(f,"%*sInverse Matrix (%s):%s",
+ indent,"", print_matrix_stat(mat,buf,sizeof(buf),true), eol );
+
+ fprintf(f,
+ "%*s x' = %11.3f * x + %11.3f * y + %11.3f * z + %11.3f%s"
+ "%*s y' = %11.3f * x + %11.3f * y + %11.3f * z + %11.3f%s"
+ "%*s z' = %11.3f * x + %11.3f * y + %11.3f * z + %11.3f%s"
+ "%s",
+
+ indent,"",
+ mat->inv_matrix.x[0],
+ mat->inv_matrix.x[1],
+ mat->inv_matrix.x[2],
+ mat->inv_matrix.x[3],
+ eol,
+
+ indent,"",
+ mat->inv_matrix.y[0],
+ mat->inv_matrix.y[1],
+ mat->inv_matrix.y[2],
+ mat->inv_matrix.y[3],
+ eol,
+
+ indent,"",
+ mat->inv_matrix.z[0],
+ mat->inv_matrix.z[1],
+ mat->inv_matrix.z[2],
+ mat->inv_matrix.z[3],
+ eol, sep );
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/dclib-xdump.c b/project/dclib/dclib-xdump.c
new file mode 100644
index 0000000..c404966
--- /dev/null
+++ b/project/dclib/dclib-xdump.c
@@ -0,0 +1,2214 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <stdio.h>
+#include <stddef.h>
+#include "dclib-xdump.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// data ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const ccp XDumpCommandName[XDUMPC__N] =
+{
+ "DUMP",
+ "DIFF",
+ "SCAN",
+};
+
+//-----------------------------------------------------------------------------
+
+const ccp XDumpFormatName[XDUMPF__N] =
+{
+ "AUTO",
+ "INT_1",
+ "INT_2",
+ "INT_3",
+ "INT_4",
+ "INT_5",
+ "INT_6",
+ "INT_7",
+ "INT_8",
+ "FLOAT",
+ "DOUBLE",
+};
+
+const char XDumpFormatOption[XDUMPF__N+1] = "A12345678FD";
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// output helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static bool PrintFormat
+(
+ struct XDump_t *xd, // internal copy of params
+ int force // >0: ignore 'format_printed'
+ // >1: print always
+)
+{
+ DASSERT(xd);
+ if ( !xd->f || !xd->format_name )
+ return false;
+
+ if ( force > 1
+ || ( force > 0 && xd->print_format )
+ || ( xd->print_format && !xd->format_printed )
+ )
+ {
+ xd->format_printed = true;
+ fprintf(xd->f,"%s%*s%sFORMAT: %s",
+ xd->prefix, xd->indent,"",
+ xd->mode_c ? "//" : "#",
+ xd->format_name );
+
+ if ( xd->format > XDUMPF_INT_1 )
+ fprintf(xd->f," %s%s",
+ xd->endian == DC_BIG_ENDIAN ? "BE" : "LE",
+ xd->eol );
+ else
+ fputs(xd->eol,xd->f);
+
+ return true;
+ }
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDumpStandardOutput
+(
+ struct XDump_t *xd, // internal copy of params
+ ccp prefix, // NULL or additional prefix (used by diff)
+ ccp dump, // dump to print
+ ccp text // NULL or text to print
+)
+{
+ DASSERT(xd);
+ DASSERT(dump);
+
+ if (xd->f)
+ {
+ fprintf(xd->f,"%s%*s%s",
+ xd->prefix, xd->indent,"", prefix ? prefix : "" );
+
+ if (xd->print_addr)
+ {
+ if (xd->mode_c)
+ fprintf(xd->f,"/*%*llx*/ ",xd->addr_fw,xd->addr);
+
+ else
+ fprintf(xd->f,"%*llx:%s",
+ xd->addr_fw, xd->addr,
+ xd->extra_space >= 0 ? " " : "" );
+ }
+
+ if (xd->print_number)
+ {
+ if (xd->min_number_fw)
+ fprintf(xd->f,"%-*s",xd->min_number_fw,dump);
+ else
+ fputs(dump,xd->f);
+ }
+
+ if ( text && xd->print_text )
+ {
+ if (xd->mode_c)
+ fprintf(xd->f," // %s",text);
+ else
+ fprintf(xd->f,"%s :%s:",
+ xd->extra_space >= 0 ? " " : "", text );
+ }
+
+ fputs(xd->eol,xd->f);
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void print_text ( char *buf, const u8 *data, int n )
+{
+ while ( n-- > 0 )
+ {
+ const u8 ch = *data++;
+ *buf++ = ch >= ' ' && ch < 0x7f ? ch : '.';
+ }
+ *buf = 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// XParam_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[XParam_t]]
+
+typedef struct XParam_t
+{
+ const u8 *d; // pointer to input data
+ const u8 *end; // end of input data
+
+ uint size; // size of data to dump
+ bool term; // true: data complete
+ bool recalc_align; // true: recalculate alignment
+ bool next_line; // true: continue at next line
+ const u64 *addr_break; // NULL or pointer to next break
+
+ uint skip; // number of columns to skip
+ uint line_size; // current line size (source bytes)
+ uint max_col; // max number of columns to print
+ int extra_space; // extra space counter
+
+ // buffers
+ char text_buf[XDUMP_GOOD_TEXT_BUF_SIZE];
+ char num_buf[XDUMP_GOOD_NUM_BUF_SIZE];
+}
+XParam_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void SetupXParam
+ ( XParam_t *p, XDump_t *xd, cvp data, uint size, bool term )
+{
+ DASSERT(p);
+ DASSERT(xd);
+ DASSERT(data);
+
+ if ( size > INT_MAX )
+ {
+ size = INT_MAX; // limit because of return value
+ term = false;
+ }
+
+ memset( p, 0, offsetof(XParam_t,num_buf) + 15 & ~7 );
+ p->size = size;
+ p->term = term;
+ p->d = data;
+ p->end = p->d + size;
+ p->recalc_align = xd->mode_align;
+ p->addr_break = xd->addr_break;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static uint SetupXParamLine ( XParam_t *p, XDump_t *xd )
+{
+ DASSERT(p);
+ DASSERT(xd);
+
+ if ( p->recalc_align )
+ {
+ p->recalc_align = false;
+ p->skip = ( xd->addr % xd->bytes_per_line ) / xd->bytes_per_col;
+ const int delta = p->skip * xd->bytes_per_col;
+ xd->addr -= delta;
+ if (xd->print_number)
+ p->size += delta;
+ }
+
+ p->next_line = false;
+ p->line_size = xd->bytes_per_line < p->size
+ ? xd->bytes_per_line
+ : p->term ? p->size : 0;
+
+ if ( p->addr_break )
+ {
+ while ( *p->addr_break && xd->addr >= *p->addr_break )
+ p->addr_break++;
+
+ if (!*p->addr_break)
+ p->addr_break = 0;
+ else
+ {
+ const s64 max = *p->addr_break - xd->addr;
+ if ( p->line_size > max )
+ p->line_size = max;
+ }
+ }
+
+ p->max_col = p->line_size / xd->bytes_per_col;
+ p->line_size = p->max_col * xd->bytes_per_col;
+ p->extra_space = xd->col_space ? xd->col_space+1 : 0;
+
+ if (xd->print_text)
+ {
+ if (p->skip)
+ {
+ const uint skip_size = p->skip * xd->bytes_per_col;
+ if ( skip_size < p->line_size )
+ {
+ memset(p->text_buf,' ',skip_size);
+ print_text(p->text_buf+skip_size,p->d,p->line_size-skip_size);
+ }
+ else
+ *p->text_buf = 0; // should never happen!
+ }
+ else
+ print_text(p->text_buf,p->d,p->line_size);
+ }
+
+ return p->max_col;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool PrintNullSummary ( XDump_t *xd )
+{
+ if ( !xd->null_lines || !xd->out_func )
+ return false;
+
+ char buf[100];
+ const uint skipped = xd->null_lines * xd->bytes_per_line;
+ snprintf(buf,sizeof(buf),"%s#NULL: 0x%x bytes",
+ xd->extra_space >= 0 ? " " : "", skipped );
+
+ xd->addr -= skipped;
+ xd->out_func(xd,0,buf,0);
+ xd->addr += skipped;
+ xd->null_lines = 0;
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool CheckXParamNull ( XParam_t *p, XDump_t *xd )
+{
+ DASSERT(p);
+ DASSERT(xd);
+
+ static u8 nullbuf[XDUMP_MAX_BYTES_PER_LINE] = {};
+
+
+ const bool skip = p->line_size == xd->bytes_per_line
+ && !memcmp(p->d,nullbuf,xd->bytes_per_line);
+ if (skip)
+ {
+ xd->null_lines++;
+ xd->addr += p->line_size;
+ p->d += p->line_size;
+ p->size -= p->line_size;
+
+ return true;
+ }
+
+ if ( xd->null_lines && ( !skip || p->size <= xd->bytes_per_line ))
+ PrintNullSummary(xd);
+
+ return skip;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void AbortXParamLine ( XParam_t *p, XDump_t *xd, uint col )
+{
+ const uint col1 = col + 1;
+ if ( col1 < p->max_col )
+ {
+ p->max_col = col1;
+ p->line_size = col1 * xd->bytes_per_col;
+ p->text_buf[p->line_size] = 0;
+ p->recalc_align = xd->mode_align;
+ p->next_line = true;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// output functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static int DumpInt32
+(
+ // returns <0 on error, or number of dumped bytes
+
+ XDump_t *xd, // current params
+ const void *p_data, // data to dump
+ uint p_size, // size of 'p_data'
+ bool p_term // false: print only whole lines
+ // true: print complete data
+)
+{
+ DASSERT(xd);
+ if ( !p_data || !xd->out_func )
+ return 0;
+
+ XParam_t p;
+ SetupXParam(&p,xd,p_data,p_size,p_term);
+
+ while(SetupXParamLine(&p,xd))
+ {
+ if ( xd->mode_ignore && CheckXParamNull(&p,xd) )
+ continue;
+
+ if (xd->print_number)
+ {
+ char *num = p.num_buf;
+ uint col;
+ for ( col = 0; col < xd->cols_per_line; col++ )
+ {
+ if (!--p.extra_space)
+ {
+ p.extra_space = xd->col_space;
+ *num++ = ' ';
+ }
+
+ if ( p.skip > 0 )
+ {
+ p.skip--;
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ else if ( col < p.max_col )
+ {
+ u32 val;
+ switch(xd->bytes_per_col)
+ {
+ case 2: val = xd->endian_func->rd16(p.d); p.d += 2; break;
+ case 3: val = xd->endian_func->rd24(p.d); p.d += 3; break;
+ case 4: val = xd->endian_func->rd32(p.d); p.d += 4; break;
+ default: val = *p.d++; break;
+ }
+ num += sprintf(num,xd->num_format1,val);
+ if ( xd->have_trigger && val == (u32)xd->trigger )
+ AbortXParamLine(&p,xd,col);
+ }
+ else if ( p.term && !p.next_line && !xd->mode_c )
+ {
+ if ( p.d < p.end )
+ num += sprintf(num, "%*c%u", xd->num_format_fw-1,
+ '>', (int)(p.end-p.d) );
+ else if ( xd->print_endmarker )
+ num += sprintf(num,"%*c", xd->num_format_fw, '/' );
+ else if ( xd->print_text )
+ {
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ p.term = false; // print only once
+ }
+ else if ( xd->print_text )
+ {
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ }
+ if (!xd->print_text)
+ while ( num > p.num_buf && num[-1] == ' ' )
+ num--;
+ *num = 0;
+ ASSERT( num < p.num_buf + sizeof(p.num_buf));
+ xd->last_number_fw = num - p.num_buf;
+ }
+ else
+ {
+ p.d += p.line_size - p.skip * xd->bytes_per_col;
+ p.skip = 0;
+ }
+
+ int stat = xd->out_func(xd,0,p.num_buf,p.text_buf);
+ if ( stat < 0 )
+ return stat;
+
+ xd->addr += p.line_size;
+ p.size -= p.line_size;
+ }
+ const uint written = p.d - (u8*)p_data;
+ xd->written += written;
+ return written;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int DumpInt64
+(
+ // returns <0 on error, or number of dumped bytes
+
+ XDump_t *xd, // current params
+ const void *p_data, // data to dump
+ uint p_size, // size of 'p_data'
+ bool p_term // false: print only whole lines
+ // true: print complete data
+)
+{
+ DASSERT(xd);
+ if ( !p_data || !xd->out_func )
+ return 0;
+
+ XParam_t p;
+ SetupXParam(&p,xd,p_data,p_size,p_term);
+
+ while(SetupXParamLine(&p,xd))
+ {
+ if ( xd->mode_ignore && CheckXParamNull(&p,xd) )
+ continue;
+
+ if (xd->print_number)
+ {
+ char *num = p.num_buf;
+ uint col;
+ for ( col = 0; col < xd->cols_per_line; col++ )
+ {
+ if (!--p.extra_space)
+ {
+ p.extra_space = xd->col_space;
+ *num++ = ' ';
+ }
+
+ if ( p.skip > 0 )
+ {
+ p.skip--;
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ else if ( col < p.max_col )
+ {
+ u64 val;
+ switch(xd->bytes_per_col)
+ {
+ case 5: val = xd->endian_func->rd40(p.d); p.d += 5; break;
+ case 6: val = xd->endian_func->rd48(p.d); p.d += 6; break;
+ case 7: val = xd->endian_func->rd56(p.d); p.d += 7; break;
+ default: val = xd->endian_func->rd64(p.d); p.d += 8; break;
+ }
+ num += sprintf(num,xd->num_format1,val);
+ if ( xd->have_trigger && val == (u32)xd->trigger )
+ AbortXParamLine(&p,xd,col);
+ }
+ else if ( p.term && !p.next_line && !xd->mode_c )
+ {
+ if ( p.d < p.end )
+ num += sprintf(num, "%*c%u", xd->num_format_fw-1,
+ '>', (int)(p.end-p.d) );
+ else if ( xd->print_endmarker )
+ num += sprintf(num,"%*c", xd->num_format_fw, '/' );
+ else if (xd->print_text)
+ {
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ p.term = false; // print only once
+ }
+ else if (xd->print_text)
+ {
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ }
+ *num = 0;
+ ASSERT( num < p.num_buf + sizeof(p.num_buf));
+ xd->last_number_fw = num - p.num_buf;
+ }
+ else
+ {
+ p.d += p.line_size - p.skip * xd->bytes_per_col;
+ p.skip = 0;
+ }
+
+ int stat = xd->out_func(xd,0,p.num_buf,p.text_buf);
+ if ( stat < 0 )
+ return stat;
+
+ xd->addr += p.line_size;
+ p.size -= p.line_size;
+ }
+
+ const uint written = p.d - (u8*)p_data;
+ xd->written += written;
+ return written;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int DumpFloat
+(
+ // returns <0 on error, or number of dumped bytes
+
+ XDump_t *xd, // current params
+ const void *p_data, // data to dump
+ uint p_size, // size of 'p_data'
+ bool p_term // false: print only whole lines
+ // true: print complete data
+)
+{
+ DASSERT(xd);
+ if ( !p_data || !xd->out_func )
+ return 0;
+
+ XParam_t p;
+ SetupXParam(&p,xd,p_data,p_size,p_term);
+
+ while(SetupXParamLine(&p,xd))
+ {
+ if ( xd->mode_ignore && CheckXParamNull(&p,xd) )
+ continue;
+
+ if (xd->print_number)
+ {
+ char *num = p.num_buf;
+ uint col;
+ for ( col = 0; col < xd->cols_per_line; col++ )
+ {
+ if (!--p.extra_space)
+ {
+ p.extra_space = xd->col_space;
+ *num++ = ' ';
+ }
+
+ if ( p.skip > 0 )
+ {
+ p.skip--;
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ else if ( col < p.max_col )
+ {
+ double val;
+ ccp format;
+ if ( xd->bytes_per_col == 4 )
+ {
+ val = xd->endian_func->rdf4(p.d);
+ format = fabsf(val) <= 9999.0 && fabsf(val) >= 1e-3
+ ? xd->num_format1 : xd->num_format2;
+ p.d += 4;
+ }
+ else
+ {
+ val = xd->endian_func->rdf8(p.d);
+ format = fabs(val) <= 999999999.0 && fabs(val) >= 1e-6
+ ? xd->num_format1 : xd->num_format2;
+ p.d += 8;
+ }
+ num += sprintf(num,format,val);
+ }
+ else if ( p.term && !p.next_line && !xd->mode_c )
+ {
+ if ( p.d < p.end )
+ num += sprintf(num, "%*c%u", xd->num_format_fw-1,
+ '>', (int)(p.end-p.d) );
+ else if ( xd->print_text )
+ num += sprintf(num,"%*c", xd->num_format_fw, '/' );
+ else if (xd->print_text)
+ {
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ p.term = false; // print only once
+ }
+ else if (xd->print_text)
+ {
+ memset(num,' ',xd->num_format_fw);
+ num += xd->num_format_fw;
+ }
+ }
+ *num = 0;
+ ASSERT( num < p.num_buf + sizeof(p.num_buf));
+ xd->last_number_fw = num - p.num_buf;
+ }
+ else
+ {
+ p.d += p.line_size - p.skip * xd->bytes_per_col;
+ p.skip = 0;
+ }
+
+ int stat = xd->out_func(xd,0,p.num_buf,p.text_buf);
+ if ( stat < 0 )
+ return stat;
+
+ xd->addr += p.line_size;
+ p.size -= p.line_size;
+ }
+
+ const uint written = p.d - (u8*)p_data;
+ xd->written += written;
+ return written;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// setup ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeXDump ( XDump_t *xd )
+{
+ DASSERT(xd);
+ memset(xd,0,sizeof(*xd));
+
+ xd->f = stdout;
+ xd->print_format =
+ xd->print_addr =
+ xd->print_number =
+ xd->print_endmarker =
+ xd->print_text =
+ xd->print_diff_sep =
+ xd->print_summary = true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void SetupXDump ( XDump_t *xd, XDumpCommand_t cmd )
+{
+ DASSERT(xd);
+ char buf[100];
+
+ xd->cmd = (uint)cmd < XDUMPC__N ? cmd : XDUMPC_DIFF;
+
+ if ( xd->endian != DC_SECOND_ENDIAN )
+ xd->endian = DC_DEFAULT_ENDIAN;
+ xd->endian_func = xd->endian == DC_LITTLE_ENDIAN ? &le_func : &be_func;
+
+
+ //--- address field width
+
+ xd->addr = xd->start_addr;
+ xd->addr_fw = xd->min_addr_fw;
+ if ( !xd->min_addr_fw || xd->addr_fw < xd->min_addr_fw )
+ {
+ uint len = snprintf(buf,sizeof(buf),"%llx",xd->addr);
+ if ( xd->addr_fw < len )
+ xd->addr_fw = len;
+
+ const u64 size = xd->assumed_size ? xd->assumed_size : 0x100;
+ len = snprintf(buf,sizeof(buf),"%llx", xd->addr + size - 1 );
+ if ( xd->addr_fw < len )
+ xd->addr_fw = len;
+
+ if (xd->max_addr_fw)
+ {
+ if ( xd->addr_fw > xd->max_addr_fw )
+ xd->addr_fw = xd->max_addr_fw;
+ if ( xd->addr_fw < xd->min_addr_fw )
+ xd->addr_fw = xd->min_addr_fw;
+ }
+ }
+
+
+ //--- misc
+
+ xd->indent = NormalizeIndent(xd->indent);
+ if (!xd->out_func)
+ xd->out_func = XDumpStandardOutput;
+ if (!xd->prefix)
+ xd->prefix = EmptyString;
+ if (!xd->eol)
+ xd->eol = "\n";
+
+ xd->num_format2 = 0;
+ xd->format_printed = 0;
+
+
+ //--- format
+
+ if ( (uint)xd->format >= XDUMPF__N )
+ xd->format = XDUMPF_AUTO;
+
+ if ( xd->format == XDUMPF_AUTO && xd->cmd != XDUMPC_SCAN )
+ xd->format = XDUMPF_INT_1;
+
+ switch(xd->format)
+ {
+ case XDUMPF_INT_2:
+ xd->bytes_per_col = 2;
+ xd->dump_func = DumpInt32;
+ xd->format_name = xd->mode_dec ? "DEC2" : "HEX2";
+ xd->max_format_fw = 7;
+ xd->num_format1
+ = xd->mode_c ? ( xd->mode_dec ? "%5u," : "0x%04x," )
+ : xd->mode_zero ? ( xd->mode_dec ? " %05u" : " %04x" )
+ : ( xd->mode_dec ? " %5u" : " %4x" );
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1);
+ break;
+
+ case XDUMPF_INT_3:
+ xd->bytes_per_col = 3;
+ xd->dump_func = DumpInt32;
+ xd->format_name = xd->mode_dec ? "DEC3" : "HEX3";
+ xd->max_format_fw = 9;
+ xd->num_format1
+ = xd->mode_c ? ( xd->mode_dec ? "%8u," : "0x%06x," )
+ : xd->mode_zero ? ( xd->mode_dec ? " %08u" : " %06x" )
+ : ( xd->mode_dec ? " %8u" : " %6x" );
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1);
+ break;
+
+ case XDUMPF_INT_4:
+ xd->bytes_per_col = 4;
+ xd->dump_func = DumpInt32;
+ xd->format_name = xd->mode_dec ? "DEC4" : "HEX4";
+ xd->max_format_fw = 11;
+ xd->num_format1
+ = xd->mode_c ? ( xd->mode_dec ? "%10u," : "0x%08x," )
+ : xd->mode_zero ? ( xd->mode_dec ? " %010u" : " %08x" )
+ : ( xd->mode_dec ? " %10u" : " %8x" );
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1);
+ break;
+
+ case XDUMPF_INT_5:
+ xd->bytes_per_col = 5;
+ xd->dump_func = DumpInt64;
+ xd->format_name = xd->mode_dec ? "DEC5" : "HEX5";
+ xd->max_format_fw = 13;
+ xd->num_format1
+ = xd->mode_c ? ( xd->mode_dec ? "%12llu," : "0x%010llx," )
+ : xd->mode_zero ? ( xd->mode_dec ? " %012llu" : " %010llx" )
+ : ( xd->mode_dec ? " %12llu" : " %10llx" );
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1ull);
+ break;
+
+ case XDUMPF_INT_6:
+ xd->bytes_per_col = 6;
+ xd->dump_func = DumpInt64;
+ xd->format_name = xd->mode_dec ? "DEC6" : "HEX6";
+ xd->max_format_fw = 16;
+ xd->num_format1
+ = xd->mode_c ? ( xd->mode_dec ? "%15llu," : "0x%012llx," )
+ : xd->mode_zero ? ( xd->mode_dec ? " %015llu" : " %012llx" )
+ : ( xd->mode_dec ? " %15llu" : " %12llx" );
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1ull);
+ break;
+
+ case XDUMPF_INT_7:
+ xd->bytes_per_col = 7;
+ xd->dump_func = DumpInt64;
+ xd->format_name = xd->mode_dec ? "DEC7" : "HEX7";
+ xd->max_format_fw = 18;
+ xd->num_format1
+ = xd->mode_c ? ( xd->mode_dec ? "%17llu," : "0x%014llx," )
+ : xd->mode_zero ? ( xd->mode_dec ? " %017llu" : " %014llx" )
+ : ( xd->mode_dec ? " %17llu" : " %14llx" );
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1ull);
+ break;
+
+ case XDUMPF_INT_8:
+ xd->bytes_per_col = 8;
+ xd->dump_func = DumpInt64;
+ xd->format_name = xd->mode_dec ? "DEC8" : "HEX8";
+ xd->max_format_fw = 20;
+ xd->num_format1
+ = xd->mode_c ? ( xd->mode_dec ? "%19llu," : "0x%016llx," )
+ : xd->mode_zero ? ( xd->mode_dec ? " %019llu" : " %016llx" )
+ : ( xd->mode_dec ? " %19llu" : " %16llx" );
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1ull);
+ break;
+
+ case XDUMPF_FLOAT:
+ xd->bytes_per_col = 4;
+ xd->dump_func = DumpFloat;
+ xd->format_name = "FLOAT";
+ xd->max_format_fw = 12;
+ xd->num_format1 = xd->mode_c ? "%11.5g," : " %11.5f";
+ xd->num_format2 = xd->mode_c ? "%11.5g," : " %11.5g";
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1.1);
+ break;
+
+ case XDUMPF_DOUBLE:
+ xd->bytes_per_col = 8;
+ xd->dump_func = DumpFloat;
+ xd->format_name = "DOUBLE";
+ xd->max_format_fw = 21;
+ xd->num_format1 = xd->mode_c ? "%20.8g," : " %20.8f";
+ xd->num_format2 = xd->mode_c ? "%20.8g," : " %20.8g";
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1.1);
+ break;
+
+ default:
+ //case XDUMPF_AUTO:
+ //case XDUMPF_INT_1:
+ xd->bytes_per_col = 1;
+ xd->dump_func = DumpInt32;
+ xd->format_name = xd->mode_dec ? "DEC1" : "HEX1";
+ xd->max_format_fw = 5;
+ xd->num_format1
+ = xd->mode_c ? ( xd->mode_dec ? "%3u," : "0x%02x," )
+ : xd->mode_zero ? ( xd->mode_dec ? " %03u" : " %02x" )
+ : ( xd->mode_dec ? " %3u" : " %2x" );
+ xd->num_format_fw = snprintf(buf,sizeof(buf),xd->num_format1,1);
+ break;
+ }
+
+
+ //--- bytes per line
+
+ uint min = xd->min_width ? xd->min_width : 16;
+ uint max = xd->max_width ? xd->max_width : min + 2*xd->bytes_per_col/3;
+ if ( max > XDUMP_MAX_BYTES_PER_LINE )
+ max = XDUMP_MAX_BYTES_PER_LINE;
+ if ( min > max || !xd->min_width )
+ min = max;
+
+ uint ncols = ( min + xd->bytes_per_col - 1 ) / xd->bytes_per_col;
+ uint ncols_max = max / xd->bytes_per_col;
+ if ( ncols > ncols_max )
+ ncols = ncols_max;
+
+ xd->cols_per_line = ncols ? ncols : 1;
+ xd->bytes_per_line = xd->cols_per_line * xd->bytes_per_col;
+
+
+ //--- extra space
+
+ if ( xd->extra_space < 0 || xd->cols_per_line <= 3 )
+ xd->col_space = 0;
+ else
+ {
+ xd->col_space = xd->extra_space;
+ if (!xd->col_space)
+ {
+ xd->col_space = 4 / xd->bytes_per_col;
+ if ( xd->col_space < 2 )
+ xd->col_space = 2;
+ }
+
+ if ( xd->col_space >= xd->cols_per_line )
+ xd->col_space = 0;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print xdump params ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void PrintXDumpParam
+(
+ FILE *f, // output file, abort if NULL
+ int indent, // indention
+ const ColorSet_t *colset, // NULL or color set
+ const XDump_t *xd // data to print
+)
+{
+ if ( !f || !xd )
+ return;
+
+ indent = NormalizeIndent(indent);
+ if (!colset)
+ colset = GetColorSet0();
+
+ fprintf(f,
+ "%*s%s" "Command: %s%s%s\n"
+ "%*s%s" "Format: %s%s, %s%s%s => %s%s\n"
+ "%*s%s" "Endian: %s%s%s\n",
+ indent,"", colset->name, colset->value,
+ (uint)xd->cmd < XDUMPC__N ? XDumpCommandName[xd->cmd] : "?",
+ colset->reset,
+ indent,"", colset->name, colset->value,
+ (uint)xd->format < XDUMPF__N ? XDumpFormatName[xd->format] : "?",
+ xd->mode_dec ? "decimal" : "hexadecimal",
+ xd->mode_zero ? " with leading zeros" : "",
+ xd->mode_c ? ", print in C syntax" : "",
+ xd->format_name ? xd->format_name : "?",
+ colset->reset,
+ indent,"", colset->name, colset->value,
+ xd->endian == DC_LITTLE_ENDIAN ? "LITTLE"
+ : xd->endian == DC_BIG_ENDIAN ? "BIG" : "?",
+ colset->reset );
+
+ if (xd->num_format2)
+ fprintf(f,
+ "%*s%s" "Num format: %s'%s' & '%s' (fw=%u,max=%u)%s\n",
+ indent,"", colset->name, colset->value,
+ xd->num_format1 ? xd->num_format1 : "?",
+ xd->num_format2, xd->num_format_fw, xd->max_format_fw,
+ colset->reset );
+ else
+ fprintf(f,
+ "%*s%s" "Num format: %s'%s' (fw=%u,max=%u)%s\n",
+ indent,"", colset->name, colset->value,
+ xd->num_format1 ? xd->num_format1 : "?",
+ xd->num_format_fw, xd->max_format_fw,
+ colset->reset );
+
+ if (xd->assumed_size)
+ fprintf(f, "%*s%s"
+ "Address: %sstart=0x%llx, current=0x%llx, size=0x%llx%s%s\n",
+ indent,"", colset->name, colset->value,
+ xd->start_addr, xd->addr, xd->assumed_size,
+ xd->mode_align ? ", aligned" : "",
+ colset->reset );
+ else
+ fprintf(f, "%*s%s"
+ "Address: %sstart=0x%llx, current=0x%llx%s%s\n",
+ indent,"", colset->name, colset->value,
+ xd->start_addr, xd->addr,
+ xd->mode_align ? ", aligned" : "",
+ colset->reset );
+
+ if (xd->have_trigger)
+ fprintf(f, "%*s%s"
+ "Line trigger: %s%llu = 0x%llx%s%s\n",
+ indent,"", colset->name, colset->value,
+ xd->trigger, xd->trigger,
+ xd->trigger == '\n' ? " (LF)" :
+ xd->trigger == '\r' ? " (CR)" :
+ xd->trigger == '\t' ? " (TAB)" :
+ xd->trigger == ' ' ? " (SPACE)" :
+ xd->trigger == 0 ? " (NULL)" :
+ "",
+ colset->reset );
+
+ fprintf(f,
+ "%*s%s" "Address fw: %smin=%u, max=%u, active=%u%s\n",
+ indent,"", colset->name, colset->value,
+ xd->min_addr_fw, xd->max_addr_fw, xd->addr_fw,
+ colset->reset );
+
+ fprintf(f,
+ "%*s%s" "Print: %s%sformat, %saddress, %snumbers, %stext, %ssummary, %sseparator%s\n",
+ indent,"", colset->name, colset->value,
+ xd->print_format ? "" : "no ",
+ xd->print_addr ? "" : "no ",
+ xd->print_number ? "" : "no ",
+ xd->print_text ? "" : "no ",
+ xd->print_summary ? "" : "no ",
+ xd->print_diff_sep ? "" : "no ",
+ colset->reset );
+
+ fprintf(f,
+ "%*s%s" "Output file: %s%s, indent=%d, prefix=%s, eol=%s%s\n",
+ indent,"", colset->name, colset->value,
+ xd->f == stdout ? "STDOUT"
+ : xd->f == stderr ? "STDERR"
+ : xd->f ? "FILE" : "NONE",
+ xd->indent,
+ !xd->prefix ? "NULL" : !*xd->prefix ? "EMPTY" : "DEFINED",
+ !xd->eol ? "NULL"
+ : !*xd->eol ? "EMPTY"
+ : !strcmp(xd->eol,"\n") ? "LF"
+ : !strcmp(xd->eol,"\r") ? "CR"
+ : !strcmp(xd->eol,"\r\n") ? "CR+LF"
+ : "DEFINED",
+ colset->reset );
+
+ fprintf(f,
+ "%*s%s" "Bytes/line: %smin=%u, max=%u%s\n"
+ "%*s%s" " =>: %sbytes/column = %u, columns/line = %u, bytes/line = %u%s\n"
+ "%*s%s" "Extra space: %severy %u column => every %u column%s\n",
+ indent,"", colset->name, colset->value,
+ xd->min_width, xd->max_width,
+ colset->reset,
+ indent,"", colset->name, colset->value,
+ xd->bytes_per_col, xd->cols_per_line, xd->bytes_per_line,
+ colset->reset,
+ indent,"", colset->name, colset->value,
+ xd->extra_space, xd->col_space,
+ colset->reset );
+
+ if (xd->mode_ignore)
+ fprintf(f,
+ "%*s" "%sNULL bytes: %sPrint a summary for lines with NULL bytes only.%s\n",
+ indent,"", colset->name, colset->value, colset->reset );
+ fprintf(f,
+ "%*s%s" "Functions: %sdump=%s, output=%s, endian=%s%s\n",
+ indent,"", colset->name, colset->value,
+ !xd->dump_func ? "-" : "SET",
+ !xd->out_func ? "-"
+ : xd->out_func == XDumpStandardOutput
+ ? "STANDARD" : "SET",
+ !xd->endian_func ? "-"
+ : xd->endian_func == &le_func ? "LITTLE"
+ : xd->endian_func == &be_func ? "BIG"
+ : "SET",
+ colset->reset );
+
+ fprintf(f,
+ "%*s%s" "User param: %sint=%d, ptr=%s%s\n",
+ indent,"", colset->name, colset->value,
+ xd->user_int, xd->user_ptr ? "SET" : "-",
+ colset->reset );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// dump interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void SetupRemainder
+ ( XDump_t *xd, const XDump_t *src, XDumpCommand_t cmd, uint size )
+{
+ DASSERT(xd);
+ DASSERT(src);
+
+ memcpy(xd,src,sizeof(*xd));
+ xd->format = XDUMPF_INT_1;
+ xd->start_addr = xd->addr;
+ xd->assumed_size = size;
+ xd->min_width = size;
+ xd->max_width = size;
+ xd->extra_space = 0;
+ xd->mode_align = false;
+ xd->mode_ignore = false;
+
+ if (xd->print_text)
+ xd->min_number_fw = xd->last_number_fw;
+
+ SetupXDump(xd,cmd);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int XDumpHelper
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; SetupXDump() already done!
+ const void *data, // data to dump
+ uint size, // size of 'data'
+ bool term // false: print only whole lines
+ // true: print complete data
+)
+{
+ DASSERT(xd);
+ DASSERT(data||!size);
+
+ PrintFormat(xd,0);
+ int stat = xd->dump_func(xd,data,size,term);
+ if ( stat < 0 )
+ return stat;
+
+ if (term)
+ {
+ PrintNullSummary(xd);
+
+ if ( stat < size )
+ {
+ XDump_t local;
+ SetupRemainder(&local,xd,XDUMPC_DUMP,size-stat);
+ PrintFormat(&local,0);
+
+ int stat2 = local.dump_func(&local,data+stat,local.min_width,term);
+ xd->addr = local.addr;
+ xd->written = local.written;
+
+ if ( stat2 < 0 )
+ return stat2;
+ stat += stat2;
+ }
+
+ if ( xd->f && xd->print_summary )
+ {
+ fprintf(xd->f,"%s%*s",xd->prefix,xd->indent,"");
+ if ( xd->print_addr )
+ {
+ if (xd->mode_c)
+ fprintf(xd->f,"//%*llx// [%llu Bytes]%s",
+ xd->addr_fw, xd->addr,
+ xd->written, xd->eol );
+
+ else
+ fprintf(xd->f,"%*llx:%s [%llu Bytes]%s",
+ xd->addr_fw, xd->addr,
+ xd->extra_space >= 0 ? " " : "",
+ xd->written, xd->eol );
+ }
+ else
+ {
+ if (xd->mode_c)
+ fprintf(xd->f,"// [%llu Bytes]%s",
+ xd->written, xd->eol );
+
+ else
+ fprintf(xd->f,"%s [%llu Bytes]%s",
+ xd->extra_space >= 0 ? " " : "",
+ xd->written, xd->eol );
+ }
+ }
+ }
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDump
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+ const void *data, // data to dump
+ uint size, // size of 'data'
+ bool term // false: print only whole lines
+ // true: print complete data
+)
+{
+ XDump_t local_xd;
+ if (!xd)
+ {
+ InitializeXDump(&local_xd);
+ xd = &local_xd;
+ }
+ SetupXDump(xd,XDUMPC_DUMP);
+
+ return XDumpHelper(xd,data,size,term);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDumpByFile
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+ FILE *f, // input file
+ u64 max_size // >0: limit size to print
+)
+{
+ DASSERT(f);
+
+ XDump_t local_xd;
+ if (!xd)
+ {
+ InitializeXDump(&local_xd);
+ xd = &local_xd;
+ }
+ SetupXDump(xd,XDUMPC_DUMP);
+
+ char buf[0x4100];
+ uint start = 0;
+ if (!max_size)
+ max_size = M1(max_size);
+
+ uint count = 0;
+ while ( max_size > 0 )
+ {
+ uint max_read = ( sizeof(buf) - start ) / 0x1000 * 0x1000;
+ if ( max_read > max_size )
+ max_read = max_size;
+
+ size_t stat = fread(buf+start,1,max_read,f);
+ if (!stat)
+ break;
+ max_size -= stat;
+ stat += start;
+
+ int dstat = XDumpHelper(xd,buf,stat,false);
+ if ( dstat < 0 )
+ return dstat;
+ count += dstat;
+
+ start = stat - dstat;
+ if (start)
+ memmove(buf,buf+dstat,start);
+ }
+
+ int dstat = XDumpHelper(xd,buf,start,true);
+ return dstat < 0 ? dstat : count + dstat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDump16 // wrapper for a standard hexdump
+(
+ // returns <0 on error, or number of dumped bytes
+
+ FILE *f_out, // output file
+ int indent, // indention
+ u64 addr, // start address
+ const void *data, // data
+ uint size // size of 'data'
+)
+{
+ XDump_t xd;
+ InitializeXDump(&xd);
+ xd.f = f_out;
+ xd.indent = indent;
+ xd.start_addr = addr;
+ xd.assumed_size = size;
+
+ return XDump(&xd,data,size,true);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// diff interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct XDiff_t
+{
+ const void *data; // data to compare
+ uint size; // size of 'data'
+ bool term; // 'data' reached end
+
+ XDump_t xd; // dump parameters
+
+ //--- buffers to catch output
+ char num_buf[XDUMP_GOOD_NUM_BUF_SIZE+4];
+ char text_buf[XDUMP_GOOD_TEXT_BUF_SIZE];
+}
+XDiff_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int XDiffCatchOutput
+(
+ struct XDump_t *xd, // internal copy of params
+ ccp prefix, // NULL or additional prefix (used by diff)
+ ccp dump, // dump to print
+ ccp text // text to print
+)
+{
+ DASSERT(xd);
+ DASSERT(dump);
+ DASSERT(text);
+
+ XDiff_t *diff = xd->user_ptr;
+ DASSERT(diff);
+
+ StringCopyS(diff->num_buf,sizeof(diff->num_buf),dump);
+ StringCopyS(diff->text_buf,sizeof(diff->text_buf),text);
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void SetupXDiff
+(
+ XDiff_t *xdiff, // data to setup
+
+ XDump_t *xd, // NULL or template
+
+ const void *data, // data to compare
+ uint size, // size of 'data'
+ bool term // 'data' reached end
+)
+{
+ DASSERT(xdiff);
+
+ xdiff->data = data;
+ xdiff->size = size;
+ xdiff->term = term;
+ *xdiff->num_buf = *xdiff->text_buf = 0;
+
+ if (xd)
+ memcpy(&xdiff->xd,xd,sizeof(xdiff->xd));
+ else
+ InitializeXDump(&xdiff->xd);
+
+ xd = &xdiff->xd;
+ SetupXDump(xd,XDUMPC_DIFF);
+
+ xd->user_ptr = xdiff;
+ xd->out_func = XDiffCatchOutput;
+ xd->mode_ignore = false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool XDiffText
+(
+ // returns true if differ
+
+ XDump_t *xd, // original params; SetupXDump() already done!
+ XDiff_t *d1, // first diff file
+ XDiff_t *d2, // second diff file
+ bool colorize // true: use red and green on output
+)
+{
+ DASSERT(xd);
+ DASSERT(d1);
+ DASSERT(d2);
+
+ char *n1 = d1->num_buf;
+ char *n2 = d2->num_buf;
+
+ int len1 = strlen(n1);
+ int len2 = strlen(n2);
+ if ( len1 < len2 )
+ {
+ memset( n1 + len1, ' ', len2-len1 );
+ n1[len2] = 0;
+ len1 = len2;
+ }
+ else if ( len1 > len2 )
+ {
+ memset( n2 + len2, ' ', len1-len2 );
+ n2[len1] = 0;
+ }
+
+ if ( xd->min_number_fw < len1 )
+ xd->min_number_fw = len1;
+
+ if (!strcmp(n1,n2))
+ return false;
+
+ for(;;)
+ {
+ while ( *n1 == ' ' && *n2 == ' ' )
+ n1++, n2++;
+ if ( !*n1 || !*n2 )
+ break;
+
+ char *x = n2;
+ while ( (u8)*n2 > ' ' && *n1 == *n2 && *n2 != '/' && *n2 != '/' )
+ n1++, n2++;
+ if ( n2 > x && (u8)*n2 <= ' ' )
+ {
+ while ( x < n2 )
+ *x++ = ' ';
+ x[-1] = '.';
+ }
+ else
+ while ( (u8)*n1 > ' ' || (u8)*n2 > ' ' )
+ n1++, n2++;
+ }
+
+ xd->out_func(xd,"< ",d1->num_buf,d1->text_buf);
+ xd->out_func(xd,"> ",d2->num_buf,d2->text_buf);
+ if ( xd->f && xd->print_diff_sep )
+ fputs(xd->eol,xd->f);
+
+ xd->diff_count++;
+ return true;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+#undef OPTIMIZED_SEARCH
+#define OPTIMIZED_SEARCH 0
+
+static int XDiffHelper
+(
+ // returns <0 on error, or number of dumped bytes
+
+ XDump_t *xd, // original params; SetupXDump() already done!
+ XDiff_t *d1, // first diff file
+ XDiff_t *d2, // second diff file
+ uint limit_lines, // >0: limit number of differ-lines
+ bool colorize // true: use red and green on output
+)
+{
+ DASSERT(xd);
+ DASSERT(d1);
+ DASSERT(d2);
+ DASSERT( d1->xd.bytes_per_col == d1->xd.bytes_per_col );
+ DASSERT( d1->xd.bytes_per_line == d1->xd.bytes_per_line );
+ DASSERT( d1->xd.format == d1->xd.format );
+
+
+ //--- size calculations & setup
+
+ uint count = 0;
+ int size1 = d1->size;
+ int size2 = d2->size;
+ if ( !d1->term && !d2->term )
+ {
+ if ( size1 < size2 )
+ size2 = size1;
+ else if ( size1 > size2 )
+ size1 = size2;
+ }
+
+ if (!d1->term)
+ size1 = size1 / xd->bytes_per_line * xd->bytes_per_line;
+
+ if (!d2->term)
+ size2 = size2 / xd->bytes_per_line * xd->bytes_per_line;
+
+ const u8 *p1 = d1->data;
+ const u8 *p2 = d2->data;
+
+ const u64 *save_addr_break = xd->addr_break;
+ const u64 *addr_break = save_addr_break;
+ xd->addr_break = 0;
+
+
+ //--- main loop
+
+ int done = 0;
+ while ( size1 > 0 && size2 > 0
+ || !done && ( size1 > 0 || size2 > 0 ) )
+ {
+ //--- optimized search
+
+ #if OPTIMIZED_SEARCH
+ uint same = CountEqual(p1,p2, size1 < size2 ? size1 : size2 );
+ if ( same >= xd->bytes_per_line )
+ {
+ same = same / xd->bytes_per_line * xd->bytes_per_line;
+ p1 += same;
+ p2 += same;
+ xd->addr += same;
+ count += same;
+ size1 -= same;
+ size2 -= same;
+ }
+ #endif
+
+ //---
+ int dumpsize1 = size1 < (int)xd->bytes_per_line ? size1 : xd->bytes_per_line;
+ int dumpsize2 = size2 < (int)xd->bytes_per_line ? size2 : xd->bytes_per_line;
+
+ bool have_break = false;
+ if (addr_break)
+ {
+ while ( *addr_break && xd->addr >= *addr_break )
+ addr_break++;
+
+ if (!*addr_break)
+ addr_break = 0;
+ else
+ {
+ const s64 max = *addr_break - xd->addr;
+ if ( dumpsize1 > max || dumpsize2 > max )
+ {
+ have_break = true;
+ if ( dumpsize1 > max ) dumpsize1 = max;
+ if ( dumpsize2 > max ) dumpsize2 = max;
+ }
+ }
+ }
+
+ #if !OPTIMIZED_SEARCH
+ const uint diffsize = dumpsize1 < dumpsize2 ? dumpsize1 : dumpsize2;
+ #endif
+ uint donesize = dumpsize1 > dumpsize2 ? dumpsize1 : dumpsize2;
+
+//DEL fprintf(stderr,">>> %d,%d done=%d, donesize=%d, diffsize=%d\n",size1,size2,done,donesize,diffsize);
+
+ #if !OPTIMIZED_SEARCH
+ if ( diffsize > 0
+ && ( diffsize < xd->bytes_per_line || memcmp(p1,p2,diffsize) ))
+ #endif
+ {
+ if ( dumpsize1 != dumpsize2 && !xd->diff_size )
+ {
+ xd->diff_size = dumpsize1 - dumpsize2;
+ if (xd->diff_size)
+ xd->diff_count++;
+ }
+
+ if ( dumpsize1 >= (int)xd->bytes_per_col )
+ {
+ const int stat = xd->dump_func(&d1->xd,p1,dumpsize1,true);
+ if ( stat >= 0 && stat < dumpsize1 && donesize > stat )
+ donesize = stat;
+ }
+ else if ( dumpsize1 > 0 )
+ {
+ SetupRemainder(&d1->xd,&d1->xd,XDUMPC_DIFF,dumpsize1);
+ d1->xd.dump_func(&d1->xd,p1,dumpsize1,true);
+ }
+ else
+ {
+ done++;
+ strcpy(d1->num_buf,"/");
+ *d1->text_buf = 0;
+ }
+
+ if ( dumpsize2 >= (int)xd->bytes_per_col )
+ {
+ const int stat = xd->dump_func(&d2->xd,p2,dumpsize2,true);
+ if ( stat >= 0 && stat < dumpsize2 && donesize > stat )
+ donesize = stat;
+ }
+ else if ( dumpsize2 > 0 )
+ {
+ SetupRemainder(&d2->xd,&d2->xd,XDUMPC_DIFF,dumpsize2);
+ d2->xd.dump_func(&d2->xd,p2,dumpsize2,true);
+ }
+ else
+ {
+ done++;
+ strcpy(d2->num_buf,"/");
+ *d2->text_buf = 0;
+ }
+
+ XDiffText(xd,d1,d2,colorize);
+ if ( limit_lines && !--limit_lines )
+ break;
+ }
+
+ p1 += donesize;
+ p2 += donesize;
+ xd->addr += donesize;
+ count += donesize;
+ size1 -= donesize;
+ size2 -= donesize;
+
+ if (have_break)
+ {
+ if (!d1->term)
+ size1 = size1 / xd->bytes_per_line * xd->bytes_per_line;
+
+ if (!d2->term)
+ size2 = size2 / xd->bytes_per_line * xd->bytes_per_line;
+ }
+ }
+
+ xd->addr_break = save_addr_break;
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDiff
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+
+ const void *data1, // data1 to compare
+ uint size1, // size of 'data1'
+ bool term1, // true: 'data1' completed
+
+ const void *data2, // data2 to compare
+ uint size2, // size of 'data2'
+ bool term2, // true: 'data2' completed
+
+ uint limit_lines, // >0: limit number of differ-lines
+ bool colorize // true: use red and green on output
+)
+{
+ // fast test for identical data
+ if ( term1 && term2 && size1 == size2 && ( !size1 || !memcmp(data1,data2,size1) ))
+ return 0;
+
+ XDump_t local_xd;
+ if (!xd)
+ {
+ InitializeXDump(&local_xd);
+ xd = &local_xd;
+ }
+ SetupXDump(xd,XDUMPC_DIFF);
+
+ XDiff_t diff1, diff2;
+ SetupXDiff(&diff1,xd,data1,size1,term1);
+ SetupXDiff(&diff2,xd,data2,size2,term2);
+ return XDiffHelper(xd,&diff1,&diff2,limit_lines,colorize);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDiffByFile
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+ FILE *f1, // first input file
+ FILE *f2, // second input file
+ u64 max_size, // >0: limit comparing size
+ uint limit_lines, // >0: limit number of differ-lines
+ bool colorize // true: use red and green on output
+)
+{
+ DASSERT(f1);
+ DASSERT(f2);
+
+ XDump_t local_xd;
+ if (!xd)
+ {
+ InitializeXDump(&local_xd);
+ xd = &local_xd;
+ }
+ SetupXDump(xd,XDUMPC_DIFF);
+
+ char buf1[0x4100];
+ char buf2[sizeof(buf1)];
+
+ XDiff_t diff1, diff2;
+ SetupXDiff(&diff1,xd,buf1,0,false);
+ SetupXDiff(&diff2,xd,buf2,0,false);
+
+ uint start1 = 0, start2 = 0;
+ if (!max_size)
+ max_size = M1(max_size);
+
+ uint count = 0;
+ while ( max_size > 0 )
+ {
+ uint max_read1 = ( sizeof(buf1) - start1 ) / 0x1000 * 0x1000;
+ if ( max_read1 > max_size )
+ max_read1 = max_size;
+ size_t stat1 = fread(buf1+start1,1,max_read1,f1);
+
+ uint max_read2 = ( sizeof(buf2) - start2 ) / 0x1000 * 0x1000;
+ if ( max_read2 > max_size )
+ max_read2 = max_size;
+ size_t stat2 = fread(buf2+start2,1,max_read2,f2);
+
+ uint stat = stat1 < stat2 ? stat1 : stat2;
+ max_size -= stat;
+
+ diff1.size = stat1 + start1;
+ diff1.term = !stat1;
+ diff2.size = stat2 + start2;
+ diff2.term = !stat2;
+
+ int dstat = XDiffHelper(xd,&diff1,&diff2,limit_lines,colorize);
+
+ if ( dstat <= 0 )
+ return dstat;
+ count += dstat;
+ if (xd->diff_size)
+ return count;
+
+ start1 = stat1 + start1 - dstat;
+ if (start1)
+ memmove(buf1,buf1+dstat,start1);
+
+ start2 = stat2 + start2 - dstat;
+ if (start2)
+ memmove(buf2,buf2+dstat,start2);
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDiff16
+(
+ // returns <0 on error, or number of dumped bytes
+
+ FILE *f_out, // output file
+ int indent, // indention
+ u64 addr, // start address
+
+ const void *data1, // data
+ uint size1, // size of 'data'
+
+ const void *data2, // data
+ uint size2, // size of 'data'
+
+ uint limit_lines, // >0: limit number of differ-lines
+ bool colorize // true: use red and green on output
+)
+{
+ XDump_t xd;
+ InitializeXDump(&xd);
+ xd.f = f_out;
+ xd.indent = indent;
+ xd.start_addr = addr;
+ xd.assumed_size = size1 > size2 ? size1 : size2;
+
+ return XDiff( &xd, data1,size1,true,
+ data2,size2,true, limit_lines, colorize );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[XScan_t]]
+
+typedef struct XScan_t
+{
+ FastBuf_t *dest; // not NULL: append scanned data here & ignore 'fout'
+ FILE *fout; // valid output file
+ u64 written; // number of written bytes
+
+ bool auto_format; // true: detect '#FORMAT:'
+ XDumpFormat_t format; // current scan format
+ dcEndian_t endian; // current endian
+ uint num_base; // 8:octal, 10:decimal, 16:hexadecimal
+ ccp num_allowed; // string with allowed first characters
+
+ ccp prefix; // not NULL: skip this prefix
+ uint prefix_len; // length of prefix
+
+ const endian_func_t *endian_func; // endian functions
+}
+XScan_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void UpdateXScan ( XScan_t *scan )
+{
+ DASSERT(scan);
+
+ if ( !scan->dest && !scan->fout )
+ scan->fout = stdout;
+
+ if (scan->prefix)
+ {
+ ccp prefix = scan->prefix;
+ while ( *prefix > 0 && *prefix <= ' ' )
+ prefix++;
+ scan->prefix = *prefix ? prefix : 0;
+ scan->prefix_len = strlen(prefix);
+ }
+ else
+ scan->prefix_len = 0;
+
+ scan->endian_func = scan->endian == DC_LITTLE_ENDIAN ? &le_func : &be_func;
+
+ if ( scan->format == XDUMPF_AUTO )
+ scan->format = XDUMPF_INT_1;
+
+ if ( scan->num_base < 2 || scan->num_base > 36 )
+ scan->num_base = 16;
+
+ scan->num_allowed
+ = scan->format == XDUMPF_FLOAT || scan->format == XDUMPF_DOUBLE
+ ? "+-."
+ : "+-~!";
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void SetupXScan
+(
+ XScan_t *scan, // data to setup
+ XDump_t *xd, // NULL or template
+ FastBuf_t *dest // not NULL: append scanned data here
+)
+{
+ DASSERT(scan);
+ memset(scan,0,sizeof(*scan));
+
+ if (xd)
+ {
+ scan->fout = xd->f;
+ scan->auto_format = xd->format == XDUMPF_AUTO;
+ scan->format = xd->format;
+ scan->endian = xd->endian;
+ scan->num_base = xd->mode_dec ? 10 : 16;
+ scan->prefix = xd->prefix;
+ }
+ else
+ {
+ scan->fout = stdout;
+ scan->auto_format = true;
+ scan->format = XDUMPF_INT_1;
+ scan->endian = DC_DEFAULT_ENDIAN;
+ scan->num_base = 16;
+ scan->prefix = 0;
+ }
+
+ scan->dest = dest;
+ UpdateXScan(scan);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void XScanFormat
+(
+ // returns <0 on error, or number of dumped bytes
+
+ XScan_t *scan, // valid scan parameters
+ ccp src, // source pointer
+ ccp eol // end of line
+)
+{
+ DASSERT(scan);
+ DASSERT(src);
+ DASSERT(eol);
+ DASSERT( src <= eol );
+
+ noPRINT(stderr,"FORMAT: %.*s\n",(int)(eol-src),src);
+
+ enum { MD_HEX, MD_DEC, MD_OCT, MD_NUM, MD_ENDIAN };
+
+ static const KeywordTab_t keytab[] =
+ {
+ { MD_HEX, "HEX", 0, XDUMPF_AUTO },
+ { MD_HEX, "HEX-1", "HEX1", XDUMPF_INT_1 },
+ { MD_HEX, "HEX-2", "HEX2", XDUMPF_INT_2 },
+ { MD_HEX, "HEX-3", "HEX3", XDUMPF_INT_3 },
+ { MD_HEX, "HEX-4", "HEX4", XDUMPF_INT_4 },
+ { MD_HEX, "HEX-5", "HEX5", XDUMPF_INT_5 },
+ { MD_HEX, "HEX-6", "HEX6", XDUMPF_INT_6 },
+ { MD_HEX, "HEX-7", "HEX7", XDUMPF_INT_7 },
+ { MD_HEX, "HEX-8", "HEX8", XDUMPF_INT_8 },
+
+ { MD_DEC, "DEC", 0, XDUMPF_AUTO },
+ { MD_DEC, "DEC-1", "DEC1", XDUMPF_INT_1 },
+ { MD_DEC, "DEC-2", "DEC2", XDUMPF_INT_2 },
+ { MD_DEC, "DEC-3", "DEC3", XDUMPF_INT_3 },
+ { MD_DEC, "DEC-4", "DEC4", XDUMPF_INT_4 },
+ { MD_DEC, "DEC-5", "DEC5", XDUMPF_INT_5 },
+ { MD_DEC, "DEC-6", "DEC6", XDUMPF_INT_6 },
+ { MD_DEC, "DEC-7", "DEC7", XDUMPF_INT_7 },
+ { MD_DEC, "DEC-8", "DEC8", XDUMPF_INT_8 },
+
+ { MD_OCT, "OCT", 0, XDUMPF_AUTO },
+ { MD_OCT, "OCT-1", "OCT1", XDUMPF_INT_1 },
+ { MD_OCT, "OCT-2", "OCT2", XDUMPF_INT_2 },
+ { MD_OCT, "OCT-3", "OCT3", XDUMPF_INT_3 },
+ { MD_OCT, "OCT-4", "OCT4", XDUMPF_INT_4 },
+ { MD_OCT, "OCT-5", "OCT5", XDUMPF_INT_5 },
+ { MD_OCT, "OCT-6", "OCT6", XDUMPF_INT_6 },
+ { MD_OCT, "OCT-7", "OCT7", XDUMPF_INT_7 },
+ { MD_OCT, "OCT-8", "OCT8", XDUMPF_INT_8 },
+
+ { MD_NUM, "1", 0, XDUMPF_INT_1 },
+ { MD_NUM, "2", 0, XDUMPF_INT_2 },
+ { MD_NUM, "3", 0, XDUMPF_INT_3 },
+ { MD_NUM, "4", 0, XDUMPF_INT_4 },
+ { MD_NUM, "5", 0, XDUMPF_INT_5 },
+ { MD_NUM, "6", 0, XDUMPF_INT_6 },
+ { MD_NUM, "7", 0, XDUMPF_INT_7 },
+ { MD_NUM, "8", 0, XDUMPF_INT_8 },
+
+ { MD_NUM, "FLOAT", 0, XDUMPF_FLOAT },
+ { MD_NUM, "DOUBLE", 0, XDUMPF_DOUBLE },
+
+ { MD_ENDIAN, "LE", "LITTLE", DC_LITTLE_ENDIAN },
+ { MD_ENDIAN, "BE", "BIG", DC_BIG_ENDIAN },
+
+ {0,0,0,0}
+ };
+
+ for(;;)
+ {
+ while ( src < eol && !isalnum((int)*src) )
+ src++;
+
+ char namebuf[20], *name = namebuf;
+ while ( src < eol && (u8)*src > ' ' )
+ {
+ if ( name < namebuf + sizeof(namebuf) - 1 )
+ *name++ = toupper((int)*src);
+ src++;
+ }
+ *name = 0;
+
+ if ( name == namebuf )
+ break;
+
+ noPRINT(stderr,"SCAN FORMAT: %s\n",namebuf);
+ const KeywordTab_t *key = ScanKeyword(0,namebuf,keytab);
+ if (!key)
+ continue;
+
+ XDumpFormat_t format = key->opt;
+ switch(key->id)
+ {
+ case MD_HEX:
+ scan->num_base = 16;
+ break;
+
+ case MD_DEC:
+ scan->num_base = 10;
+ break;
+
+ case MD_OCT:
+ scan->num_base = 8;
+ break;
+
+ case MD_NUM:
+ break;
+
+ case MD_ENDIAN:
+ scan->endian = key->opt;
+ format = XDUMPF_AUTO;
+ break;
+ }
+ if ( format != XDUMPF_AUTO )
+ scan->format = format;
+
+ noPRINT(stderr,"F: %lld %lld => base=%d, format=%d, endian=%d\n",
+ key->id, key->opt,
+ scan->num_base, scan->format, scan->endian );
+ }
+ UpdateXScan(scan);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static size_t XScanHelper
+(
+ // returns <0 on error, or number of dumped bytes
+
+ XScan_t *scan, // valid scan parameters
+ ccp source, // source pointer
+ ccp end, // end of source
+ bool term, // true: accept uncomplete last line
+ u64 max_size // >0: limit output size
+)
+{
+ DASSERT(scan);
+ DASSERT( scan->dest || scan->fout );
+ DASSERT(source);
+ DASSERT(end);
+ DASSERT( source <= end );
+
+ if (!max_size)
+ max_size = M1(max_size);
+ max_size -= scan->written;
+
+ ccp src = source;
+ u8 destbuf[0x4000+10];
+ u8 *destmax = destbuf + sizeof(destbuf) - 10;
+ u8 *dest = destbuf;
+
+ while ( src < end && max_size > 0 )
+ {
+ //--- skip all leading contral characters
+
+ while ( src < end && (u8)*src <= ' ' )
+ src++;
+
+
+ //--- find end of line
+
+ bool found_eol = term;
+ ccp eol = src;
+ while ( eol < end )
+ {
+ const u8 ch = *eol;
+ if ( !ch || ch == '\n' || ch == '\r' )
+ {
+ found_eol = true;
+ break;
+ }
+ eol++;
+ }
+ if (!found_eol)
+ break;
+
+ PRINT0("LINE: |%.*s|\n",(int)(eol-src),src);
+
+
+ //--- skip prefix
+
+ if ( scan->prefix_len
+ && scan->prefix_len <= eol-src
+ && !memcmp(scan->prefix,src,scan->prefix_len) )
+ {
+ src += scan->prefix_len;
+ while ( src < eol && (u8)*src <= ' ' )
+ src++;
+ }
+
+ if ( src == eol )
+ continue;
+
+
+ //--- check for "#FORMAT:"
+
+ if (!strncasecmp(src,"#FORMAT:",8))
+ {
+ if (scan->auto_format)
+ XScanFormat(scan,src+8,eol);
+ src = eol;
+ continue;
+ }
+
+
+ //--- skip address column
+
+ ccp ptr = src;
+ while ( ptr < eol && isalnum((int)*ptr) )
+ ptr++;
+ noPRINT(stderr,"ADDR: |%.*s|\n",(int)(ptr-src),src);
+ if ( ptr > src && ptr < eol && *ptr == ':' )
+ src = ptr + 1;
+
+
+ //--- scan numbers until unknown character
+
+ noPRINT(stderr,"SCAN: |%.*s| base=%d\n",(int)(eol-src),src,scan->num_base);
+
+ for(;;)
+ {
+ while ( src < eol && ( (u8)*src <= ' ' || *src == ',' ) )
+ src++;
+ if ( src == eol
+ || (u8)TableNumbers[(u8)*src] >= scan->num_base
+ && !strchr(scan->num_allowed,*src) )
+ {
+ break;
+ }
+
+ if ( dest >= destmax )
+ {
+ size_t len = dest - destbuf;
+ if ( len > max_size )
+ len = max_size;
+ if (scan->dest)
+ AppendFastBuf(scan->dest,destbuf,len);
+ else
+ fwrite(destbuf,1,len,scan->fout);
+ max_size -= len;
+ scan->written += len;
+ dest = destbuf;
+ }
+
+ //u64 num;
+ //double d;
+ char *end;
+
+ switch(scan->format)
+ {
+ case XDUMPF_INT_2:
+ scan->endian_func->wr16(dest,str2ll(src,&end,scan->num_base));
+ dest += 2;
+ break;
+
+ case XDUMPF_INT_3:
+ scan->endian_func->wr24(dest,str2ll(src,&end,scan->num_base));
+ dest += 3;
+ break;
+
+ case XDUMPF_INT_4:
+ scan->endian_func->wr32(dest,str2ll(src,&end,scan->num_base));
+ dest += 4;
+ break;
+
+ case XDUMPF_INT_5:
+ scan->endian_func->wr40(dest,str2ll(src,&end,scan->num_base));
+ dest += 5;
+ break;
+
+ case XDUMPF_INT_6:
+ scan->endian_func->wr48(dest,str2ll(src,&end,scan->num_base));
+ dest += 6;
+ break;
+
+ case XDUMPF_INT_7:
+ scan->endian_func->wr56(dest,str2ll(src,&end,scan->num_base));
+ dest += 7;
+ break;
+
+ case XDUMPF_INT_8:
+ scan->endian_func->wr64(dest,str2ll(src,&end,scan->num_base));
+ dest += 8;
+ break;
+
+ case XDUMPF_FLOAT:
+ scan->endian_func->wrf4(dest,strtod(src,&end));
+ dest += 4;
+ break;
+
+ case XDUMPF_DOUBLE:
+ scan->endian_func->wrf8(dest,strtod(src,&end));
+ dest += 8;
+ break;
+
+ default:
+ *dest++ = str2ll(src,&end,scan->num_base);
+ break;
+ }
+ if ( src == end )
+ break;
+ src = end;
+ }
+
+ //--- prepare next line
+
+ src = eol;
+ }
+
+ if ( dest > destbuf )
+ {
+ size_t len = dest - destbuf;
+ if ( len > max_size )
+ len = max_size;
+ if (scan->dest)
+ AppendFastBuf(scan->dest,destbuf,len);
+ else
+ fwrite(destbuf,1,len,scan->fout);
+ scan->written += len;
+ }
+
+ return src - source;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XScan
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+ FastBuf_t *dest, // not NULL: append scanned data here
+
+ const void *data, // data to scan
+ uint size, // size of 'data'
+ bool term, // true: accept uncomplete last line
+ u64 max_size // >0: limit output size
+)
+{
+ if ( !data || !size )
+ return 0;
+
+ XScan_t scan;
+ SetupXScan(&scan,xd,dest);
+ return XScanHelper(&scan,data,data+size,term,max_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+s64 XScanByFile
+(
+ // returns <0 on error, or number of written bytes
+ XDump_t *xd, // params; if NULL: use local version
+ FastBuf_t *dest, // not NULL: append scanned data here
+ FILE *f, // input file
+ u64 max_size // >0: limit output size
+)
+{
+ if (!f)
+ return 0;
+
+ if (!max_size)
+ max_size = M1(max_size);
+
+ XScan_t scan;
+ SetupXScan(&scan,xd,dest);
+
+ char buf[0x4100];
+ uint start = 0;
+
+ while ( scan.written < max_size )
+ {
+ const uint max_read = ( sizeof(buf) - start ) / 0x1000 * 0x1000;
+
+ size_t stat = fread(buf+start,1,max_read,f);
+ if (!stat)
+ break;
+ stat += start;
+
+ const bool eof = feof(f) != 0;
+ int scanstat = XScanHelper(&scan,buf,buf+stat,eof,max_size);
+ if ( scanstat < 0 )
+ return scanstat;
+
+ start = stat - scanstat;
+ if (start)
+ memmove(buf,buf+scanstat,start);
+ if (eof)
+ break;
+ }
+
+ if (start)
+ {
+ int scanstat = XScanHelper(&scan,buf,buf+start,true,max_size);
+ if ( scanstat < 0 )
+ return scanstat;
+ }
+
+ return scan.written;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
diff --git a/project/dclib/dclib-xdump.h b/project/dclib/dclib-xdump.h
new file mode 100644
index 0000000..2943cb4
--- /dev/null
+++ b/project/dclib/dclib-xdump.h
@@ -0,0 +1,348 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DCLIB_XDUMP_H
+#define DCLIB_XDUMP_H 1
+
+#include "dclib-basics.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// consts and structs ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define XDUMP_MAX_BYTES_PER_LINE 600
+#define XDUMP_GOOD_NUM_BUF_SIZE (6*XDUMP_MAX_BYTES_PER_LINE+1)
+#define XDUMP_GOOD_TEXT_BUF_SIZE (XDUMP_MAX_BYTES_PER_LINE+1)
+
+#define XDUMP_MAX_LINE_WIDTH (XDUMP_GOOD_NUM_BUF_SIZE+XDUMP_GOOD_TEXT_BUF_SIZE+30)
+
+///////////////////////////////////////////////////////////////////////////////
+// [[XDumpCommand_t]]
+
+typedef enum XDumpCommand_t
+{
+ XDUMPC_DUMP,
+ XDUMPC_DIFF,
+ XDUMPC_SCAN,
+ XDUMPC__N
+}
+__attribute__ ((packed)) XDumpCommand_t;
+
+extern const ccp XDumpCommandName[XDUMPC__N];
+
+///////////////////////////////////////////////////////////////////////////////
+// [[XDumpFormat_t]]
+
+typedef enum XDumpFormat_t
+{
+ XDUMPF_AUTO,
+ XDUMPF_INT_1,
+ XDUMPF_INT_2,
+ XDUMPF_INT_3,
+ XDUMPF_INT_4,
+ XDUMPF_INT_5,
+ XDUMPF_INT_6,
+ XDUMPF_INT_7,
+ XDUMPF_INT_8,
+ XDUMPF_FLOAT,
+ XDUMPF_DOUBLE,
+ XDUMPF__N,
+}
+__attribute__ ((packed)) XDumpFormat_t;
+
+extern const ccp XDumpFormatName[XDUMPF__N];
+extern const char XDumpFormatOption[XDUMPF__N+1];
+
+///////////////////////////////////////////////////////////////////////////////
+// [[XDumpOutputFunc]]
+
+struct XDump_t;
+typedef int (*XDumpOutputFunc)
+(
+ struct XDump_t *xd, // internal copy of params
+ ccp prefix, // NULL or additional prefix (used by diff)
+ ccp dump, // dump to print
+ ccp text // text to print
+);
+
+int XDumpStandardOutput
+(
+ struct XDump_t *xd, // internal copy of params
+ ccp prefix, // NULL or additional prefix (used by diff)
+ ccp dump, // dump to print
+ ccp text // text to print
+);
+
+///////////////////////////////////////////////////////////////////////////////
+// [[XDumpDumpFunc]]
+
+typedef int (*XDumpDumpFunc)
+(
+ // returns <0 on error, or number of dumped bytes
+
+ struct XDump_t *xd, // current params
+ const void *data, // data to dump
+ uint size, // size of 'data'
+ bool term // false: print only whole lines
+ // true: print complete data
+);
+
+///////////////////////////////////////////////////////////////////////////////
+// [[XDump_t]]
+
+typedef struct XDump_t
+{
+ //--- options
+
+ XDumpCommand_t cmd; // command
+ XDumpFormat_t format; // scan or output format
+ dcEndian_t endian; // endian
+
+ bool mode_dec; // print integers as decimal instead of hex
+ bool mode_zero; // print leading zeros
+ bool mode_c; // print in c format
+ bool mode_align; // align addresses by 'addr % bytes_per_line'
+ bool mode_ignore; // ignore lines with NULL bytes only
+
+ bool print_format; // print an info line about the format
+ bool print_addr; // print address field
+ bool print_number; // print number field
+ bool print_endmarker;// print "/" as end of data marker
+ bool print_text; // print ascii text field
+ bool print_summary; // print a summary at end of dump
+ bool print_diff_sep; // print an empty line after differ lines
+
+ bool have_trigger; // true: trigger is active
+ u64 trigger; // the trigger value
+
+ u64 start_addr; // start address
+ u64 assumed_size; // assumed output size, used for 'addr_fw'
+ uint min_addr_fw; // min field width of address
+ uint max_addr_fw; // max field width of address; 0=AUTO
+ uint min_number_fw; // >0: minimal field width for number dump
+ uint min_width; // minimal output width in bytes per line
+ uint max_width; // maximal output width in bytes per line
+ int extra_space; // >0: print an extra space after each # column
+ // =0: auto
+
+ FILE *f; // not NULL: output file
+ int indent; // indention before 'prefix'
+ ccp prefix; // not NULL: prefix each line by this
+ ccp eol; // NULL or eol
+
+ XDumpOutputFunc out_func; // NULL or a private function
+
+
+ //--- user parameters, not used, not changed
+
+ int user_int;
+ void *user_ptr;
+
+
+ //--- runtime parameters
+
+ ccp num_format1; // pointer to first numeric format
+ ccp num_format2; // pointer to second numeric format
+ uint num_format_fw; // current field width of a number
+ uint max_format_fw; // max possible fw for format
+ ccp format_name; // name of the format
+
+ u64 written; // total written bytes
+ u64 addr; // current address
+ uint addr_fw; // field width of address
+ const u64 *addr_break; // NULL or null terminated list:
+ // break line at this addresses
+
+ uint bytes_per_col; // bytes per column
+ uint cols_per_line; // columns per line
+ uint bytes_per_line; // bytes per line
+ uint col_space; // >0: print an extra space after each # column
+ uint null_lines; // number of currently skipped NULL lines
+
+ bool format_printed; // true: format line printed
+ uint last_number_fw; // last field with of number column
+ uint diff_count; // number of lines with differences
+ uint diff_size; // <0: size1>size2 / >0: size1>size2
+
+ //--- functions
+
+ XDumpDumpFunc dump_func; // dump processing function
+ const endian_func_t *endian_func; // endian functions
+}
+XDump_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeXDump ( XDump_t *xd );
+void SetupXDump ( XDump_t *xd, XDumpCommand_t cmd );
+
+static inline void ResetXDump ( XDump_t *xd ) {}
+
+void PrintXDumpParam
+(
+ FILE *f, // output file, abort if NULL
+ int indent, // indention
+ const ColorSet_t *colset, // NULL or color set
+ const XDump_t *xd // data to print
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// dump interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int XDump
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+ const void *data, // data to dump
+ uint size, // size of 'data'
+ bool term // false: print only whole lines
+ // true: print complete data
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDumpByFile
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+ FILE *f, // input file
+ u64 max_size // >0: limit size to print
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDump16 // wrapper for a standard hexdump
+(
+ // returns <0 on error, or number of dumped bytes
+
+ FILE *f_out, // output file
+ int indent, // indention
+ u64 addr, // start address
+ const void *data, // data
+ uint size // size of 'data'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// diff interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int XDiff
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+
+ const void *data1, // data1 to compare
+ uint size1, // size of 'data1'
+ bool term1, // true: 'data1' completed
+
+ const void *data2, // data2 to compare
+ uint size2, // size of 'data2'
+ bool term2, // true: 'data2' completed
+
+ uint limit_lines, // >0: limit number of differ-lines
+ bool colorize // true: use red and green on output
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDiffByFile
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+ FILE *f1, // first input file
+ FILE *f2, // second input file
+ u64 max_size, // >0: limit comparing size
+ uint limit_lines, // >0: limit number of differ-lines
+ bool colorize // true: use red and green on output
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+int XDiff16
+(
+ // returns <0 on error, or number of dumped bytes
+
+ FILE *f_out, // output file
+ int indent, // indention
+ u64 addr, // start address
+
+ const void *data1, // data
+ uint size1, // size of 'data'
+
+ const void *data2, // data
+ uint size2, // size of 'data'
+
+ uint limit_lines, // >0: limit number of differ-lines
+ bool colorize // true: use red and green on output
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int XScan
+(
+ // returns <0 on error, or number of dumped bytes
+ XDump_t *xd, // params; if NULL: use local version
+ FastBuf_t *dest, // not NULL: append scanned data here
+
+ const void *data, // data to scan
+ uint size, // size of 'data'
+ bool term, // true: accept uncomplete last line
+ u64 max_size // >0: limit output size
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+s64 XScanByFile
+(
+ // returns <0 on error, or number of written bytes
+ XDump_t *xd, // params; if NULL: use local version
+ FastBuf_t *dest, // not NULL: append scanned data here
+ FILE *f, // input file
+ u64 max_size // >0: limit output size
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DCLIB_XDUMP_H
+
diff --git a/project/dclib/lib-bmg.c b/project/dclib/lib-bmg.c
new file mode 100755
index 0000000..4e42c9c
--- /dev/null
+++ b/project/dclib/lib-bmg.c
@@ -0,0 +1,6346 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+#include <errno.h>
+
+#include "lib-bmg.h"
+#include "dclib-utf8.h"
+#include "dclib-xdump.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// compiler options ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#if 1
+ #define ENABLE_BMG_REGEX 1
+#else
+ #define ENABLE_BMG_REGEX 0
+#endif
+
+#if ENABLE_BMG_REGEX
+ #include "dclib-regex.h"
+#endif
+
+//-----------------------------------------------------------------------------
+
+#if defined(TEST) || defined(DEBUG) || HAVE_WIIMM_EXT
+ #define COMPARE_CREATE 1 // 0=off, 1=on
+#else
+ #define COMPARE_CREATE 0
+#endif
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// global bmg options ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint opt_bmg_force_count = 0; // >0: force some operations
+uint opt_bmg_align = 0x20; // alignment of raw-bg sections
+bool opt_bmg_export = 0; // true: optimize for exports
+bool opt_bmg_no_attrib = 0; // true: suppress attributes
+bool opt_bmg_x_escapes = 0; // true: use x-scapes insetad of z-escapes
+bool opt_bmg_old_escapes = 0; // true: use only old escapes
+int opt_bmg_single_line = 0; // >0: single line, >1: suppress atrributes
+bool opt_bmg_support_mkw = 0; // true: support MKW specific extensions
+bool opt_bmg_support_ctcode = 0; // true: support MKW/CT-CODE specific extensions
+bool opt_bmg_support_lecode = 0; // true: support MKW/LE-CODE specific extensions
+uint opt_bmg_rcup_fill_limit = 0; // >0: limit racing cups to reduce BMG size
+uint opt_bmg_track_fill_limit= 0; // >0: limit tracks to reduce BMG size
+bool opt_bmg_inline_attrib = true; // true: print attrinbutes inline
+int opt_bmg_colors = 1; // use c-escapss: 0:off, 1:auto, 2:on
+ColNameLevelBMG opt_bmg_color_name = BMG_CNL_ALL;
+uint opt_bmg_max_recurse = 10; // max recurse depth
+bool opt_bmg_allow_print = 0; // true: allow '$' to print a log message
+bool opt_bmg_use_slots = 0; // true: use predifined slots
+bool opt_bmg_use_raw_sections= 0; // true: use raw sections
+
+//-----------------------------------------------------------------------------
+
+int opt_bmg_encoding = BMG_ENC__UNDEFINED;
+uint opt_bmg_inf_size = 0;
+OffOn_t opt_bmg_mid = OFFON_AUTO;
+bool opt_bmg_force_attrib = false;
+bool opt_bmg_def_attrib = false;
+
+u8 bmg_force_attrib[BMG_ATTRIB_SIZE];
+u8 bmg_def_attrib[BMG_ATTRIB_SIZE];
+
+bmg_t *bmg_macros = 0;
+
+//-----------------------------------------------------------------------------
+
+const KeywordTab_t PatchKeysBMG[] =
+{
+ // 'opt'
+ // 0: no param
+ // 1: file param
+ // 2: string param
+
+ { BMG_PM_PRINT, "PRINT", 0, 1 },
+ { BMG_PM_FORMAT, "FORMAT", 0, 2 },
+ #if ENABLE_BMG_REGEX
+ { BMG_PM_REGEX, "REGEXP", 0, 2 },
+ { BMG_PM_RM_REGEX, "RM-REGEXP", "RMREGEXP", 2 },
+ #endif
+ { BMG_PM_ID, "ID", 0, 0 },
+ { BMG_PM_ID_ALL, "ID-ALL", "IDALL", 0 },
+ { BMG_PM_UNICODE, "UNICODE", 0, 0 },
+ { BMG_PM_RM_ESCAPES, "RM-ESCAPES", "RMESCAPES", 0 },
+
+ { BMG_PM_REPLACE, "REPLACE", 0, 1 },
+ { BMG_PM_INSERT, "INSERT", 0, 1 },
+ { BMG_PM_OVERWRITE, "OVERWRITE", 0, 1 },
+ { BMG_PM_DELETE, "DELETE", 0, 1 },
+ { BMG_PM_MASK, "MASK", 0, 1 },
+ { BMG_PM_EQUAL, "EQUAL", 0, 1 },
+ { BMG_PM_NOT_EQUAL, "NOTEQUAL", "NEQUAL", 1 },
+
+ { BMG_PM_GENERIC, "GENERIC", 0, 0 },
+ { BMG_PM_RM_CUPS, "RM-CUPS", "RMCUPS", 0 },
+
+ { BMG_PM_CT_COPY, "CT-COPY", "CTCOPY", 0 },
+ { BMG_PM_CT_FORCE_COPY, "CT-FORCE-COPY","CTFORCECOPY", 0 },
+ { BMG_PM_CT_FILL, "CT-FILL", "CTFILL", 0 },
+
+ { BMG_PM_LE_COPY, "LE-COPY", "LECOPY", 0 },
+ { BMG_PM_LE_FORCE_COPY, "LE-FORCE-COPY","LEFORCECOPY", 0 },
+ { BMG_PM_LE_FILL, "LE-FILL", "LEFILL", 0 },
+
+ { BMG_PM_X_COPY, "X-COPY", "XCOPY", 0 },
+ { BMG_PM_X_FORCE_COPY, "X-FORCE-COPY", "XFORCECOPY", 0 },
+ { BMG_PM_X_FILL, "X-FILL", "XFILL", 0 },
+
+ { BMG_PM_RM_FILLED, "RM-FILLED", "XFILLED", 0 },
+
+ { 0,0,0,0 }
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// print & scan string16 ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static const KeywordTab_t bmg_color[] =
+{
+ { 0x0000, "OFF", "NONE", BMG_CNL_BASICS },
+ { 0x0000, "GRAY", "GREY", BMG_CNL_BASICS },
+ { 0x0001, "RED4", 0, BMG_CNL_BASICS },
+ { 0x0002, "WHITE", 0, BMG_CNL_BASICS },
+
+ { 0x0008, "TRANSP", "TRANSPARENT", BMG_CNL_BASICS },
+ { 0x0008, "CLEAR", 0, BMG_CNL_BASICS },
+
+ { 0x0010, "YOR0", "YR0", BMG_CNL_YOR },
+ { 0x0011, "YOR1", "YR1", BMG_CNL_YOR },
+ { 0x0012, "YOR2", "YR2", BMG_CNL_YOR },
+ { 0x0013, "YOR3", "YR3", BMG_CNL_YOR },
+ { 0x0014, "YOR4", "YR4", BMG_CNL_YOR },
+ { 0x0015, "YOR5", "YR5", BMG_CNL_YOR },
+ { 0x0016, "YOR6", "YR6", BMG_CNL_YOR },
+ { 0x0017, "YOR7", "YR7", BMG_CNL_YOR },
+
+ { 0x0020, "RED2", 0, BMG_CNL_BASICS },
+ { 0x0021, "BLUE1", "BLUE", BMG_CNL_BASICS },
+
+ { 0x0030, "YELLOW", 0, BMG_CNL_BASICS },
+ { 0x0031, "BLUE2", 0, BMG_CNL_BASICS },
+ { 0x0032, "RED3", 0, BMG_CNL_BASICS },
+ { 0x0033, "GREEN", 0, BMG_CNL_BASICS },
+
+ { 0x0040, "RED1", "RED", BMG_CNL_BASICS },
+
+ {0,0,0,0}
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetWordLength16BMG ( const u16 *source )
+{
+ DASSERT(source);
+ u16 code = ntohs(*source);
+ if ( code != 0x1a )
+ return 1;
+ const uint len = ( ((u8*)source)[2] + 1 & 0xfe ) / 2;
+ return len ? len : 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetLength16BMG
+(
+ const u16 *source, // source
+ uint max_len // max possible length
+)
+{
+ DASSERT(source);
+
+ const u16 *src = source, *src_end = src + max_len;
+
+ while ( src < src_end )
+ {
+ u16 code = ntohs(*src++);
+ if (!code)
+ return src - source - 1;
+
+ if ( code == 0x1a )
+ {
+ // nintendos special escape sequence!
+ const uint len = *(u8*)src+1 & 0xfe;
+ if ( len > 0 )
+ src += len - 1;
+ }
+ }
+
+ const uint len = src - source;
+ return len < max_len ? len : max_len;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#undef TEST_MODE // 0:off, 1:compare, 2:replace
+#if HAVE_WIIMM_EXT
+ #define TEST_MODE 1
+#else
+ #define TEST_MODE 0
+#endif
+//--------------------------------------------------
+
+uint PrintString16BMG
+(
+ char *buf, // destination buffer
+ uint buf_size, // size of 'buf'
+ const u16 *src, // source
+ int src_len, // length of source in u16 words, -1:NULL term
+ u16 utf8_max, // max code printed as UTF-8
+ uint quote, // 0:no quotes, 1:escape ", 2: print in quotes
+ int use_color // 0:no, 1:use, 2:use all
+)
+{
+ DASSERT(buf);
+ DASSERT( buf_size >= 100 );
+ DASSERT(src);
+
+ //--------------------------------------------------
+ #if TEST_MODE > 1
+ //--------------------------------------------------
+
+ if ( buf_size < 2 )
+ {
+ *buf = 0;
+ return 0;
+ }
+
+ struct { FastBuf_t b; char space[1000]; } fb;
+ InitializeFastBuf(&fb,sizeof(fb));
+ PrintFastBuf16BMG(&fb.b,src,src_len,utf8_max,quote,use_color);
+ mem_t mem = GetFastBufMem(&fb.b);
+
+ if ( mem.len >= buf_size )
+ mem.len = buf_size - 1;
+ memcpy(buf,mem.ptr,mem.len);
+ buf[mem.len] = 0;
+ ResetFastBuf(&fb.b);
+ return mem.len;
+
+ //--------------------------------------------------
+ #else // TEST_MODE <= 1
+ //--------------------------------------------------
+
+ #if TEST_MODE == 1
+ struct { FastBuf_t b; char space[1000]; } fb;
+ InitializeFastBuf(&fb,sizeof(fb));
+ PrintFastBuf16BMG(&fb.b,src,src_len,utf8_max,quote,use_color);
+ #endif
+
+ if ( buf_size < 30 )
+ {
+ #if TEST_MODE == 1
+ ResetFastBuf(&fb.b);
+ #endif
+ *buf = 0;
+ return 0;
+ }
+
+ #undef PRINT_UTF8_MAX
+ //#define PRINT_UTF8_MAX 0xdb7f
+
+ if ( utf8_max < 0x7f )
+ utf8_max = 0x7f;
+ #ifdef PRINT_UTF8_MAX
+ else if ( utf8_max > PRINT_UTF8_MAX )
+ utf8_max = PRINT_UTF8_MAX;
+ #endif
+
+ char *dest = buf, *last_u = 0;
+ char *dest_end = dest + buf_size - 25;
+ const u16 * src_end = src + ( src_len < 0 ? buf_size : src_len );
+
+ if (quote>1)
+ *dest++ = '"';
+
+ while ( dest < dest_end && src < src_end )
+ {
+ u16 code = ntohs(*src++);
+ if ( !code && src_len < 0 )
+ break;
+
+ if ( code == 0x1a )
+ {
+ // nintendos special escape sequence!
+ uint total_words = *(u8*)src+1 >> 1;
+ if ( total_words >= 3 && src + total_words - 1 <= src_end )
+ {
+ //HexDump(stdout,1,0,2,2*total_words,src-1,2*total_words);
+ const u16 code2 = ntohs(*src++);
+ if ( code2 == 0x800 && use_color > 0 && be16(src) == 1 )
+ {
+ const u16 color = be16(src+1);
+ const KeywordTab_t *kt;
+ for ( kt = bmg_color; kt->name1 && kt->id != color; kt++ )
+ ;
+ if ( kt->name1 && opt_bmg_color_name >= kt->opt )
+ {
+ *dest++ = '\\';
+ *dest++ = 'c';
+ *dest++ = '{';
+ ccp name = kt->name1;
+ while (*name)
+ *dest++ = tolower((int)*name++);
+ *dest++ = '}';
+ }
+ else
+ dest += sprintf(dest,"\\c{%x}",color);
+ src += 2;
+ continue;
+ }
+
+ if ( code2 == 0x801 )
+ {
+ if ( opt_bmg_old_escapes || last_u != dest )
+ dest = snprintfE(dest,dest_end,"\\u{%x}",be32(src));
+ else
+ dest = snprintfE(dest-1,dest_end,",%x}",be32(src));
+ last_u = dest;
+ src += 2;
+ continue;
+ }
+
+ if ( ( total_words > 6 || opt_bmg_x_escapes ) && opt_bmg_old_escapes )
+ {
+ src -= 2;
+ while ( total_words-- > 0 )
+ dest = snprintfE(dest,dest_end,"\\x{%x}",ntohs(*src++));
+ continue;
+ }
+ else if ( opt_bmg_x_escapes )
+ {
+ if ( dest < dest_end )
+ {
+ dest = StringCopyE(dest,dest_end,"\\x{1a");
+ src--;
+ while ( total_words-- > 1 )
+ dest = snprintfE(dest,dest_end,",%x",ntohs(*src++));
+ }
+ *dest++ = '}';
+ continue;
+ }
+
+ u64 xcode = 0;
+ switch( total_words & 3 )
+ {
+ case 3 & 3: xcode = be16(src); src += 1; break;
+ case 4 & 3: xcode = be32(src); src += 2; break;
+ case 5 & 3: xcode = be48(src); src += 3; break;
+ case 6 & 3: xcode = be64(src); src += 4; break;
+ }
+
+ dest = snprintfE(dest,dest_end,"\\z{%x,%llx",code2,xcode);
+ while ( total_words > 6 )
+ {
+ dest = snprintfE(dest,dest_end,",%llx",be64(src));
+ src += 4;
+ total_words -= 4;
+ }
+ *dest++ = '}';
+ continue;
+ }
+ }
+
+ if ( code < ' ' || code == 0x7f || code == 0xff )
+ {
+ *dest++ = '\\';
+ switch(code)
+ {
+ case '\\': *dest++ = '\\'; break;
+ case '\a': *dest++ = 'a'; break;
+ case '\b': *dest++ = 'b'; break;
+ case '\f': *dest++ = 'f'; break;
+ case '\n': *dest++ = 'n'; break;
+ case '\r': *dest++ = 'r'; break;
+ case '\t': *dest++ = 't'; break;
+ case '\v': *dest++ = 'v'; break;
+
+ default:
+ *dest++ = ( code >> 6 & 3 ) | '0';
+ *dest++ = ( code >> 3 & 7 ) | '0';
+ *dest++ = ( code & 7 ) | '0';
+ }
+ }
+ #ifdef PRINT_UTF8_MAX
+ else if ( code > utf8_max )
+ dest += sprintf(dest,"\\x{%x}",code);
+ #else
+ else if ( quote && code == '"' )
+ {
+ *dest++ = '\\';
+ *dest++ = '"';
+ }
+ else if ( code > utf8_max
+ || code >= 0xdb80 && code <= 0xf8ff // surrogates & private use
+ || ( code & 0xffff ) >= 0xfffe // invalid
+ )
+ {
+ dest = snprintfE(dest,dest_end,"\\x{%x}",code);
+ }
+ #endif
+ else
+ dest = PrintUTF8Char(dest,code);
+ }
+
+ if ( dest > buf && dest[-1] == ' ' )
+ {
+ dest[-1] = '\\';
+ *dest++ = '0';
+ *dest++ = '4';
+ *dest++ = '0';
+ }
+
+ if (quote>1)
+ *dest++ = '"';
+
+ *dest = 0;
+
+ #if TEST_MODE == 1
+ const mem_t mem = GetFastBufMem(&fb.b);
+ if ( mem.len < buf_size )
+ {
+
+ XDump_t xd;
+ InitializeXDump(&xd);
+ xd.f = stderr;
+ xd.indent = 2;
+ xd.assumed_size = mem.len;
+ XDiff( &xd, buf,dest-buf,true, mem.ptr,mem.len,true, 5,false );
+ }
+ ResetFastBuf(&fb.b);
+ #endif
+
+ return dest - buf;
+
+ //--------------------------------------------------
+ #endif // TEST_MODE <= 1
+ //--------------------------------------------------
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintFastBuf16BMG
+(
+ // return the number of appended bytes
+
+ FastBuf_t *fb, // destination buffer (data appended)
+ const u16 *src, // source
+ int src_len, // length of source in u16 words, -1:NULL term
+ u16 utf8_max, // max code printed as UTF-8
+ uint quote, // 0:no quotes, 1:escape ", 2: print in quotes
+ int use_color // >0: use \c{...}
+)
+{
+ DASSERT(fb);
+ DASSERT(src);
+
+ #undef PRINT_UTF8_MAX
+ //#define PRINT_UTF8_MAX 0xdb7f
+
+ if ( utf8_max < 0x7f )
+ utf8_max = 0x7f;
+ #ifdef PRINT_UTF8_MAX
+ else if ( utf8_max > PRINT_UTF8_MAX )
+ utf8_max = PRINT_UTF8_MAX;
+ #endif
+
+ const uint start_len = GetFastBufLen(fb);
+
+ char buf[1300];
+ char *dest = buf;
+ char *dest_term = dest + sizeof(buf) - 1;
+ char *dest_end = dest_term - 270; // escape sequence with 256 bytes possible
+ char *last_u = 0;
+
+ if ( src_len < 0 )
+ src_len = GetLength16BMG(src,1000000);
+ const u16 * src_end = src + src_len;
+
+ if (quote>1)
+ *dest++ = '"';
+
+ while ( src < src_end )
+ {
+ if ( dest >= dest_end )
+ {
+ AppendFastBuf(fb,buf,dest-buf);
+ dest = buf;
+ last_u = 0;
+ }
+
+ u16 code = ntohs(*src++);
+ if ( !code && src_len < 0 )
+ break;
+
+ if ( code == 0x1a )
+ {
+ // nintendos special escape sequence!
+ uint total_words = *(u8*)src+1 >> 1;
+ if ( total_words >= 3 && src + total_words - 1 <= src_end )
+ {
+ //HexDump(stdout,1,0,2,2*total_words,src-1,2*total_words);
+ const u16 code2 = ntohs(*src++);
+ if ( code2 == 0x800 && use_color > 0 && be16(src) == 1 )
+ {
+ const u16 color = be16(src+1);
+ const KeywordTab_t *kt;
+ for ( kt = bmg_color; kt->name1 && kt->id != color; kt++ )
+ ;
+ if ( kt->name1 && opt_bmg_color_name >= kt->opt )
+ {
+ *dest++ = '\\';
+ *dest++ = 'c';
+ *dest++ = '{';
+ ccp name = kt->name1;
+ while (*name)
+ *dest++ = tolower((int)*name++);
+ *dest++ = '}';
+ }
+ else
+ dest += sprintf(dest,"\\c{%x}",color);
+ src += 2;
+ continue;
+ }
+
+ if ( code2 == 0x801 )
+ {
+ if ( opt_bmg_old_escapes || last_u != dest )
+ dest = snprintfE(dest,dest_term,"\\u{%x}",be32(src));
+ else
+ dest = snprintfE(dest-1,dest_term,",%x}",be32(src));
+ last_u = dest;
+ src += 2;
+ continue;
+ }
+
+ if ( ( total_words > 6 || opt_bmg_x_escapes ) && opt_bmg_old_escapes )
+ {
+ src -= 2;
+ while ( total_words-- > 0 )
+ dest = snprintfE(dest,dest_term,"\\x{%x}",ntohs(*src++));
+ continue;
+ }
+ else if ( opt_bmg_x_escapes )
+ {
+ if ( dest < dest_term )
+ {
+ dest = StringCopyE(dest,dest_term,"\\x{1a");
+ src--;
+ while ( total_words-- > 1 )
+ dest = snprintfE(dest,dest_term,",%x",ntohs(*src++));
+ }
+ *dest++ = '}';
+ continue;
+ }
+
+ u64 xcode = 0;
+ switch( total_words & 3 )
+ {
+ case 3 & 3: xcode = be16(src); src += 1; break;
+ case 4 & 3: xcode = be32(src); src += 2; break;
+ case 5 & 3: xcode = be48(src); src += 3; break;
+ case 6 & 3: xcode = be64(src); src += 4; break;
+ }
+
+ dest = snprintfE(dest,dest_term,"\\z{%x,%llx",code2,xcode);
+ while ( total_words > 6 )
+ {
+ dest = snprintfE(dest,dest_term,",%llx",be64(src));
+ src += 4;
+ total_words -= 4;
+ }
+ *dest++ = '}';
+ continue;
+ }
+ }
+
+ if ( code < ' ' || code == 0x7f || code == 0xff )
+ {
+ *dest++ = '\\';
+ switch(code)
+ {
+ case '\\': *dest++ = '\\'; break;
+ case '\a': *dest++ = 'a'; break;
+ case '\b': *dest++ = 'b'; break;
+ case '\f': *dest++ = 'f'; break;
+ case '\n': *dest++ = 'n'; break;
+ case '\r': *dest++ = 'r'; break;
+ case '\t': *dest++ = 't'; break;
+ case '\v': *dest++ = 'v'; break;
+
+ default:
+ *dest++ = ( code >> 6 & 3 ) | '0';
+ *dest++ = ( code >> 3 & 7 ) | '0';
+ *dest++ = ( code & 7 ) | '0';
+ }
+ }
+ #ifdef PRINT_UTF8_MAX
+ else if ( code > utf8_max )
+ dest += sprintf(dest,"\\x{%x}",code);
+ #else
+ else if ( quote && code == '"' )
+ {
+ *dest++ = '\\';
+ *dest++ = '"';
+ }
+ else if ( code > utf8_max
+ || code >= 0xdb80 && code <= 0xf8ff // surrogates & private use
+ || ( code & 0xffff ) >= 0xfffe // invalid
+ )
+ {
+ dest = snprintfE(dest,dest_term,"\\x{%x}",code);
+ }
+ #endif
+ else
+ dest = PrintUTF8Char(dest,code);
+ }
+
+ if ( dest > buf && dest[-1] == ' ' )
+ {
+ dest[-1] = '\\';
+ *dest++ = '0';
+ *dest++ = '4';
+ *dest++ = '0';
+ }
+
+ if (quote>1)
+ *dest++ = '"';
+
+ AppendFastBuf(fb,buf,dest-buf);
+ return GetFastBufLen(fb) - start_len;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if 0
+uint ScanFastBuf16BMG
+(
+
+ FastBuf_t *fb, // destination buffer
+ ccp src, // UTF-8 source
+ int src_len, // length of source, -1: determine with strlen()
+ const bmg_t *bmg // NULL or support for \m{...}
+);
+#endif
+
+uint ScanString16BMG
+(
+ u16 * buf, // destination buffer
+ uint buf_size, // size of 'buf' in u16 words
+ ccp src, // UTF-8 source
+ int src_len, // length of source, -1: determine with strlen()
+ const bmg_t *bmg // NULL or support for \m{...}
+)
+{
+ DASSERT(buf);
+ DASSERT(src);
+
+ u16 * dest = buf;
+ u16 * dest_end = dest + buf_size;
+ ccp src_end = src + ( src_len < 0 ? strlen(src) : src_len );
+
+ while ( dest < dest_end && src < src_end )
+ {
+ if ( *src == '\\' && src+1 < src_end )
+ {
+ src++;
+ switch(*src++)
+ {
+ case '\\': *dest++ = htons('\\'); break;
+ case 'a': *dest++ = htons('\a'); break;
+ case 'b': *dest++ = htons('\b'); break;
+ case 'f': *dest++ = htons('\f'); break;
+ case 'n': *dest++ = htons('\n'); break;
+ case 'r': *dest++ = htons('\r'); break;
+ case 't': *dest++ = htons('\t'); break;
+ case 'v': *dest++ = htons('\v'); break;
+
+ case 'm':
+ case 'M':
+ if ( src < src_end && *src == '{' )
+ {
+ const char mode = src[-1];
+ src++;
+ bmg_scan_mid_t scan;
+ for(;;)
+ {
+ while ( src < src_end && *src == ',' )
+ src++;
+ if ( ScanBMGMID(&scan,0,src,src_end) < 1 )
+ break;
+ src = scan.scan_end;
+
+ PRINT("\\M{%x} : bmg=%d\n",scan.mid[0],bmg!=0);
+
+ const bmg_item_t *dptr = 0;
+ if ( bmg && mode == 'm' )
+ dptr = FindItemBMG(bmg,scan.mid[0]);
+ if ( !dptr && bmg_macros )
+ dptr = FindItemBMG(bmg_macros,scan.mid[0]);
+
+ if (!dptr)
+ {
+ char buf[16];
+ snprintf(buf,sizeof(buf),"\\%c{%x}",mode,scan.mid[0]);
+ ccp src = buf;
+ while ( *src && dest < dest_end )
+ *dest++ = htons(*src++);
+ }
+ else if ( dptr->text )
+ {
+ const u16 *copy = dptr->text;
+ const u16 *copy_end = copy + dptr->len;
+ while ( copy < copy_end && dest < dest_end )
+ *dest++ = *copy++;
+ }
+ if ( *src != ',' )
+ break;
+ }
+ if ( *src == '}' )
+ src++;
+ }
+ break;
+
+ case 'x':
+ {
+ const bool have_brace = *src == '{';
+ if (have_brace)
+ src++;
+ for(;;)
+ {
+ char *end;
+ u16 code = strtoul(src,&end,16);
+ src = end;
+ if ( dest < dest_end )
+ *dest++ = htons(code);
+ if ( !have_brace || *src != ',' )
+ break;
+ src++;
+ }
+ if ( have_brace && *src == '}' )
+ src++;
+ }
+ break;
+
+ case 'z':
+ {
+ const bool have_brace = *src == '{';
+ if (have_brace)
+ src++;
+ char * end;
+ const u16 code1 = strtoul(src,&end,16);
+ src = end;
+ uint total_words = code1+0x100 >> 9;
+ if ( dest + total_words <= dest_end )
+ {
+ *dest++ = htons(0x1a);
+ *dest++ = htons(code1);
+ u64 xcode = 0;
+ if ( *src == ',' )
+ {
+ xcode = strtoull(src+1,&end,16);
+ src = end;
+ }
+ switch( total_words & 3 )
+ {
+ case 3 & 3: write_be16(dest,xcode); dest += 1; break;
+ case 4 & 3: write_be32(dest,xcode); dest += 2; break;
+ case 5 & 3: write_be48(dest,xcode); dest += 3; break;
+ case 6 & 3: write_be64(dest,xcode); dest += 4; break;
+ }
+ while ( total_words > 6 )
+ {
+ total_words -= 4;
+ if ( *src == ',' )
+ {
+ xcode = strtoull(src+1,&end,16);
+ src = end;
+ }
+ else
+ xcode = 0;
+ write_be64(dest,xcode);
+ dest += 4;
+ }
+ while ( *src == ',' )
+ {
+ strtoull(src+1,&end,16);
+ src = end;
+ }
+ }
+ if ( have_brace && *src == '}' )
+ src++;
+ }
+ break;
+
+ case 'u':
+ {
+ const bool have_brace = *src == '{';
+ if (have_brace)
+ src++;
+ for(;;) // >>>>> comma list support since v1.44 <<<<<
+ {
+ char * end;
+ u32 code = strtoul(src,&end,16);
+ src = end;
+ if ( dest + 4 <= dest_end )
+ {
+ *dest++ = htons(0x001a);
+ *dest++ = htons(0x0801);
+ *dest++ = htons(code>>16);
+ *dest++ = htons(code);
+ }
+ if ( !have_brace || *src != ',' )
+ break;
+ src++;
+ }
+ if ( have_brace && *src == '}' )
+ src++;
+ }
+ break;
+
+ case 'c':
+ {
+ const bool have_brace = *src == '{';
+ if (have_brace)
+ src++;
+
+ u16 code = 0;
+ if ( *src >= '0' && *src <= '9' )
+ {
+ char *end;
+ code = strtoul(src,&end,16);
+ src = end;
+ }
+ else
+ {
+ char keyname[50], *kp = keyname;
+ while (isalnum((int)*src))
+ {
+ if ( kp < keyname + sizeof(keyname) - 1 )
+ *kp++ = *src;
+ src++;
+ }
+ *kp = 0;
+
+ const KeywordTab_t *key = ScanKeyword(0,keyname,bmg_color);
+ if (key)
+ code = key->id;
+ else
+ code = strtoul(keyname,0,16);
+ }
+ if ( have_brace && *src == '}' )
+ src++;
+
+ if ( dest + 4 <= dest_end )
+ {
+ *dest++ = htons(0x001a);
+ *dest++ = htons(0x0800);
+ *dest++ = htons(0x0001);
+ *dest++ = htons(code);
+ }
+ }
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ src--;
+ u16 code = 0;
+ int idx;
+ for ( idx = 0; idx < 3 && *src >= '0' && *src <= '7'; idx++ )
+ code = code << 3 | *src++ - '0';
+ *dest++ = htons(code);
+ }
+ break;
+
+ default:
+ *dest++ = htons('\\');
+ src--;
+ break;
+ }
+ }
+ else
+ *dest++ = htons(ScanUTF8AnsiChar(&src));
+ }
+
+ return dest - buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static unsigned long int strtoul16
+(
+ const u16 * ptr16,
+ u16 ** end16,
+ int base
+)
+{
+ DASSERT(ptr16);
+
+ char buf[100];
+ char * dest = buf;
+ const u16 * p16 = ptr16;
+ while ( dest < buf + sizeof(buf)-1 )
+ {
+ int ch = be16(p16++);
+ if ( ch >= '0' && ch <= '9'
+ || ch >= 'a' && ch <= 'f'
+ || ch >= 'A' && ch <= 'F'
+ || ch == 'x'
+ || ch == 'X'
+ || isspace(ch)
+ )
+ {
+ *dest++ = ch;
+ }
+ else
+ break;
+ }
+ DASSERT( dest < buf + sizeof(buf) );
+ *dest = 0;
+
+ char *end;
+ unsigned long num = strtoul(buf,&end,base);
+ if (end16)
+ *end16 = (u16*)ptr16 + (end-buf);
+ return num;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static u16 * skip_ctrl16 ( const u16 * ptr )
+{
+ DASSERT(ptr);
+ for(;;)
+ {
+ const u16 ch = be16(ptr);
+ if ( !ch || ch > ' ' )
+ return (u16*)ptr;
+ ptr++;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static u16 * ScanRange16U32
+(
+ const u16 * arg, // text to scan (valid pointer)
+ u32 * p_stat, // NULL or return status:
+ // number of scanned numbers (0..2)
+ u32 * p_n1, // first return value (valid pointer)
+ u32 * p_n2, // second return value (valid pointer)
+ u32 min, // min allowed value (fix num)
+ u32 max // max allowed value (fix num)
+)
+{
+ DASSERT(arg);
+ DASSERT(p_n1);
+ DASSERT(p_n2);
+ TRACE("ScanRange16U32()\n");
+
+ int stat = 0;
+
+ u32 n1 = ~(u32)0, n2 = 0;
+ arg = skip_ctrl16(arg);
+
+ if ( be16(arg) == '-' )
+ n1 = min;
+ else
+ {
+ u16 * end;
+ u32 num = strtoul16(arg,&end,0);
+ if ( arg == end )
+ goto abort;
+
+ stat = 1;
+ arg = skip_ctrl16(end);
+ n1 = num;
+ }
+
+ if ( be16(arg) != '-' )
+ {
+ stat = 1;
+ n2 = n1;
+ goto abort;
+ }
+
+ arg = skip_ctrl16(arg+1);
+ u16 * end;
+ n2 = strtoul16(arg,&end,0);
+ if ( end == arg )
+ n2 = max;
+ stat = 2;
+ arg = end;
+
+ abort:
+
+ if ( stat > 0 )
+ {
+ if ( n1 < min )
+ n1 = min;
+ if ( n2 > max )
+ n2 = max;
+ }
+
+ if ( !stat || n1 > n2 )
+ {
+ stat = 0;
+ n1 = ~(u32)0;
+ n2 = 0;
+ }
+
+ if (p_stat)
+ *p_stat = stat;
+ *p_n1 = n1;
+ *p_n2 = n2;
+
+ arg = skip_ctrl16(arg);
+
+ TRACE("END ScanRange16U32() stat=%u, n=%u..%u\n",stat,n1,n2);
+ return (u16*)arg;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ScanSectionsBMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bmg_sect_list_t * ScanSectionsBMG ( cvp data, uint size )
+{
+ //--- initialize
+
+ if ( !data || size < sizeof(bmg_header_t) || memcmp(data,BMG_MAGIC,8) )
+ return 0;
+
+ bmg_sect_list_t sl;
+ memset(&sl,0,sizeof(sl));
+ sl.header = (bmg_header_t*)data;
+ sl.source_size = ntohl(sl.header->size);
+ sl.n_sections = ntohl(sl.header->n_sections);
+
+ FastBuf_t fb;
+ InitializeFastBufAlloc(&fb,10*sizeof(bmg_sect_info_t));
+
+ bmg_sect_info_t si;
+
+
+ //--- scan sections
+
+ u8 *data_end = (u8*)data + size;
+ bmg_section_t *sect = (bmg_section_t*)(sl.header+1);
+ for(;;)
+ {
+ if ( (u8*)sect + sizeof(*sect) > data_end )
+ break;
+
+ memset(&si,0,sizeof(si));
+ si.offset = (u8*)sect - (u8*)data;
+ si.sect_size = si.real_size = ntohl(sect->size);
+ if ( (u8*)sect + si.real_size > data_end )
+ si.real_size = data_end - (u8*)sect;
+
+ si.sect = sect;
+ si.info = "?";
+
+ if (!memcmp(sect->magic,BMG_INF_MAGIC,sizeof(sect->magic)))
+ {
+ sl.pinf = (bmg_inf_t*)sect;
+ si.head_size = sizeof(bmg_inf_t);
+ si.elem_size = ntohs(sl.pinf->inf_size);
+ si.n_elem_head = ntohs(sl.pinf->n_msg);
+ si.known = true;
+ si.supported = true;
+ si.info = "offset & attributes";
+ }
+ else if (!memcmp(sect->magic,BMG_DAT_MAGIC,sizeof(sect->magic)))
+ {
+ sl.pdat = (bmg_dat_t*)sect;
+ si.head_size = sizeof(bmg_dat_t);
+ si.known = true;
+ si.supported = true;
+ si.info = "string pool";
+ }
+ else if (!memcmp(sect->magic,BMG_STR_MAGIC,sizeof(sect->magic)))
+ {
+ sl.pstr = (bmg_str_t*)sect;
+ si.head_size = sizeof(bmg_str_t);
+ si.known = true;
+ si.supported = false;
+ si.info = "secondary string pool";
+ }
+ else if (!memcmp(sect->magic,BMG_MID_MAGIC,sizeof(sect->magic)))
+ {
+ sl.pmid = (bmg_mid_t*)sect;
+ si.head_size = sizeof(bmg_mid_t);
+ si.elem_size = sizeof(u32);
+ si.n_elem_head = ntohs(sl.pinf->n_msg);
+ si.known = true;
+ si.supported = true;
+ si.info = "message ids";
+ }
+ else if (!memcmp(sect->magic,BMG_FLW_MAGIC,sizeof(sect->magic)))
+ {
+ sl.pflw = (bmg_flw_t*)sect;
+ si.known = true;
+ }
+ else if (!memcmp(sect->magic,BMG_FLI_MAGIC,sizeof(sect->magic)))
+ {
+ sl.pfli = (bmg_fli_t*)sect;
+ si.known = true;
+ }
+
+ if (si.elem_size)
+ {
+ si.n_elem = ( si.real_size - si.head_size ) / si.elem_size;
+ if ( si.n_elem_head && si.n_elem > si.n_elem_head )
+ si.n_elem = si.n_elem_head;
+ }
+
+ if ( si.head_size && si.head_size < si.real_size )
+ {
+ si.is_empty = true;
+ const u8 *ptr = (u8*)sect + si.head_size;
+ const u8 *end = (u8*)sect + si.real_size;
+ while ( ptr < end )
+ if ( *ptr++ )
+ {
+ si.is_empty = false;
+ break;
+ }
+ }
+
+ sect = (bmg_section_t*)( (u8*)sect + si.real_size );
+ AppendFastBuf(&fb,&si,sizeof(si));
+ sl.n_info++;
+ }
+
+
+ //--- finalize
+
+ memset(&si,0,sizeof(si));
+ AppendFastBuf(&fb,&si,sizeof(si));
+
+ mem_t mem = GetFastBufMem(&fb);
+ sl.this_size = sizeof(sl) + mem.len;
+ bmg_sect_list_t *dest = MALLOC(sl.this_size);
+ memcpy(dest,&sl,sizeof(sl));
+ memcpy(dest->info,mem.ptr,mem.len);
+ ResetFastBuf(&fb);
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const bmg_sect_info_t * SearchSectionBMG
+(
+ const bmg_sect_list_t *sl, // valid, created by ScanSectionsBMG()
+ cvp search, // magic to search
+ bool abbrev, // if not found: allow abbreviations _> strcmp()
+ bool icase // if not found: ignore case and try again
+)
+{
+ DASSERT(sl);
+
+ // make sure, that magic is null terminated
+ char magic[5];
+ memcpy(magic,search,4);
+ magic[4] = 0;
+
+
+ //--- search exact
+
+ const bmg_sect_info_t *si;
+ for ( si = sl->info; si->sect; si++ )
+ if (!memcmp(magic,si->sect->magic,4))
+ return si;
+
+
+ //--- search abbrev
+
+ const uint mlen = strlen(magic);
+ if (!mlen)
+ abbrev = false;
+
+ if (abbrev)
+ {
+ for ( si = sl->info; si->sect; si++ )
+ if (!memcmp(magic,si->sect->magic,mlen))
+ return si;
+ }
+
+
+ //--- search icase
+
+ if (icase)
+ {
+ uint i;
+ for ( i = 0; i < 4; i++ )
+ magic[i] = tolower((int)magic[i]);
+
+ const bmg_sect_info_t *abbrev_found = 0;
+ char buf[5];
+
+ for ( si = sl->info; si->sect; si++ )
+ {
+ for ( i = 0; i < 4; i++ )
+ buf[i] = tolower((int)si->sect->magic[i]);
+
+ if (!memcmp(magic,buf,4))
+ return si;
+ if ( abbrev && !memcmp(magic,buf,mlen) )
+ abbrev_found = si;
+ }
+ return abbrev_found;
+ }
+
+ return 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_raw_section_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bmg_raw_section_t * GetRawSectionBMG ( bmg_t *bmg, cvp magic )
+{
+ DASSERT(bmg);
+ DASSERT(magic);
+
+ bmg_raw_section_t *raw;
+ for ( raw = bmg->first_raw; raw; raw = raw->next )
+ if (!memcmp(raw->magic,magic,sizeof(raw->magic)))
+ {
+ PRINT("GetRawSectionBMG(%s), found\n",PrintID(magic,4,0));
+ return raw;
+ }
+
+ PRINT("GetRawSectionBMG(%s), not found\n",PrintID(magic,4,0));
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bmg_raw_section_t * CreateRawSectionBMG ( bmg_t *bmg, cvp magic )
+{
+ bmg_raw_section_t **raw;
+ for ( raw = &bmg->first_raw; *raw; raw = &(*raw)->next )
+ if (!memcmp((*raw)->magic,magic,sizeof((*raw)->magic)))
+ {
+ PRINT("CreateRawSectionBMG(%s), found\n",PrintID(magic,4,0));
+ return (*raw);
+ }
+
+ DASSERT(raw);
+ PRINT("CreateRawSectionBMG(%s), create\n",PrintID(magic,4,0));
+ bmg_raw_section_t *p = CALLOC(1,sizeof(*p));
+ memcpy(p->magic,magic,sizeof(p->magic));
+ InitializeFastBuf(&p->data,sizeof(p->data));
+ return *raw = p;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// attribute helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetAttribBMG
+(
+ const bmg_t * bmg, // pointer to bmg
+ bmg_item_t * item // dest item
+)
+{
+ DASSERT(bmg);
+ DASSERT(item);
+
+ item->slot = BMG_NO_PREDEF_SLOT;
+ item->attrib_used = bmg->attrib_used;
+ memcpy(item->attrib,bmg->attrib,sizeof(item->attrib));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void CopyAttribBMG
+(
+ const bmg_t * dest, // pointer to destination bmg
+ bmg_item_t * dptr, // dest item
+ const bmg_item_t * sptr // source item
+)
+{
+ DASSERT(dest);
+ DASSERT(dptr);
+ DASSERT(sptr);
+
+ DASSERT( dest->attrib_used <= BMG_ATTRIB_SIZE );
+ DASSERT( sptr->attrib_used <= BMG_ATTRIB_SIZE );
+
+ if ( sptr->slot != BMG_NO_PREDEF_SLOT )
+ dptr->slot = sptr->slot;
+
+ if ( opt_bmg_force_attrib )
+ {
+ //dptr->attrib_used = dest->attrib_used;
+ memcpy(dptr->attrib,bmg_force_attrib,dptr->attrib_used);
+ }
+ else if ( sptr->attrib_used )
+ {
+ if ( dest->attrib_used <= sptr->attrib_used )
+ dptr->attrib_used = dest->attrib_used;
+ else
+ {
+ dptr->attrib_used = sptr->attrib_used;
+ memset( dptr->attrib+dptr->attrib_used, 0, BMG_ATTRIB_SIZE - sptr->attrib_used );
+ }
+ memcpy(dptr->attrib,sptr->attrib,dptr->attrib_used);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static char * scan_attrib ( u8 *dest, uint size, ccp src )
+{
+ DASSERT(dest);
+ DASSERT( size <= BMG_ATTRIB_SIZE );
+ memset(dest,0,BMG_ATTRIB_SIZE);
+
+ while ( *src == ' ' || *src == '\t' )
+ src++;
+ const bool have_brackets = *src == '[';
+ if ( have_brackets )
+ src++;
+
+ int idx = 0, add_idx = 0;
+ for(;;)
+ {
+ while ( *src == ' ' || *src == '\t' )
+ src++;
+
+ if ( *src == ',' )
+ {
+ src++;
+ idx++;
+ add_idx = 0;
+ }
+ else if ( *src == '/' )
+ {
+ src++;
+ idx = ALIGN32(idx+1,4);
+ add_idx = 0;
+ }
+ else if ( *src == '%' )
+ {
+ src++;
+ char *end;
+ u32 num = str2ul(src,&end,16);
+ if ( src == end )
+ break;
+
+ idx = ALIGN32(idx+add_idx,4);
+ noPRINT("U32[0x%02x/%02x]: %8x\n",idx,size,num);
+
+ char buf[4];
+ write_be32(buf,num);
+ const int max_copy = (int)size - (int)idx;
+ if ( max_copy > 0 )
+ memcpy(dest+idx,buf, max_copy < 4 ? max_copy : 4 );
+ idx += 3;
+ add_idx = 1;
+ src = end;
+ }
+ else
+ {
+ char *end;
+ u8 num = str2ul(src,&end,16);
+ if ( src == end )
+ break;
+
+ idx += add_idx;
+ noPRINT("U8[0x%02x/%02x]: %8x\n",idx,size,num);
+ if ( idx < size )
+ dest[idx] = num;
+ add_idx = 1;
+ src = end;
+ }
+ }
+ //HexDump16(stderr,0,0,dest,size);
+
+ if ( have_brackets && *src == ']' )
+ src++;
+ return (char*)src;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void copy_param
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src // pointer to source bmg
+)
+{
+ DASSERT(dest);
+ DASSERT(src);
+
+ if ( !dest->param_defined && src->param_defined )
+ {
+ memcpy(dest->attrib,src->attrib,sizeof(dest->attrib));
+ dest->inf_size = src->inf_size;
+ dest->have_mid = src->have_mid;
+ dest->attrib_used = src->attrib_used;
+ dest->use_color_names = src->use_color_names;
+ dest->use_mkw_messages = src->use_mkw_messages;
+ dest->param_defined = true;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_item_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// special values for bmg_item_t::text
+u16 bmg_null_entry[] = {0};
+
+//////////////////////////////////////////////////////////////////////////////
+
+void FreeItemBMG( bmg_item_t * bi )
+{
+ DASSERT(bi);
+ if (bi->text)
+ {
+ if ( bi->alloced_size && !isSpecialEntryBMG(bi->text) )
+ FREE( bi->text);
+ bi->text = bmg_null_entry;
+ }
+ bi->len = bi->alloced_size = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AssignItemText16BMG
+(
+ bmg_item_t * bi, // valid item
+ const u16 * text16, // text to store
+ int len // length of 'text16'.
+ // if -1: detect length by finding
+ // first NULL value.
+)
+{
+ DASSERT(bi);
+ if ( !text16 || isSpecialEntryBMG(text16) )
+ {
+ FreeItemBMG(bi);
+ bi->text = (u16*)text16;
+ return;
+ }
+
+ if ( len < 0 )
+ {
+ const u16 * ptr = text16;
+ while (*ptr)
+ ptr++;
+ len = ptr - text16;
+ }
+
+ if ( bi->text && bi->alloced_size )
+ {
+ if ( len+1 <= bi->alloced_size )
+ {
+ memcpy(bi->text,text16,len*sizeof(*bi->text));
+ bi->text[len] = 0;
+ bi->len = len;
+ return;
+ }
+ FREE(bi->text);
+ }
+
+ bi->alloced_size = len + 1;
+ bi->len = len;
+ bi->text = CALLOC(bi->alloced_size,sizeof(*bi->text));
+ memcpy(bi->text,text16,len*sizeof(*bi->text));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AssignItemScanTextBMG
+(
+ bmg_item_t * bi, // valid item
+ ccp utf8, // UTF-8 text to scan
+ int len // length of 'utf8'.
+ // if -1: detect length with strlen()
+)
+{
+ DASSERT(bi);
+
+ if (!utf8)
+ AssignItemText16BMG(bi,0,0);
+ else
+ {
+ const uint bufsize = 10000;
+ u16 buf[bufsize];
+ len = ScanString16BMG(buf,bufsize,utf8,len,0);
+ AssignItemText16BMG(bi,buf,len);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AssignItemTextBMG
+(
+ bmg_item_t * bi, // valid item
+ ccp utf8, // UTF-8 text to store
+ int len // length of 'utf8'.
+ // if -1: detect length with strlen()
+)
+{
+ DASSERT(bi);
+
+ if (!utf8)
+ AssignItemText16BMG(bi,0,0);
+ else
+ {
+ u16 buf[10000];
+ u16 *ptr = buf;
+ u16 *end = buf + sizeof(buf)/sizeof(*buf);
+
+ if ( len < 0 )
+ {
+ while ( ptr < end )
+ {
+ ulong code = ScanUTF8AnsiChar(&utf8);
+ if (!code)
+ break;
+ *ptr++ = htons(code);
+ }
+ }
+ else
+ {
+ ccp utf8_end = utf8 + len;
+ while ( utf8 < utf8_end && ptr < end )
+ *ptr++ = htons(ScanUTF8AnsiCharE(&utf8,utf8_end));
+ }
+ AssignItemText16BMG(bi,buf,ptr-buf);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsItemEqualBMG
+(
+ const bmg_item_t * bi1, // NULL or valid item
+ const bmg_item_t * bi2 // NULL or valid item
+)
+{
+ if ( bi1 == bi2 )
+ return true;
+ if ( !bi1 || !bi2 )
+ return false;
+
+ return bi1->len == bi2->len
+ && bi1->attrib_used == bi2->attrib_used
+ && !memcmp(bi1->text,bi2->text,bi1->len*sizeof(*bi2->text))
+ && !memcmp(bi1->attrib,bi2->attrib,bi1->attrib_used);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static uint find_item_helper ( const bmg_t * bmg, u32 mid, bool * found )
+{
+ DASSERT(bmg);
+ DASSERT(found);
+
+ int beg = 0;
+ int end = bmg->item_used - 1;
+ while ( beg <= end )
+ {
+ const uint idx = (beg+end)/2;
+ const bmg_item_t * item = bmg->item + idx;
+ if ( mid < item->mid )
+ end = idx - 1 ;
+ else if ( mid > item->mid )
+ beg = idx + 1;
+ else
+ {
+ *found = true;
+ return idx;
+ }
+ }
+
+ *found = false;
+ return beg;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bmg_item_t * FindItemBMG ( const bmg_t * bmg, u32 mid )
+{
+ DASSERT(bmg);
+
+ bool found;
+ uint idx = find_item_helper(bmg,mid,&found);
+ DASSERT( idx < bmg->item_used || idx == bmg->item_used && !found );
+
+ return found ? bmg->item + idx : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bmg_item_t * FindAnyItemBMG ( const bmg_t * bmg, u32 mid )
+{
+ DASSERT(bmg);
+
+ bool found;
+ uint idx = find_item_helper(bmg,mid,&found);
+ DASSERT( idx < bmg->item_used || idx == bmg->item_used && !found );
+
+ bmg_item_t * item = bmg->item + idx;
+ if (!found)
+ {
+ bmg_item_t * item_end = bmg->item + bmg->item_used;
+ while ( item < item_end && item->mid < mid )
+ item++;
+ }
+ return item;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bmg_item_t * InsertItemBMG
+ ( bmg_t *bmg, u32 mid, u8 *attrib, uint attrib_used, bool *old_item )
+{
+ DASSERT(bmg);
+ DASSERT( attrib_used <= BMG_ATTRIB_SIZE );
+
+ if (opt_bmg_force_attrib)
+ {
+ attrib_used = bmg->attrib_used;
+ attrib = bmg_force_attrib;
+ }
+ else if ( attrib_used > bmg->attrib_used )
+ attrib_used = bmg->attrib_used;
+
+ bmg->param_defined = true; // don't accept modifications on patch
+
+ //if ( attrib && attrib_used ) HexDump16(stderr,0,0x100,attrib,attrib_used);
+
+ bool found;
+ uint idx = find_item_helper(bmg,mid,&found);
+ DASSERT( idx < bmg->item_used || idx == bmg->item_used && !found );
+
+ if (old_item)
+ *old_item = found;
+
+ if (found)
+ {
+ bmg_item_t * item = bmg->item + idx;
+ if ( attrib && attrib_used )
+ {
+ item->attrib_used = bmg->attrib_used;
+ memcpy(item->attrib,bmg->attrib,sizeof(item->attrib));
+ memcpy(item->attrib,attrib,attrib_used);
+ }
+ return item;
+ }
+
+ DASSERT( bmg->item_used <= bmg->item_size );
+ if ( bmg->item_used == bmg->item_size )
+ {
+ bmg->item_size += 1000;
+ bmg->item = REALLOC(bmg->item,bmg->item_size*sizeof(*bmg->item));
+ }
+
+ bmg_item_t * item = bmg->item + idx;
+ memmove( item+1, item, (bmg->item_used-idx) * sizeof(*bmg->item) );
+ bmg->item_used++;
+ DASSERT( idx < bmg->item_used );
+ memset(item,0,sizeof(*item));
+ item->mid = mid;
+ item->slot = BMG_NO_PREDEF_SLOT;
+
+ memcpy(item->attrib,bmg->attrib,sizeof(item->attrib));
+ if ( attrib && attrib_used )
+ {
+ item->attrib_used = bmg->attrib_used;
+ memcpy(item->attrib,attrib,attrib_used);
+ }
+ return item;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool DeleteItemBMG ( bmg_t *bmg, u32 mid )
+{
+ DASSERT(bmg);
+
+ bmg_item_t *dptr = FindItemBMG(bmg,mid);
+ if (dptr)
+ {
+ FreeItemBMG(dptr);
+ dptr->text = 0;
+ ResetAttribBMG(bmg,dptr);
+ return true;
+ }
+
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool HavePredifinedSlots ( bmg_t *bmg )
+{
+ DASSERT(bmg);
+
+ bmg_item_t *bi, *bi_end = bmg->item + bmg->item_used;
+ for ( bi = bmg->item; bi < bi_end; bi++ )
+ if ( bi->text && bi->slot != BMG_NO_PREDEF_SLOT )
+ return bmg->have_predef_slots = true;
+ return bmg->have_predef_slots = false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u16 * ModifiyItem
+(
+ // return a fixed 'ptr'
+ bmg_item_t * bi, // valid item, prepare text modification
+ const u16 * ptr // NULL or a pointer within 'bi->text'
+)
+{
+ DASSERT(bi);
+
+ if (!bi->alloced_size)
+ {
+ const uint size = (bi->len+1) * sizeof(*bi->text);
+ u16 *text = MALLOC(size);
+ memcpy(text,bi->text,size);
+ if (ptr)
+ ptr = text + ( ptr - bi->text );
+ bi->text = text;
+ bi->alloced_size = bi->len;
+ }
+ return (u16*)ptr;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MKW hooks ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static int GetTrackIndexBMG0 ( uint idx, int result_on_invalid )
+{
+ static const u8 track_pos[BMG_N_TRACK] =
+ {
+ 8, 1, 2, 4, 0, 5, 6, 7, 9,15,11, 3, 14,10,12,13,
+ 16,20,25,26, 27,31,23,18, 21,30,29,17, 24,22,19,28
+ };
+
+ return idx < BMG_N_TRACK
+ ? track_pos[idx]
+ : idx < BMG_N_CT_TRACK
+ ? idx
+ : result_on_invalid;
+}
+
+int (*GetTrackIndexBMG) ( uint idx, int result_on_invalid ) = GetTrackIndexBMG0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int GetArenaIndexBMG0 ( uint idx, int result_on_invalid )
+{
+ static const u8 arena_pos[BMG_N_ARENA] = { 1,0,3,2,4, 7,8,9,5,6 };
+
+ return idx < BMG_N_ARENA
+ ? arena_pos[idx]
+ : result_on_invalid;
+}
+
+int (*GetArenaIndexBMG) ( uint idx, int result_on_invalid ) = GetArenaIndexBMG0;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// bmg encoding support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+const KeywordTab_t TabEncodingBMG[] =
+{
+ // if opt==1: not implemented yet
+ { BMG_ENC_CP1252, "CP-1252", "CP1252", 0 },
+ { BMG_ENC_CP1252, "1", 0, 0 },
+
+ { BMG_ENC_UTF16BE, "UTF-16BE", "UTF16BE", 0 },
+ { BMG_ENC_UTF16BE, "2", 0, 0 },
+
+ { BMG_ENC_SHIFT_JIS, "SHIFT-JIS", "SHIFTJIS", 0 },
+ { BMG_ENC_SHIFT_JIS, "3", "SJIS", 0 },
+
+ { BMG_ENC_UTF8, "UTF-8", "UTF8", 0 },
+ { BMG_ENC_UTF8, "4", 0, 0 },
+
+ { BMG_ENC__UNDEFINED, "AUTO", "UNDEFINED", 0 },
+ { 0,0,0,0 }
+};
+
+//-----------------------------------------------------------------------------
+
+ccp GetEncodingNameBMG ( int encoding, ccp return_if_invalid )
+{
+ switch (encoding)
+ {
+ case BMG_ENC_CP1252: return "CP1252";
+ case BMG_ENC_UTF16BE: return "UTF-16/be";
+ case BMG_ENC_SHIFT_JIS: return "Shift-JIS";
+ case BMG_ENC_UTF8: return "UTF-8";
+ }
+ return return_if_invalid;
+}
+
+//-----------------------------------------------------------------------------
+
+int CheckEncodingBMG ( int encoding, int return_if_invalid )
+{
+ return encoding >= BMG_ENC__MIN && encoding <= BMG_ENC__MAX
+ ? encoding : return_if_invalid;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// setup/reset bmg_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void AssignEncodingBMG ( bmg_t * bmg, int encoding )
+{
+ if ( encoding >= BMG_ENC__MIN && encoding <= BMG_ENC__MAX )
+ bmg->encoding = encoding;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AssignInfSizeBMG ( bmg_t * bmg, uint inf_size )
+{
+ bmg->param_defined = true;
+
+ if ( inf_size < 4 || inf_size > BMG_INF_LIMIT )
+ {
+ ERROR0(ERR_WARNING,
+ "INF data size is outside allowed range 4..%u => ignored: %s\n",
+ BMG_INF_LIMIT, bmg->fname );
+ return;
+ }
+
+ if ( inf_size > BMG_INF_MAX_SIZE )
+ ERROR0(ERR_WARNING,
+ "INF data size is %u bytes, but only %u bytes are supported."
+ " The additional bytes are always assumed to be NULL: %s",
+ inf_size, BMG_INF_MAX_SIZE, bmg->fname );
+
+ bmg->inf_size = opt_bmg_inf_size ? opt_bmg_inf_size : inf_size;
+ bmg->attrib_used = bmg->inf_size - 4;
+ if ( bmg->attrib_used > sizeof(bmg->attrib) )
+ bmg->attrib_used = sizeof(bmg->attrib);
+ if (opt_bmg_def_attrib)
+ memcpy(bmg->attrib,bmg_def_attrib,bmg->attrib_used);
+
+ if ( inf_size != 8 )
+ {
+ // not MKW => change defaults
+ if ( bmg->use_color_names == 2 )
+ bmg->use_color_names = 1;
+ if ( bmg->use_mkw_messages == 2 )
+ bmg->use_mkw_messages = 1;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeBMG ( bmg_t * bmg )
+{
+ ASSERT( MID_VERSUS_POINTS >= MID_PARAM_BEG
+ && MID_VERSUS_POINTS < MID_PARAM_END );
+
+ DASSERT(bmg);
+ memset(bmg,0,sizeof(*bmg));
+ bmg->have_mid = 1;
+ bmg->encoding = BMG_ENC__DEFAULT;
+ bmg->use_slots = opt_bmg_use_slots;
+ bmg->use_raw_sections = opt_bmg_use_raw_sections;
+
+ bmg->unknown_inf_0c = BMG_INF_DEFAULT_0C;
+ bmg->unknown_mid_0a = BMG_MID_DEFAULT_0A;
+ bmg->unknown_mid_0c = BMG_MID_DEFAULT_0C;
+
+ //-- setup for MKW
+
+ if (opt_bmg_support_mkw)
+ {
+ bmg->use_color_names = 1;
+ bmg->use_mkw_messages = 1;
+ AssignInfSizeBMG(bmg,BMG_INF_STD_SIZE);
+
+ if ( !opt_bmg_def_attrib && bmg->attrib_used == 4 )
+ write_be32(bmg->attrib,BMG_INF_STD_ATTRIB);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetBMG ( bmg_t * bmg )
+{
+ DASSERT(bmg);
+
+ bmg_item_t * bi = bmg->item;
+ bmg_item_t * bi_end = bi + bmg->item_used;
+ while ( bi < bi_end )
+ FreeItemBMG(bi++);
+
+ FREE(bmg->item);
+
+ if (bmg->data_alloced)
+ FREE(bmg->data);
+ FREE(bmg->raw_data);
+ FreeString(bmg->fname);
+
+ bmg_raw_section_t *raw = bmg->first_raw;
+ while (raw)
+ {
+ ResetFastBuf(&raw->data);
+ bmg_raw_section_t *del = raw;
+ raw = raw->next;
+ FREE(del);
+ }
+
+ memset(bmg,0,sizeof(*bmg));
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ScanRawBMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static int CompareU32 ( const u32 *a, const u32 *b )
+{
+ return *a < *b ? -1 : *a > *b;
+}
+
+//-----------------------------------------------------------------------------
+
+static u32 FindMajority32 ( cvp first, uint n_elem, uint delta )
+{
+ DASSERT( first || !n_elem );
+
+ if ( n_elem < 3 )
+ return n_elem ? *(u32*)first : 0;
+
+ u32 *buf = MALLOC(n_elem*sizeof(*buf));
+
+ uint i;
+ for ( i = 0; i < n_elem; i++ )
+ {
+ buf[i] = *(u32*)first;
+ first = (u32*)( (u8*)first + delta );
+ }
+ //HexDump16(stderr,0,0,buf,64);
+ qsort(buf,n_elem,sizeof(*buf),(qsort_func)CompareU32);
+
+ uint max_val = 0, max_count = 0;
+ const u32 *ptr = buf;
+ const u32 *end = ptr + n_elem;
+ while ( ptr < end )
+ {
+ const u32 *beg = ptr;
+ const u32 val = *ptr++;
+ while ( ptr < end && *ptr == val )
+ ptr++;
+ if ( ptr - beg > max_count )
+ {
+ max_count = ptr - beg;
+ max_val = val;
+ }
+ }
+
+ FREE(buf);
+ return max_val;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void scan_raw_cp1252
+ ( bmg_item_t *bi, const u8 *start, const u8 *end, FastBuf_t *fb )
+{
+ u8 buf[4];
+ const u8 *ptr;
+ for ( ptr = start; ptr < end && *ptr; )
+ {
+ u16 code = *ptr++;
+ if ( code >= 0x80 && code < 0xa0 )
+ code = TableCP1252_80[code-0x80];
+ if ( code == 0x1a )
+ {
+ const uint len = *ptr++;
+ if ( len > 2 )
+ {
+ buf[0] = 0;
+ buf[1] = 0x1a;
+ buf[2] = len+1;
+ AppendFastBuf(fb,buf,3);
+ AppendFastBuf(fb,ptr,len-2);
+ if (!(len&1))
+ AppendFastBuf(fb,buf,1);
+ ptr += len - 2;
+ }
+ }
+ else
+ {
+ code = htons(code);
+ AppendFastBuf(fb,&code,2);
+ }
+ }
+
+ const mem_t mem = GetFastBufMem(fb);
+ AssignItemText16BMG(bi,(u16*)mem.ptr,mem.len/2);
+}
+
+//-----------------------------------------------------------------------------
+
+static void scan_raw_utf16 ( bmg_item_t *bi, const u8 *start, const u8 *end )
+{
+ const u8 *ptr;
+ for ( ptr = start; ptr < end; ptr += 2 )
+ {
+ if (!ptr[0])
+ {
+ if (!ptr[1])
+ break;
+ if ( ptr[1] == 0x1a )
+ {
+ ptr += 2;
+ const uint skip = *ptr + 1 & 0x1e;
+ if ( skip > 4 )
+ ptr += skip - 4;
+ }
+ }
+ }
+
+ AssignItemText16BMG( bi, (u16*)start, ( ptr - start )/2 );
+}
+
+//-----------------------------------------------------------------------------
+
+static void scan_raw_shift_jis
+ ( bmg_item_t *bi, const u8 *start, const u8 *end, FastBuf_t *fb )
+{
+ u8 buf[4];
+ const u8 *ptr;
+ for ( ptr = start; ptr < end; )
+ {
+ int code = ScanShiftJISChar(&ptr);
+ if (!code)
+ break;
+ if ( code == 0x1a )
+ {
+ const uint len = *ptr++;
+ if ( len > 2 )
+ {
+ buf[0] = 0;
+ buf[1] = 0x1a;
+ buf[2] = len+1;
+ AppendFastBuf(fb,buf,3);
+ AppendFastBuf(fb,ptr,len-2);
+ if (!(len&1))
+ AppendFastBuf(fb,buf,1);
+ ptr += len - 2;
+ }
+ }
+ else if ( code >= 0 )
+ {
+ code = htons(code);
+ AppendFastBuf(fb,&code,2);
+ }
+ }
+
+ const mem_t mem = GetFastBufMem(fb);
+ AssignItemText16BMG(bi,(u16*)mem.ptr,mem.len/2);
+}
+
+//-----------------------------------------------------------------------------
+
+static void scan_raw_utf8
+ ( bmg_item_t *bi, const u8 *start, const u8 *end, FastBuf_t *fb )
+{
+ u8 buf[4];
+ const u8 *ptr;
+ for ( ptr = start; ptr < end && *ptr; )
+ {
+ u32 code = ScanUTF8AnsiCharE((ccp*)&ptr,(ccp)end);
+ if ( code >= 0x80 && code < 0xa0 )
+ code = TableCP1252_80[code -0x80];
+ if ( code == 0x1a )
+ {
+ const uint len = *ptr++;
+ if ( len > 2 )
+ {
+ buf[0] = 0;
+ buf[1] = 0x1a;
+ buf[2] = len+1;
+ AppendFastBuf(fb,buf,3);
+ AppendFastBuf(fb,ptr,len-2);
+ if (!(len&1))
+ AppendFastBuf(fb,buf,1);
+ ptr += len - 2;
+ }
+ }
+ else
+ {
+ code = htons(code);
+ AppendFastBuf(fb,&code,2);
+ }
+ }
+
+ const mem_t mem = GetFastBufMem(fb);
+ AssignItemText16BMG(bi,(u16*)mem.ptr,mem.len/2);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanRawBMG ( bmg_t * bmg )
+{
+ DASSERT(bmg);
+ DASSERT(bmg->data);
+
+ const bmg_header_t * bh = (bmg_header_t*)bmg->data;
+ DASSERT(!memcmp(bh->magic,BMG_MAGIC,sizeof(bh->magic)));
+
+ u32 data_size = ntohl(bh->size);
+ if ( data_size > bmg->data_size )
+ {
+ ERROR0(ERR_WARNING,
+ "BMG is %u bytes shorter than announced: %s",
+ data_size - bmg->data_size, bmg->fname );
+ data_size = bmg->data_size;
+ }
+
+ const bmg_sect_list_t *sl = ScanSectionsBMG(bmg->data,data_size);
+ if ( !sl || !sl->header )
+ return ERROR0(ERR_INVALID_DATA,"Invalid BMG data: %s",bmg->fname);
+
+
+ //--- check encoding
+
+ bmg->encoding = bh->encoding;
+ switch (bmg->encoding)
+ {
+ case BMG_ENC_CP1252:
+ case BMG_ENC_UTF16BE:
+ case BMG_ENC_SHIFT_JIS:
+ case BMG_ENC_UTF8:
+ //>>> ok! <<<
+ break;
+
+ default:
+ if (!opt_bmg_force_count)
+ return ERROR0(ERR_ERROR,
+ "Can't read BMG with encoding #%u (%s): %s\n",
+ bmg->encoding, GetEncodingNameBMG(bmg->encoding,"?"),
+ bmg->fname );
+ ERROR0(ERR_WARNING,
+ "Try to read BMG with unsupported encoding #%u (%s): %s\n",
+ bmg->encoding, GetEncodingNameBMG(bmg->encoding,"?"),
+ bmg->fname );
+ }
+
+
+ //--- iterate sections
+
+ const bmg_sect_info_t *si;
+ for ( si = sl->info; si->sect; si++ )
+ {
+ if ( si->real_size < si->sect_size )
+ ERROR0(ERR_WARNING,
+ "Size of BMG section %s set to %u bytes (%u lesser than announced): %s\n",
+ PrintID(si->sect->magic,4,0),
+ si->real_size, si->sect_size - si->real_size, bmg->fname );
+
+ if ( !si->supported )
+ {
+ bmg_raw_section_t *raw = CreateRawSectionBMG(bmg,si->sect->magic);
+ DASSERT(raw);
+ AssignFastBuf( &raw->data, si->sect->data,
+ si->real_size - sizeof(bmg_section_t) );
+ raw->total_size = si->real_size;
+ PRINT("SCAN/RAW SECTION »%s« data: %u = 0x%x, total: %u = 0x%x\n",
+ PrintID(raw->magic,4,0),
+ GetFastBufLen(&raw->data), GetFastBufLen(&raw->data),
+ raw->total_size, raw->total_size );
+ }
+ }
+
+ PRINT("%p %p %p\n",sl->pinf,sl->pdat,sl->pmid);
+ const bmg_inf_t *pinf = bmg->inf = sl->pinf;
+ const bmg_dat_t *pdat = bmg->dat = sl->pdat;
+ const bmg_mid_t *pmid = bmg->mid = sl->pmid;
+
+ if ( !sl->pinf || !sl->pdat )
+ return ERROR0(ERR_INVALID_DATA,"Corrupted BMG file: %s\n",bmg->fname);
+
+ bmg->unknown_inf_0c = ntohl(pinf->unknown_0c);
+
+ uint max_item;
+ bmg->have_mid = pmid != 0;
+ if (bmg->have_mid)
+ {
+ bmg->unknown_mid_0a = ntohs(pmid->unknown_0a);
+ bmg->unknown_mid_0c = ntohl(pmid->unknown_0c);
+
+ max_item = ( ntohl(pmid->size) - sizeof(*pmid) ) / sizeof(*pmid->mid);
+ if ( max_item > ntohs(pmid->n_msg) )
+ max_item = ntohs(pmid->n_msg);
+ PRINT("BMG with MID1: N=%u [inf=%u,mid=%u], %s\n",
+ max_item, ntohs(pinf->n_msg), ntohs(pmid->n_msg), bmg->fname );
+ }
+ else
+ {
+ max_item = ntohs(pinf->n_msg);
+ PRINT("BMG without MID1: N=%u, %s\n",max_item,bmg->fname);
+ }
+
+ const uint max_offset = ( ntohl(pdat->size) - sizeof(*pdat) )
+ / sizeof(*pdat->text_pool);
+ const u8 *text_end = pdat->text_pool + max_offset;
+
+ const u8 *raw_inf = (u8*)pinf->list;
+ const uint src_inf_size = ntohs(pinf->inf_size);
+ AssignInfSizeBMG(bmg,src_inf_size);
+
+
+ //--- find default attributes
+
+ if (!opt_bmg_def_attrib)
+ {
+ uint idx;
+ for ( idx = 0; idx < bmg->attrib_used; idx += 4 )
+ {
+ const u32 major = FindMajority32(raw_inf+idx+4,max_item,src_inf_size);
+ noPRINT("-> IDX: %2u -> %8x\n",idx,ntohl(major));
+ memcpy(bmg->attrib+idx,&major,4);
+ }
+ //HexDump16(stderr,8,0x10,bmg->attrib,bmg->attrib_used);
+ }
+
+
+ //--- decode raw files
+
+ struct { FastBuf_t b; char space[500]; } fb_mgr;
+ InitializeFastBuf(&fb_mgr,sizeof(fb_mgr));
+ #if HAVE_PRINT
+ uint ins_count = 0, empty_count = 0;
+ #endif
+
+ uint prev_mid = 0;
+ uint slot, unsort_count = 0, ffff_count = 0;
+ for ( slot = 0; slot < max_item; slot++, raw_inf += src_inf_size )
+ {
+ bmg_inf_item_t *item = (bmg_inf_item_t*)raw_inf;
+ const u32 offset = ntohl(item->offset);
+
+ u32 mid = pmid ? ntohl(pmid->mid[slot]) : slot;
+ if ( mid == 0xffff && !offset )
+ ffff_count++;
+ if ( mid < prev_mid )
+ unsort_count++;
+ prev_mid = mid;
+
+ #ifdef TEST
+ if ( offset >= max_offset )
+ return ERROR0(ERR_INVALID_DATA,
+ "Invalid pointer at file offset 0x%zx (INF item #%u): %s\n",
+ (u8*)&pinf->list[slot].offset - bmg->data, slot, bmg->fname);
+ #else
+ if ( offset >= max_offset )
+ return ERROR0(ERR_INVALID_DATA,"Corrupted BMG file: %s\n",bmg->fname);
+ #endif
+
+ #if HAVE_PRINT0
+ {
+ bmg_item_t *bi = FindItemBMG(bmg,mid);
+ if (bi)
+ PRINT("MID EXISTS: %x, slot:%x\n",mid,slot);
+ }
+ #endif
+
+ bmg_item_t *bi = InsertItemBMG(bmg,mid,item->attrib,bmg->attrib_used,0);
+ DASSERT(bi);
+ bi->slot = slot < BMG_NO_PREDEF_SLOT ? slot : BMG_NO_PREDEF_SLOT;
+
+ if (!offset)
+ {
+ #if HAVE_PRINT
+ empty_count++;
+ #endif
+ AssignItemText16BMG(bi,bmg_null_entry,0);
+ continue;
+ }
+
+ switch (bmg->encoding)
+ {
+ case BMG_ENC_CP1252:
+ ClearFastBuf(&fb_mgr.b);
+ scan_raw_cp1252( bi, pdat->text_pool + offset, text_end, &fb_mgr.b );
+ break;
+
+ case BMG_ENC_UTF16BE:
+ scan_raw_utf16( bi, pdat->text_pool + offset, text_end );
+ break;
+
+ case BMG_ENC_SHIFT_JIS:
+ ClearFastBuf(&fb_mgr.b);
+ scan_raw_shift_jis( bi, pdat->text_pool + offset, text_end, &fb_mgr.b );
+ break;
+
+ case BMG_ENC_UTF8:
+ ClearFastBuf(&fb_mgr.b);
+ scan_raw_utf8( bi, pdat->text_pool + offset, text_end, &fb_mgr.b );
+ break;
+
+ default:
+ // fallback for cast opt_bmg_force_count>0
+ DASSERT(opt_bmg_force_count);
+ ClearFastBuf(&fb_mgr.b);
+ scan_raw_cp1252( bi, pdat->text_pool + offset, text_end, &fb_mgr.b );
+ break;
+ }
+
+ #if HAVE_PRINT
+ ins_count++;
+ #endif
+ }
+
+ #if HAVE_PRINT
+ PRINT("BMG: %u+%u=%u/%u messages inserted => %u | n(unsort)=%d, n(ffff)=%d\n",
+ empty_count, ins_count, empty_count+ins_count,
+ max_item, bmg->item_used,
+ unsort_count, ffff_count );
+ #endif
+ ResetFastBuf(&fb_mgr.b);
+
+ bmg->have_predef_slots = pmid && max_item > 1
+ && ( unsort_count > 1 || ffff_count > 1 );
+
+ if (bmg->have_predef_slots)
+ {
+ bmg_item_t *bi = FindItemBMG(bmg,0xffff);
+ if ( bi && isSpecialEntryBMG(bi->text) )
+ AssignItemText16BMG(bi,0,0);
+ }
+ else
+ {
+ // clear slots settings
+ bmg_item_t *bi, *bi_end = bmg->item + bmg->item_used;
+ for ( bi = bmg->item; bi < bi_end; bi++ )
+ bi->slot = BMG_NO_PREDEF_SLOT;
+ }
+
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ScanTextBMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int (*AtHookBMG) ( bmg_t * bmg, ccp line, ccp line_end ) = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+static char * GetNext ( ccp ptr, ccp end )
+{
+ DASSERT(ptr);
+ DASSERT(end);
+
+ while ( ptr < end && ( *ptr == ' ' || *ptr == '\t' || *ptr == '\r' ) )
+ ptr++;
+ return (char*)ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// [[xbuf]] GetText ( FastBuf_t *fb ccp ptr, ccp end, uint *len )
+
+static char * GetText ( char *buf, uint bufsize, ccp ptr, ccp end, uint *len )
+{
+ DASSERT(buf);
+ DASSERT(bufsize>10);
+
+ char *dest = buf;
+ char *dest_end = buf + bufsize - 1;
+
+ ptr = GetNext(ptr,end);
+
+ if ( ptr < end && ( *ptr == '\'' || *ptr == '\"' ) )
+ {
+ const char quote = *ptr++;
+ while ( ptr < end && *ptr != quote )
+ {
+ if ( dest < dest_end )
+ *dest++ = *ptr;
+ ptr++;
+ }
+ if ( *ptr == quote )
+ ptr++;
+ }
+ else
+ {
+ while ( ptr < end && (uchar)*ptr > ' ' )
+ {
+ if ( dest < dest_end )
+ *dest++ = *ptr;
+ ptr++;
+ }
+ }
+
+ *dest = 0;
+ if (len)
+ *len = dest - buf;
+ return GetNext(ptr,end);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanTextBMG ( bmg_t * bmg )
+{
+ DASSERT(bmg);
+ TRACE("SCAN BMG/TEXT: %s\n",bmg->fname);
+ bmg->is_text_src = true;
+
+ ccp ptr = (ccp)bmg->data;
+ ccp end = ptr + bmg->data_size;
+ ptr += GetTextBOMLen(ptr,bmg->data_size);
+
+ ccp bmg_name = 0;
+// [[xbuf]]
+ char xbuf[2*BMG_MSG_BUF_SIZE];
+ enumError err_stat = ERR_OK;
+ u32 cond = 0;
+
+ ParamField_t assign;
+ InitializeParamField(&assign);
+ assign.free_data = true;
+
+ for(;;)
+ {
+ while ( ptr < end && (uchar)*ptr <= ' ' )
+ ptr++;
+ if ( ptr >= end )
+ break;
+
+ ccp start = ptr = GetNext(ptr,end);
+ while ( ptr < end && *ptr != '\r' && *ptr != '\n' )
+ ptr++;
+
+ if ( *start == '@' )
+ {
+ if (AtHookBMG)
+ {
+ const int stat = AtHookBMG(bmg,start,ptr);
+ if ( stat < -1 )
+ continue;
+ if ( stat >= 0 )
+ {
+ err_stat = stat;
+ break;
+ }
+ }
+
+ start = GetNext(start+1,ptr);
+ const bool silent = start < ptr && *start == '!';
+ if (silent)
+ start = GetNext(start+1,ptr);
+
+ if ( *start == '?' )
+ {
+ // SYNTAX/IF: '@?' MID_COND
+
+ u32 mid;
+ ScanMidBMG(bmg,&mid,0,0,start+1,end,(char**)&start);
+ if ( ptr >= end )
+ break;
+ cond = mid;
+ continue;
+ }
+
+ if ( *start == '=' )
+ {
+ // SYNTAX/MATCH: '@=' PATTERN [':'] LINE
+
+ uint len;
+// [[xbuf]]
+ start = GetText(xbuf,sizeof(xbuf),start+1,ptr,&len);
+ if (len)
+ {
+// [[xbuf]]
+ PRINT("MATCH |%s|\n",xbuf);
+ if (!bmg_name)
+ {
+ bmg_name = GetNameBMG(bmg,0);
+ PRINT("NAME |%s|\n",bmg_name);
+ }
+
+// [[xbuf?]]
+ if (MatchPattern(xbuf,bmg_name,'/'))
+ {
+ ptr = GetNext(start,ptr);
+ if ( *ptr == ':' )
+ ptr++;
+ }
+ }
+ continue;
+ }
+
+ if ( *start == '$' )
+ {
+ // SYNTAX/ECHO: '@$' TEXT_TO_EOL
+
+ if (opt_bmg_allow_print)
+ {
+ start++;
+ printf("#%.*s\n",(int)(ptr-start),start);
+ }
+ continue;
+ }
+
+ const bool close_current = *start == '>';
+ if ( close_current || *start == '<' )
+ {
+ // SYNTAX/GOTO: '@' ['!'] '>' FILE
+ // SYNTAX/INCLUDE: '@' ['!'] '<' FILE
+
+ uint len;
+// [[xbuf]]
+ start = GetText(xbuf,sizeof(xbuf),start+1,ptr,&len);
+ if ( len && bmg->recurse_depth < opt_bmg_max_recurse )
+ {
+ PRINT("BMG/%s: %s\n",close_current?"GOTO":"INCLUDE",xbuf);
+ bmg_t bmg2;
+// [[xbuf?]]
+ const enumError err
+ = LoadBMG(&bmg2,true,bmg->fname,xbuf,silent?FM_SILENT:0);
+ if ( err == ERR_OK )
+ PatchOverwriteBMG(bmg,&bmg2,true);
+ ResetBMG(&bmg2);
+ if ( close_current && err != ERR_CANT_OPEN )
+ break;
+ }
+ continue;
+ }
+
+ char namebuf[100], *nptr = namebuf;
+ while ( start < ptr && *start > ' ' && *start < 'z' && *start != '=' )
+ {
+ if ( nptr < namebuf + sizeof(namebuf) - 1 )
+ *nptr++ = toupper((int)*start);
+ start++;
+ }
+ *nptr = 0;
+ start = GetNext(start,ptr);
+
+
+ //--- settings without mandatory '='
+
+ const bool have_equal = start < ptr && *start == '=';
+ if (have_equal)
+ start = GetNext(start+1,ptr);
+
+ if (!strcmp(namebuf,"SECTION"))
+ {
+ bmg->current_raw = 0;
+ uint len = ScanEscapedString(namebuf,sizeof(namebuf),
+ start, ptr-start, false, -1, 0 );
+ if (!len)
+ continue;
+ if ( len > 4 )
+ {
+ ERROR0(ERR_WARNING,
+ "@SECTION name exceeds 4 [%u] characters: %s\n",
+ len, namebuf );
+ continue;
+ }
+ while ( len < 4 )
+ namebuf[len++] = 0;
+ PRINT("@SECTION »%s« %02x %02x %02x %02x\n",
+ PrintID(namebuf,4,0),
+ namebuf[0], namebuf[1], namebuf[2], namebuf[3] );
+ bmg->current_raw = CreateRawSectionBMG(bmg,namebuf);
+ DASSERT(bmg->current_raw);
+ ClearFastBuf(&bmg->current_raw->data);
+ continue;
+ }
+
+ if (!strcmp(namebuf,"X"))
+ {
+ if (!bmg->current_raw)
+ continue;
+
+ // skip addr
+
+ ccp p = start;
+ while (isalnum((int)*p))
+ p++;
+ if ( p > start && *p == ':' )
+ start = p+1;
+
+ for (;;)
+ {
+ start = GetNext(start,ptr);
+ if ( start >= ptr || *start == ':' )
+ break;
+ char *end;
+ uint num = str2ul(start,&end,16);
+ if ( end == start )
+ break;
+ start = end;
+ AppendCharFastBuf(&bmg->current_raw->data,num);
+ }
+ continue;
+ }
+
+
+ //--- settings with mandatory '='
+
+ if ( !have_equal || start >= ptr )
+ continue;
+
+ noPRINT("PARAM: %s = |%.*s|\n",namebuf,(int)(ptr-start),start);
+ if (!strcmp(namebuf,"ENCODING"))
+ AssignEncodingBMG(bmg,str2l(start,0,10));
+ else if (!strcmp(namebuf,"BMG-MID"))
+ bmg->have_mid = str2ul(start,0,10) > 0;
+ else if (!strcmp(namebuf,"INF-SIZE"))
+ AssignInfSizeBMG(bmg,str2ul(start,0,10));
+ else if (!strcmp(namebuf,"DEFAULT-ATTRIBS"))
+ {
+ noPRINT("--- SCAN ATTRIB: %.*s\n",(int)(ptr-start),start);
+ scan_attrib(bmg->attrib,bmg->attrib_used,start);
+ //HEXDUMP16(0,0,bmg->attrib,bmg->attrib_used);
+ }
+ else if (!strcmp(namebuf,"UNKNOWN-INF32-0C"))
+ bmg->unknown_inf_0c = str2ul(start,0,10);
+ else if (!strcmp(namebuf,"UNKNOWN-MID16-0A"))
+ bmg->unknown_mid_0a = str2ul(start,0,10);
+ else if (!strcmp(namebuf,"UNKNOWN-MID32-0C"))
+ bmg->unknown_mid_0c = str2ul(start,0,10);
+ continue;
+ }
+
+
+ //--- scan message ids
+
+ u32 mid1 = 0, mid2 = 0, mid3 = 0;
+ const int mid_stat
+ = ScanMidBMG(bmg,&mid1,&mid2,&mid3,start,end,(char**)&start);
+
+ if ( mid_stat <= 0 ) // not a valid message line
+ continue;
+
+ //--- scan predifined slot
+
+ bmg_slot_t slot = BMG_NO_PREDEF_SLOT;
+ if ( *start == '@' )
+ {
+ char *end;
+ uint num = str2l(++start,&end,16);
+ PRINT0("SLOT[%#x] = %#x\n",mid1,num);
+ if ( end > start && num < BMG_NO_PREDEF_SLOT )
+ slot = num;
+ start = GetNext(end,ptr);
+ }
+
+
+ //--- scan attributes
+
+ u8 attrib_buf[BMG_ATTRIB_SIZE];
+ memcpy(attrib_buf,bmg->attrib,sizeof(attrib_buf));
+ uint attrib_used = 0;
+
+ if ( *start == '[' )
+ {
+ ccp aptr = scan_attrib(attrib_buf,bmg->attrib_used,start+1);
+ aptr = GetNext(aptr,ptr);
+ if ( *aptr == ']' )
+ {
+ attrib_used = bmg->attrib_used;
+ start = GetNext(aptr+1,ptr);
+ }
+ }
+
+ if ( *start == '/' || *start == '~' )
+ {
+ if ( *start == '~' )
+ {
+ attrib_used = bmg->attrib_used;
+ start = GetNext(start+1,ptr);
+
+ if ( *start == '[' )
+ scan_attrib(attrib_buf,attrib_used,start);
+ else
+ {
+ memset(attrib_buf,0,sizeof(attrib_buf));
+ const u32 attr = str2ul(start,0,10);
+ write_be32(attrib_buf,attr);
+ }
+ }
+
+ bmg_item_t * bi = InsertItemBMG(bmg,mid1,attrib_buf,attrib_used,0);
+ DASSERT(bi);
+ bi->slot = slot;
+ AssignItemText16BMG(bi,bmg_null_entry,0);
+ if (mid2)
+ {
+ bi = InsertItemBMG(bmg,mid2,attrib_buf,attrib_used,0);
+ DASSERT(bi);
+ bi->slot = slot;
+ AssignItemText16BMG(bi,bmg_null_entry,0);
+ }
+ if (mid3)
+ {
+ bi = InsertItemBMG(bmg,mid3,attrib_buf,attrib_used,0);
+ DASSERT(bi);
+ bi->slot = slot;
+ AssignItemText16BMG(bi,bmg_null_entry,0);
+ }
+ continue;
+ }
+
+ if ( *start == ':' )
+ {
+ start = GetNext(start+1,end);
+ ccp ptr = start;
+ while ( ptr < end && isalnum((int)*ptr) )
+ ptr++;
+
+ if ( ptr > start )
+ {
+ u32 *tab[] = { &mid1, &mid2, &mid3, 0 }, **tptr;
+ for ( tptr = tab; *tptr; tptr++ )
+ {
+ const u32 mid = **tptr;
+ if (mid)
+ {
+ DeleteItemBMG(bmg,mid);
+
+ char midbuf[20];
+ snprintf(midbuf,sizeof(midbuf),"%x",mid);
+ noPRINT("BMG ASSIGN: %5s = |%.*s|\n",
+ midbuf,(int)(ptr-start),start);
+ InsertParamField(&assign,midbuf,false,mid,MEMDUP(start,ptr-start));
+ }
+ }
+ }
+ continue;
+ }
+
+ if ( *start != '=' ) // not a valid line
+ continue;
+
+// [[xbuf]]
+ // assume that xbuf is large enough
+ // => make only simple and silent buffer overrun tests
+
+// [[xbuf?]]
+ char * in_beg = xbuf + sizeof(xbuf)/2;
+ char * in_end = xbuf + sizeof(xbuf) - 0x10;
+ DASSERT( in_beg < in_end );
+ char * in_ptr = in_beg;
+
+ for(;;)
+ {
+ if ( ptr[-1] == '\r' )
+ ptr--;
+ start++;
+ if ( *start == ' ' )
+ start++;
+ if ( start > ptr )
+ start = ptr;
+
+ int len = ptr - start;
+ if ( len > in_end - in_ptr )
+ len = in_end - in_ptr;
+ memcpy(in_ptr,start,len);
+ in_ptr += len;
+
+ while ( ptr < end && (uchar)*ptr <= ' ' )
+ ptr++;
+ if ( ptr == end || *ptr != '+' )
+ break;
+
+ start = ptr;
+ while ( ptr < end && *ptr != '\n' )
+ ptr++;
+ }
+ *in_ptr = 0;
+
+ const int in_len = in_ptr - in_beg;
+ noPRINT("%x,%x,%x |%.*s|\n",mid1,mid2,mid3,in_len,in_beg);
+// [[xbuf]] ScanFastBuf16BMG();
+ u16 * dest_buf = (u16*)xbuf;
+ const uint len16
+ = ScanString16BMG( dest_buf, sizeof(xbuf)/sizeof(u16), in_beg, in_len, bmg );
+
+ bmg_item_t * bi = InsertItemBMG(bmg,mid1,attrib_buf,attrib_used,0);
+ DASSERT(bi);
+ bi->cond = cond;
+ bi->slot = slot;
+ AssignItemText16BMG(bi,dest_buf,len16);
+ if (mid2)
+ {
+ bi = InsertItemBMG(bmg,mid2,attrib_buf,attrib_used,0);
+ DASSERT(bi);
+ bi->cond = cond;
+ bi->slot = slot;
+ AssignItemText16BMG(bi,dest_buf,len16);
+ }
+ if (mid3)
+ {
+ bi = InsertItemBMG(bmg,mid3,attrib_buf,attrib_used,0);
+ DASSERT(bi);
+ bi->cond = cond;
+ bi->slot = slot;
+ AssignItemText16BMG(bi,dest_buf,len16);
+ }
+ }
+
+
+ //--- manage assignments
+
+ bool dirty = assign.used > 0;
+ while (dirty)
+ {
+ dirty = false;
+ ParamFieldItem_t *pfi = assign.field, *end;
+ for ( end = pfi + assign.used; pfi < end; pfi++ )
+ {
+ if (!pfi->num)
+ continue;
+
+ bmg_item_t *bi = FindItemBMG(bmg,pfi->num);
+ noPRINT("## %5x = %5s = |%s| -> found=%d, len=%d\n",
+ pfi->num, pfi->key, (ccp)pfi->data, bi!=0, bi ? bi->len : -1 );
+ pfi->key = 0;
+ if ( bi && bi->len )
+ {
+ pfi->num = 0;
+ continue;
+ }
+
+ bmg_scan_mid_t scan;
+ ScanBMGMID(&scan,bmg,(ccp)pfi->data,0);
+ if ( scan.status < 1 )
+ {
+ pfi->num = 0;
+ continue;
+ }
+
+ uint i;
+ for ( i = 0; i < scan.n_mid; i++ )
+ {
+ bmg_item_t *src = FindItemBMG(bmg,scan.mid[i]);
+ if ( src && src->text )
+ {
+ bmg_item_t *dest
+ = InsertItemBMG(bmg,pfi->num,src->attrib,src->attrib_used,0);
+ DASSERT(dest);
+
+ // src is invalid now => search again
+ src = FindItemBMG(bmg,scan.mid[i]);
+ if (src)
+ {
+ AssignItemText16BMG(dest,src->text,src->len);
+ dest->attrib_used = src->attrib_used;
+ memcpy(dest->attrib,src->attrib,dest->attrib_used);
+ dirty = true;
+ pfi->num = 0;
+ }
+ break;
+ }
+ }
+ }
+ }
+ ResetParamField(&assign);
+
+
+ //--- terminate
+
+ if (bmg->data_alloced)
+ FREE(bmg->data);
+ bmg->data = 0;
+ bmg->data_size = 0;
+
+ return err_stat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ScanBMG(), LoadBMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ScanBMG
+(
+ bmg_t * bmg, // pointer to valid bmg
+ bool initialize, // true: initialize 'bmg'
+ ccp fname, // NULL or filename for error messages
+ const u8 * data, // data / if NULL: use internal data
+ size_t data_size // size of 'data' if data not NULL
+)
+{
+ if (initialize)
+ InitializeBMG(bmg);
+ else if (data)
+ ResetBMG(bmg);
+
+ if (data)
+ {
+ bmg->data = (u8*)data;
+ bmg->data_size = data_size;
+ }
+
+ if (fname)
+ bmg->fname = STRDUP(fname);
+
+ const bmg_header_t * bh = (bmg_header_t*)bmg->data;
+ if ( !memcmp(bh->magic,BMG_MAGIC,sizeof(bh->magic))
+ && ntohl(bh->size) <= bmg->data_size )
+ return ScanRawBMG(bmg);
+
+ ccp ptr = (ccp)bmg->data + GetTextBOMLen(bmg->data,bmg->data_size);
+ if ( !memcmp(ptr,BMG_TEXT_MAGIC,4) )
+ return ScanTextBMG(bmg);
+
+ return ERROR0(ERR_INVALID_DATA,"No BMG file: %s\n",bmg->fname);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadBMG
+(
+ bmg_t * bmg, // pointer to valid bmg
+ bool initialize, // true: initialize 'bmg'
+ ccp parent_fname, // NULL or filename of parent for dir-extract
+ ccp fname, // filename of source
+ FileMode_t file_mode // open modes
+)
+{
+ DASSERT(bmg);
+ DASSERT(fname);
+ PRINT("LoadBMG(%p,init=%d) fname=%s\n", bmg, initialize, fname );
+
+ if (initialize)
+ InitializeBMG(bmg);
+ else
+ ResetBMG(bmg);
+
+ if (!strcmp(fname,"0"))
+ {
+ bmg->is_text_src = false;
+ bmg->fname = STRDUP(fname);
+ return ERR_OK;
+ }
+
+ char path[PATH_MAX];
+ if ( *fname != '/' && parent_fname )
+ {
+ ccp slash = strrchr(parent_fname,'/');
+ char *dest;
+ if (slash)
+ dest = StringCopySM(path,sizeof(path)-2,parent_fname,slash-parent_fname);
+ else
+ dest = StringCopyS(path,sizeof(path)-2,parent_fname);
+ *dest++ = '/';
+ StringCopyE(dest,path+sizeof(path),fname);
+ fname = path;
+ }
+ noPRINT(">>> fname: |%s|d\n",fname);
+
+ u8 *data;
+ uint size;
+ enumError err = OpenReadFile( fname, 0, file_mode, 0,0,
+ &data, &size, &bmg->fname, &bmg->fatt );
+ if ( err == ERR_OK )
+ {
+ bmg->data = data;
+ bmg->data_size = size;
+ bmg->data_alloced = true;
+ return ScanBMG(bmg,false,0,0,0);
+ }
+
+ FREE(data);
+ return err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_create_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void grow_bi_list ( bmg_create_t *bc, uint slot )
+{
+ if ( bc->bi_size <= slot )
+ {
+ const uint esize = sizeof(*bc->bi_list);
+ const uint old_size = bc->bi_size;
+ const uint byte_size = ( slot + slot/8 + 10 ) * esize;
+ bc->bi_size = GetGoodAllocSize(byte_size) / esize;
+ bc->bi_list = REALLOC( bc->bi_list, bc->bi_size * esize );
+ memset( bc->bi_list + old_size, 0, ( bc->bi_size - old_size ) * esize );
+ PRINT("GROW BI-LIST: %u/%u/%u\n",slot,bc->bi_used,bc->bi_size);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void InitializeCreateBMG ( bmg_create_t *bc, bmg_t *bmg )
+{
+ DASSERT(bc);
+ DASSERT(bmg);
+
+ memset(bc,0,sizeof(*bc));
+ bc->bmg = bmg;
+ bc->have_mid = opt_bmg_mid == OFFON_AUTO
+ ? bmg->have_mid : opt_bmg_mid >= OFFON_ON;
+
+ uint n_predef = 0, other_count = 0;
+ bmg_item_t *bi, *bi_end = bmg->item + bmg->item_used;
+ for ( bi = bmg->item; bi < bi_end; bi++ )
+ if (bi->text)
+ {
+ if ( bi->slot == BMG_NO_PREDEF_SLOT )
+ other_count++;
+ else if ( n_predef <= bi->slot )
+ n_predef = bi->slot + 1;
+ }
+
+ bmg->have_predef_slots = n_predef >= 0;
+ if (n_predef)
+ {
+ grow_bi_list( bc, n_predef + other_count );
+
+ uint next = n_predef;
+ for ( bi = bc->bmg->item; bi < bi_end; bi++ )
+ if (bi->text)
+ {
+ uint slot = bi->slot;
+ if ( slot >= n_predef || bc->bi_list[slot] )
+ {
+ slot = next++;
+ grow_bi_list(bc,slot);
+ }
+ PRINT0(">>> ASSIGN %4x = %4x\n",slot,bi->mid);
+ bc->bi_list[slot] = bi;
+ }
+
+ bc->bi_used = next;
+ PRINT("BI-LIST: %u/%u/%u\n",next,bc->bi_used,bc->bi_size);
+ }
+
+ uint n_item = bc->bi_used;
+ if ( n_item < bmg->item_used )
+ n_item = bmg->item_used;
+
+ InitializeFastBufAlloc( &bc->inf, n_item * bmg->inf_size );
+ InitializeFastBufAlloc( &bc->dat, n_item * 50 );
+ InitializeFastBufAlloc( &bc->mid, n_item * sizeof(u32) );
+
+ if (bc->have_mid)
+ {
+ uint i;
+ for ( i = 0; i < bc->bi_used; i++ )
+ AppendBE32FastBuf(&bc->mid,0xffff);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void ResetCreateBMG ( bmg_create_t *bc )
+{
+ DASSERT(bc);
+
+ FREE(bc->bi_list);
+ ResetFastBuf(&bc->inf);
+ ResetFastBuf(&bc->dat);
+ ResetFastBuf(&bc->mid);
+ memset(bc,0,sizeof(0));
+}
+
+//-----------------------------------------------------------------------------
+
+bmg_item_t * GetFirstBI ( bmg_create_t *bc )
+{
+ DASSERT(bc);
+ DASSERT(bc->bmg);
+
+ bc->bi = bc->bmg->item;
+ bc->bi_end = bc->bi + bc->bmg->item_used;
+ bc->slot = -1;
+ return GetNextBI(bc);
+}
+
+//-----------------------------------------------------------------------------
+
+bmg_item_t * GetNextBI ( bmg_create_t *bc )
+{
+ DASSERT(bc);
+ DASSERT(bc->bmg);
+
+ bmg_t *bmg = bc->bmg;
+
+ bmg_item_t *bi = 0;
+ int slot = bc->slot + 1;
+ if (bc->bi_list)
+ {
+ while ( slot < bc->bi_used && !bc->bi_list[slot] )
+ slot++;
+ if ( slot < bc->bi_used )
+ {
+ bi = bc->bi_list[slot];
+ PRINT0(">>> BI/LIST: %4x: %04x\n",slot,bi->mid);
+ }
+ }
+ else if ( bc->bi < bc->bi_end )
+ {
+ bi = bc->bi++;
+ PRINT0(">>> BI/DIRECT: %4x: %04x\n",slot,bi->mid);
+ }
+
+ if (!bi)
+ {
+ bc->slot = -1;
+ return 0;
+ }
+
+ //--- found: assign data
+
+ bc->slot = slot;
+
+ u32 *pmid = (u32*)WriteFastBuf( &bc->mid, bc->slot*sizeof(u32),
+ 0, sizeof(u32) );
+ DASSERT(pmid);
+ *pmid = htonl(bi->mid);
+
+ const uint inf_size = bmg->inf_size;
+ bmg_inf_item_t *ii = (bmg_inf_item_t*)WriteFastBuf
+ ( &bc->inf, bc->slot * inf_size, 0, inf_size );
+ DASSERT(ii);
+ if ( !isSpecialEntryBMG(bi->text) )
+ ii->offset = htonl(GetFastBufLen(&bc->dat));
+ memcpy(ii->attrib,bi->attrib,bmg->attrib_used);
+
+ if ( bc->n_msg <= bc->slot )
+ bc->n_msg = bc->slot + 1;
+ return bi;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CreateRawBMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static void create_raw_cp1252 ( bmg_create_t *bc )
+{
+ DASSERT(bc);
+ DASSERT(bc->bmg);
+ DASSERT(!GetFastBufLen(&bc->dat));
+
+ AppendCharFastBuf(&bc->dat,0);
+
+ bmg_item_t *bi;
+ for ( bi = GetFirstBI(bc); bi; bi = GetNextBI(bc) )
+ {
+ if ( !isSpecialEntryBMG(bi->text) )
+ {
+ const u16 *ptr = bi->text;
+ const u16 *end = ptr + bi->len;
+ while ( ptr < end )
+ {
+ u16 code = ntohs(*ptr++);
+ if ( code == 0x1a )
+ {
+ AppendCharFastBuf(&bc->dat,code);
+ uint len = *(u8*)ptr;
+ AppendCharFastBuf(&bc->dat,len-1);
+ AppendFastBuf(&bc->dat,(u8*)ptr+1,len-3);
+ ptr += (len-1)/2;
+ }
+ else if (code)
+ {
+ if ( code >= 0x100 )
+ {
+ uint i;
+ for ( i = 0; i < 0x20; i++ )
+ if ( TableCP1252_80[i] == code )
+ {
+ code = i + 0x80;
+ break;
+ }
+ }
+ if ( code < 0x100 )
+ AppendCharFastBuf(&bc->dat,code);
+ }
+ }
+ AppendCharFastBuf(&bc->dat,0);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static void create_raw_utf16 ( bmg_create_t *bc )
+{
+ DASSERT(bc);
+ DASSERT(bc->bmg);
+ DASSERT(!GetFastBufLen(&bc->dat));
+
+ AppendBE16FastBuf(&bc->dat,0);
+
+ bmg_item_t *bi;
+ for ( bi = GetFirstBI(bc); bi; bi = GetNextBI(bc) )
+ {
+ if ( !isSpecialEntryBMG(bi->text) )
+ {
+ AppendFastBuf( &bc->dat, bi->text, bi->len*2 );
+ AppendBE16FastBuf(&bc->dat,0);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static void create_raw_shift_jis ( bmg_create_t *bc )
+{
+ DASSERT(bc);
+ DASSERT(bc->bmg);
+ DASSERT(!GetFastBufLen(&bc->dat));
+
+ AppendCharFastBuf(&bc->dat,0);
+
+ bmg_item_t *bi;
+ for ( bi = GetFirstBI(bc); bi; bi = GetNextBI(bc) )
+ {
+ if ( !isSpecialEntryBMG(bi->text) )
+ {
+ const u16 *ptr = bi->text;
+ const u16 *end = ptr + bi->len;
+ while ( ptr < end )
+ {
+ const u16 code = ntohs(*ptr++);
+ if ( code == 0x1a )
+ {
+ AppendCharFastBuf(&bc->dat,code);
+ uint len = *(u8*)ptr;
+ AppendCharFastBuf(&bc->dat,len-1);
+ AppendFastBuf(&bc->dat,(u8*)ptr+1,len-3);
+ ptr += (len-1)/2;
+ }
+ else
+ {
+ const int val = GetShiftJISChar(code);
+ if ( val >= 0x100 )
+ AppendBE16FastBuf(&bc->dat,val);
+ else if ( val > 0 )
+ AppendCharFastBuf(&bc->dat,val);
+ }
+ }
+ AppendCharFastBuf(&bc->dat,0);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static void create_raw_utf8 ( bmg_create_t *bc )
+{
+ DASSERT(bc);
+ DASSERT(bc->bmg);
+ DASSERT(!GetFastBufLen(&bc->dat));
+
+ AppendCharFastBuf(&bc->dat,0);
+
+ bmg_item_t *bi;
+ for ( bi = GetFirstBI(bc); bi; bi = GetNextBI(bc) )
+ {
+ if ( !isSpecialEntryBMG(bi->text) )
+ {
+ const u16 *ptr = bi->text;
+ const u16 *end = ptr + bi->len;
+ while ( ptr < end )
+ {
+ u16 code = ntohs(*ptr++);
+ if ( code == 0x1a )
+ {
+ AppendCharFastBuf(&bc->dat,code);
+ uint len = *(u8*)ptr;
+ AppendCharFastBuf(&bc->dat,len-1);
+ AppendFastBuf(&bc->dat,(u8*)ptr+1,len-3);
+ ptr += (len-1)/2;
+ }
+ else if (code)
+ AppendUTF8CharFastBuf(&bc->dat,code);
+ }
+ AppendCharFastBuf(&bc->dat,0);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CreateRawBMG
+(
+ bmg_t * bmg // pointer to valid BMG
+)
+{
+ DASSERT(bmg);
+ TRACE("CreateRawBMG()\n");
+
+
+ //--- setup bmg_create_t
+
+ bmg_create_t bc;
+ InitializeCreateBMG(&bc,bmg);
+ PRINT("fb:inf: %s\n", GetFastBufStatus(&bc.inf) );
+ PRINT("fb:dat: %s\n", GetFastBufStatus(&bc.dat) );
+ PRINT("fb:mid: %s\n", GetFastBufStatus(&bc.mid) );
+
+
+ //--- check encoding
+
+ const int encoding = CheckEncodingBMG(opt_bmg_encoding,bmg->encoding);
+ switch (encoding)
+ {
+ case BMG_ENC_CP1252:
+ create_raw_cp1252(&bc);
+ break;
+
+ case BMG_ENC_UTF16BE:
+ create_raw_utf16(&bc);
+ break;
+
+ case BMG_ENC_SHIFT_JIS:
+ SetupGetShiftJISCache();
+ create_raw_shift_jis(&bc);
+ break;
+
+ case BMG_ENC_UTF8:
+ create_raw_utf8(&bc);
+ break;
+
+ default:
+ return ERROR0(ERR_ERROR,
+ "Can't create BMG with encoding #%u (%s): %s\n",
+ encoding, GetEncodingNameBMG(encoding,"?"), bmg->fname );
+ }
+
+
+ //--- count messages
+
+#if 0
+ bmg_item_t *bi, *bi_end = bmg->item + bmg->item_used;
+ u32 n_msg = 0;
+
+ for ( bi = bmg->item; bi < bi_end; bi++ )
+ {
+ if (bi->text)
+ n_msg++;
+ }
+#endif
+
+ //--- count size of raw sections
+
+ uint total_raw_size = 0, raw_count = 0;
+ if (bmg->use_raw_sections)
+ {
+ bmg_raw_section_t *raw;
+ for ( raw = bmg->first_raw; raw; raw = raw->next )
+ {
+ const uint good_size = sizeof(bmg_section_t) + GetFastBufLen(&raw->data);
+ if ( raw->total_size < good_size )
+ raw->total_size = ALIGN32(good_size, raw->next ? opt_bmg_align : 4 );
+ total_raw_size += raw->total_size;
+ raw_count++;
+ }
+ }
+
+
+ //--- calculate sizes
+
+ const mem_t inf = GetFastBufMem(&bc.inf);
+ const mem_t mid = GetFastBufMem(&bc.mid);
+ const mem_t dat = GetFastBufMem(&bc.dat);
+
+ const u32 inf_size = ALIGN32( sizeof(bmg_inf_t) + inf.len, opt_bmg_align );
+ const u32 dat_size = ALIGN32( sizeof(bmg_dat_t) + dat.len, opt_bmg_align );
+ const u32 mid_size = bc.have_mid
+ ? ALIGN32( sizeof(bmg_mid_t) + mid.len, opt_bmg_align ) : 0;
+
+ const u32 total_size = sizeof(bmg_header_t)
+ + inf_size + dat_size + mid_size
+ + total_raw_size;
+
+ PRINT("SIZE(%u msg): %zx + %x[/%x] + %x + %x = %x\n",
+ bc.n_msg, sizeof(bmg_header_t),
+ inf_size, bmg->inf_size, dat_size, mid_size, total_size );
+
+
+ //--- alloc data
+
+ FREE(bmg->raw_data);
+ bmg->raw_data = CALLOC(total_size,1);
+ bmg->raw_data_size = total_size;
+
+
+ //---- setup bmg header
+
+ bmg_header_t *bh = (bmg_header_t*)bmg->raw_data;
+ bh->size = htonl(total_size);
+ bh->n_sections = htonl( ( bc.have_mid ? 3 : 2 ) + raw_count );
+ bh->encoding = encoding;
+ memcpy(bh->magic,BMG_MAGIC,sizeof(bh->magic));
+
+ u8 *dest = (u8*)(bh+1);
+ DASSERT( dest <= bmg->raw_data + total_size );
+
+
+ //---- setup inf section
+
+ bmg_inf_t *pinf = (bmg_inf_t*)dest;
+ pinf->size = htonl(inf_size);
+ pinf->n_msg = htons(bc.n_msg);
+ pinf->inf_size = htons(bmg->inf_size);
+ pinf->unknown_0c = htonl(bmg->unknown_inf_0c);
+ memcpy(pinf->magic,BMG_INF_MAGIC,sizeof(pinf->magic));
+ memcpy(pinf->list,inf.ptr,inf.len);
+
+ dest += inf_size;
+ DASSERT( dest <= bmg->raw_data + total_size );
+
+
+ //---- setup dat section
+
+ bmg_dat_t * pdat = (bmg_dat_t*)dest;
+ pdat->size = htonl(dat_size);
+ memcpy(pdat->magic,BMG_DAT_MAGIC,sizeof(pdat->magic));
+ memcpy(pdat->text_pool,dat.ptr,dat.len);
+
+ dest += dat_size;
+ DASSERT( dest <= bmg->raw_data + total_size );
+
+
+ //---- setup mid section
+
+ if (bc.have_mid)
+ {
+ bmg_mid_t *pmid = (bmg_mid_t*)dest;
+ pmid->size = htonl(mid_size);
+ pmid->n_msg = htons(bc.n_msg);
+ pmid->unknown_0a = htons(bmg->unknown_mid_0a);
+ pmid->unknown_0c = htonl(bmg->unknown_mid_0c);
+ memcpy(pmid->magic,BMG_MID_MAGIC,sizeof(pmid->magic));
+ memcpy(pmid->mid,mid.ptr,mid.len);
+
+ dest += mid_size;
+ DASSERT( dest <= bmg->raw_data + total_size );
+ }
+
+
+ //---- copy raw sections
+
+ if (total_raw_size)
+ {
+ bmg_raw_section_t *raw;
+ for ( raw = bmg->first_raw; raw; raw = raw->next )
+ {
+ PRINT("SAVE/RAW SECTION »%s« data: %u = 0x%x, total: %u = 0x%x\n",
+ PrintID(raw->magic,4,0),
+ GetFastBufLen(&raw->data), GetFastBufLen(&raw->data),
+ raw->total_size, raw->total_size );
+
+ bmg_section_t *sect = (bmg_section_t*)dest;
+ memcpy(sect->magic,raw->magic,sizeof(sect->magic));
+ sect->size = htonl(raw->total_size);
+ const mem_t mem = GetFastBufMem(&raw->data);
+ memcpy(sect->data,mem.ptr,mem.len);
+
+ dest += raw->total_size;
+ DASSERT( dest <= bmg->raw_data + total_size );
+ }
+ }
+
+
+ //--- clean
+
+ DASSERT( dest == bmg->raw_data + total_size );
+
+ PRINT("fb:inf: %s\n", GetFastBufStatus(&bc.inf) );
+ PRINT("fb:mid: %s\n", GetFastBufStatus(&bc.mid) );
+ PRINT("fb:dat: %s\n", GetFastBufStatus(&bc.dat) );
+ ResetCreateBMG(&bc);
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// SaveRawBMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SaveRawBMG
+(
+ bmg_t * bmg, // pointer to valid bmg
+ ccp fname, // filename of destination
+ FileMode_t fmode, // create-file mode
+ bool set_time // true: set time stamps
+)
+{
+ DASSERT(bmg);
+ DASSERT(fname);
+ PRINT("SaveRawBMG(%s,%d)\n",fname,set_time);
+
+
+ //--- create raw data
+
+ enumError err = CreateRawBMG(bmg);
+ if (err)
+ return err;
+ DASSERT(bmg->raw_data);
+ DASSERT(bmg->raw_data_size);
+
+
+ //--- write to file
+
+ File_t F;
+ err = CreateFile(&F,true,fname,fmode);
+ if ( err > ERR_WARNING || !F.f )
+ return err;
+ SetFileAttrib(&F.fatt,&bmg->fatt,0);
+
+ if ( fwrite(bmg->raw_data,1,bmg->raw_data_size,F.f) != bmg->raw_data_size )
+ FILEERROR1(&F,ERR_WRITE_FAILED,"Write failed: %s\n",fname);
+ return ResetFile(&F,set_time);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// SaveTextBMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * PrintAttribBMG
+(
+ char *buf, // destination buffer
+ uint buf_size, // size of 'buf', BMG_ATTRIB_BUF_SIZE is good
+ const u8 *attrib, // NULL or attrib to print
+ uint attrib_used, // used length of attrib
+ const u8 *def_attrib, // NULL or default attrib
+ bool force // true: always print '[]'
+)
+{
+ DASSERT(buf);
+ DASSERT( buf_size >= 10 );
+ DASSERT( attrib_used <= BMG_ATTRIB_SIZE);
+
+ static u8 all0[BMG_ATTRIB_SIZE] = {0};
+
+ if ( !attrib
+ || !attrib_used
+// [[BMG_ATTRIB_SIZE]]
+ || def_attrib && !memcmp(attrib,def_attrib,attrib_used) )
+ {
+ if (force)
+ {
+ *buf++ = '[';
+ *buf++ = ']';
+ }
+ *buf = 0;
+ return buf;
+ }
+
+ char *buf_end = buf + buf_size - 2;
+ *buf++ = '[';
+
+// [[BMG_ATTRIB_SIZE]]
+ if (!memcmp(attrib,all0,attrib_used))
+ *buf++ = '0';
+ else
+ {
+ uint idx;
+ for ( idx = 0; idx < attrib_used; idx += 4 )
+ {
+ if ( idx && buf < buf_end )
+ *buf++ = '/';
+
+ u32 attrib_val = be32(attrib+idx);
+ ccp sep = "";
+ while (attrib_val)
+ {
+ const uint val = (attrib_val >> 24) & 0xff;
+ if (val)
+ buf = snprintfE(buf,buf_end,"%s%x",sep,val);
+ else if ( *sep && buf < buf_end )
+ *buf++ = *sep;
+ sep = ",";
+ attrib_val <<= 8;
+ }
+ }
+ while ( buf[-1] == '/' )
+ buf--;
+ }
+ *buf++ = ']';
+ *buf = 0;
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void print_message
+ ( File_t *F, bmg_t *bmg, const bmg_item_t *bi, ccp mid_name )
+{
+ DASSERT(bmg);
+ DASSERT(bi);
+
+ if ( bmg->active_cond != bi->cond )
+ {
+ bmg->active_cond = bi->cond;
+ fprintf(F->f,"@? %x\n",bmg->active_cond);
+ }
+
+ ccp slot;
+ char slot_buf[10];
+ if (!bmg->have_predef_slots)
+ slot = EmptyString;
+ else if ( bi->slot == BMG_NO_PREDEF_SLOT )
+ slot = " ";
+ else
+ {
+ snprintf(slot_buf,sizeof(slot_buf)," @%04x",bi->slot);
+ slot = slot_buf;
+ }
+
+ char attrib[100+BMG_ATTRIB_BUF_SIZE];
+// [[xbuf]]
+ char xbuf[BMG_MSG_BUF_SIZE];
+
+ const bool print_attrib
+ = !opt_bmg_no_attrib
+ && opt_bmg_single_line < 2
+ && bi->attrib_used
+ && memcmp(bi->attrib,bmg->attrib,bmg->attrib_used);
+
+ if ( print_attrib && !opt_bmg_inline_attrib )
+ {
+ if ( bmg->attrib_used == 4 )
+ {
+ // classic, compatible with all versions
+ fprintf(F->f,"%s\t~%s 0x%08x\r\n",mid_name,slot,be32(bi->attrib));
+ }
+ else
+ {
+ // use new attrib vector
+ PrintAttribBMG( attrib, sizeof(attrib),
+ bi->attrib, bi->attrib_used, bmg->attrib, true );
+ fprintf(F->f,"%s\t~%s %s\r\n",mid_name,slot,attrib);
+ }
+ }
+
+ if ( print_attrib && opt_bmg_inline_attrib )
+ {
+ char *dest = attrib + snprintfS(attrib,sizeof(attrib),"%s%s ",mid_name,slot);
+ dest = PrintAttribBMG( dest, attrib+sizeof(attrib)-dest,
+ bi->attrib, bi->attrib_used, bmg->attrib, false );
+ *dest++ = ' ';
+ *dest = 0;
+ DASSERT( dest < attrib + sizeof(attrib) - 1 );
+ }
+ else
+ if (*slot)
+ snprintf(attrib,sizeof(attrib),"%s%s ",mid_name,slot);
+ else
+ snprintf(attrib,sizeof(attrib),"%s\t",mid_name);
+
+ if ( bi->text == bmg_null_entry )
+ fprintf(F->f,"%s/\r\n",attrib);
+ else if ( bi->text )
+ {
+ fputs(attrib,F->f);
+ const int use_colors = opt_bmg_colors != 1
+ ? opt_bmg_colors
+ : bmg->use_color_names > 0;
+// [[xbuf?]]
+ uint len = PrintString16BMG( xbuf, sizeof(xbuf), bi->text, bi->len,
+ BMG_UTF8_MAX, 0, use_colors );
+// [[xbuf?]]
+ ccp ptr = xbuf;
+ ccp end = ptr + len;
+ ccp sep = "=";
+ do
+ {
+ ccp start = ptr;
+ ccp temp_end = ptr + 72;
+ if ( temp_end > end )
+ temp_end = end;
+ ccp good_break = 0;
+ if ( opt_bmg_single_line || opt_bmg_export )
+ ptr = end;
+
+ bool first_loop = true;
+ while ( ptr < temp_end && !good_break )
+ {
+ for ( ; ptr <= temp_end; ptr++ )
+ {
+ if ( ptr == end )
+ {
+ good_break = ptr;
+ break;
+ }
+
+ if ( *ptr == '\\' )
+ {
+ if ( ++ptr >= temp_end )
+ break;
+ if ( *ptr == 'n' )
+ {
+ good_break = ++ptr;
+ break;
+ }
+ if ( *ptr != '\\' && ptr-start > 40 )
+ {
+ good_break = ptr-1;
+ if (!first_loop)
+ break;
+ }
+ }
+ else if ( *ptr == ' ' && ptr-start > 40 )
+ {
+ good_break = ptr;
+ if (!first_loop)
+ break;
+ }
+ }
+ temp_end = end;
+ first_loop = false;
+ }
+ if ( good_break && good_break < end - 8 )
+ ptr = good_break;
+
+ fprintf(F->f,"%s %.*s\r\n", sep, (int)(ptr-start), start );
+ sep = "\t+";
+
+ } while ( ptr < end );
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SaveTextBMG
+(
+ bmg_t * bmg, // pointer to valid bmg
+ ccp fname, // filename of destination
+ FileMode_t fmode, // create-file mode
+ bool set_time, // true: set time stamps
+ bool force_numeric, // true: force numric MIDs
+ uint brief_count // >0: suppress syntax info
+ // >1: suppress all comments
+ // >2: suppress '#BMG' magic
+)
+{
+ // use DOS/Windows line format -> unix can handle it ;)
+
+ DASSERT(bmg);
+ DASSERT(fname);
+ PRINT("SaveTextBMG(%s,%d)\n",fname,set_time);
+ bmg->active_cond = 0;
+
+ enum { BRIEF_ALL, BRIEF_NO_SYNTAX, BRIEF_NO_COMMENT, BRIEF_NO_MAGIC };
+
+ bool already_printed[BMG__VIP_N];
+ memset(already_printed,0,sizeof(already_printed));
+ char mid_name[10], abuf[BMG_ATTRIB_BUF_SIZE];
+
+
+ static ccp param_announce =
+ "# All parameters begin with '@'. Unknown parameters are ignored on scanning.\r\n";
+
+ static const char text_head[] =
+ {
+ "#BMG <<< The first 4 characters '#BMG' are the magic for a BMG text file.\r\n"
+ "# <<< Don't remove them!\r\n"
+ "#\r\n"
+ "# Details about BMG text files are available in the Web:\r\n"
+ "# * Syntax and Semantics: https://szs.wiimm.de/doc/bmg/text\r\n"
+ "# * The BMG file format: https://szs.wiimm.de/r/wiki/BMG\r\n"
+ "#\r\n"
+ "#\f\r\n"
+ "#------------------------------------------------------------------------------\r\n"
+ };
+
+ static const char text_param[] =
+ {
+ "%s"
+ "\r\n"
+ "# Define encoding of BMG: 1=CP1252, 2=UTF-16/be, 3=Shift-JIS, 4=UTF-8\r\n"
+ "@ENCODING = %u\r\n"
+ "\r\n"
+ "# Create »MID1« section: 0:off, 1=on\r\n"
+ "@BMG-MID = %u\r\n"
+ "\r\n"
+ "# Size of each element of section 'INF1' (MKW=8).\r\n"
+ "# This setting has impact to attributes and MKW features.\r\n"
+ "@INF-SIZE = 0x%02x\r\n"
+ "\r\n"
+ "# Default attribute values for this BMG (MKW=[1])\r\n"
+ "@DEFAULT-ATTRIBS = %s\r\n"
+ "\r\n"
+ "# Use MKW specific color names: 0=off, 1=auto, 2=on\r\n"
+ "@COLOR-NAMES = %u\r\n"
+ "\r\n"
+ "# Use MKW messages and track names: 0=off, 1=auto, 2=on\r\n"
+ "@MKW-MESSAGES = %u\r\n"
+ "\r\n"
+ "#\f\r\n"
+ "#------------------------------------------------------------------------------\r\n"
+ };
+
+ static const char unknown_param[] =
+ {
+ "%s"
+ "\r\n"
+ "# This part define values for unknown parameters of section headers.\r\n"
+ "@UNKNOWN-INF32-0C = %#10x # 32 bit value of section INF1 offset 0x0c\r\n"
+ "@UNKNOWN-MID16-0A = %#10x # 16 bit value of section MID1 offset 0x0a\r\n"
+ "@UNKNOWN-MID32-0C = %#10x # 32 bit value of section MID1 offset 0x0c\r\n"
+ "\r\n"
+ "#\f\r\n"
+ "#------------------------------------------------------------------------------\r\n"
+ };
+
+
+ //--- open file
+
+ File_t F;
+ enumError err = CreateFile(&F,true,fname,fmode);
+ if ( err > ERR_WARNING || !F.f )
+ return err;
+ SetFileAttrib(&F.fatt,&bmg->fatt,0);
+
+ const bool print_full = brief_count == BRIEF_ALL && !opt_bmg_export;
+ if (print_full)
+ fwrite(text_head,1,sizeof(text_head)-1,F.f);
+ else if ( brief_count < BRIEF_NO_MAGIC )
+ fprintf(F.f,"%s\r\n\r\n",BMG_TEXT_MAGIC);
+
+ HavePredifinedSlots(bmg);
+ const int encoding = CheckEncodingBMG(opt_bmg_encoding,bmg->encoding);
+
+ const bool print_settings // print only, if unusual
+ = encoding != BMG_ENC__DEFAULT
+ || bmg->attrib_used != 4
+ || be32(bmg->attrib) != BMG_INF_STD_ATTRIB
+ || !bmg->have_mid
+ || !bmg->use_color_names
+ || !bmg->use_mkw_messages
+ ;
+
+ if (print_settings)
+ {
+ PrintAttribBMG(abuf,sizeof(abuf),bmg->attrib,bmg->attrib_used,0,true);
+ if (print_full)
+ {
+ fprintf(F.f,text_param,
+ param_announce,
+ encoding, bmg->have_mid, bmg->inf_size, abuf,
+ bmg->use_color_names, bmg->use_mkw_messages );
+ param_announce = "";
+ }
+ else if ( brief_count < BRIEF_NO_MAGIC )
+ fprintf(F.f,"@ENCODING = %u\r\n"
+ "@BMG-MID = %u\r\n"
+ "@INF-SIZE = 0x%02x\r\n"
+ "@DEFAULT-ATTRIBS = %s\r\n"
+ "@COLOR-NAMES = %u\r\n"
+ "@MKW-MESSAGES = %u\r\n"
+ "\r\n",
+ encoding, bmg->have_mid, bmg->inf_size, abuf,
+ bmg->use_color_names, bmg->use_mkw_messages );
+ }
+
+ if ( print_settings
+ || bmg->unknown_inf_0c != BMG_INF_DEFAULT_0C
+ || bmg->unknown_mid_0a != BMG_MID_DEFAULT_0A
+ || bmg->unknown_mid_0c != BMG_MID_DEFAULT_0C
+ )
+ {
+ if (print_full)
+ fprintf(F.f,unknown_param,
+ param_announce,
+ bmg->unknown_inf_0c,
+ bmg->unknown_mid_0a, bmg->unknown_mid_0c );
+ else if ( brief_count < BRIEF_NO_MAGIC )
+ fprintf(F.f,"@UNKNOWN-INF32-0C = %#x\r\n"
+ "@UNKNOWN-MID16-0A = %#x\r\n"
+ "@UNKNOWN-MID32-0C = %#x\r\n"
+ "\r\n",
+ bmg->unknown_inf_0c,
+ bmg->unknown_mid_0a, bmg->unknown_mid_0c );
+ }
+
+
+ //--- messages
+
+ ccp intro = "\r\n";
+ static const char intro_other[] = "\r\n#--- other messages\r\n\r\n";
+
+ const bool support_mkw
+ = opt_bmg_support_mkw
+ && !force_numeric
+ && !opt_bmg_export
+ && bmg->attrib_used == 4
+ && be32(bmg->attrib) == BMG_INF_STD_ATTRIB
+ && bmg->have_mid
+ && !bmg->have_predef_slots;
+
+ if (support_mkw)
+ {
+ //--- find track names
+
+ int idx;
+ uint last_cup = 0;
+ for ( idx = 0; idx < BMG_N_TRACK; idx++ )
+ {
+ const int tidx = GetTrackIndexBMG(idx,-1);
+ if ( tidx < 0 )
+ continue;
+
+ const bmg_item_t * bi1 = FindItemBMG(bmg,MID_TRACK1_BEG+tidx);
+ if (bi1)
+ {
+ const bmg_item_t * bi2 = FindItemBMG(bmg,MID_TRACK2_BEG+tidx);
+ if ( bi2 && IsItemEqualBMG(bi1,bi2) )
+ {
+ const uint cup = idx / 4 + '1';
+ if ( brief_count < BRIEF_NO_COMMENT && last_cup != cup )
+ {
+ if (!last_cup)
+ {
+ intro = intro_other;
+ fprintf(F.f,"\r\n#--- standard track names\r\n\r\n");
+ }
+ else
+ fputs("\r\n",F.f);
+ last_cup = cup;
+ }
+ snprintf(mid_name,sizeof(mid_name), " T%c%c", cup, idx % 4 + '1' );
+ print_message(&F,bmg,bi1,mid_name);
+
+ already_printed[ bi1->mid - MID__VIP_BEG ] = true;
+ already_printed[ bi2->mid - MID__VIP_BEG ] = true;
+
+ if (opt_bmg_support_lecode)
+ {
+ bi2 = FindItemBMG(bmg,MID_LE_TRACK_BEG+tidx);
+ if ( bi2 && IsItemEqualBMG(bi1,bi2) )
+ already_printed[ bi2->mid - MID__VIP_BEG ] = true;
+ }
+ else if (opt_bmg_support_ctcode)
+ {
+ bi2 = FindItemBMG(bmg,MID_CT_TRACK_BEG+tidx);
+ if ( bi2 && IsItemEqualBMG(bi1,bi2) )
+ already_printed[ bi2->mid - MID__VIP_BEG ] = true;
+ }
+ }
+ }
+ }
+
+
+ //--- find battle names
+
+ last_cup = 0;
+ for ( idx = 0; idx < BMG_N_ARENA; idx++ )
+ {
+ const int tidx = GetArenaIndexBMG(idx,-1);
+ if ( tidx < 0 )
+ continue;
+
+ const bmg_item_t * bi1 = FindItemBMG(bmg,MID_ARENA1_BEG+tidx);
+ if (bi1)
+ {
+ const bmg_item_t * bi2 = FindItemBMG(bmg,MID_ARENA2_BEG+tidx);
+ if ( bi2 && IsItemEqualBMG(bi1,bi2) )
+ {
+ const uint cup = idx / 5 + '1';
+ if ( brief_count < BRIEF_NO_COMMENT && last_cup != cup )
+ {
+ if (!last_cup)
+ {
+ intro = intro_other;
+ fprintf(F.f,"\r\n#--- battle track names\r\n\r\n");
+ }
+ else
+ fputs("\r\n",F.f);
+ last_cup = cup;
+ }
+ snprintf(mid_name,sizeof(mid_name), " U%c%c", cup, idx % 5 + '1' );
+ print_message(&F,bmg,bi1,mid_name);
+
+ already_printed[ bi1->mid - MID__VIP_BEG ] = true;
+ already_printed[ bi2->mid - MID__VIP_BEG ] = true;
+
+ if (opt_bmg_support_lecode)
+ {
+ bi2 = FindItemBMG(bmg,MID_LE_ARENA_BEG+tidx);
+ if ( bi2 && IsItemEqualBMG(bi1,bi2) )
+ already_printed[ bi2->mid - MID__VIP_BEG ] = true;
+ }
+ else if (opt_bmg_support_ctcode)
+ {
+ bi2 = FindItemBMG(bmg,MID_CT_ARENA_BEG+tidx);
+ if ( bi2 && IsItemEqualBMG(bi1,bi2) )
+ already_printed[ bi2->mid - MID__VIP_BEG ] = true;
+ }
+ }
+ }
+ }
+
+
+ //--- find chat strings
+
+ const bmg_item_t * bi1 = FindAnyItemBMG(bmg,MID_CHAT_BEG);
+ const bmg_item_t * bi1end = FindAnyItemBMG(bmg,MID_CHAT_END);
+ if ( bi1 < bi1end )
+ {
+ if ( brief_count < BRIEF_NO_COMMENT )
+ {
+ intro = intro_other;
+ fprintf(F.f,"\r\n#--- online chat\r\n\r\n");
+ }
+
+ for ( ; bi1 < bi1end; bi1++ )
+ {
+ DASSERT( bi1->mid >= MID_CHAT_BEG && bi1->mid <= MID_CHAT_END );
+ snprintf(mid_name,sizeof(mid_name)," M%02u",bi1->mid-MID_CHAT_BEG+1);
+ print_message(&F,bmg,bi1,mid_name);
+ already_printed[bi1->mid-MID__VIP_BEG] = true;
+ }
+ }
+ }
+
+
+ //--- print messages not already printed
+
+ struct sep_t { u32 mid; ccp info; };
+
+ static const struct sep_t sep_std_tab[] =
+ {
+ {0,0}
+ };
+
+ static const struct sep_t sep_mkw_tab[] =
+ {
+ { MID_ENGINE_BEG, "engines" },
+ { MID_ENGINE_END, 0 },
+ { MID_CHAT_BEG, "online chat" },
+ { MID_CHAT_END, 0 },
+
+ { MID_RCUP_BEG, "racing cup names" },
+ { MID_RCUP_END, 0 },
+ { MID_TRACK1_BEG, "track names, 1. copy" },
+ { MID_TRACK1_END, 0 },
+ { MID_BCUP_BEG, "battle cup names" },
+ { MID_BCUP_END, 0 },
+ { MID_TRACK2_BEG, "track names, 2. copy" },
+ { MID_TRACK2_END, 0 },
+
+ { MID_ARENA1_BEG, "arena names, 1. copy" },
+ { MID_ARENA1_END, 0 },
+ { MID_ARENA2_BEG, "arena names, 2. copy" },
+ { MID_ARENA2_END, 0 },
+
+ { MID_PARAM_IDENTIFY, "identification for third party tools" },
+ { MID_PARAM_IDENTIFY, 0 },
+
+ { MID_PARAM_BEG, "parameters for mkw-ana" },
+ { MID_PARAM_END, 0 },
+
+ { MID_CT_TRACK_BEG, "CT-CODE: track names" },
+ { MID_CT_TRACK_END, 0 },
+ { MID_CT_CUP_BEG, "CT-CODE: cup names" },
+ { MID_CT_CUP_END, 0 },
+ { MID_CT_CUP_REF_BEG, "CT-CODE: track-to-cup reference" },
+ { MID_CT_CUP_REF_END, 0 },
+
+ { MID_X_MESSAGE_BEG, "new messages for CT/LE-CODE" },
+ { MID_X_MESSAGE_END, 0 },
+
+ { MID_LE_CUP_BEG, "LE-CODE: cup names" },
+ { MID_LE_CUP_END, 0 },
+ { MID_LE_TRACK_BEG, "LE-CODE: track names" },
+ { MID_LE_TRACK_END, 0 },
+ { MID_LE_CUP_REF_BEG, "LE-CODE: track-to-cup reference" },
+ { MID_LE_CUP_REF_END, 0 },
+
+ {0,0}
+ };
+
+ #ifdef TEST
+ {
+ fflush(stdout);
+ const struct sep_t *sep;
+ for ( sep = sep_std_tab+1; sep->mid; sep++ )
+ if ( sep[-1].mid > sep->mid )
+ fprintf(stderr,
+ "Wrong order in sep_std_tab[%zu]:\n\t%04x %s\n\t%04x %s\n",
+ sep-sep_std_tab, sep[-1].mid, sep[-1].info, sep->mid, sep->info );
+ for ( sep = sep_mkw_tab+1; sep->mid; sep++ )
+ if ( sep[-1].mid > sep->mid )
+ fprintf(stderr,
+ "Wrong order in sep_mkw_tab[%zu]:\n\t%04x %s\n\t%04x %s\n",
+ sep-sep_mkw_tab, sep[-1].mid, sep[-1].info, sep->mid, sep->info );
+ fflush(stderr);
+ }
+ #endif
+
+ const struct sep_t *sep_tab = support_mkw ? sep_mkw_tab : sep_std_tab;
+ const struct sep_t *sep = brief_count < BRIEF_NO_COMMENT ? sep_tab : 0;
+
+ bmg_item_t * bi = bmg->item;
+ bmg_item_t * bi_end = bi + bmg->item_used;
+ for ( ; bi < bi_end; bi++ )
+ {
+ const u32 mid = bi->mid;
+ if ( mid < MID__VIP_BEG
+ || mid >= MID__VIP_END
+ || !already_printed[mid-MID__VIP_BEG]
+ )
+ {
+ if ( sep && sep->mid && mid >= sep->mid )
+ {
+ sep++;
+ while ( sep->mid && mid >= sep->mid )
+ sep++;
+ if ( sep > sep_tab+1 && sep[-2].mid == mid && sep[-2].info )
+ fprintf(F.f,"\r\n#--- [%x] %s\r\n", mid, sep[-2].info );
+ else if ( sep[-1].info )
+ fprintf(F.f,"\r\n#--- [%x:%x] %s\r\n",
+ sep[-1].mid, sep->mid-1, sep[-1].info );
+ else
+ fputs("\r\n",F.f);
+ intro = 0;
+ }
+
+ if ( brief_count < BRIEF_NO_COMMENT && intro )
+ {
+ fputs(intro,F.f);
+ intro = 0;
+ }
+ snprintf(mid_name,sizeof(mid_name),"%6x",bi->mid);
+ print_message(&F,bmg,bi,mid_name);
+ }
+ }
+
+
+ //--- raw sections
+
+ if ( bmg->use_raw_sections && bmg->first_raw )
+ {
+ XDump_t xd;
+ InitializeXDump(&xd);
+ SetupXDump(&xd,XDUMPC_DUMP);
+ xd.f = F.f;
+ xd.indent = 1;
+ xd.prefix = "@X";
+ xd.eol = "\r\n";
+ xd.format = XDUMPF_INT_1;
+ xd.print_format = false;
+ xd.print_summary= false;
+
+ bmg_raw_section_t *raw;
+ for ( raw = bmg->first_raw; raw; raw = raw->next )
+ {
+ PRINT("HEXDUMP SECTION »%s« data: %u = 0x%x, total: %u = 0x%x\n",
+ PrintID(raw->magic,4,0),
+ GetFastBufLen(&raw->data), GetFastBufLen(&raw->data),
+ raw->total_size, raw->total_size );
+
+ char name[20];
+ fprintf(F.f,"\r\n#\f\r\n#--------------------------------------"
+ "----------------------------------------\r\n\r\n"
+ "@SECTION \"%s\"\r\n\r\n",
+ PrintEscapedString( name, sizeof(name),
+ raw->magic, sizeof(raw->magic),
+ CHMD_ESC, '"', 0) );
+
+ const mem_t data = GetFastBufMem(&raw->data);
+ xd.assumed_size = data.len;
+ XDump(&xd,data.ptr,data.len,true);
+ }
+ }
+
+
+ //--- terminate
+
+ if ( brief_count < 2 )
+ fputs("\r\n",F.f);
+ ResetFile(&F,set_time);
+ return ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// ScanBMGMID() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanBMGMID
+(
+ // returns 'scan->status'
+
+ bmg_scan_mid_t *scan, // result, never NULL
+ bmg_t *bmg, // NULL or bmg to update parameters
+ ccp src, // source pointer
+ ccp src_end // NULL or end of source
+)
+{
+ DASSERT(scan);
+
+ //---- setup
+
+ memset(scan,0,sizeof(*scan));
+
+ ccp ptr = src;
+ if (!src)
+ return 0;
+ if (!src_end)
+ src_end = src + strlen(src);
+
+
+ //--- skip blanks and controls
+
+ while ( ptr < src_end && (uchar)*ptr <= ' ' )
+ ptr++;
+ if ( ptr == src_end )
+ goto abort;
+
+
+ //--- find keyword
+
+ ccp key = ptr;
+ while ( ptr < src_end && isalnum((int)*ptr) )
+ ptr++;
+ const uint keylen = ptr - key;
+
+ if ( keylen == 3 )
+ {
+ if ( ( *key == 'T' || *key == 't' )
+ && key[1] >= '1' && key[1] <= '8'
+ && key[2] >= '1' && key[2] <= '4' )
+ {
+ int idx = 4 * ( key[1] - '1' ) + ( key[2] - '1' );
+ DASSERT(GetTrackIndexBMG);
+ idx = GetTrackIndexBMG(idx,-1);
+ if ( idx >= 0 )
+ {
+ scan->mid[0] = MID_TRACK1_BEG + idx;
+ scan->mid[1] = MID_TRACK2_BEG + idx;
+ scan->n_mid = 2;
+ if (opt_bmg_support_lecode)
+ {
+ scan->mid[2] = MID_LE_TRACK_BEG + idx;
+ scan->n_mid++;
+ }
+ else if (opt_bmg_support_ctcode)
+ {
+ scan->mid[2] = MID_CT_TRACK_BEG + idx;
+ scan->n_mid++;
+ }
+ if ( bmg && bmg->use_mkw_messages == 1 )
+ bmg->use_mkw_messages = 2;
+ }
+ }
+ else if ( ( *key == 'U' || *key == 'u' )
+ && key[1] >= '1' && key[1] <= '2'
+ && key[2] >= '1' && key[2] <= '5' )
+ {
+ int idx = 5 * ( key[1] - '1' ) + ( key[2] - '1' );
+ DASSERT(GetArenaIndexBMG);
+ idx = GetArenaIndexBMG(idx,-1);
+ if ( idx >= 0 )
+ {
+ scan->mid[0] = MID_ARENA1_BEG + idx;
+ scan->mid[1] = MID_ARENA2_BEG + idx;
+ scan->n_mid = 2;
+ if (opt_bmg_support_lecode)
+ {
+ scan->mid[2] = MID_LE_ARENA_BEG + idx;
+ scan->n_mid++;
+ }
+ else if (opt_bmg_support_ctcode)
+ {
+ scan->mid[2] = MID_CT_ARENA_BEG + idx;
+ scan->n_mid++;
+ }
+ if ( bmg && bmg->use_mkw_messages == 1 )
+ bmg->use_mkw_messages = 2;
+ }
+ }
+ else if ( *key == 'M' || *key == 'm' )
+ {
+ char *end;
+ u32 num = strtoul(key+1,&end,10);
+ if ( num > 0 && num <= BMG_N_CHAT && end == ptr )
+ {
+ scan->mid[0] = MID_CHAT_BEG + num - 1;
+ scan->n_mid = 1;
+ if ( bmg && bmg->use_mkw_messages == 1 )
+ bmg->use_mkw_messages = 2;
+ }
+ }
+ }
+
+ if ( !scan->n_mid && keylen )
+ {
+ char *end;
+ uint num = strtoul(key,&end,16);
+ if ( end == ptr )
+ {
+ scan->mid[0] = num;
+ scan->n_mid = 1;
+ }
+ else
+ scan->status = -1;
+ }
+
+ if ( scan->n_mid > 0 )
+ while ( ptr < src_end && ( *ptr == ' ' || *ptr == '\t' ) )
+ ptr++;
+
+ abort:
+ if (!scan->status)
+ scan->status = scan->n_mid;
+ scan->scan_end = scan->status > 0 ? (char*)ptr : (char*)src;
+ return scan->status;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanMidBMG
+(
+ // returns:
+ // -1: invalid MID
+ // 0: empty line
+ // 1: single mid found
+ // 2: double mid found (track or arena)
+ // 3: triple mid found (track or arena, CTCODE)
+
+ bmg_t *bmg, // NULL or bmg to update parameters
+ u32 *ret_mid1, // return value: pointer to first MID
+ u32 *ret_mid2, // return value: pointer to second MID
+ u32 *ret_mid3, // return value: pointer to third MID
+ ccp src, // source pointer
+ ccp src_end, // NULL or end of source
+ char **scan_end // return value: NULL or end of scanned 'source'
+)
+{
+// [[2do]]
+ #if defined(TEST) || defined(DEBUG) || 1
+
+ bmg_scan_mid_t scan;
+ ScanBMGMID(&scan,bmg,src,src_end);
+
+ if (ret_mid1)
+ *ret_mid1 = scan.n_mid > 0 ? scan.mid[0] : 0;
+ if (ret_mid2)
+ *ret_mid2 = scan.n_mid > 1 ? scan.mid[1] : 0;
+ if (ret_mid3)
+ *ret_mid3 = scan.n_mid > 2 ? scan.mid[2] : 0;
+ if (scan_end)
+ *scan_end = scan.scan_end;
+ return scan.status;
+
+ #else
+ //---- setup
+
+ u32 mid1 = 0, mid2 = 0, mid3 = 0;
+ int stat = 0;
+ ccp ptr = src;
+
+ if (!src)
+ goto abort;
+ if (!src_end)
+ src_end = src + strlen(src);
+
+
+ //--- skip blanks and controls
+
+ while ( ptr < src_end && (uchar)*ptr <= ' ' )
+ ptr++;
+ if ( ptr == src_end )
+ goto abort;
+
+
+ //--- find keyword
+
+ ccp key = ptr;
+ while ( ptr < src_end && isalnum((int)*ptr) )
+ ptr++;
+ const uint keylen = ptr - key;
+
+ if ( keylen == 3 )
+ {
+ if ( ( *key == 'T' || *key == 't' )
+ && key[1] >= '1' && key[1] <= '8'
+ && key[2] >= '1' && key[2] <= '4' )
+ {
+ int idx = 4 * ( key[1] - '1' )+ ( key[2] - '1' );
+ DASSERT(GetTrackIndexBMG);
+ idx = GetTrackIndexBMG(idx,-1);
+ if ( idx >= 0 )
+ {
+ mid1 = MID_TRACK1_BEG + idx;
+ mid2 = MID_TRACK2_BEG + idx;
+ stat = 2;
+ if (opt_bmg_support_lecode)
+ {
+ mid3 = MID_LE_TRACK_BEG + idx;
+ stat++;
+ }
+ else if (opt_bmg_support_ctcode)
+ {
+ mid3 = MID_CT_TRACK_BEG + idx;
+ stat++;
+ }
+ if ( bmg && bmg->use_mkw_messages == 1 )
+ bmg->use_mkw_messages = 2;
+ }
+ }
+ else if ( ( *key == 'U' || *key == 'u' )
+ && key[1] >= '1' && key[1] <= '2'
+ && key[2] >= '1' && key[2] <= '5' )
+ {
+ int idx = 5 * ( key[1] - '1' ) + ( key[2] - '1' );
+ DASSERT(GetArenaIndexBMG);
+ idx = GetArenaIndexBMG(idx,-1);
+ if ( idx >= 0 )
+ {
+ mid1 = MID_ARENA1_BEG + idx;
+ mid2 = MID_ARENA2_BEG + idx;
+ stat = 2;
+ if (opt_bmg_support_lecode)
+ {
+ mid3 = MID_LE_ARENA_BEG + idx;
+ stat++;
+ }
+ else if (opt_bmg_support_ctcode)
+ {
+ mid3 = MID_CT_ARENA_BEG + idx;
+ stat++;
+ }
+ if ( bmg && bmg->use_mkw_messages == 1 )
+ bmg->use_mkw_messages = 2;
+ }
+ }
+ else if ( *key == 'M' || *key == 'm' )
+ {
+ char *end;
+ u32 num = strtoul(key+1,&end,10);
+ if ( num > 0 && num <= BMG_N_CHAT && end == ptr )
+ {
+ mid1 = MID_CHAT_BEG + num - 1;
+ stat = 1;
+ if ( bmg && bmg->use_mkw_messages == 1 )
+ bmg->use_mkw_messages = 2;
+ }
+ }
+ }
+
+ if ( !stat && keylen )
+ {
+ char *end;
+ uint num = strtoul(key,&end,16);
+ if ( end == ptr )
+ {
+ mid1 = num;
+ stat = 1;
+ }
+ else
+ stat = -1;
+ }
+
+ if ( stat > 0 )
+ while ( ptr < src_end && ( *ptr == ' ' || *ptr == '\t' ) )
+ ptr++;
+
+ abort:
+ if (ret_mid1)
+ *ret_mid1 = mid1;
+ if (ret_mid2)
+ *ret_mid2 = mid2;
+ if (ret_mid3)
+ *ret_mid3 = mid3;
+ if (scan_end)
+ *scan_end = stat > 0 ? (char*)ptr : (char*)src;
+ return stat;
+ #endif
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// patch bmg: helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp (*GetContainerNameBMG) ( const bmg_t *bmg ) = 0;
+
+//-----------------------------------------------------------------------------
+
+ccp GetNameBMG ( const bmg_t *bmg, uint *name_len )
+{
+ DASSERT(bmg);
+
+ ccp name = 0;
+ if (GetContainerNameBMG)
+ name = GetContainerNameBMG(bmg);
+
+ if ( !name && bmg->fname )
+ {
+ name = bmg->src_is_arch ? 0 : strstr(bmg->fname,".d/");
+ if (name)
+ {
+ while ( name > bmg->fname && name[-1] != '/' )
+ name--;
+ }
+ else
+ {
+ name = strrchr(bmg->fname,'/');
+ name = name ? name + 1 : bmg->fname;
+ }
+ }
+
+ if (!name)
+ name = "";
+ else
+ while ( *name == '.' )
+ name++;
+
+ if (name_len)
+ {
+ ccp end = strchr(name,'.');
+ *name_len = end ? end-name : strlen(name);
+ }
+
+ return name;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef struct format_message_t
+{
+ bmg_t *dest; // valid destination bmg
+ uint mod_count; // incremented for each modification
+
+ ccp full_name; // NULL or full name of source file
+ uint full_len; // length of 'full_name'
+ ccp good_name; // NULL or good name of source file
+ uint good_len; // length of 'good_name'
+}
+format_message_t;
+
+//-----------------------------------------------------------------------------
+
+static bool format_message
+(
+ format_message_t *fm, // valid object
+ bmg_item_t *dptr, // valid message
+ const u16 *format, // format string
+ uint format_len // length of 'format'
+)
+{
+ DASSERT(fm);
+ DASSERT(fm->dest);
+ DASSERT(dptr);
+ DASSERT(format);
+
+
+ //--- line support
+
+ typedef struct line_t
+ {
+ const u16 *beg;
+ const u16 *end;
+ }
+ line_t;
+
+ const uint MAX_LINE = 100;
+ line_t line[100];
+ uint n_line = 0;
+
+
+ //--- setup dest and source
+
+ u16 buf[10000];
+ u16 *dest = buf;
+ u16 *dest_end = buf + sizeof(buf)/sizeof(*buf);
+ u16* marker = buf;
+
+ const u16 *source = format;
+ const u16 *source_end = format + format_len;
+
+
+ //--- main loop
+
+ while ( dest < dest_end && source < source_end )
+ {
+ if ( be16(source) != '%' )
+ {
+ *dest++ = *source++;
+ continue;
+ }
+
+ if ( source[0] == source[1] )
+ {
+ source++;
+ *dest++ = *source++;
+ continue;
+ }
+
+ PRINT("%04x = %c\n",be16(source), be16(source) & 0xff );
+ const u16 *start = source++;
+
+ u32 p1, p2, stat;
+ source = ScanRange16U32(source,&stat,&p1,&p2,0,~(u32)0);
+ if ( stat == 1 )
+ p1 = 0;
+ else if ( stat < 1 )
+ {
+ p1 = 0;
+ p2 = M1(p2);
+ }
+
+ char buf8[50];
+ uint copy_len = 0;
+ const u8 * copy_src8 = 0;
+ const u16 * copy_src16 = 0;
+
+ const u16 conv_char = be16(source++);
+ switch (conv_char)
+ {
+ case 's':
+ case 'S':
+ copy_len = dptr->len;
+ copy_src16 = dptr->text;
+ break;
+
+ case 'i':
+ copy_len = snprintf(buf8,sizeof(buf8),"%x",dptr->mid);
+ copy_src8 = (u8*)buf8;
+ break;
+
+ case 'I':
+ copy_len = snprintf(buf8,sizeof(buf8),"%04x",dptr->mid);
+ copy_src8 = (u8*)buf8;
+ break;
+
+ case 'n':
+ case 'N':
+ if (!fm->full_name)
+ {
+ fm->full_name = GetNameBMG(fm->dest,&fm->full_len);
+ if ( fm->full_len > 4 && !strncasecmp(fm->full_name,"Menu",4) )
+ {
+ fm->good_name = fm->full_name + 4;
+ fm->good_len = fm->full_len - 4;
+ }
+ else
+ {
+ fm->good_name = fm->full_name;
+ fm->good_len = fm->full_len;
+ }
+ }
+
+ if ( conv_char == 'N' )
+ {
+ copy_len = fm->good_len;
+ copy_src8 = (u8*)fm->good_name;
+ }
+ else
+ {
+ copy_len = fm->full_len;
+ copy_src8 = (u8*)fm->full_name;
+ }
+ break;
+
+ case 'm':
+ marker = dest;
+ break;
+
+ case 'l':
+ case 'L':
+ case 'M':
+ if (!n_line)
+ {
+ const u16 *src = dptr->text;
+ const u16 *end = src + dptr->len;
+ while ( n_line < MAX_LINE )
+ {
+ line[n_line].beg = src;
+ while ( src < end )
+ {
+ if ( ntohs(*src) == '\n' )
+ break;
+
+ src += GetWordLength16BMG(src);
+ }
+ line[n_line].end = src;
+ n_line++;
+ if ( src >= end )
+ break;
+ src++;
+ }
+ PRINT("L: %u lines found\n",n_line);
+ }
+
+ if ( stat == 1 )
+ p1 = p2;
+ if ( p2 > n_line-1 )
+ p2 = n_line-1;
+ PRINT("L: select %u..%u/%u\n",p1,p2,n_line);
+
+ if ( p1 <= p2 )
+ {
+ copy_src16 = line[p1].beg;
+ copy_len = line[p2].end - copy_src16;
+ }
+
+ if ( conv_char == 'M' )
+ {
+ if ( !copy_len && marker )
+ dest = marker;
+ //marker = 0;
+ copy_len = 0;
+ }
+ else
+ {
+ if ( !copy_len && conv_char == 'L' && marker )
+ dest = marker;
+ //if ( conv_char == 'L' )
+ // marker = 0;
+ }
+ p1 = 0;
+ p2 = M1(p2);
+ break;
+
+ default:
+ copy_len = source - start;
+ copy_src16 = start;
+ break;
+ }
+
+ if (copy_len)
+ {
+ // correct p1 and p2
+ if ( p1 > copy_len )
+ p1 = copy_len;
+ if ( p2 > copy_len )
+ p2 = copy_len;
+
+ u32 count = p2 - p1;
+ if (copy_src16)
+ {
+ copy_src16 += p1;
+ while ( count-- > 0 && dest < dest_end )
+ *dest++ = *copy_src16++;
+ }
+ else if (copy_src8)
+ {
+ copy_src8 += p1;
+ while ( count-- > 0 && dest < dest_end )
+ *dest++ = htons(*copy_src8++);
+ }
+ }
+ }
+
+ const uint new_len = dest - buf;
+ if ( new_len != dptr->len || memcmp(buf,dptr->text,new_len*sizeof(*dest)) )
+ {
+ AssignItemText16BMG(dptr,buf,dest-buf);
+ fm->mod_count++;
+ return true;
+ }
+ return false;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// patch bmg: functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchPrintBMG
+(
+ bmg_t * bmg_dest, // pointer to destination bmg
+ const bmg_t * bmg_src // pointer to source bmg
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // source - mix mix //
+ //--------------------------------------//
+
+ DASSERT(bmg_dest);
+ DASSERT(bmg_src);
+ copy_param(bmg_dest,bmg_src);
+
+ format_message_t fm;
+ memset(&fm,0,sizeof(fm));
+ fm.dest = bmg_dest;
+
+ bmg_item_t * dptr = bmg_dest->item;
+ bmg_item_t * dend = dptr + bmg_dest->item_used;
+
+ const bmg_item_t * sptr = bmg_src->item;
+ const bmg_item_t * send = sptr + bmg_src->item_used;
+
+ for ( ; dptr < dend; dptr++ )
+ {
+ u32 mid = dptr->mid;
+ while ( sptr < send && sptr->mid < mid )
+ sptr++;
+ if ( sptr == send )
+ break;
+ if ( sptr->mid == mid )
+ format_message(&fm,dptr,sptr->text,sptr->len);
+ }
+ return fm.mod_count > 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchFormatBMG
+(
+ bmg_t * bmg_dest, // pointer to destination bmg
+ const bmg_t * bmg_src, // pointer to source bmg
+ ccp format // format parameter
+)
+{
+ DASSERT(bmg_dest);
+ DASSERT(bmg_src);
+ copy_param(bmg_dest,bmg_src);
+
+ const uint form_size = 2000;
+ u16 form_buf[form_size];
+ uint form_len = ScanString16BMG(form_buf,form_size,format,-1,bmg_dest);
+
+ format_message_t fm;
+ memset(&fm,0,sizeof(fm));
+ fm.dest = bmg_dest;
+
+ bmg_item_t * dptr = bmg_dest->item;
+ bmg_item_t * dend = dptr + bmg_dest->item_used;
+ for ( ; dptr < dend; dptr++ )
+ format_message(&fm,dptr,form_buf,form_len);
+
+ return fm.mod_count > 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchRegexBMG
+(
+ bmg_t * bmg, // pointer to destination bmg
+ ccp regex // format parameter
+)
+{
+ #if ENABLE_BMG_REGEX
+ DASSERT(bmg);
+ DASSERT(regex);
+
+ PRINT("REGEX=%s\n",regex);
+
+ Regex_t re;
+ enumError err = ScanRegex(&re,true,regex);
+ if ( err || !re.valid )
+ {
+ ResetRegex(&re);
+ return false;
+ }
+
+ char buf1[1000];
+ struct { FastBuf_t b; char space[500]; } res;
+ InitializeFastBuf(&res,sizeof(res));
+
+ bool dirty = false;
+ bmg_item_t * iptr = bmg->item;
+ bmg_item_t * iend = iptr + bmg->item_used;
+
+ for ( ; iptr < iend; iptr++ )
+ {
+ uint len1 = PrintString16BMG( buf1, sizeof(buf1), iptr->text, iptr->len,
+ BMG_UTF8_MAX, 0, true );
+// printf("-> [%d] %s\n",len1,buf1);
+
+ const int stat = ReplaceRegex(&re,&res.b,buf1,len1);
+ if ( stat >= 0 )
+ {
+// printf("{%d] %.*s\n",stat,stat,GetFastBufString(&res.b));
+ AssignItemScanTextBMG(iptr,GetFastBufString(&res.b),stat);
+ dirty = true;
+ }
+ }
+
+ ResetFastBuf(&res.b);
+ ResetRegex(&re);
+ return dirty;
+
+ #else
+ return false;
+ #endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchRmRegexBMG
+(
+ bmg_t * bmg, // pointer to destination bmg
+ ccp regex // format parameter
+)
+{
+ #if ENABLE_BMG_REGEX
+ DASSERT(bmg);
+ DASSERT(regex);
+
+ PRINT("REGEX=%s\n",regex);
+
+ Regex_t re;
+ enumError err = ScanRegex(&re,true,regex);
+ if ( err || !re.valid )
+ {
+ ResetRegex(&re);
+ return false;
+ }
+
+ char buf1[1000];
+ struct { FastBuf_t b; char space[500]; } res;
+ InitializeFastBuf(&res,sizeof(res));
+
+ bool dirty = false;
+ bmg_item_t * iptr = bmg->item;
+ bmg_item_t * iend = iptr + bmg->item_used;
+
+ for ( ; iptr < iend; iptr++ )
+ {
+ uint len1 = PrintString16BMG( buf1, sizeof(buf1), iptr->text, iptr->len,
+ BMG_UTF8_MAX, 0, true );
+ //printf("-> [%d] %s\n",len1,buf1);
+
+ int stat = ReplaceRegex(&re,&res.b,buf1,len1);
+ if ( stat >= 0 )
+ {
+ if (stat)
+ AssignItemScanTextBMG(iptr,GetFastBufString(&res.b),stat);
+ else
+ {
+ FreeItemBMG(iptr);
+ iptr->text = 0;
+ ResetAttribBMG(bmg,iptr);
+ }
+ dirty = true;
+ }
+ }
+
+ ResetFastBuf(&res.b);
+ ResetRegex(&re);
+ return dirty;
+
+ #else
+ return false;
+ #endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchReplaceBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src // pointer to source bmg
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // source - patch both //
+ //--------------------------------------//
+
+ DASSERT(dest);
+ DASSERT(src);
+ copy_param(dest,src);
+
+ bmg_item_t * dptr = dest->item;
+ bmg_item_t * dend = dptr + dest->item_used;
+
+ const bmg_item_t * sptr = src->item;
+ const bmg_item_t * send = sptr + src->item_used;
+
+ bool dirty = false;
+ for ( ; dptr < dend; dptr++ )
+ {
+ if (!dptr->text)
+ continue;
+
+ u32 mid = dptr->mid;
+ while ( sptr < send && sptr->mid < mid )
+ sptr++;
+ if ( sptr == send )
+ break;
+
+ if ( sptr->mid == mid
+ && !IsItemEqualBMG(dptr,sptr)
+ && ( !sptr->cond || FindItemBMG(dest,sptr->cond) ) )
+ {
+ FreeItemBMG(dptr);
+ DASSERT( dptr->mid == sptr->mid );
+
+ CopyAttribBMG(dest,dptr,sptr);
+ dptr->len = sptr->len;
+ dptr->text = sptr->text;
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchInsertBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src // pointer to source bmg
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // source patch source both //
+ //--------------------------------------//
+
+ DASSERT(dest);
+ DASSERT(src);
+ copy_param(dest,src);
+
+ const bmg_item_t * sptr = src->item;
+ const bmg_item_t * send = sptr + src->item_used;
+
+ bool dirty = false;
+ for ( ; sptr < send; sptr++ )
+ {
+ if ( sptr->cond && !FindItemBMG(dest,sptr->cond) )
+ continue;
+
+ bool old_item;
+ bmg_item_t * dptr = InsertItemBMG(dest,sptr->mid,0,0,&old_item);
+ if (!old_item)
+ {
+ FreeItemBMG(dptr);
+ DASSERT( dptr->mid == sptr->mid );
+
+ CopyAttribBMG(dest,dptr,sptr);
+ dptr->len = sptr->len;
+ dptr->text = sptr->text;
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchOverwriteBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src, // pointer to source bmg
+ bool dup_strings // true: allocate mem for copied strings
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // source patch patch both //
+ //--------------------------------------//
+
+ DASSERT(dest);
+ DASSERT(src);
+ copy_param(dest,src);
+
+ const bmg_item_t * sptr = src->item;
+ const bmg_item_t * send = sptr + src->item_used;
+
+ bool dirty = false;
+ for ( ; sptr < send; sptr++ )
+ {
+ if ( sptr->cond && !FindItemBMG(dest,sptr->cond) )
+ continue;
+
+ bool old_item;
+ bmg_item_t * dptr = InsertItemBMG(dest,sptr->mid,0,0,&old_item);
+ if ( !old_item || !IsItemEqualBMG(dptr,sptr) )
+ {
+ if (dup_strings)
+ AssignItemText16BMG(dptr,sptr->text,sptr->len);
+ else
+ {
+ FreeItemBMG(dptr);
+ dptr->len = sptr->len;
+ dptr->text = sptr->text;
+ }
+ DASSERT( dptr->mid == sptr->mid );
+
+ CopyAttribBMG(dest,dptr,sptr);
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchDeleteBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src // pointer to source bmg
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // source - - - //
+ //--------------------------------------//
+
+ DASSERT(dest);
+ DASSERT(src);
+ copy_param(dest,src);
+
+ const bmg_item_t * sptr = src->item;
+ const bmg_item_t * send = sptr + src->item_used;
+
+ bool dirty = false;
+ for ( ; sptr < send; sptr++ )
+ {
+ bmg_item_t * dptr = FindItemBMG(dest,sptr->mid);
+ if (dptr)
+ {
+ FreeItemBMG(dptr);
+ DASSERT( dptr->mid == sptr->mid );
+ dptr->text = 0;
+ ResetAttribBMG(dest,dptr);
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchMaskBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src // pointer to source bmg
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // - - source both //
+ //--------------------------------------//
+
+ DASSERT(dest);
+ DASSERT(src);
+ copy_param(dest,src);
+
+ bmg_item_t * dptr = dest->item;
+ bmg_item_t * dend = dptr + dest->item_used;
+
+ bool dirty = false;
+ for ( ; dptr < dend; dptr++ )
+ {
+ bmg_item_t * sptr = FindItemBMG(src,dptr->mid);
+ if (!sptr)
+ {
+ FreeItemBMG(dptr);
+ dptr->text = 0;
+ ResetAttribBMG(dest,dptr);
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchEqualBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src // pointer to source bmg
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // - - - both //
+ //--------------------------------------//
+
+ DASSERT(dest);
+ DASSERT(src);
+ copy_param(dest,src);
+
+ bmg_item_t * dptr = dest->item;
+ bmg_item_t * dend = dptr + dest->item_used;
+
+ const bmg_item_t * sptr = src->item;
+ const bmg_item_t * send = sptr + src->item_used;
+
+ bool dirty = false;
+ for ( ; dptr < dend; dptr++ )
+ {
+ u32 mid = dptr->mid;
+ while ( sptr < send && sptr->mid < mid )
+ sptr++;
+ if ( sptr == send )
+ break;
+ if ( sptr == send || sptr->mid != mid || !IsItemEqualBMG(dptr,sptr) )
+ {
+ FreeItemBMG(dptr);
+ dptr->text = 0;
+ ResetAttribBMG(dest,dptr);
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchNotEqualBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src // pointer to source bmg
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // - - source - //
+ //--------------------------------------//
+
+ DASSERT(dest);
+ DASSERT(src);
+ copy_param(dest,src);
+
+ bmg_item_t * dptr = dest->item;
+ bmg_item_t * dend = dptr + dest->item_used;
+
+ const bmg_item_t * sptr = src->item;
+ const bmg_item_t * send = sptr + src->item_used;
+
+ bool dirty = false;
+ for ( ; dptr < dend; dptr++ )
+ {
+ u32 mid = dptr->mid;
+ while ( sptr < send && sptr->mid < mid )
+ sptr++;
+ if ( sptr == send )
+ break;
+ if ( sptr != send && sptr->mid == mid && IsItemEqualBMG(dptr,sptr) )
+ {
+ FreeItemBMG(dptr);
+ dptr->text = 0;
+ ResetAttribBMG(dest,dptr);
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool generic_helper
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src, // pointer to source bmg
+ uint dest_id, // index fo message to create
+ uint src_id, // relevant source message
+ uint src_beg, // source message for begining tests
+ uint src_end // source message for end tests
+)
+{
+ DASSERT(dest);
+ DASSERT(src);
+
+ //--- find source messages
+
+ const bmg_item_t *s0 = FindItemBMG(src,src_id);
+ if ( !s0 || !s0->text || !s0->len )
+ return false;
+
+ const bmg_item_t *s1 = FindItemBMG(src,src_beg);
+ if ( !s1 || !s1->text || !s1->len )
+ return false;
+
+ const bmg_item_t *s2 = FindItemBMG(src,src_end);
+ if ( !s2 || !s2->text || !s2->len )
+ return false;
+
+
+ //--- compare beginning of text
+
+ const u16 *b0 = s0->text;
+ const u16 *b1 = s1->text;
+ const uint maxlen = s0->len < s1->len ? s0->len : s1->len;
+ const u16 *b0end = b0 + maxlen;
+
+ while ( b0 < b0end && *b0 == *b1 )
+ b0++, b1++;
+ if ( b0 == b0end )
+ return false;
+
+ while ( b0 > s0->text && isalnum((int)be16(b0-1)) )
+ b0--, b1--;
+
+ PRINT("GENERIC: |%c%c%c| |%c%c%c|\n",
+ be16(b0+0) & 0xff, be16(b0+1) & 0xff, be16(b0+2) & 0xff,
+ be16(b1+0) & 0xff, be16(b1+1) & 0xff, be16(b1+2) & 0xff );
+
+
+ //--- compare end of text
+
+ const u16 *e0 = s0->text + s0->len - 1;
+ const u16 *e2 = s2->text + s2->len - 1;
+ while ( e0 > b0 && *e0 == *e2 )
+ e0--, e2--;
+ e0++, e2++;
+
+ const u16 *e0end = s0->text + s0->len;
+ while ( b0 < e0end && isalnum((int)be16(b0-1)) )
+ e0++, e2++;
+
+ if ( b0 < e0 )
+ {
+ const uint len = e0 - b0;
+ bmg_item_t *dptr = InsertItemBMG(dest,dest_id,0,0,0);
+ DASSERT(dptr);
+ if ( !dptr->text
+ || dptr->len != len
+ || memcmp(dptr->text,b0,len*sizeof(*b0)) )
+ {
+ AssignItemText16BMG(dptr,b0,len);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+bool PatchGenericBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src // pointer to source bmg
+)
+{
+ DASSERT(dest);
+ DASSERT(src);
+ bool dirty = false;
+
+ dirty |= generic_helper(dest,src, MID_G_SMALL, 0x1f44, 0x1f46, 0x1f46 );
+ dirty |= generic_helper(dest,src, MID_G_MEDIUM, 0x1f45, 0x1f46, 0x1f46 );
+ dirty |= generic_helper(dest,src, MID_G_LARGE, 0x1f46, 0x1f44, 0x1f44 );
+
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchIdBMG
+(
+ bmg_t * bmg_dest, // pointer to destination bmg
+ bool prefix_all // true: prefix all messages
+)
+{
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ // prefix n.a. n.a. n.a. //
+ //--------------------------------------//
+
+
+ uint name_len;
+ ccp name = GetNameBMG(bmg_dest,&name_len);
+ noPRINT("\e[31;1m#### NAME=%s\e[0m\n",name);
+ if ( name_len > 4 && !strncasecmp(name,"menu",4) )
+ name += 4, name_len -= 4;
+
+ u16 buf[10000];
+ u16 *bufend = buf + sizeof(buf)/sizeof(*buf);
+
+ bool dirty = false;
+ bmg_item_t * dptr = bmg_dest->item;
+ bmg_item_t * dend = dptr + bmg_dest->item_used;
+
+ for ( ; dptr < dend; dptr++ )
+ {
+ if ( dptr->text && ( prefix_all || dptr->text != bmg_null_entry ) )
+ {
+ u16 *dest = buf;
+ if (name_len)
+ *dest++ = htons(*name);
+ char num[20];
+ snprintf(num,sizeof(num),"%x",dptr->mid);
+ ccp src = num;
+ while (*src)
+ *dest++ = htons(*src++);
+
+ if ( dptr->text != bmg_null_entry )
+ {
+ *dest++ = htons(':');
+
+ uint count = bufend - dest;
+ if ( count > dptr->len )
+ count = dptr->len;
+ memcpy(dest,dptr->text,count*sizeof(*dest));
+ dest += count;
+ }
+ AssignItemText16BMG(dptr,buf,dest-buf);
+ dirty = true;
+ }
+ }
+
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchRmEscapesBMG
+(
+ bmg_t * bmg, // pointer to destination bmg
+ bool unicode, // replace '\u{16bit}' by unicode chars
+ bool rm_escapes // remove 1A escapes
+)
+{
+ DASSERT(bmg);
+ PRINT("patch_bmg_rm_escapes(unicode=%d,rm_esc=%d)\n",unicode,rm_escapes);
+
+ bool dirty = false;
+ bmg_item_t * iptr = bmg->item;
+ bmg_item_t * iend = iptr + bmg->item_used;
+
+ for ( ; iptr < iend; iptr++ )
+ {
+ // fast find first escape
+ u16 *sp = iptr->text, *end = sp + iptr->len;
+ if (!sp)
+ continue;
+ while ( sp < end && ntohs(*sp) != 0x1a )
+ sp++;
+ if ( sp == end )
+ continue;
+
+ sp = ModifiyItem(iptr,sp);
+ end = iptr->text + iptr->len;
+ DASSERT( iptr->alloced_size > 0 );
+
+ u16 *dp = sp;
+ while ( sp < end )
+ {
+ if ( ntohs(*sp) != 0x1a )
+ {
+ *dp++ = *sp++;
+ continue;
+ }
+
+ if ( unicode && ntohs(sp[1]) == 0x0801 && !ntohs(sp[2]) )
+ {
+ const u16 code = ntohs(sp[3]);
+ if ( code >= ' ' )
+ {
+ *dp++ = sp[3];
+ sp += 4;
+ HexDump16(stdout,0,0x100,iptr->text,(dp-iptr->text)*2);
+ HexDump16(stdout,0,0x200,sp,(iptr->text+iptr->len-sp)*2);
+ continue;
+ }
+ }
+
+ const uint esc_len = ((u8*)sp)[2] & 0xfe;
+ if (!rm_escapes)
+ {
+ memmove(dp,sp,esc_len);
+ dp += esc_len/2;
+ }
+ sp += esc_len/2;
+ }
+ iptr->len = dp - iptr->text;
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchRmCupsBMG
+(
+ bmg_t * bmg // pointer to destination bmg
+)
+{
+ DASSERT(bmg);
+ bool dirty = false;
+ uint mid;
+ for ( mid = MID_RCUP_BEG; mid < MID_RCUP_END; mid++ )
+ {
+ bmg_item_t * ptr = FindItemBMG(bmg,mid);
+ if (ptr)
+ {
+ FreeItemBMG(ptr);
+ DASSERT( ptr->mid == mid );
+ ptr->text = 0;
+ ResetAttribBMG(bmg,ptr);
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static uint bmg_copy_helper
+(
+ bmg_t * bmg, // pointer to bmg
+ bool force, // true: force replace
+ uint mid_src_1, // first source message id
+ uint mid_src_2, // NULL or second source message id
+ uint mid_src_3, // NULL or third source message id
+ uint mid_dest, // destination message id
+ uint n_mid // numbers of messages to procced
+)
+{
+ DASSERT(bmg);
+
+ uint i, count = 0;
+ for ( i = 0; i < n_mid; i++ )
+ {
+ bmg_item_t * sptr = FindItemBMG(bmg,mid_src_1+i);
+ if ( !sptr && mid_src_2 )
+ {
+ sptr = FindItemBMG(bmg,mid_src_2+i);
+ if ( !sptr && mid_src_3 )
+ sptr = FindItemBMG(bmg,mid_src_3+i);
+ }
+
+ if (sptr)
+ {
+ bool old_item;
+ bmg_item_t * dptr = InsertItemBMG(bmg,mid_dest+i,0,0,&old_item);
+ if ( force || !old_item || !dptr->len )
+ {
+ AssignItemText16BMG(dptr,sptr->text,sptr->len);
+ CopyAttribBMG(bmg,dptr,sptr);
+ count++;
+ }
+ }
+ }
+
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchCTCopyBMG
+(
+ bmg_t * bmg // pointer to destination bmg
+)
+{
+ uint dirty_count
+ = bmg_copy_helper( bmg, false, MID_TRACK1_BEG, MID_TRACK2_BEG, 0,
+ MID_CT_TRACK_BEG, BMG_N_TRACK )
+ + bmg_copy_helper( bmg, false, MID_ARENA1_BEG, MID_ARENA2_BEG, 0,
+ MID_CT_ARENA_BEG, BMG_N_ARENA )
+ + bmg_copy_helper( bmg, false, MID_RCUP_BEG, 0, 0,
+ MID_CT_CUP_BEG,
+ MID_RCUP_END - MID_RCUP_BEG )
+ + bmg_copy_helper( bmg, false, MID_BCUP_BEG, 0, 0,
+ MID_CT_BCUP_BEG,
+ MID_BCUP_END - MID_BCUP_BEG );
+ return dirty_count > 0;
+}
+
+//-----------------------------------------------------------------------------
+
+bool PatchLECopyBMG
+(
+ bmg_t * bmg // pointer to destination bmg
+)
+{
+ uint dirty_count
+ = bmg_copy_helper( bmg, false, MID_ARENA1_BEG, MID_ARENA2_BEG, MID_CT_ARENA_BEG,
+ MID_LE_ARENA_BEG, BMG_N_ARENA )
+ + bmg_copy_helper( bmg, false, MID_TRACK1_BEG, MID_TRACK2_BEG, MID_CT_TRACK_BEG,
+ MID_LE_TRACK_BEG, BMG_N_TRACK )
+ + bmg_copy_helper( bmg, false, MID_RCUP_BEG, MID_CT_CUP_BEG, 0,
+ MID_LE_CUP_BEG,
+ MID_RCUP_END - MID_RCUP_BEG )
+ + bmg_copy_helper( bmg, false, MID_BCUP_BEG, MID_CT_BCUP_BEG, 0,
+ MID_LE_BCUP_BEG,
+ MID_BCUP_END - MID_BCUP_BEG );
+ return dirty_count > 0;
+}
+
+//-----------------------------------------------------------------------------
+
+bool PatchXCopyBMG
+(
+ bmg_t * bmg // pointer to destination bmg
+)
+{
+ if (opt_bmg_support_lecode)
+ return PatchLECopyBMG(bmg);
+ if (opt_bmg_support_ctcode)
+ return PatchCTCopyBMG(bmg);
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchCTForceCopyBMG
+(
+ bmg_t * bmg // pointer to destination bmg
+)
+{
+ uint dirty_count
+ = bmg_copy_helper( bmg, true, MID_TRACK1_BEG, MID_TRACK2_BEG, 0,
+ MID_CT_TRACK_BEG, BMG_N_TRACK )
+ + bmg_copy_helper( bmg, true, MID_ARENA1_BEG, MID_ARENA2_BEG, 0,
+ MID_CT_ARENA_BEG, BMG_N_ARENA )
+ + bmg_copy_helper( bmg, true, MID_RCUP_BEG, 0, 0,
+ MID_CT_CUP_BEG,
+ MID_RCUP_END - MID_RCUP_BEG )
+ + bmg_copy_helper( bmg, true, MID_BCUP_BEG, 0, 0,
+ MID_CT_BCUP_BEG,
+ MID_BCUP_END - MID_BCUP_BEG );
+ return dirty_count > 0;
+}
+
+//-----------------------------------------------------------------------------
+
+bool PatchLEForceCopyBMG
+(
+ bmg_t * bmg // pointer to destination bmg
+)
+{
+ uint dirty_count
+ = bmg_copy_helper( bmg, true, MID_TRACK1_BEG, MID_TRACK2_BEG, MID_CT_TRACK_BEG,
+ MID_LE_TRACK_BEG, BMG_N_TRACK )
+ + bmg_copy_helper( bmg, true, MID_ARENA1_BEG, MID_ARENA2_BEG, MID_CT_ARENA_BEG,
+ MID_LE_ARENA_BEG, BMG_N_ARENA )
+ + bmg_copy_helper( bmg, true, MID_RCUP_BEG, MID_CT_CUP_BEG, 0,
+ MID_LE_CUP_BEG,
+ MID_RCUP_END - MID_RCUP_BEG )
+ + bmg_copy_helper( bmg, true, MID_BCUP_BEG, MID_CT_BCUP_BEG, 0,
+ MID_LE_BCUP_BEG,
+ MID_BCUP_END - MID_BCUP_BEG );
+ return dirty_count > 0;
+}
+
+//-----------------------------------------------------------------------------
+
+bool PatchXForceCopyBMG
+(
+ bmg_t * bmg // pointer to destination bmg
+)
+{
+ if (opt_bmg_support_lecode)
+ return PatchLEForceCopyBMG(bmg);
+ if (opt_bmg_support_ctcode)
+ return PatchCTForceCopyBMG(bmg);
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchIdentificationBMG ( bmg_t *bmg, ct_bmg_t *ctb )
+{
+ DASSERT(bmg);
+ DASSERT(ctb);
+
+ bmg_item_t *bi = InsertItemBMG(bmg,MID_PARAM_IDENTIFY,0,0,0);
+ DASSERT(bi);
+ AssignItemTextBMG(bi,GetCtBMGIdentification(ctb,true),-1);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+
+bool PatchFillBMG
+ ( bmg_t *bmg, ct_mode_t ct_mode, uint rcup_limit, uint track_limit )
+{
+ ct_bmg_t ctb;
+ ct_mode = SetupCtBMG(&ctb,ct_mode,ct_mode);
+ if (!ctb.is_ct_code)
+ return false;
+
+
+ //--- check existence of at least one relevvant message
+
+ bool found = false;
+ bmg_item_t *dptr = bmg->item;
+ bmg_item_t *dend = dptr + bmg->item_used;
+ for ( ; dptr < dend; dptr++ )
+ {
+ const u32 m = dptr->mid;
+ if ( m >= MID_TRACK1_BEG && m < MID_TRACK1_END
+ || m >= MID_TRACK2_BEG && m < MID_TRACK2_END
+ || m >= MID_ARENA1_BEG && m < MID_ARENA1_END
+ || m >= MID_ARENA2_BEG && m < MID_ARENA2_END
+ || m >= MID_RCUP_BEG && m < MID_RCUP_END
+ || m >= MID_BCUP_BEG && m < MID_BCUP_END
+ || m >= ctb.track_name1.beg && m < ctb.track_name1.end
+ || m >= ctb.track_name2.beg && m < ctb.track_name2.end
+ || m >= ctb.arena_name1.beg && m < ctb.arena_name1.end
+ || m >= ctb.arena_name2.beg && m < ctb.arena_name2.end
+ || m >= ctb.rcup_name.beg && m < ctb.rcup_name.end
+ || m >= ctb.bcup_name.beg && m < ctb.bcup_name.end
+ || m == ctb.random.beg
+ )
+ {
+ if ( dptr->text && dptr->text != bmg_null_entry )
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return false;
+
+
+ //--- random
+
+ char buf[100];
+ bool dirty = false;
+
+ if (ctb.random.beg)
+ {
+ bool old_item;
+ bmg_item_t * dptr = InsertItemBMG(bmg,ctb.random.beg,0,0,&old_item);
+ if ( !old_item || !dptr->len )
+ {
+ AssignItemTextBMG(dptr,"???",3);
+ ResetAttribBMG(bmg,dptr);
+ dirty = true;
+ }
+ }
+
+
+ //--- fill arena names
+
+ uint mid;
+ for ( mid = ctb.arena_name1.beg; mid < ctb.arena_name1.end; mid++ )
+ {
+ bool old_item;
+ bmg_item_t * dptr = InsertItemBMG(bmg,mid,0,0,&old_item);
+ if ( !old_item || !dptr->len )
+ {
+ const uint len
+ = snprintf(buf,sizeof(buf),"_A%u_",mid-ctb.arena_name1.beg);
+ AssignItemTextBMG(dptr,buf,len);
+ ResetAttribBMG(bmg,dptr);
+ dirty = true;
+ }
+ }
+
+
+ //--- fill track names
+
+ if (!track_limit)
+ track_limit = opt_bmg_track_fill_limit;
+ uint max_track;
+ if (track_limit)
+ {
+ max_track = ctb.track_name1.beg + track_limit;
+ if ( max_track > ctb.track_name1.end )
+ max_track = ctb.track_name1.end;
+ }
+ else
+ max_track = ctb.track_name1.end;
+
+ for ( mid = ctb.track_name1.beg; mid < max_track; mid++ )
+ {
+ bool old_item;
+ bmg_item_t * dptr = InsertItemBMG(bmg,mid,0,0,&old_item);
+ if ( !old_item || !dptr->len )
+ {
+ const uint len
+ = snprintf(buf,sizeof(buf),"_T%03X_",mid-ctb.track_name1.beg);
+ AssignItemTextBMG(dptr,buf,len);
+ ResetAttribBMG(bmg,dptr);
+ dirty = true;
+ }
+ }
+
+
+ //--- fill battle cup names
+
+ for ( mid = ctb.bcup_name.beg; mid < ctb.bcup_name.end; mid++ )
+ {
+ bool old_item;
+ bmg_item_t * dptr = InsertItemBMG(bmg,mid,0,0,&old_item);
+ if ( !old_item || !dptr->len )
+ {
+ const uint len
+ = snprintf(buf,sizeof(buf),"_B%u_",mid-ctb.bcup_name.beg);
+ AssignItemTextBMG(dptr,buf,len);
+ ResetAttribBMG(bmg,dptr);
+ dirty = true;
+ }
+ }
+
+
+ //--- fill racing cup names
+
+ if (!rcup_limit)
+ rcup_limit = opt_bmg_rcup_fill_limit;
+ uint max_cup;
+ if (rcup_limit)
+ {
+ max_cup = ctb.rcup_name.beg + rcup_limit;
+ if ( max_cup > ctb.rcup_name.end )
+ max_cup = ctb.rcup_name.end;
+ }
+ else
+ max_cup = ctb.rcup_name.end;
+
+ for ( mid = ctb.rcup_name.beg; mid < max_cup; mid++ )
+ {
+ bool old_item;
+ bmg_item_t * dptr = InsertItemBMG(bmg,mid,0,0,&old_item);
+ if ( !old_item || !dptr->len )
+ {
+ const uint len
+ = snprintf(buf,sizeof(buf),"_R%u_",mid-ctb.rcup_name.beg);
+ AssignItemTextBMG(dptr,buf,len);
+ ResetAttribBMG(bmg,dptr);
+ dirty = true;
+ }
+ }
+
+ //--- identification
+
+ if (dirty)
+ PatchIdentificationBMG(bmg,&ctb);
+
+ return dirty;
+}
+
+//-----------------------------------------------------------------------------
+
+bool PatchXFillBMG ( bmg_t *bmg, uint rcup_limit, uint track_limit )
+{
+ if (opt_bmg_support_lecode)
+ return PatchLEFillBMG(bmg,rcup_limit,track_limit);
+
+ if (opt_bmg_support_ctcode)
+ return PatchCTFillBMG(bmg,rcup_limit,track_limit);
+
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchRmFilledBMG ( bmg_t *bmg )
+{
+ DASSERT(bmg);
+
+ bool dirty = false;
+ bmg_item_t * iptr = bmg->item;
+ bmg_item_t * iend = iptr + bmg->item_used;
+
+ for ( ; iptr < iend; iptr++ )
+ {
+ if ( iptr->mid >= 0x4000
+ && iptr->len > 3
+ && ntohs(iptr->text[0]) == '_'
+ && ntohs(iptr->text[iptr->len-1]) == '_' )
+ {
+ u16 ch = ntohs(iptr->text[1]);
+ if ( ch == 'A' || ch == 'T' || ch == 'B' || ch == 'R' )
+ {
+ FreeItemBMG(iptr);
+ iptr->text = 0;
+ ResetAttribBMG(bmg,iptr);
+ dirty = true;
+ }
+ }
+ }
+
+ return dirty;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// PatchRemove*BMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool PatchRemoveBMG ( bmg_t * bmg, int mid1, int mid2 )
+{
+ DASSERT(bmg);
+
+ bmg_item_t * ptr = bmg->item;
+ bmg_item_t * end = ptr + bmg->item_used;
+
+ bool dirty = false;
+ for ( ; ptr < end && ptr->mid < mid2; ptr++ )
+ {
+ if ( ptr->mid >= mid1 )
+ {
+ FreeItemBMG(ptr);
+ ptr->text = 0;
+ ResetAttribBMG(bmg,ptr);
+ dirty = true;
+ }
+ }
+ return dirty;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// PatchBMG() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PatchBMG
+(
+ bmg_t *dest, // pointer to destination bmg
+ const bmg_t *src, // pointer to source bmg
+ PatchModeBMG_t patch_mode, // patch mode
+ ccp string, // NULL or string parameter
+ bool dup_strings // true: alloc mem for copied strings (OVERWRITE)
+)
+{
+ PRINT("PatchBMG(%p,%p,%u,%s,%d)\n",
+ dest, src, patch_mode, string, dup_strings );
+ DASSERT(dest);
+
+ bool dirty = false;
+ switch(patch_mode)
+ {
+ case BMG_PM_PRINT: dirty = PatchPrintBMG(dest,src); break;
+ case BMG_PM_FORMAT: dirty = PatchFormatBMG(dest,src,string); break;
+ case BMG_PM_REGEX: dirty = PatchRegexBMG(dest,string); break;
+ case BMG_PM_RM_REGEX: dirty = PatchRmRegexBMG(dest,string); break;
+ case BMG_PM_ID: dirty = PatchIdBMG(dest,false); break;
+ case BMG_PM_ID_ALL: dirty = PatchIdBMG(dest,true); break;
+ case BMG_PM_UNICODE: dirty = PatchRmEscapesBMG(dest,true,false); break;
+ case BMG_PM_RM_ESCAPES: dirty = PatchRmEscapesBMG(dest,false,true); break;
+
+ case BMG_PM_REPLACE: dirty = PatchReplaceBMG(dest,src); break;
+ case BMG_PM_INSERT: dirty = PatchInsertBMG(dest,src); break;
+ case BMG_PM_OVERWRITE: dirty = PatchOverwriteBMG(dest,src,dup_strings); break;
+ case BMG_PM_DELETE: dirty = PatchDeleteBMG(dest,src); break;
+ case BMG_PM_MASK: dirty = PatchMaskBMG(dest,src); break;
+ case BMG_PM_EQUAL: dirty = PatchEqualBMG(dest,src); break;
+ case BMG_PM_NOT_EQUAL: dirty = PatchNotEqualBMG(dest,src); break;
+
+ case BMG_PM_GENERIC: dirty = PatchGenericBMG(dest,dest); break;
+ case BMG_PM_RM_CUPS: dirty = PatchRmCupsBMG(dest); break;
+
+ case BMG_PM_CT_COPY: dirty = PatchCTCopyBMG(dest); break;
+ case BMG_PM_CT_FORCE_COPY: dirty = PatchCTForceCopyBMG(dest); break;
+ case BMG_PM_CT_FILL: dirty = PatchCTFillBMG(dest,0,0); break;
+
+ case BMG_PM_LE_COPY: dirty = PatchLECopyBMG(dest); break;
+ case BMG_PM_LE_FORCE_COPY: dirty = PatchLEForceCopyBMG(dest); break;
+ case BMG_PM_LE_FILL: dirty = PatchLEFillBMG(dest,0,0); break;
+
+ case BMG_PM_X_COPY: dirty = PatchXCopyBMG(dest); break;
+ case BMG_PM_X_FORCE_COPY: dirty = PatchXForceCopyBMG(dest); break;
+ case BMG_PM_X_FILL: dirty = PatchXFillBMG(dest,0,0); break;
+
+ case BMG_PM_RM_FILLED: dirty = PatchRmFilledBMG(dest); break;
+
+ default: return ERROR0(ERR_NOT_IMPLEMENTED,0);
+ }
+
+ if (dirty)
+ TouchFileAttrib(&dest->fatt);
+
+ return dirty ? ERR_DIFFER : ERR_OK;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CT/LE support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ct_mode_t NormalizeCtModeBMG ( ct_mode_t mode, ct_mode_t fallback )
+{
+ for(;;)
+ {
+ switch(mode)
+ {
+ case CTM_NINTENDO:
+ case CTM_CTCODE:
+ case CTM_LECODE2:
+ return mode;
+
+ case CTM_CTCODE_BASE:
+ return CTM_CTCODE;
+
+ case CTM_LECODE1:
+ case CTM_LECODE_BASE:
+ return CTM_LECODE_DEF;
+
+ case CTM_OPTIONS:
+ if (opt_bmg_support_lecode)
+ return CTM_LECODE2;
+ if (opt_bmg_support_ctcode)
+ return CTM_CTCODE;
+ return CTM_NINTENDO;
+
+ case CTM_NO_INIT:
+ case CTM_AUTO:
+ break;
+ }
+
+ mode = fallback;
+ fallback = CTM_OPTIONS;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ct_mode_t SetupCtModeBMG ( ct_mode_t mode )
+{
+ mode = NormalizeCtModeBMG(mode,CTM_NINTENDO);
+ switch (mode)
+ {
+ case CTM_NINTENDO:
+ opt_bmg_support_mkw = true;
+ opt_bmg_support_ctcode = false;
+ opt_bmg_support_lecode = false;
+ break;
+
+ case CTM_CTCODE:
+ opt_bmg_support_mkw = true;
+ opt_bmg_support_ctcode = true;
+ opt_bmg_support_lecode = false;
+ break;
+
+ case CTM_LECODE2:
+ opt_bmg_support_mkw = true;
+ opt_bmg_support_ctcode = false;
+ opt_bmg_support_lecode = true;
+ break;
+
+ default:
+ opt_bmg_support_mkw = false;
+ opt_bmg_support_ctcode = false;
+ opt_bmg_support_lecode = false;
+ break;
+ }
+ return mode;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetCtModeNameBMG ( ct_mode_t mode, bool full )
+{
+ switch(mode)
+ {
+ case CTM_NINTENDO: return "Nintendo";
+ case CTM_CTCODE: return "CT-CODE";
+ case CTM_LECODE1: return full ? "LE-CODE v1" : "LE-CODE";
+ case CTM_LECODE2: return full ? "LE-CODE v2" : "LE-CODE";
+ default: return "?";
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ct_mode_t SetupCtBMG ( ct_bmg_t *ctb, ct_mode_t mode, ct_mode_t fallback )
+{
+ mode = NormalizeCtModeBMG(mode,fallback);
+ if (ctb)
+ {
+ ctb->ct_mode = mode;
+ ctb->identify = MID_PARAM_IDENTIFY;
+ ctb->param.beg = MID_PARAM_BEG;
+ ctb->param.end = MID_PARAM_END;
+ ctb->param.n = ctb->param.end - ctb->param.beg;
+
+ switch(mode)
+ {
+ case CTM_CTCODE:
+ ctb->version = 0;
+ ctb->is_ct_code = 1;
+ ctb->le_phase = 0;
+
+ ctb->rcup_name.beg = MID_CT_RCUP_BEG;
+ ctb->rcup_name.end = MID_CT_RCUP_END;
+ ctb->bcup_name.beg = MID_CT_BCUP_BEG;
+ ctb->bcup_name.end = MID_CT_BCUP_END;
+
+ ctb->track_name1.beg = MID_CT_TRACK_BEG;
+ ctb->track_name1.end = MID_CT_TRACK_END;
+ ctb->track_name2.beg = 0;
+ ctb->track_name2.end = 0;
+
+ ctb->arena_name1.beg = MID_CT_ARENA_BEG;
+ ctb->arena_name1.end = MID_CT_ARENA_END;
+ ctb->arena_name2.beg = 0;
+ ctb->arena_name2.end = 0;
+
+ ctb->cup_ref.beg = MID_CT_CUP_REF_BEG;
+ ctb->cup_ref.end = MID_CT_CUP_REF_END;
+
+ ctb->random.beg = MID_CT_RANDOM;
+ ctb->random.end = MID_CT_RANDOM+1;
+ break;
+
+ case CTM_LECODE1:
+ case CTM_LECODE2:
+ ctb->version = mode == CTM_LECODE2 ? 2 : 1;
+ ctb->le_phase = ctb->version;
+ ctb->is_ct_code = 2;
+
+ ctb->rcup_name.beg = MID_LE_RCUP_BEG;
+ ctb->rcup_name.end = MID_LE_RCUP_END;
+ ctb->bcup_name.beg = MID_LE_BCUP_BEG;
+ ctb->bcup_name.end = MID_LE_BCUP_END;
+
+ ctb->track_name1.beg = MID_LE_TRACK_BEG;
+ ctb->track_name1.end = MID_LE_TRACK_END;
+ ctb->track_name2.beg = 0;
+ ctb->track_name2.end = 0;
+
+ ctb->arena_name1.beg = MID_LE_ARENA_BEG;
+ ctb->arena_name1.end = MID_LE_ARENA_END;
+ ctb->arena_name2.beg = 0;
+ ctb->arena_name2.end = 0;
+
+ ctb->cup_ref.beg = MID_LE_CUP_REF_BEG;
+ ctb->cup_ref.end = MID_LE_CUP_REF_END;
+
+ ctb->random.beg = 0;
+ ctb->random.end = 0;
+ break;
+
+ default:
+ ctb->version = 0;
+ ctb->is_ct_code = 0;
+ ctb->le_phase = 0;
+
+ ctb->rcup_name.beg = MID_RCUP_BEG;
+ ctb->rcup_name.end = MID_RCUP_END;
+ ctb->bcup_name.beg = MID_BCUP_BEG;
+ ctb->bcup_name.end = MID_BCUP_END;
+
+ ctb->track_name1.beg = MID_TRACK1_BEG;
+ ctb->track_name1.end = MID_TRACK1_END;
+ ctb->track_name2.beg = MID_TRACK2_BEG;
+ ctb->track_name2.end = MID_TRACK2_END;
+
+ ctb->arena_name1.beg = MID_ARENA1_BEG;
+ ctb->arena_name1.end = MID_ARENA1_END;
+ ctb->arena_name2.beg = MID_ARENA2_BEG;
+ ctb->arena_name2.end = MID_ARENA2_END;
+
+ ctb->cup_ref.beg = 0;
+ ctb->cup_ref.end = 0;
+
+ ctb->random.beg = MID_RANDOM;
+ ctb->random.end = MID_RANDOM+1;
+ break;
+ }
+
+ u32 min = M1(u32), max = 0;
+ bmg_range_t *ptr;
+ for ( ptr = &ctb->rcup_name; ptr <= &ctb->random; ptr++ )
+ {
+ ptr->n = ptr->end - ptr->beg;
+ if (ptr->n)
+ {
+ if ( min > ptr->beg ) min = ptr->beg;
+ if ( max < ptr->end ) max = ptr->end;
+ }
+ }
+ ctb->range.beg = min;
+ ctb->range.end = max;
+ ctb->range.n = max - min;
+ }
+ return mode;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#undef TAB
+#define TAB(nam) ctb->nam.beg, ctb->nam.n
+
+ccp GetCtBMGIdentification ( ct_bmg_t *ctb, bool full )
+{
+ DASSERT(ctb);
+
+ ccp name = GetCtModeNameBMG(ctb->ct_mode,false);
+ char buf[200];
+ int len;
+ if (full)
+ len = snprintf(buf,sizeof(buf),
+ "%x, %s, v=%x, ct=%x, le=%x, par=%x+%x,"
+ " rcup=%x+%x, bcup=%x+%x, t1=%x+%x, t2=%x+%x,"
+ " a1=%x+%x, a2=%x+%x, ref=%x+%x",
+ ctb->ct_mode, name, ctb->version,
+ ctb->is_ct_code==1, ctb->le_phase,
+ TAB(param),
+ TAB(rcup_name), TAB(bcup_name),
+ TAB(track_name1), TAB(track_name2),
+ TAB(arena_name1), TAB(arena_name2),
+ TAB(cup_ref) );
+ else
+ len = snprintf(buf,sizeof(buf),"%x, %s v%d",ctb->ct_mode,name,ctb->version);
+ return len > 0 ? CopyCircBuf(buf,len+1) : EmptyString;
+}
+
+#undef TAB
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void DumpCtMidHelper1 ( FILE *f, int indent, u32 id, ccp info )
+{
+ DASSERT(f);
+ if (id)
+ fprintf(f,"%*s%-20s %04x\n", indent, "", info, id );
+}
+
+//-----------------------------------------------------------------------------
+
+static void DumpCtMidHelper2 ( FILE *f, int indent, bmg_range_t *id, ccp info )
+{
+ DASSERT(f);
+ DASSERT(id);
+ if (id->beg)
+ fprintf(f,"%*s%-20s %04x .. %04x, %5u = 0x%04x\n",
+ indent, "", info, id->beg, id->end, id->n, id->n );
+}
+
+//-----------------------------------------------------------------------------
+
+void DumpCtBMG ( FILE *f, int indent, ct_bmg_t *ctb )
+{
+ DASSERT(f);
+ DASSERT(ctb);
+ indent = NormalizeIndent(indent);
+
+ #if defined(TEST) || defined(DEBUG)
+ fprintf(f,"%*s%s\n",indent,"",GetCtBMGIdentification(ctb,true));
+ #else
+ fprintf(f,"%*s%s\n",indent,"",GetCtBMGIdentification(ctb,false));
+ #endif
+
+ indent += 2;
+ DumpCtMidHelper1( f,indent, ctb->identify, "Identification:" );
+ DumpCtMidHelper2( f,indent, &ctb->param, "Parameters:" );
+ DumpCtMidHelper2( f,indent, &ctb->range, "Data range:" );
+
+ DumpCtMidHelper2( f,indent, &ctb->rcup_name, " Racing cup names:" );
+ DumpCtMidHelper2( f,indent, &ctb->bcup_name, " Battle cup names:" );
+ DumpCtMidHelper2( f,indent, &ctb->track_name1, " Track names 1:" );
+ DumpCtMidHelper2( f,indent, &ctb->track_name2, " Track names 2:" );
+ DumpCtMidHelper2( f,indent, &ctb->arena_name1, " Battle names 1:" );
+ DumpCtMidHelper2( f,indent, &ctb->arena_name2, " Battle names 2:" );
+ DumpCtMidHelper2( f,indent, &ctb->cup_ref, " Cup reference:" );
+ DumpCtMidHelper1( f,indent, ctb->random.beg, " Random text:" );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
diff --git a/project/dclib/lib-bmg.h b/project/dclib/lib-bmg.h
new file mode 100755
index 0000000..9ab5967
--- /dev/null
+++ b/project/dclib/lib-bmg.h
@@ -0,0 +1,1125 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DC_LIB_BMG_H
+#define DC_LIB_BMG_H 1
+
+#define _GNU_SOURCE 1
+#include "dclib-basics.h"
+#include "dclib-file.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// basic consts ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define BMG_ATTRIB_SIZE 40 // attrib size in bytes
+#define BMG_ATTRIB_BUF_SIZE (10+4*BMG_ATTRIB_SIZE) // good buf size for attrib string
+#define BMG_INF_MAX_SIZE (4+BMG_ATTRIB_SIZE) // max supported inf size
+#define BMG_INF_LIMIT 1000 // max allowed inf size
+
+// [[xbuf]]
+#define BMG_MSG_BUF_SIZE 20050 // good buf size for messages
+
+///////////////////////////////////////////////////////////////////////////////
+
+#define BMG_MAGIC "MESGbmg1"
+#define BMG_MAGIC8_NUM 0x4d455347626d6731ull
+
+#define BMG_TEXT_MAGIC "#BMG"
+#define BMG_TEXT_MAGIC_NUM 0x23424d47
+
+#define BMG_UTF8_MAX 0xfffd
+#define BMG_UTF8_MAX_DIFF 0x058f // behind: from-right-to-left letters
+
+#define BMG_NO_PREDEF_SLOT 0xffff // only slots <this can be predefined
+typedef u16 bmg_slot_t; // type of slot
+
+typedef struct bmg_t bmg_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MKW specific consts ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enum
+{
+ BMG_N_CHAT = 96,
+ BMG_N_RCUP = 8,
+ BMG_N_BCUP = 2,
+ BMG_N_TRACK = 32,
+ BMG_N_ARENA = 10,
+
+ MID_RCUP_BEG = 0x23f0,
+ MID_RCUP_END = MID_RCUP_BEG + BMG_N_RCUP,
+ MID_BCUP_BEG = 0x2489,
+ MID_BCUP_END = MID_BCUP_BEG + BMG_N_BCUP,
+
+ MID_ENGINE_BEG = 0x0589,
+ MID_ENGINE_END = MID_ENGINE_BEG + 4,
+
+ MID_CHAT_BEG = 0x1194,
+ MID_CHAT_END = MID_CHAT_BEG + BMG_N_CHAT,
+
+
+ //-- parameters
+
+ MID_PARAM_IDENTIFY = 0x3def,
+
+ MID_PARAM_BEG = 0x3ff0,
+ MID_VERSUS_POINTS = MID_PARAM_BEG,
+ MID_PARAM_END = 0x4000,
+
+
+ //-- extended messages for CT/LE
+
+ MID_X_MESSAGE_BEG = 0x6000,
+ MID_X_MESSAGE_END = 0x6200,
+
+
+ //-- CT-CODE
+
+ BMG_N_CT_RCUP = 0x3e,
+ BMG_N_CT_BCUP = 2,
+ BMG_N_CT_TRACK = 0x1fe,
+ BMG_N_CT_ARENA = BMG_N_ARENA,
+
+ MID_CT_TRACK_BEG = 0x4000,
+ MID_CT_TRACK_END = MID_CT_TRACK_BEG + BMG_N_CT_TRACK,
+ MID_CT_ARENA_BEG = MID_CT_TRACK_BEG + BMG_N_TRACK,
+ MID_CT_ARENA_END = MID_CT_ARENA_BEG + BMG_N_CT_ARENA,
+ MID_CT_RANDOM = MID_CT_TRACK_BEG + 0xff,
+
+ MID_CT_CUP_BEG = 0x4200,
+ MID_CT_RCUP_BEG = MID_CT_CUP_BEG,
+ MID_CT_RCUP_END = MID_CT_RCUP_BEG + BMG_N_CT_RCUP,
+ MID_CT_BCUP_BEG = MID_CT_RCUP_END,
+ MID_CT_BCUP_END = MID_CT_BCUP_BEG + BMG_N_CT_BCUP,
+ MID_CT_CUP_END = MID_CT_BCUP_END,
+
+ MID_CT_CUP_REF_BEG = 0x4300,
+ MID_CT_CUP_REF_END = MID_CT_CUP_REF_BEG + BMG_N_CT_TRACK,
+
+ MID_CT_BEG = MID_CT_TRACK_BEG,
+ MID_CT_END = MID_CT_CUP_REF_END,
+
+
+ //-- LE-CODE
+
+ BMG_N_LE_RCUP = 0x400,
+ BMG_N_LE_BCUP = 2,
+ BMG_N_LE_TRACK = 0x1000,
+ BMG_N_LE_ARENA = BMG_N_ARENA,
+ BMG_LE_FIRST_CTRACK = 0x44,
+
+ MID_LE_CUP_BEG = 0x6800,
+ MID_LE_RCUP_BEG = MID_LE_CUP_BEG,
+ MID_LE_RCUP_END = MID_LE_RCUP_BEG + BMG_N_LE_RCUP,
+ MID_LE_BCUP_BEG = MID_LE_RCUP_END,
+ MID_LE_BCUP_END = MID_LE_BCUP_BEG + BMG_N_LE_BCUP,
+ MID_LE_CUP_END = MID_LE_BCUP_END,
+
+ MID_LE_TRACK_BEG = 0x7000,
+ MID_LE_TRACK_END = MID_LE_TRACK_BEG + BMG_N_LE_TRACK,
+ MID_LE_ARENA_BEG = MID_LE_TRACK_BEG + BMG_N_TRACK,
+ MID_LE_ARENA_END = MID_LE_ARENA_BEG + BMG_N_LE_ARENA,
+
+ MID_LE_CUP_REF_BEG = 0x8000,
+ MID_LE_CUP_REF_END = MID_LE_CUP_REF_BEG + BMG_N_LE_TRACK,
+
+ MID_LE_BEG = MID_LE_CUP_BEG,
+ MID_LE_END = MID_LE_CUP_REF_END,
+
+
+ //-- max(CT,LE)
+
+ BMG_MAX_RCUP = 0x3fe,
+ BMG_MAX_BCUP = 2,
+ BMG_MAX_TRACK = 0x1000,
+ BMG_MAX_ARENA = BMG_N_ARENA,
+
+
+ //-- tracks
+
+ MID_TRACK1_BEG = 0x2454,
+ MID_TRACK1_END = MID_TRACK1_BEG + BMG_N_TRACK,
+ MID_TRACK2_BEG = 0x2490,
+ MID_TRACK2_END = MID_TRACK2_BEG + BMG_N_TRACK,
+
+ MID_ARENA1_BEG = 0x24b8,
+ MID_ARENA1_END = MID_ARENA1_BEG + BMG_N_ARENA,
+ MID_ARENA2_BEG = 0x24cc,
+ MID_ARENA2_END = MID_ARENA2_BEG + BMG_N_ARENA,
+
+ MID_RANDOM = 0x1101,
+
+
+ //--- misc
+
+ MID_GENERIC_BEG = 0xfff0,
+ MID_G_SMALL = MID_GENERIC_BEG,
+ MID_G_MEDIUM,
+ MID_G_LARGE,
+ MID_GENERIC_END,
+ BMG_N_GENERIC = MID_GENERIC_END - MID_GENERIC_BEG,
+ //MID_VEHICLE = 0xd66,
+ MID_KARTS = 0xd67,
+ MID_BIKES = 0xd68,
+
+ MID__VIP_BEG = MID_CHAT_BEG,
+ MID__VIP_END = MID_LE_END,
+ BMG__VIP_N = MID__VIP_END - MID__VIP_BEG
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// global bmg options ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enum ColNameLevelBMG // color names as escapes
+{
+ BMG_CNL_NONE, // disable color names
+ BMG_CNL_BASICS, // use only old standard color names
+ BMG_CNL_YOR, // + use YOR* names
+
+ BMG_CNL_ALL = BMG_CNL_YOR // default
+}
+ColNameLevelBMG;
+
+extern uint opt_bmg_force_count; // >0: force some operations
+extern uint opt_bmg_align; // alignment of raw-bg sections
+extern bool opt_bmg_export; // true: optimize for exports
+extern bool opt_bmg_no_attrib; // true: suppress attributes
+extern bool opt_bmg_x_escapes; // true: use x-scapes insetad of z-escapes
+extern bool opt_bmg_old_escapes; // true: use only old escapes
+extern int opt_bmg_single_line; // >0: single line, >1: suppress atrributes
+extern bool opt_bmg_inline_attrib; // true: print attrinbutes inline
+extern bool opt_bmg_support_mkw; // true: support MKW specific extensions
+extern bool opt_bmg_support_ctcode; // true: support MKW/CT-CODE specific extensions
+extern bool opt_bmg_support_lecode; // true: support MKW/LE-CODE specific extensions
+extern uint opt_bmg_rcup_fill_limit;// >0: limit racing cups to reduce BMG size
+extern uint opt_bmg_track_fill_limit;// >0: limit tracks to reduce BMG size
+extern int opt_bmg_colors; // use c-escapes: 0:off, 1:auto, 2:on
+extern ColNameLevelBMG opt_bmg_color_name;
+extern uint opt_bmg_max_recurse; // max recurse depth
+extern bool opt_bmg_allow_print; // true: allow '$' to print a log message
+extern bool opt_bmg_use_slots; // true: use predifined slots
+extern bool opt_bmg_use_raw_sections;// true: use raw sections
+
+//-----------------------------------------------------------------------------
+
+extern int opt_bmg_encoding;
+extern uint opt_bmg_inf_size;
+extern OffOn_t opt_bmg_mid;
+extern bool opt_bmg_force_attrib;
+extern bool opt_bmg_def_attrib;
+
+extern u8 bmg_force_attrib[BMG_ATTRIB_SIZE];
+extern u8 bmg_def_attrib[BMG_ATTRIB_SIZE];
+
+
+// 'PatchKeysBMG->opt'
+// 0: no param
+// 1: file param
+// 2: string param
+extern const KeywordTab_t PatchKeysBMG[];
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CT/LE support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[ct_mode_t]]
+
+typedef enum ct_mode_t
+{
+ CTM_NO_INIT, // don't initialize, don't change value
+ CTM_NINTENDO, // original Nintendo mdoe with 32 tracks
+ CTM_AUTO, // use fallback
+ CTM_OPTIONS, // use options opt_bmg_support_{le,ct}code
+
+ CTM_CTCODE_BASE = 0x10, // base for all CT-CODEs
+ CTM_CTCODE, // CT-CODE (v1)
+
+ CTM_LECODE_BASE = 0x20, // base for all LE-CODEs
+ CTM_LECODE1, // LE-CODE phase 1 [[not longer used]]
+ CTM_LECODE2, // LE-CODE phase 2
+
+ CTM_LECODE_MIN = CTM_LECODE2, // minimal LE-PHASE
+ CTM_LECODE_DEF = CTM_LECODE2, // default LE-PHASE
+ CTM_LECODE_MAX = CTM_LECODE2, // maximal LE-PHASE
+}
+ct_mode_t;
+
+ct_mode_t NormalizeCtModeBMG ( ct_mode_t mode, ct_mode_t fallback );
+ct_mode_t SetupCtModeBMG ( ct_mode_t mode );
+ccp GetCtModeNameBMG ( ct_mode_t mode, bool full );
+
+///////////////////////////////////////////////////////////////////////////////
+// [[bmg_range_t]]
+
+typedef struct bmg_range_t
+{
+ u32 beg; // id of first MID
+ u32 end; // id of last MID+1
+ u32 n; // number of MIDs := end-beg
+}
+bmg_range_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[ct_bmg_t]]
+
+typedef struct ct_bmg_t
+{
+ ct_mode_t ct_mode; // normalized CT mode
+ int version; // a version number
+ uint is_ct_code; // 0:off, 1:CT-CODE, 2:CT+LE-CODE
+ uint le_phase; // >0: LE-CODE enabled in phase #
+
+ u32 identify; // MID if ifentification
+ bmg_range_t param; // MID range of parameters
+
+ bmg_range_t range; // MID range of this section (min,max)
+ bmg_range_t rcup_name; // MID range of racing cup names
+ bmg_range_t bcup_name; // MID range of battle cup names (maybe part of rcup_name)
+ bmg_range_t track_name1; // MID range of track names, first set
+ bmg_range_t track_name2; // 0 or MID range of track names, second set
+ bmg_range_t arena_name1; // MID range of arena names, first set
+ bmg_range_t arena_name2; // 0 or MID range of arena names, second set
+ bmg_range_t cup_ref; // MID range of battle track_to_cup references
+ bmg_range_t random; // MID for RANDOM
+}
+ct_bmg_t;
+
+ct_mode_t SetupCtBMG ( ct_bmg_t *ctb, ct_mode_t mode, ct_mode_t fallback );
+static inline void ResetCtBMG ( ct_bmg_t *ctb ) {}
+ccp GetCtBMGIdentification ( ct_bmg_t *ctb, bool full ); // message for MID_PARAM_IDENTIFY
+void DumpCtBMG ( FILE *f, int indent, ct_bmg_t *ctb );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// global string functions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// returns 1..N
+uint GetWordLength16BMG ( const u16 *source );
+
+uint GetLength16BMG
+(
+ const u16 * sourse, // source
+ uint max_len // max possible length
+);
+
+//-----------------------------------------------------------------------------
+
+uint PrintString16BMG
+(
+ char *buf, // destination buffer
+ uint buf_size, // size of 'buf'
+ const u16 *src, // source
+ int src_len, // length of source in u16 words, -1:NULL term
+ u16 utf8_max, // max code printed as UTF-8
+ uint quote, // 0:no quotes, 1:escape ", 2: print in quotes
+ int use_color // >0: use \c{...}
+);
+
+// [[xbuf]] [[2do]] not implemented yet
+uint PrintFastBuf16BMG
+(
+ FastBuf_t *fb, // destination buffer (data appended)
+ const u16 *src, // source
+ int src_len, // length of source in u16 words, -1:NULL term
+ u16 utf8_max, // max code printed as UTF-8
+ uint quote, // 0:no quotes, 1:escape ", 2: print in quotes
+ int use_color // >0: use \c{...}
+);
+
+//-----------------------------------------------------------------------------
+
+uint ScanString16BMG
+(
+ u16 *buf, // destination buffer
+ uint buf_size, // size of 'buf' in u16 words
+ ccp src, // UTF-8 source
+ int src_len, // length of source, -1: determine with strlen()
+ const bmg_t *bmg // NULL or support for \m{...}
+);
+
+// [[xbuf]] [[2do]] not implemented yet
+uint ScanFastBuf16BMG
+(
+ FastBuf_t *fb, // destination buffer (data appended)
+ ccp src, // UTF-8 source
+ int src_len, // length of source, -1: determine with strlen()
+ const bmg_t *bmg // NULL or support for \m{...}
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_header_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[bmg_section_t]]
+
+typedef struct bmg_section_t
+{
+ /*00*/ char magic[4]; // a magic to identity the section
+ /*04*/ be32_t size; // total size of the section
+ /*08*/ u8 data[0]; // section data
+}
+__attribute__ ((packed)) bmg_section_t;
+
+//-----------------------------------------------------------------------------
+// [[bmg_encoding_t]]
+
+typedef enum bmg_encoding_t
+{
+ BMG_ENC_CP1252 = 1,
+ BMG_ENC_UTF16BE = 2, // used in MKWii and AC
+ BMG_ENC_SHIFT_JIS = 3,
+ BMG_ENC_UTF8 = 4,
+
+ BMG_ENC__MIN = BMG_ENC_CP1252,
+ BMG_ENC__DEFAULT = BMG_ENC_UTF16BE,
+ BMG_ENC__MAX = BMG_ENC_UTF8,
+ BMG_ENC__UNDEFINED = -1, // only for option --bmg-encoding
+}
+__attribute__ ((packed)) bmg_encoding_t;
+
+extern const KeywordTab_t TabEncodingBMG[];
+ccp GetEncodingNameBMG ( int encoding, ccp return_if_invalid );
+int CheckEncodingBMG ( int encoding, int return_if_invalid );
+
+//-----------------------------------------------------------------------------
+// [[bmg_header_t]]
+
+typedef struct bmg_header_t
+{
+ /*00*/ char magic[8]; // = BMG_MAGIC
+ /*08*/ be32_t size; // total size of file
+ /*0c*/ be32_t n_sections; // number of sections
+ /*10*/ bmg_encoding_t encoding; // text encoding
+ /*11*/ u8 unknown[15]; // unknown data
+ /*20*/ //bmg_section_t section[0]; // first section header
+ // -> disabled because MVC
+}
+__attribute__ ((packed)) bmg_header_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_inf_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define BMG_INF_MAGIC "INF1"
+#define BMG_INF_STD_ATTRIB 0x01000000
+#define BMG_INF_STD_SIZE 8
+#define BMG_INF_DEFAULT_0C 0
+
+//-----------------------------------------------------------------------------
+// [[bmg_inf_item_t]]
+
+typedef struct bmg_inf_item_t
+{
+ /*00*/ u32 offset; // offset into text pool
+ /*04*/ u8 attrib[0]; // attribute bytes
+ // NKWii: MSByte used for font selection.
+}
+__attribute__ ((packed)) bmg_inf_item_t;
+
+//-----------------------------------------------------------------------------
+// [[bmg_inf_t]]
+
+typedef struct bmg_inf_t
+{
+ /*00*/ char magic[4]; // = BMG_INF_MAGIC
+ /*04*/ be32_t size; // total size of the section
+ /*08*/ be16_t n_msg; // number of messages
+ /*0a*/ be16_t inf_size; // size of inf items
+ /*0c*/ be32_t unknown_0c; // = BMG_INF_DEFAULT_0C = 0 for MKW
+ /*10*/ bmg_inf_item_t list[0]; // message list with (offset,attribute)
+}
+__attribute__ ((packed)) bmg_inf_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_dat_t + bmg_str_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define BMG_DAT_MAGIC "DAT1"
+#define BMG_STR_MAGIC "STR1"
+
+//-----------------------------------------------------------------------------
+// [[bmg_dat_t]]
+
+typedef struct bmg_dat_t
+{
+ /*00*/ char magic[4]; // = BMG_DAT_MAGIC
+ /*04*/ be32_t size; // total size of the section
+ /*08*/ u8 text_pool[];
+}
+__attribute__ ((packed)) bmg_dat_t;
+
+//-----------------------------------------------------------------------------
+
+// [[bmg_str_t]]
+typedef bmg_dat_t bmg_str_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_mid_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define BMG_MID_MAGIC "MID1"
+#define BMG_MID_DEFAULT_0A 0x1000
+#define BMG_MID_DEFAULT_0C 0
+
+//-----------------------------------------------------------------------------
+// [[bmg_mid_t]]
+
+typedef struct bmg_mid_t
+{
+ /*00*/ char magic[4]; // = BMG_MID_MAGIC
+ /*04*/ be32_t size; // total size of the section
+ /*08*/ be16_t n_msg; // number of messages
+ /*0a*/ be16_t unknown_0a; // = BMG_MID_DEFAULT_0A = 0x1000 for MKW
+ /*0c*/ be32_t unknown_0c; // = BMG_MID_DEFAULT_0C = 0 for MKW
+ /*10*/ u32 mid[0]; // message id table
+}
+__attribute__ ((packed)) bmg_mid_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_flw_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define BMG_FLW_MAGIC "FLW1"
+
+//-----------------------------------------------------------------------------
+// [[bmg_flw_t]]
+
+typedef struct bmg_flw_t
+{
+ /*00*/ char magic[4]; // = BMG_FLW_MAGIC
+ /*04*/ be32_t size; // total size of the section
+ /*08*/ u8 unknown[]; // ??? [[2do]]
+}
+__attribute__ ((packed)) bmg_flw_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_fli_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define BMG_FLI_MAGIC "FLI1"
+
+//-----------------------------------------------------------------------------
+// [[bmg_fli_t]]
+
+typedef struct bmg_fli_t
+{
+ /*00*/ char magic[4]; // = BMG_FLI_MAGIC
+ /*04*/ be32_t size; // total size of the section
+ /*08*/ u8 unknown[]; // ??? [[2do]]
+}
+__attribute__ ((packed)) bmg_fli_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// bmg_sect_info_t & bmg_sect_list_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[bmg_sect_info_t]]
+
+typedef struct bmg_sect_info_t
+{
+ uint offset; // offset from beginning of file
+ uint sect_size; // section size by section header
+ uint real_size; // real section size
+ uint head_size; // size of section header
+ uint elem_size; // =0: unknown, >0: element size
+ uint n_elem_head; // elem_size>0: number of elements by header
+ uint n_elem; // 0: number of real elements
+ bmg_section_t *sect; // pointer to raw source data
+ // if NULL: list terminator
+ bool known; // true: section known
+ bool supported; // true: section fully supported
+ bool is_empty; // true: complete data is NULL
+// bool use_str1; // true: SINF1 uses 2x 16-bit offsets to STR1
+ ccp info; // short description
+}
+bmg_sect_info_t;
+
+//-----------------------------------------------------------------------------
+// [[bmg_sect_list_t]]
+
+typedef struct bmg_sect_list_t
+{
+ // all pointers point into raw source data
+
+ uint source_size; // valid source size
+ uint this_size; // total size of this including info[]
+
+ bmg_header_t *header; // NULL or pointer to BMG file header
+ bmg_inf_t *pinf; // NULL or pointer to INF1 header
+ bmg_dat_t *pdat; // NULL or pointer to DAT1 header
+ bmg_str_t *pstr; // NULL or pointer to STR1 header
+ bmg_mid_t *pmid; // NULL or pointer to MID1 header
+ bmg_flw_t *pflw; // NULL or pointer to FLW1 header
+ bmg_fli_t *pfli; // NULL or pointer to FLI1 header
+
+ uint n_sections; // number if secton, get by bmg header
+ uint n_info; // number of info records excluding terimination
+ bmg_sect_info_t info[]; // list of sections,
+ // terminated by a record with sect==NULL
+}
+bmg_sect_list_t;
+
+//-----------------------------------------------------------------------------
+
+// result is alloced => FREE(result)
+bmg_sect_list_t * ScanSectionsBMG ( cvp data, uint size );
+
+const bmg_sect_info_t * SearchSectionBMG
+(
+ const bmg_sect_list_t *sl, // valid, created by ScanSectionsBMG()
+ cvp search, // magic to search
+ bool abbrev, // if not found: allow abbreviations
+ bool icase // if not found: ignore case and try again
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_raw_section_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[bmg_raw_section_t]]
+
+typedef struct bmg_raw_section_t
+{
+ char magic[4]; // section magic
+ FastBuf_t data; // data buffer
+ uint total_size; // total size of section
+ // if 0: calculate it
+ struct
+ bmg_raw_section_t *next; // pointer to next element
+}
+bmg_raw_section_t;
+
+//-----------------------------------------------------------------------------
+
+bmg_raw_section_t * GetRawSectionBMG ( bmg_t *bmg, cvp magic );
+bmg_raw_section_t * CreateRawSectionBMG ( bmg_t *bmg, cvp magic );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_item_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[bmg_item_t]]
+
+typedef struct bmg_item_t
+{
+ u32 mid; // message ID
+ u32 cond; // >0: message 'cond' must be defined
+ bmg_slot_t slot; // BMG_NO_PREDEF_SLOT or predifined slot
+
+ u16 attrib_used; // used size of 'attrib'
+ u8 attrib[BMG_ATTRIB_SIZE];// attribute buffer, copy from 'inf'
+ // always padded with NULL
+
+ u16 *text; // pointer to text
+ u16 len; // length in u16 words of 'text'
+ u16 alloced_size; // alloced size in u16 words of 'text'
+ // if 0: pointer to an other area
+ // ==> do not free or modify
+}
+__attribute__ ((packed)) bmg_item_t;
+
+//-----------------------------------------------------------------------------
+
+// special value for bmg_item_t::text
+extern u16 bmg_null_entry[]; // = {0}
+
+static inline bool isSpecialEntryBMG ( const u16 *text )
+{
+ return text == bmg_null_entry;
+}
+
+//-----------------------------------------------------------------------------
+
+void FreeItemBMG( bmg_item_t * bi );
+
+void AssignItemTextBMG
+(
+ bmg_item_t * bi, // valid item
+ ccp utf8, // UTF-8 text to store
+ int len // length of 'utf8'.
+ // if -1: detect length with strlen()
+);
+
+void AssignItemScanTextBMG
+(
+ bmg_item_t * bi, // valid item
+ ccp utf8, // UTF-8 text to scan
+ int len // length of 'utf8'.
+ // if -1: detect length with strlen()
+);
+
+void AssignItemText16BMG
+(
+ bmg_item_t * bi, // valid item
+ const u16 * text16, // text to store
+ int len // length of 'text16'.
+ // if -1: detect length by finding
+ // first NULL value.
+);
+
+bool IsItemEqualBMG
+(
+ const bmg_item_t * bi1, // NULL or valid item
+ const bmg_item_t * bi2 // NULL or valid item
+);
+
+u16 * ModifiyItem
+(
+ // return a fixed 'ptr'
+ bmg_item_t * bi, // valid item, prepare text modification
+ const u16 * ptr // NULL or a pointer within 'bi->text'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[bmg_t]]
+
+struct szs_file_t;
+
+typedef struct bmg_t
+{
+ //--- base info
+
+ ccp fname; // alloced filename of loaded file
+ FileAttrib_t fatt; // file attribute
+ bool is_text_src; // true: source was text
+
+ //--- container, used by SZS tools
+
+ struct szs_file_t *szs; // NULL or pointer to containing SZS
+
+ //--- multiple load support
+
+ bool src_is_arch; // true: source is an archive
+ uint src_count; // number of scanned sources
+ enumError max_err; // max error
+ uint recurse_depth; // current recurse depth
+
+ //--- raw data
+
+ u8 * data; // raw data
+ uint data_size; // size of 'data'
+ bool data_alloced; // true: 'data' must be freed
+
+ bmg_inf_t * inf; // pointer to 'INF1' data
+ bmg_dat_t * dat; // pointer to 'DAT1' data
+ bmg_mid_t * mid; // pointer to 'MID1' data
+
+ //--- raw sections
+
+ bmg_raw_section_t *first_raw; // NULL or pointer to chain of raw sections
+ bmg_raw_section_t *current_raw; // NULL or pointer to current raw sections
+
+ //--- section header data
+
+ u32 unknown_inf_0c; // section INF1 offset 0x0c
+ u16 unknown_mid_0a; // section MID1 offset 0x0a
+ u32 unknown_mid_0c; // section MID1 offset 0x0c
+
+ //--- text map
+
+ bmg_item_t * item; // item list
+ uint item_used; // number of used items
+ uint item_size; // number of alloced items
+
+ //--- attributes and other parameters
+
+ bmg_encoding_t encoding; // type of encoding
+ uint inf_size; // size of each 'inf' element
+ bool have_mid; // TRUE: enable MID1 creation
+ u8 attrib[BMG_ATTRIB_SIZE];// attribute buffer for defaults
+ u16 attrib_used; // used size of 'attrib', ALIGN(4)
+ u8 use_color_names; // 0:off, 1:auto, 2:on
+ u8 use_mkw_messages; // 0:off, 1:auto, 2:on
+ bool param_defined; // true: params above finally defined
+
+ bool have_predef_slots; // temporary, set by HavePredifinedSlots()
+ bool use_slots; // init by opt_bmg_use_slots
+ bool use_raw_sections; // init by opt_bmg_use_raw_sections
+ u32 active_cond; // active condition (for text output)
+
+ //--- raw data, created by CreateRawBMG()
+
+ u8 * raw_data; // NULL or data
+ uint raw_data_size; // size of 'raw_data'
+
+} bmg_t;
+
+//-----------------------------------------------------------------------------
+
+extern bmg_t *bmg_macros;
+
+//-----------------------------------------------------------------------------
+
+bmg_item_t * FindItemBMG ( const bmg_t *bmg, u32 mid );
+bmg_item_t * FindAnyItemBMG ( const bmg_t *bmg, u32 mid );
+bmg_item_t * InsertItemBMG ( bmg_t *bmg, u32 mid,
+ u8 *attrib, uint attrib_used, bool *old_item );
+bool DeleteItemBMG ( bmg_t *bmg, u32 mid );
+
+void ResetAttribBMG
+(
+ const bmg_t * bmg, // pointer to bmg
+ bmg_item_t * item // dest item
+);
+
+void CopyAttribBMG
+(
+ const bmg_t * dest, // pointer to destination bmg
+ bmg_item_t * dptr, // dest item
+ const bmg_item_t * sptr // source item
+);
+
+bool HavePredifinedSlots ( bmg_t *bmg );
+
+//-----------------------------------------------------------------------------
+
+void InitializeBMG ( bmg_t * bmg );
+void ResetBMG ( bmg_t * bmg );
+void AssignEncodingBMG ( bmg_t * bmg, int encoding );
+void AssignInfSizeBMG ( bmg_t * bmg, uint inf_size );
+
+//-----------------------------------------------------------------------------
+
+enumError ScanBMG
+(
+ bmg_t * bmg, // pointer to valid bmg
+ bool initialize, // true: initialize 'bmg'
+ ccp fname, // NULL or filename for error messages
+ const u8 * data, // data / if NULL: use internal data
+ size_t data_data // size of 'data' if data not NULL
+);
+
+enumError ScanRawBMG ( bmg_t * bmg );
+enumError ScanTextBMG ( bmg_t * bmg );
+
+enumError LoadBMG
+(
+ bmg_t * bmg, // pointer to valid bmg
+ bool initialize, // true: initialize 'bmg'
+ ccp parent_fname, // NULL or filename of parent for dir-extract
+ ccp fname, // filename of source
+ FileMode_t file_mode // open modes
+);
+
+//-----------------------------------------------------------------------------
+
+char * PrintAttribBMG
+(
+ char *buf, // destination buffer
+ uint buf_size, // size of 'buf', BMG_ATTRIB_BUF_SIZE is good
+ const u8 *attrib, // NULL or attrib to print
+ uint attrib_used, // used length of attrib
+ const u8 *def_attrib, // NULL or default attrib
+ bool force // true: always print '[]'
+);
+
+//-----------------------------------------------------------------------------
+
+enumError CreateRawBMG
+(
+ bmg_t * bmg // pointer to valid BMG
+);
+
+enumError SaveRawBMG
+(
+ bmg_t * bmg, // pointer to valid bmg
+ ccp fname, // filename of destination
+ FileMode_t fmode, // create-file mode
+ bool set_time // true: set time stamps
+);
+
+enumError SaveTextBMG
+(
+ bmg_t * bmg, // pointer to valid bmg
+ ccp fname, // filename of destination
+ FileMode_t fmode, // create-file mode
+ bool set_time, // true: set time stamps
+ bool force_numeric, // true: force numric MIDs
+ uint brief_count // >0: suppress syntax info
+ // >1: suppress all comments
+ // >2: suppress '#BMG' magic
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_create_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[bmg_create_t]]
+
+typedef struct bmg_create_t
+{
+ bmg_t *bmg; // current bmg
+ FastBuf_t inf; // INF1 data
+ FastBuf_t dat; // DAT1 data
+ FastBuf_t mid; // MID1 data
+
+ bool have_mid; // true: have MID1 section
+ uint n_msg; // number of inserted messages
+
+ //--- iterator -> GetFirstBI() + GetNextBI()
+
+ int slot; // slot number
+ bmg_item_t *bi; // next bi
+ bmg_item_t *bi_end; // last bi
+
+ bmg_item_t **bi_list; // list for predifined slots
+ uint bi_used; // number of used elements in 'bi_list'
+ uint bi_size; // number of alloced elements in 'bi_list'
+}
+bmg_create_t;
+
+//-----------------------------------------------------------------------------
+
+void InitializeCreateBMG( bmg_create_t *bc, bmg_t *bmg );
+void ResetCreateBMG ( bmg_create_t *bc );
+
+bmg_item_t * GetFirstBI ( bmg_create_t *bc );
+bmg_item_t * GetNextBI ( bmg_create_t *bc );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct bmg_scan_mid_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[bmg_scan_mid_t]]
+
+typedef struct bmg_scan_mid_t
+{
+ int status; // -1 for invalid, or 'n_mid'
+ int n_mid; // number of valid members in mid (0..3)
+ u32 mid[3]; // list of message IDs
+ // mid[0]: standard MID
+ // mid[1]: twin (track or arena)
+ // mid[2]: MID for LE_CODE or CT_CODE
+ char *scan_end; // NULL or end of scanned 'source'
+}
+bmg_scan_mid_t;
+
+//-----------------------------------------------------------------------------
+
+int ScanBMGMID
+(
+ // returns 'scan->status'
+
+ bmg_scan_mid_t *scan, // result, never NULL
+ bmg_t *bmg, // NULL or bmg to update parameters
+ ccp src, // source pointer
+ ccp src_end // NULL or end of source
+);
+
+//-----------------------------------------------------------------------------
+// [[obsolete]] wrapper for ScanBMGMID();
+
+int ScanMidBMG
+(
+ // returns:
+ // -1: invalid MID
+ // 0: empty line
+ // 1: single mid found
+ // 2: double mid found (track or arena)
+ // 3: triple mid found (track or arena, CTCODE)
+
+ bmg_t *bmg, // NULL or bmg to update parameters
+ u32 *ret_mid1, // return value: pointer to first MID
+ u32 *ret_mid2, // return value: pointer to second MID
+ u32 *ret_mid3, // return value: pointer to third MID
+ ccp src, // source pointer
+ ccp src_end, // NULL or end of source
+ char **scan_end // return value: NULL or end of scanned 'source'
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// MKW hooks ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+extern int (*GetTrackIndexBMG) ( uint idx, int result_if_not_found );
+extern int (*GetArenaIndexBMG) ( uint idx, int result_if_not_found );
+extern ccp (*GetContainerNameBMG) ( const bmg_t * bmg );
+
+extern int (*AtHookBMG) ( bmg_t * bmg, ccp line, ccp line_end );
+// On return status:
+// -1: continue with scanning line
+// -2: continue with next line
+// >=0: close file and return status as error code
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// patch BMG ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[PatchModeBMG_t]]
+
+typedef enum PatchModeBMG_t
+{
+ // this mode descibes the operation:
+ // source := source x patch
+
+ //--------------------------------------//
+ // only only source source //
+ // in in != == //
+ // source patch patch patch //
+ //--------------------------------------//
+ BMG_PM_PRINT, // source - print print //
+ BMG_PM_FORMAT, // ________use_parameter________ //
+ BMG_PM_REGEX, // ________use_parameter________ //
+ BMG_PM_RM_REGEX, // ________use_parameter________ //
+ BMG_PM_ID, // prefix n.a. n.a. n.a. //
+ BMG_PM_ID_ALL, // prefix n.a. n.a. n.a. //
+ BMG_PM_UNICODE, // ___no_patch_file_available___ //
+ BMG_PM_RM_ESCAPES, // ___no_patch_file_available___ //
+ //--------------------------------------//
+ BMG_PM_REPLACE, // source - patch both //
+ BMG_PM_INSERT, // source patch source both //
+ BMG_PM_OVERWRITE, // source patch patch both //
+ BMG_PM_DELETE, // source - - - //
+ BMG_PM_MASK, // - - source both //
+ BMG_PM_EQUAL, // - - - both //
+ BMG_PM_NOT_EQUAL, // - - source - //
+ //--------------------------------------//
+ BMG_PM_GENERIC, // ___no_patch_file_available___ //
+ BMG_PM_RM_CUPS, // ___no_patch_file_available___ //
+ BMG_PM_CT_COPY, // ___no_patch_file_available___ //
+ BMG_PM_CT_FORCE_COPY, // ___no_patch_file_available___ //
+ BMG_PM_CT_FILL, // ___no_patch_file_available___ //
+ BMG_PM_LE_COPY, // ___no_patch_file_available___ //
+ BMG_PM_LE_FORCE_COPY, // ___no_patch_file_available___ //
+ BMG_PM_LE_FILL, // ___no_patch_file_available___ //
+ BMG_PM_X_COPY, // ___no_patch_file_available___ //
+ BMG_PM_X_FORCE_COPY, // ___no_patch_file_available___ //
+ BMG_PM_X_FILL, // ___no_patch_file_available___ //
+ BMG_PM_RM_FILLED, // ___no_patch_file_available___ //
+ //--------------------------------------//
+
+ //--- helpers
+ BMG_PM__MASK_CMD = 0xff, // relevant bits of the command
+ BMG_PM__SHIFT_OPT = 8 // shift the parameter option by 8 bits
+
+} PatchModeBMG_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError PatchBMG
+(
+ bmg_t *dest, // pointer to destination bmg
+ const bmg_t *src, // pointer to source bmg
+ PatchModeBMG_t patch_mode, // patch mode
+ ccp format, // NULL or format parameter
+ bool dup_strings // true: alloc mem for copied strings (OVERWRITE)
+);
+
+//-----------------------------------------------------------------------------
+
+bool PatchFormatBMG
+(
+ bmg_t *dest, // pointer to destination bmg
+ const bmg_t *src, // pointer to source bmg
+ ccp format // format parameter
+);
+
+bool PatchOverwriteBMG
+(
+ bmg_t * dest, // pointer to destination bmg
+ const bmg_t * src, // pointer to source bmg
+ bool dup_strings // true: allocate mem for copied strings
+);
+
+bool PatchPrintBMG ( bmg_t * dest, const bmg_t * src );
+bool PatchReplaceBMG ( bmg_t * dest, const bmg_t * src );
+bool PatchInsertBMG ( bmg_t * dest, const bmg_t * src );
+bool PatchDeleteBMG ( bmg_t * dest, const bmg_t * src );
+bool PatchMaskBMG ( bmg_t * dest, const bmg_t * src );
+bool PatchEqualBMG ( bmg_t * dest, const bmg_t * src );
+bool PatchNotEqualBMG ( bmg_t * dest, const bmg_t * src );
+bool PatchGenericBMG ( bmg_t * dest, const bmg_t * src );
+
+bool PatchRmCupsBMG ( bmg_t * bmg );
+
+bool PatchCTCopyBMG ( bmg_t * bmg );
+bool PatchLECopyBMG ( bmg_t * bmg );
+bool PatchXCopyBMG ( bmg_t * bmg );
+
+bool PatchCTForceCopyBMG( bmg_t * bmg );
+bool PatchLEForceCopyBMG( bmg_t * bmg );
+bool PatchXForceCopyBMG ( bmg_t * bmg );
+
+bool PatchFillBMG
+ ( bmg_t *bmg, ct_mode_t ct_mode, uint rcup_limit, uint track_limit );
+bool PatchXFillBMG ( bmg_t *bmg, uint rcup_limit, uint track_limit );
+static inline bool PatchCTFillBMG ( bmg_t *bmg, uint rcup_limit, uint track_limit )
+ { return PatchFillBMG(bmg,CTM_CTCODE,rcup_limit,track_limit); }
+static inline bool PatchLEFillBMG ( bmg_t *bmg, uint rcup_limit, uint track_limit )
+ { return PatchFillBMG(bmg,CTM_LECODE2,rcup_limit,track_limit); }
+
+bool PatchIdBMG
+(
+ bmg_t * bmg, // pointer to destination bmg
+ bool prefix_all // true: prefix all messages
+);
+
+bool PatchRmEscapesBMG
+(
+ bmg_t * bmg, // pointer to destination bmg
+ bool unicode, // replace '\u{16bit}' by unicode chars
+ bool rm_escapes // remove 1A escapes
+);
+
+// remove between mid1 and mid2-1
+bool PatchRemoveBMG ( bmg_t * bmg, int mid1, int mid2 );
+//bool PatchRemoveTracksBMG ( bmg_t * bmg, int track_limit );
+bool PatchIdentificationBMG ( bmg_t *bmg, ct_bmg_t *ctb );
+
+//-----------------------------------------------------------------------------
+
+ccp GetNameBMG ( const bmg_t *bmg, uint *name_len );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DC_LIB_BMG_H 1
diff --git a/project/dclib/lib-dol.c b/project/dclib/lib-dol.c
new file mode 100644
index 0000000..00d6972
--- /dev/null
+++ b/project/dclib/lib-dol.c
@@ -0,0 +1,1208 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+#include "lib-dol.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// dol header ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ntoh_dol_header ( dol_header_t * dest, const dol_header_t * src )
+{
+ DASSERT(dest);
+
+ if (!src)
+ src = dest;
+ else if ( dest != src )
+ memcpy(dest,src,sizeof(*dest));
+
+ const u32 * src_ptr = src->sect_off;
+ u32 * dest_ptr = dest->sect_off;
+ u32 * dest_end = (u32*)&dest->padding;
+
+ while ( dest_ptr < dest_end )
+ *dest_ptr++ = ntohl(*src_ptr++);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void hton_dol_header ( dol_header_t * dest, const dol_header_t * src )
+{
+ DASSERT(dest);
+
+ if (!src)
+ src = dest;
+ else if ( dest != src )
+ memcpy(dest,src,sizeof(*dest));
+
+ const u32 * src_ptr = src->sect_off;
+ u32 * dest_ptr = dest->sect_off;
+ u32 * dest_end = (u32*)&dest->padding;
+
+ while ( dest_ptr < dest_end )
+ *dest_ptr++ = htonl(*src_ptr++);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsDolHeader ( const void *data, uint data_size, uint file_size )
+{
+ if ( data_size < DOL_HEADER_SIZE )
+ return false;
+
+ const dol_header_t *dol = data;
+ u64 total_size = 0;
+
+ uint i;
+ for ( i = 0; i < DOL_N_SECTIONS; i++ )
+ {
+ const u32 off = ntohl(dol->sect_off[i]);
+ const u32 size = ntohl(dol->sect_size[i]);
+ const u32 addr = ntohl(dol->sect_addr[i]);
+ noPRINT("DOL: %8x %8x %08x\n",off,size,addr);
+ if ( off || size || addr )
+ {
+ if ( off & 3
+ || size & 3
+ || addr & 3
+ || off < DOL_HEADER_SIZE
+ || file_size && off + size > file_size )
+ {
+ return false;
+ }
+ total_size += size;
+ }
+ }
+
+ return total_size && total_size < 0x7fffffff;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const char dol_section_name[DOL_NN_SECTIONS+1][4] =
+{
+ "T0",
+ "T1",
+ "T2",
+ "T3",
+ "T4",
+ "T5",
+ "T6",
+
+ "D0",
+ "D1",
+ "D2",
+ "D3",
+ "D4",
+ "D5",
+ "D6",
+ "D7",
+ "D8",
+ "D9",
+ "D10",
+
+ "BSS",
+ "ENT",
+
+ ""
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// advanced interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool GetDolSectionInfo
+(
+ dol_sect_info_t * info, // valid pointer: returned data
+ const dol_header_t *dol_head, // valid DOL header
+ uint file_size, // (file) size of 'data'
+ uint section // 0 .. DOL_NN_SECTIONS
+ // (7*TEXT, 11*DATA, BSS, ENTRY )
+)
+{
+ DASSERT(dol_head);
+ DASSERT(info);
+ memset(info,0,sizeof(*info));
+
+ if ( !dol_head || file_size < sizeof(dol_header_t) || section >= DOL_NN_SECTIONS )
+ {
+ info->section = -1;
+ return false;
+ }
+
+ info->section = section;
+ StringCopyS(info->name,sizeof(info->name),GetDolSectionName(section));
+
+ if ( section < DOL_N_SECTIONS )
+ {
+ const u32 size = be32(&dol_head->sect_size[section]);
+ if (size)
+ {
+ info->sect_valid = true;
+ info->size = size;
+ const u32 off = info->off = be32(&dol_head->sect_off [section]);
+ info->addr = be32(&dol_head->sect_addr[section]);
+
+ if ( off < file_size && size < file_size && off+size <= file_size )
+ {
+ info->data_valid = true;
+ info->data = (u8*)dol_head + off;
+ }
+ }
+ return true;
+ }
+
+ if ( section == DOL_IDX_BSS )
+ {
+ info->sect_valid = true;
+ info->addr = be32(&dol_head->bss_addr);
+ info->size = be32(&dol_head->bss_size);
+ return true;
+ }
+
+ if ( section == DOL_IDX_ENTRY )
+ {
+ info->sect_valid = true;
+ info->addr = be32(&dol_head->entry_addr);
+ return true;
+ }
+
+ ASSERT(0);
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool FindFirstFreeDolSection
+(
+ // returns TRUE on found
+
+ dol_sect_info_t *info, // result
+ const dol_header_t *dh, // DOL header to analyse
+ uint mode // 0: search all sections (TEXT first)
+ // 1: search only TEXT sections
+ // 2: search only DATA sections
+ // 3: search all sections (DATA first)
+)
+{
+ DASSERT(info);
+ DASSERT(dh);
+
+ memset(info,0,sizeof(*info));
+
+ uint idx, end;
+ switch (mode)
+ {
+ case 0:
+ idx = 0;
+ end = DOL_N_SECTIONS;
+ break;
+
+ case 3:
+ if (FindFirstFreeDolSection(info,dh,2))
+ return true;
+ // fall through
+
+ case 1:
+ idx = 0;
+ end = DOL_N_TEXT_SECTIONS;
+ break;
+
+ case 2:
+ idx = DOL_N_TEXT_SECTIONS;
+ end = DOL_N_SECTIONS;
+ break;
+
+ default:
+ idx = 0;
+ end = 0;
+ break;
+ }
+
+ while ( idx < end && ntohl(dh->sect_size[idx]) )
+ idx++;
+
+ if ( idx >= end )
+ {
+ info->section = -1;
+ return false;
+ }
+
+ info->section = idx;
+ StringCopyS(info->name,sizeof(info->name),GetDolSectionName(idx));
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool FindDolSectionBySelector
+(
+ // returns TRUE on found
+
+ dol_sect_info_t *info, // result
+ const dol_header_t *dh, // DOL header to analyse
+ const dol_sect_select_t *dss // DOL section selector
+)
+{
+ DASSERT(info);
+ DASSERT(dh);
+ DASSERT(dss);
+
+ if ( dss->sect_idx >= 0 )
+ {
+ memset(info,0,sizeof(*info));
+ info->section = dss->sect_idx;
+ memcpy(info->name,dss->name,sizeof(info->name));
+ return true;
+ }
+
+ return FindFirstFreeDolSection(info,dh,dss->find_mode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 FindFreeSpaceDOL
+(
+ // return address or NULL on not-found
+
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr_beg, // first possible address
+ u32 addr_end, // last possible address
+ u32 size, // minimal size
+ u32 align, // aligning
+ u32 *space // not NULL: store available space here
+)
+{
+ DASSERT(dol_head);
+
+ MemMap_t mm;
+ InitializeMemMap(&mm);
+
+ int i;
+ for ( i = 0; i < DOL_N_SECTIONS; i++ )
+ {
+ const u32 size = ntohl(dol_head->sect_size[i]);
+ if (size)
+ {
+ const u32 addr = ntohl(dol_head->sect_addr[i]);
+ if ( addr < addr_end && addr + size > addr_beg )
+ {
+ MemMapItem_t * mi = InsertMemMap(&mm,addr,size);
+ if (mi)
+ {
+ if ( i < DOL_N_TEXT_SECTIONS )
+ snprintf(mi->info,sizeof(mi->info),"T%u",i);
+ else
+ snprintf(mi->info,sizeof(mi->info),"D%u",
+ i - DOL_N_TEXT_SECTIONS);
+ }
+ }
+ }
+ }
+ //PrintMemMap(&mm,stdout,3,"section");
+
+ u64 res_space;
+ const u32 res = FindFreeSpaceMemMap(&mm,addr_beg,addr_end,size,align,&res_space);
+ ResetMemMap(&mm);
+ if (space)
+ *space = res_space;
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 FindFreeBssSpaceDOL
+(
+ // return address or NULL on not-found
+
+ const dol_header_t *dol_head, // valid DOL header
+ u32 size, // min size
+ u32 align, // aligning
+ u32 *space // not NULL: store available space here
+)
+{
+ DASSERT(dol_head);
+ const u32 bss_addr = ntohl(dol_head->bss_addr);
+ const u32 bss_size = ntohl(dol_head->bss_size);
+ if ( !bss_addr || !bss_size )
+ {
+ if (space)
+ *space = 0;
+ return 0;
+ }
+
+ return FindFreeSpaceDOL(dol_head,bss_addr,bss_addr+bss_size,size,align,space);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpDolHeader
+(
+ FILE * f, // dump to this file
+ int indent, // indention
+ const dol_header_t *dol_head, // valid DOL header
+ uint file_size, // NULL or size of file
+ uint print_mode // bit field:
+ // 1: print file map
+ // 2: print virtual mem map
+ // 4: print delta table
+)
+{
+ DASSERT(f);
+ DASSERT(dol_head);
+
+ indent = NormalizeIndent(indent);
+
+ dol_header_t dol;
+ ntoh_dol_header(&dol,dol_head);
+
+ MemMap_t mm1, mm2, mm3;
+ InitializeMemMap(&mm1);
+ InitializeMemMap(&mm2);
+ InitializeMemMap(&mm3);
+ MemMapItem_t * mi = InsertMemMap(&mm1,0,sizeof(dol_header_t));
+ StringCopyS(mi->info,sizeof(mi->info),"DOL header");
+ if (file_size)
+ {
+ mi = InsertMemMap(&mm1,file_size,0);
+ StringCopyS(mi->info,sizeof(mi->info),"--- end of file ---");
+ }
+
+ int i;
+ for ( i = 0; i < DOL_N_SECTIONS; i++ )
+ {
+ const u32 size = dol.sect_size[i];
+ if (size)
+ {
+ const u32 off = dol.sect_off[i];
+ const u32 addr = dol.sect_addr[i];
+
+ char buf[sizeof(mi->info)], buf3[sizeof(mi->info)];
+ if ( i < DOL_N_TEXT_SECTIONS )
+ {
+ snprintf(buf,sizeof(buf),"text section T%u",i);
+ snprintf(buf3,sizeof(buf3),"%8x : T%u",addr-off,i);
+ }
+ else
+ {
+ const int j = i - DOL_N_TEXT_SECTIONS;
+ snprintf(buf,sizeof(buf),"data section D%u",j);
+ snprintf(buf3,sizeof(buf3),"%8x : D%u",addr-off,j);
+ }
+
+ mi = InsertMemMap(&mm1,off,size);
+ strcpy(mi->info,buf);
+ mi = InsertMemMap(&mm2,addr,size);
+ strcpy(mi->info,buf);
+ mi = InsertMemMap(&mm3,addr,size);
+ strcpy(mi->info,buf3);
+ }
+ }
+
+ if ( print_mode & 1 )
+ {
+ fprintf(f,"%*sMemory map of DOL file:\n\n",indent,"");
+ PrintMemMap(&mm1,f,indent+3,"section");
+ putc('\n',f);
+ }
+
+ if ( print_mode & 2 )
+ {
+ mi = InsertMemMap(&mm2,dol.bss_addr,dol.bss_size);
+ snprintf(mi->info,sizeof(mi->info),"bss section");
+ mi = InsertMemMap(&mm2,dol.entry_addr,0);
+ snprintf(mi->info,sizeof(mi->info),"entry point");
+
+ fprintf(f,"%*sMemory map of DOL image:\n\n",indent,"");
+ mm2.begin = 0xffffffff;
+ PrintMemMap(&mm2,f,indent+3,"section");
+ putc('\n',f);
+ }
+
+ if ( print_mode & 4 )
+ {
+ mm3.begin = 0xffffffff;
+ fprintf(f,"%*sDelta between file offset and virtual address:\n\n",
+ indent,"" );
+ PrintMemMap(&mm3,f,indent+3," delta : section");
+ putc('\n',f);
+ }
+
+ ResetMemMap(&mm1);
+ ResetMemMap(&mm2);
+ ResetMemMap(&mm3);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetDolSectionAddr
+(
+ // returns dsa->section
+ dol_sect_addr_t *dsa, // result: section address info
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr, // address to search
+ u32 size // >0: wanted size
+)
+{
+ DASSERT(dsa);
+ DASSERT(dol_head);
+ memset(dsa,0,sizeof(*dsa));
+ dsa->addr = addr;
+ dsa->size = size;
+
+ int sect;
+ for ( sect = 0; sect < DOL_N_SECTIONS; sect++ )
+ {
+ const u32 sect_addr = ntohl(dol_head->sect_addr[sect]);
+ const u32 sect_size = ntohl(dol_head->sect_size[sect]);
+ if ( addr >= sect_addr && addr+size <= sect_addr + sect_size )
+ {
+ dsa->sect_addr = sect_addr;
+ dsa->sect_offset = addr - sect_addr;
+ dsa->sect_size = sect_size;
+ StringCopyS(dsa->sect_name,sizeof(dsa->sect_name),GetDolSectionName(sect));
+ return dsa->section = sect;
+ }
+ }
+
+ //--- bss?
+
+ const u32 bss_addr = ntohl(dol_head->bss_addr);
+ const u32 bss_size = ntohl(dol_head->bss_size);
+ if ( addr >= bss_addr && addr+size <= bss_addr + bss_size )
+ {
+ dsa->sect_addr = bss_addr;
+ dsa->sect_offset = addr - bss_addr;
+ dsa->sect_size = bss_size;
+ StringCopyS(dsa->sect_name,sizeof(dsa->sect_name),"BSS");
+ return dsa->section = DOL_IDX_BSS;
+ }
+
+ //--- not found
+
+ return dsa->section = -1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetDolSectionAddrInfo
+(
+ // returns GetCircBuf() or EmptyString on error
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr, // address to search
+ u32 size // >0: wanted size
+)
+{
+ DASSERT(dol_head);
+ dol_sect_addr_t dsa;
+ if ( GetDolSectionAddr(&dsa,dol_head,addr,size) < 0 )
+ return EmptyString;
+
+ return PrintCircBuf(" (%s+%#x)",dsa.sect_name,dsa.sect_offset);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 GetDolOffsetByAddr
+(
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr, // address to search
+ u32 size, // >0: wanted size
+ u32 *valid_size // not NULL: return valid size
+)
+{
+ DASSERT(dol_head);
+
+ int sect;
+ for ( sect = 0; sect < DOL_N_SECTIONS; sect++ )
+ {
+ const u32 sect_addr = ntohl(dol_head->sect_addr[sect]);
+ const u32 sect_size = ntohl(dol_head->sect_size[sect]);
+ if ( addr >= sect_addr && addr < sect_addr + sect_size )
+ {
+ u32 max_size = sect_addr + sect_size - addr;
+ if (!size)
+ {
+ if (valid_size)
+ *valid_size = max_size;
+ }
+ else if ( size <= max_size )
+ {
+ if (valid_size)
+ *valid_size = size;
+ }
+ else if (valid_size)
+ *valid_size = max_size;
+ else
+ return 0;
+
+ return ntohl(dol_head->sect_off[sect]) + addr - sect_addr;
+ }
+ }
+
+ if (valid_size)
+ *valid_size = 0;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 GetDolAddrByOffset
+(
+ const dol_header_t *dol_head, // valid DOL header
+ u32 off, // address to search
+ u32 size, // >0: wanted size
+ u32 *valid_size // not NULL: return valid size
+)
+{
+ DASSERT(dol_head);
+
+ int sect;
+ for ( sect = 0; sect < DOL_N_SECTIONS; sect++ )
+ {
+ const u32 sect_off = ntohl(dol_head->sect_off[sect]);
+ const u32 sect_size = ntohl(dol_head->sect_size[sect]);
+
+ if ( off >= sect_off && off < sect_off + sect_size )
+ {
+ u32 max_size = sect_off + sect_size - off;
+ if (!size)
+ {
+ if (valid_size)
+ *valid_size = max_size;
+ }
+ else if ( size <= max_size )
+ {
+ if (valid_size)
+ *valid_size = size;
+ }
+ else if (valid_size)
+ *valid_size = max_size;
+ else
+ return 0;
+
+ return ntohl(dol_head->sect_addr[sect]) + off - sect_off;
+ }
+ }
+
+ if (valid_size)
+ *valid_size = 0;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint AddDolAddrByOffset
+(
+ const dol_header_t *dol_head, // valid DOL header
+ MemMap_t *mm, // valid destination mem map, not cleared
+ bool use_tie, // TRUE: use InsertMemMapTie()
+ u32 off, // address to search
+ u32 size // size, may overlay multiple sections
+)
+{
+ DASSERT(dol_head);
+ DASSERT(mm);
+
+ uint count = 0;
+ while ( size > 0 )
+ {
+ u32 valid_size;
+ u32 addr = GetDolAddrByOffset(dol_head,off,size,&valid_size);
+ if ( !addr || !valid_size )
+ break;
+ if (use_tie)
+ InsertMemMapTie(mm,addr,valid_size);
+ else
+ InsertMemMap(mm,addr,valid_size);
+ off += valid_size;
+ size -= valid_size;
+ }
+
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint TranslateDolOffsets
+(
+ const dol_header_t *dol_head, // valid DOL header
+ MemMap_t *mm, // valid destination mem map, not cleared
+ bool use_tie, // TRUE: use InsertMemMapTie()
+ const MemMap_t *mm_off // NULL or mem map with offsets
+)
+{
+ DASSERT(dol_head);
+ DASSERT(mm);
+ if (!mm_off)
+ return 0;
+
+ uint mi, count = 0;
+ for ( mi = 0; mi < mm_off->used; mi++ )
+ {
+ const MemMapItem_t *src = mm_off->field[mi];
+ AddDolAddrByOffset(dol_head,mm,use_tie,src->off,src->size);
+ }
+
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint TranslateAllDolOffsets
+(
+ const dol_header_t *dol_head, // valid DOL header
+ MemMap_t *mm, // valid destination mem map, not cleared
+ bool use_tie // TRUE: use InsertMemMapTie()
+)
+{
+ DASSERT(dol_head);
+ DASSERT(mm);
+
+ uint count = 0;
+
+ int i;
+ for ( i = 0; i < DOL_N_SECTIONS; i++ )
+ {
+ const u32 size = ntohl(dol_head->sect_size[i]);
+ if (size)
+ {
+ count++;
+ const u32 addr = ntohl(dol_head->sect_addr[i]);
+ MemMapItem_t *mi = use_tie
+ ? InsertMemMapTie(mm,addr,size)
+ : InsertMemMap(mm,addr,size);
+ if (*mi->info)
+ StringCopyS(mi->info,sizeof(mi->info),"...");
+ else if ( i < DOL_N_TEXT_SECTIONS )
+ snprintf(mi->info,sizeof(mi->info),"text section T%u",i);
+ else
+ {
+ const int j = i - DOL_N_TEXT_SECTIONS;
+ snprintf(mi->info,sizeof(mi->info),"data section D%u",j);
+ }
+ }
+ }
+
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint TranslateDolSections
+(
+ const dol_header_t *dol_head, // valid DOL header
+ MemMap_t *mm, // valid destination mem map, not cleared
+ bool use_tie, // TRUE: use InsertMemMapTie()
+ uint dol_sections // bitfield of sections
+)
+{
+ DASSERT(dol_head);
+ DASSERT(mm);
+
+ dol_sect_info_t sinfo;
+ uint sect, count = 0;
+ for ( sect = 0; sect < DOL_NN_SECTIONS; sect++ )
+ {
+ if ( dol_sections & 1 << sect
+ && GetDolSectionInfo(&sinfo,dol_head,sizeof(*dol_head),sect) )
+ {
+ count++;
+ uint size = sect == DOL_IDX_ENTRY ? 16 : sinfo.size;
+ MemMapItem_t *mi = use_tie
+ ? InsertMemMapTie(mm,sinfo.addr,size)
+ : InsertMemMap(mm,sinfo.addr,size);
+
+ if (*mi->info)
+ StringCopyS(mi->info,sizeof(mi->info),"...");
+ else
+ snprintf(mi->info,sizeof(mi->info),
+ "section %s",dol_section_name[sect]);
+ }
+ }
+
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CountOverlappedDolSections
+(
+ // returns the number of overlapped section by [addr,size]
+
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr, // address to search
+ u32 size // size of addr to verify
+)
+{
+ DASSERT(dol_head);
+
+ u32 end = addr + size;
+ if ( end < addr ) // overflow
+ end = M1(end);
+
+ uint sect, count = 0;
+ for ( sect = 0; sect < DOL_N_SECTIONS; sect++ )
+ {
+ const u32 sect_addr = ntohl(dol_head->sect_addr[sect]);
+ const u32 sect_size = ntohl(dol_head->sect_size[sect]);
+ if ( addr < sect_addr + sect_size && end > sect_addr )
+ count++;
+
+ noPRINT("%8x..%8x / %8x..%8x => %d/%u\n",
+ addr, end, sect_addr, sect_addr + sect_size,
+ addr < sect_addr + sect_size && end > sect_addr, count );
+ }
+ return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __APPLE__
+ void * memmem ( const void *l, size_t l_len, const void *s, size_t s_len );
+#endif // __APPLE__
+
+///////////////////////////////////////////////////////////////////////////////
+
+static u32 FindDolAddress
+(
+ const u8 *data, // DOL data beginning with dol_header_t
+ uint off_begin, // begin search here
+ uint off_end, // end search here
+ const u8 *search, // data to search
+ uint serach_size // size of 'search'
+)
+{
+ while ( off_begin < off_end )
+ {
+ const u8 *res
+ = memmem( data+off_begin, off_end-off_begin, search, serach_size );
+ if (!res)
+ return 0;
+ off_begin = res - data;
+ if (!(off_begin&3)) // check alignment
+ return off_begin;
+ off_begin++;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+
+u32 FindDolAddressOfVBI
+(
+ // search in available data and return NULL if not found
+
+ cvp data, // DOL data beginning with dol_header_t
+ uint data_size // size of 'data'
+)
+{
+ if ( data_size < sizeof(dol_header_t) )
+ return 0;
+ const dol_header_t *dh = (dol_header_t*)data;
+
+ static const u8 search1[] = { 0x7C,0xE3,0x3B,0x78, 0x38,0x87,0x00,0x34,
+ 0x38,0xA7,0x00,0x38, 0x38,0xC7,0x00,0x4C };
+ static const u8 search2[] = { 0x4E,0x80,0x00,0x20 };
+
+ int sect;
+ for ( sect = 0; sect < DOL_N_SECTIONS; sect++ )
+ {
+ const u32 off1 = ntohl(dh->sect_off[sect]);
+ const u32 size = ntohl(dh->sect_size[sect]);
+ if ( !size || off1 >= data_size )
+ continue;
+
+ u32 off_end = off1 + size;
+ if ( off_end > data_size )
+ off_end = data_size;
+
+ u32 off2 = FindDolAddress( data, off1, off_end, search1, sizeof(search1) );
+ if (off2)
+ {
+ off2 = FindDolAddress( data, off2+sizeof(search1), off_end,
+ search2, sizeof(search2) );
+ if (off2)
+ return ntohl(dh->sect_addr[sect]) + off2 - off1;
+ }
+ }
+ return 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanDolSectionName
+(
+ // returns the first uninterpreted character
+
+ ccp arg, // comma/space separated names
+ int *ret_section, // not NULL: return section index
+ // <0: error / >=0: section index
+ enumError *ret_err // not NULL: return error code
+)
+{
+ int section = -1;
+ enumError err = ERR_OK;
+
+ if (!arg)
+ err = ERR_MISSING_PARAM;
+ else
+ {
+ while ( *arg && !isalnum((int)*arg) )
+ arg++;
+
+ ccp ptr = arg;
+ char namebuf[10], *np = namebuf;
+ for(;;)
+ {
+ char ch = toupper((int)*ptr);
+ if ( ch < 'A' || ch > 'Z' )
+ break;
+ if ( np < namebuf + sizeof(namebuf) - 1 )
+ *np++ = ch;
+ ptr++;
+ }
+
+ int index = -1;
+ if (!memcmp(namebuf,"TEXT",np-namebuf))
+ index = 0;
+ else if (!memcmp(namebuf,"DATA",np-namebuf))
+ index = DOL_N_TEXT_SECTIONS;
+ else
+ {
+ if (!memcmp(namebuf,"BSS",np-namebuf))
+ {
+ section = DOL_IDX_BSS;
+ arg = ptr;
+ }
+ else if (!memcmp(namebuf,"ENTRY",np-namebuf))
+ {
+ section = DOL_IDX_ENTRY;
+ arg = ptr;
+ }
+ else
+ err = ERR_INVALID_DATA;
+ goto abort;
+ }
+
+ char *end;
+ uint num = str2ul(ptr,&end,10);
+ if ( end > ptr )
+ {
+ arg = end;
+ index += num;
+ if ( index < DOL_N_SECTIONS )
+ section = index;
+ else
+ err = ERR_INVALID_DATA;
+ }
+ }
+
+ abort:;
+ if (ret_section)
+ *ret_section = section;
+ if (ret_err)
+ *ret_err = err;
+ return (char*)arg;
+}
+
+//-----------------------------------------------------------------------------
+
+uint ScanDolSectionList
+(
+ // return a bitfield as section list
+
+ ccp arg, // comma/space separated names
+ enumError *ret_err // not NULL: return status
+)
+{
+ enumError err = ERR_OK;
+ uint sect_list = 0;
+
+ while ( arg && *arg )
+ {
+ int section;
+ enumError scan_err;
+ arg = ScanDolSectionName(arg,&section,&scan_err);
+ if (scan_err)
+ {
+ if ( err < scan_err )
+ err = scan_err;
+ break;
+ }
+
+ if ( section >= 0 )
+ sect_list |= 1 << section;
+ }
+
+ if (ret_err)
+ *ret_err = !sect_list && err < ERR_MISSING_PARAM ? ERR_MISSING_PARAM : err;
+ return sect_list;
+}
+
+//-----------------------------------------------------------------------------
+
+ccp DolSectionList2Text ( uint sect_list )
+{
+ char buf[100], *dest = buf;
+ uint sect;
+ for ( sect = 0; sect < DOL_NN_SECTIONS; sect++ )
+ if ( sect_list & 1 << sect )
+ dest = snprintfE(dest,buf+sizeof(buf),",%s",dol_section_name[sect]);
+
+ char *res = GetCircBuf(dest-buf);
+ memcpy(res,buf+1,dest-buf);
+ return res;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// add/remove DOL sections ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+
+uint CompressDOL
+(
+ // return: size of compressed DOL (inline replaced)
+
+ dol_header_t *dol, // valid dol header
+ FILE *log // not NULL: print memmap to this file
+)
+{
+ DASSERT(dol);
+
+ MemMap_t mm;
+ InitializeMemMap(&mm);
+
+ uint max_off = 0, total_data_size = 0;
+ uint s;
+ for ( s = 0; s < DOL_N_SECTIONS; s++ )
+ {
+ const u32 off = ntohl(dol->sect_off[s]);
+ const u32 size = ntohl(dol->sect_size[s]);
+ if ( off && size )
+ {
+ MemMapItem_t *mi = InsertMemMap(&mm,off,size);
+ snprintf(mi->info,sizeof(mi->info),"%u",s);
+ total_data_size += size;
+ if ( max_off < off + size )
+ max_off = off + size;
+ }
+ }
+
+ if (log)
+ PrintMemMap(&mm,log,5,"DOL sections");
+
+ u8 *temp = MALLOC(total_data_size);
+
+ uint i, new_off = 0;
+ for ( i = 0; i < mm.used; i++ )
+ {
+ MemMapItem_t *mi = mm.field[i];
+ uint sect = strtoul(mi->info,0,10);
+ DASSERT( sect < DOL_N_SECTIONS );
+ const u32 off = ntohl(dol->sect_off[sect]);
+ const u32 size = ntohl(dol->sect_size[sect]);
+
+ memcpy( temp + new_off, (u8*)dol + off, size );
+ dol->sect_off[sect] = htonl( sizeof(dol_header_t) + new_off );
+ new_off += size;
+ }
+ DASSERT( total_data_size == new_off );
+
+ memcpy(dol+1,temp,new_off);
+ FREE(temp);
+ return new_off + sizeof(dol_header_t);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint RemoveDolSections
+(
+ // return: 0:if not modifed, >0:size of compressed DOL
+
+ dol_header_t *dol, // valid dol header
+ uint stay, // bit field of sections (0..17): SET => don't remove
+ uint *res_stay, // not NULL: store bit field of vlaid sections here
+ FILE *log // not NULL: print memmap to this file
+)
+{
+ uint s, m, count = 0;
+ for ( s = 0, m = 1; s < DOL_N_SECTIONS; s++, m <<= 1 )
+ {
+ const u32 off = ntohl(dol->sect_off[s]);
+ const u32 size = ntohl(dol->sect_size[s]);
+ if ( !off || !size )
+ stay &= ~m;
+
+ if ( !(stay&m) && off )
+ {
+ dol->sect_off[s] = dol->sect_size[s] = dol->sect_addr[s] = 0;
+ count++;
+ }
+ }
+
+ if (res_stay)
+ *res_stay = stay;
+
+ return count ? CompressDOL(dol,log) : 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Gecko Code Handler ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsValidGCH
+(
+ const gch_header_t *gh, // valid header
+ uint file_size // size of file
+)
+{
+ DASSERT(gh);
+
+ if ( file_size < sizeof(*gh) || ntohl(gh->magic) != GCH_MAGIC_NUM )
+ return false;
+
+ const u32 addr = ntohl(gh->addr);
+ const u32 size = ntohl(gh->size);
+ const u32 entry = ntohl(gh->vbi_entry);
+
+ if ( addr < 0x80000000 || sizeof(*gh) + size > file_size )
+ return false;
+
+ if ( entry && ( entry < addr || entry >= addr + size - 4 ))
+ return false;
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp DecodeWCH
+(
+ // returns NULL on success, or a short error message
+
+ wch_control_t *wc, // store only, (initialized)
+ void *data, // source data, modified if type != 0
+ uint size, // size of 'data'
+ DecompressWCH_func decompress // NULL or decompression function
+)
+{
+ DASSERT(wc);
+ DASSERT( data || !size );
+
+ memset(wc,0,sizeof(*wc));
+ if ( !data || size < sizeof(wch_header_t) + sizeof(wch_segment_t) )
+ return "To small for a WCH file";
+
+ const wch_header_t *wh = data;
+ wc->wh.magic = ntohl(wh->magic);
+ wc->wh.version = ntohl(wh->version);
+ wc->wh.size = ntohl(wh->size);
+ if ( wc->wh.magic != WCH_MAGIC_NUM || wc->wh.version > 1 )
+ return "Invalid WCH header";
+
+ const wch_segment_t *ptr = wh->segment;
+ if ( wc->wh.version == 1 && decompress )
+ {
+ wc->temp_size = wc->wh.size;
+ wc->temp_data = MALLOC(wc->temp_size);
+ DASSERT(wc->temp_data);
+
+ uint data_size = size - sizeof(wch_header_t);
+ if ( decompress(wc,(void*)ptr,data_size) != ERR_OK )
+ return "Decompression of WCH failed.";
+ ptr = (wch_segment_t*)wc->temp_data;
+ }
+
+ const wch_segment_t *end = (wch_segment_t*)( (u8*)ptr + wc->wh.size );
+ noPRINT("%p %p [%zd]\n",ptr,end,(u8*)end-(u8*)ptr);
+
+ uint iseg;
+ wch_segment_t *dseg = wc->seg;
+ for ( iseg = 0; iseg < WCH_MAX_SEG; iseg++, dseg++ )
+ {
+ if (!ptr->type)
+ break;
+ dseg->type = ntohl(ptr->type);
+ dseg->addr = ntohl(ptr->addr);
+ dseg->size = ntohl(ptr->size);
+ dseg->main_entry = ntohl(ptr->main_entry);
+ dseg->main_entry_old = ntohl(ptr->main_entry_old);
+ dseg->vbi_entry = ntohl(ptr->vbi_entry);
+ dseg->vbi_entry_old = ntohl(ptr->vbi_entry_old);
+
+ u8 *src_data = (u8*)ptr->data;
+ wc->seg_data[iseg] = src_data;
+ ptr = (wch_segment_t*)(ptr->data + ALIGN32(dseg->size,4));
+ if ( ptr > end )
+ {
+ wc->is_valid = false;
+ return "Damaged WCH data";
+ }
+ }
+ wc->n_seg = iseg;
+ memset(dseg,0,sizeof(*dseg));
+ wc->is_valid = true;
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetWCH ( wch_control_t * wc )
+{
+ if ( wc && wc->temp_data )
+ {
+ FREE(wc->temp_data);
+ wc->temp_data = 0;
+ wc->temp_size = 0;
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// virtual dumps ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
diff --git a/project/dclib/lib-dol.h b/project/dclib/lib-dol.h
new file mode 100644
index 0000000..3197bcc
--- /dev/null
+++ b/project/dclib/lib-dol.h
@@ -0,0 +1,517 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef DC_LIB_DOL_H
+#define DC_LIB_DOL_H 1
+
+#define _GNU_SOURCE 1
+#include "dclib-basics.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// DOL: defs & structs ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define DOL_N_TEXT_SECTIONS 7
+#define DOL_N_DATA_SECTIONS 11
+#define DOL_N_SECTIONS 18
+
+#define DOL_IDX_BSS DOL_N_SECTIONS // alias for BSS
+#define DOL_IDX_ENTRY (DOL_N_SECTIONS+1) // alias for ENTRY
+#define DOL_NN_SECTIONS (DOL_N_SECTIONS+2) // N + aliase
+
+#define DOL_HEADER_SIZE 0x100
+
+//-----------------------------------------------------------------------------
+
+#define ASM_NOP 0x60000000
+
+//-----------------------------------------------------------------------------
+
+static int IsDolTextSection ( uint section )
+ { return section < DOL_N_TEXT_SECTIONS; }
+
+static bool IsDolDataSection ( uint section )
+ { return section - DOL_N_TEXT_SECTIONS < DOL_N_DATA_SECTIONS; }
+
+//-----------------------------------------------------------------------------
+// [[dol_header_t]]
+
+typedef struct dol_header_t
+{
+ /* 0x00 */ u32 sect_off [DOL_N_SECTIONS]; // file offset
+ /* 0x48 */ u32 sect_addr[DOL_N_SECTIONS]; // virtual address
+ /* 0x90 */ u32 sect_size[DOL_N_SECTIONS]; // section size
+ /* 0xd8 */ u32 bss_addr; // BSS address
+ /* 0xdc */ u32 bss_size; // BSS size
+ /* 0xe0 */ u32 entry_addr; // entry point
+ /* 0xe4 */ u8 padding[DOL_HEADER_SIZE-0xe4];
+}
+__attribute__ ((packed)) dol_header_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[dol_sect_info_t]]
+
+typedef struct dol_sect_info_t
+{
+ int section; // section index, <0: invalid
+ char name[4]; // name of section
+ u32 off; // file offset of data
+ u32 addr; // image address
+ u32 size; // size of section
+ bool sect_valid; // true: section is valid
+ bool data_valid; // true: data is valid (size>0 and inside DOL file)
+ bool hash_valid; // true: 'hash' is valid
+ u8 *data; // NULL (data_valid=0) or pointer to data (data_valid=1)
+ sha1_hash_t hash; // SHA1 hash of data
+}
+dol_sect_info_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[dol_sect_select_t]]
+
+typedef struct dol_sect_select_t
+{
+ int find_mode; // mode for xFindFirstFreeDolSection(), <0: skip this
+ int sect_idx; // >=0: section, <0: auto select
+ char name[4]; // a name based on 'sect_idx' and 'find_mode'
+ u32 addr; // address of section, if 0: use DATA[0x10]
+ ccp fname; // file name
+ bool use_param; // true: write params (entry and '.dol' section)
+
+ u8 *data; // not NULL: data, alloced
+ uint size; // >0: size of 'data'
+ u32 offset; // last used file offset (temporary)
+}
+dol_sect_select_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[dol_sect_addr_t]]
+
+typedef struct dol_sect_addr_t
+{
+ //--- source info
+ u32 addr; // address of data
+ u32 size; // size of data
+
+ //--- search info
+ int section; // section index, <0: invalid
+ char sect_name[4]; // empty or name of found section
+ u32 sect_addr; // NULL or section address
+ u32 sect_offset; // NULL or offset in found section
+ u32 sect_size; // NULL or size of found section
+}
+dol_sect_addr_t;
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// DOL: Interface ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ntoh_dol_header ( dol_header_t * dest, const dol_header_t * src );
+void hton_dol_header ( dol_header_t * dest, const dol_header_t * src );
+bool IsDolHeader ( const void *data, uint data_size, uint file_size );
+
+extern const char dol_section_name[DOL_NN_SECTIONS+1][4];
+static inline ccp GetDolSectionName ( uint section )
+{
+ return section < DOL_NN_SECTIONS ? dol_section_name[section] : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool GetDolSectionInfo
+(
+ dol_sect_info_t * ret_info, // valid pointer: returned data
+ const dol_header_t *dol_head, // valid DOL header
+ uint file_size, // (file) size of 'data'
+ uint section // 0 .. DOL_NN_SECTIONS
+ // (7*TEXT, 11*DATA, BSS, ENTRY )
+);
+
+//-----------------------------------------------------------------------------
+
+bool FindFirstFreeDolSection
+(
+ // returns TRUE on found
+
+ dol_sect_info_t *info, // result
+ const dol_header_t *dh, // DOL header to analyse
+ uint mode // 0: search all sections (TEXT first)
+ // 1: search only TEXT sections
+ // 2: search only DATA sections
+ // 3: search all sections (DATA first)
+);
+
+//-----------------------------------------------------------------------------
+
+bool FindDolSectionBySelector
+(
+ // returns TRUE on found
+
+ dol_sect_info_t *info, // result
+ const dol_header_t *dh, // DOL header to analyse
+ const dol_sect_select_t *dss // DOL section selector
+);
+
+//-----------------------------------------------------------------------------
+
+u32 FindFreeSpaceDOL
+(
+ // return address or NULL on not-found
+
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr_beg, // first possible address
+ u32 addr_end, // last possible address
+ u32 size, // minimal size
+ u32 align, // aligning
+ u32 *space // not NULL: store available space here
+);
+
+//-----------------------------------------------------------------------------
+
+u32 FindFreeBssSpaceDOL
+(
+ // return address or NULL on not-found
+
+ const dol_header_t *dol_head, // valid DOL header
+ u32 size, // min size
+ u32 align, // aligning
+ u32 *space // not NULL: store available space here
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpDolHeader
+(
+ FILE * f, // dump to this file
+ int indent, // indention
+ const dol_header_t *dol_head, // valid DOL header
+ uint file_size, // NULL or size of file
+ uint print_mode // bit field:
+ // 1: print file map
+ // 2: print virtual mem map
+ // 4: print delta table
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetDolSectionAddr
+(
+ // returns dsa->section
+ dol_sect_addr_t *dsa, // result: section address info
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr, // address to search
+ u32 size // >0: wanted size
+);
+
+//-----------------------------------------------------------------------------
+
+ccp GetDolSectionAddrInfo
+(
+ // returns GetCircBuf() or EmptyString on error
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr, // address to search
+ u32 size // >0: wanted size
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 GetDolOffsetByAddr
+(
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr, // address to search
+ u32 size, // >0: wanted size
+ u32 *valid_size // not NULL: return valid size
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 GetDolAddrByOffset
+(
+ const dol_header_t *dol_head, // valid DOL header
+ u32 off, // address to search
+ u32 size, // >0: wanted size
+ u32 *valid_size // not NULL: return valid size
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint AddDolAddrByOffset
+(
+ const dol_header_t *dol_head, // valid DOL header
+ MemMap_t *mm, // valid destination mem map, not cleared
+ bool use_tie, // TRUE: use InsertMemMapTie()
+ u32 off, // address to search
+ u32 size // size, may overlay multiple sections
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint TranslateDolOffsets
+(
+ const dol_header_t *dol_head, // valid DOL header
+ MemMap_t *mm, // valid destination mem map, not cleared
+ bool use_tie, // TRUE: use InsertMemMapTie()
+ const MemMap_t *mm_off // NULL or mem map with offsets
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint TranslateAllDolOffsets
+(
+ const dol_header_t *dol_head, // valid DOL header
+ MemMap_t *mm, // valid destination mem map, not cleared
+ bool use_tie // TRUE: use InsertMemMapTie()
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint TranslateDolSections
+(
+ const dol_header_t *dol_head, // valid DOL header
+ MemMap_t *mm, // valid destination mem map, not cleared
+ bool use_tie, // TRUE: use InsertMemMapTie()
+ uint dol_sections // bitfield of sections
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CountOverlappedDolSections
+(
+ // returns the number of overlapped section by [addr,size]
+
+ const dol_header_t *dol_head, // valid DOL header
+ u32 addr, // address to search
+ u32 size // size of addr to verify
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+u32 FindDolAddressOfVBI
+(
+ // search in available data and return NULL if not found
+
+ cvp data, // DOL data beginning with dol_header_t
+ uint data_size // size of 'data'
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanDolSectionName
+(
+ // returns the first uninterpreted character
+
+ ccp arg, // comma/space separated names
+ int *ret_section, // not NULL: return section index
+ // <0: error / >=0: section index
+ enumError *ret_err // not NULL: return error code
+);
+
+//-----------------------------------------------------------------------------
+
+uint ScanDolSectionList
+(
+ // return a bitfield as section list
+
+ ccp arg, // comma/space separated names
+ enumError *ret_err // not NULL: return status
+);
+
+//-----------------------------------------------------------------------------
+
+ccp DolSectionList2Text ( uint sect_list );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// add/remove DOL sections ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+uint CompressDOL
+(
+ // return: size of compressed DOL (inline replaced)
+
+ dol_header_t *dol, // valid dol header
+ FILE *log // not NULL: print memmap to this file
+);
+
+//-----------------------------------------------------------------------------
+
+uint RemoveDolSections
+(
+ // return: 0:if not modifed, >0:size of compressed DOL
+
+ dol_header_t *dol, // valid dol header
+ uint stay, // bit field of sections (0..17): SET => don't remove
+ uint *res_stay, // not NULL: store bit field of vlaid sections here
+ FILE *log // not NULL: print memmap to this file
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Gecko Code Handler ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define GCH_MAGIC_NUM 0x47044348
+
+#define GCT_MAGIC8_NUM 0x00d0c0de00d0c0deull
+#define GCT_TERM_NUM 0xf000000000000000ull
+#define GCT_SEP_NUM1 0xf0000001u
+#define GCT_REG_OFFSET 8
+
+///////////////////////////////////////////////////////////////////////////////
+// [[gch_header_t]]
+
+typedef struct gch_header_t
+{
+ // big endian!
+
+ u32 magic; // magic: 'G\x{04}CH' (Gecko Code Handler)
+ u32 addr; // address of new section
+ u32 size; // size of code section, file size must be >= 'size'
+ // a cheat section may follow
+ u32 vbi_entry; // >0: entry point for VBI interrupts
+
+ u8 data[]; // usually start of code handler
+}
+__attribute__ ((packed)) gch_header_t;
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsValidGCH
+(
+ const gch_header_t *gh, // valid header
+ uint file_size // size of file
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Wiimms Code Handler ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define WCH_MAGIC_NUM 0x57054348
+
+///////////////////////////////////////////////////////////////////////////////
+// [[wch_segment_t]]
+
+typedef struct wch_segment_t
+{
+ // big endian!
+
+ u32 type; // 'T': text section
+ // 'D': data section
+ // 'S': any section, try data first, then text
+ // 'P': patch data => patch main.dol
+ // 0 : end of segment list
+
+ u32 addr; // address, where the data is stored
+ u32 size; // size of data
+
+ u32 main_entry; // >0: `main entry point´
+ u32 main_entry_old; // >0: store old `main entry point´ at this address
+ u32 vbi_entry; // >0: `vbi entry point´
+ u32 vbi_entry_old; // >0: store old `vbi entry point´ at this address
+
+ u8 data[]; // data, aligned by ALIGN(size,4)
+ // wch_segment_t // next segment, aligned by ALIGN(size,4)
+}
+__attribute__ ((packed)) wch_segment_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[wch_header_t]]
+
+typedef struct wch_header_t
+{
+ // big endian!
+
+ u32 magic; // magic: 'W\x{05}CH' (Gecko Code Handler)
+ u32 version; // 0: raw, 1: bzip2
+ u32 size; // total size of (uncompressed) segment data
+
+ wch_segment_t segment[]; // first segment
+}
+__attribute__ ((packed)) wch_header_t;
+
+///////////////////////////////////////////////////////////////////////////////
+// [[wch_control_t]]
+
+#define WCH_MAX_SEG 50
+
+typedef struct wch_control_t
+{
+ u8 *temp_data; // not NULL: temporary data, alloced
+ uint temp_size; // not NULL: size of 'temp_data'
+
+ bool is_valid; // true: data is valid
+ wch_header_t wh; // decoded header, local endian
+
+ uint n_seg; // number of segments excluding terminator
+ wch_segment_t seg[WCH_MAX_SEG+1]; // decoded segment headers, local endian
+ // + special terminator segment (type==0)
+ u8 *seg_data[WCH_MAX_SEG+1]; // pointer to decoded data for each segment
+}
+wch_control_t;
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+typedef enumError (*DecompressWCH_func)
+(
+ // return TRUE on success
+
+ wch_control_t *wc, // valid data, includes type info
+ void *data, // source data to decompress
+ uint size // size of 'data'
+);
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp DecodeWCH
+(
+ // returns NULL on success, or a short error message
+
+ wch_control_t *wc, // store only, (initialized)
+ void *data, // source data, modified if type != 0
+ uint size, // size of 'data'
+ DecompressWCH_func decompress // NULL or decompression function
+);
+
+void ResetWCH ( wch_control_t * wc );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// E N D ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // DC_LIB_DOL_H 1
diff --git a/project/dclib/lib-mkw.c b/project/dclib/lib-mkw.c
new file mode 100644
index 0000000..8ada409
--- /dev/null
+++ b/project/dclib/lib-mkw.c
@@ -0,0 +1,1107 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * Support for Mario Kart Wii *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+#include <string.h>
+#include "lib-mkw.h"
+#include "dclib-numeric.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// assigned points: tables ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// working matrix, initialized with NINTENDO
+
+// [[24P--]]
+u8 MkwPointsTab[12*12] = // active table
+{
+ 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 15, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 15, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 15, 9, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 15, 9, 5, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 15,10, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0,
+ 15,10, 7, 5, 3, 1, 0, 0, 0, 0, 0, 0,
+ 15,11, 8, 6, 4, 2, 1, 0, 0, 0, 0, 0,
+ 15,11, 8, 6, 4, 3, 2, 1, 0, 0, 0, 0,
+ 15,12,10, 8, 6, 4, 3, 2, 1, 0, 0, 0,
+ 15,12,10, 8, 6, 5, 4, 3, 2, 1, 0, 0,
+ 15,12,10, 8, 7, 6, 5, 4, 3, 2, 1, 0
+};
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// NINTENDO => Orginal Nintendo
+
+// [[24P--]]
+const u8 MkwPointsTab_NINTENDO[12*12] =
+{
+ 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 15, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 15, 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 15, 9, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 15, 9, 5, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 15,10, 6, 3, 1, 0, 0, 0, 0, 0, 0, 0,
+ 15,10, 7, 5, 3, 1, 0, 0, 0, 0, 0, 0,
+ 15,11, 8, 6, 4, 2, 1, 0, 0, 0, 0, 0,
+ 15,11, 8, 6, 4, 3, 2, 1, 0, 0, 0, 0,
+ 15,12,10, 8, 6, 4, 3, 2, 1, 0, 0, 0,
+ 15,12,10, 8, 6, 5, 4, 3, 2, 1, 0, 0,
+ 15,12,10, 8, 7, 6, 5, 4, 3, 2, 1, 0
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// LINEAR => Absolute linear schema
+// - LAST := 0, INCREMENT := 1
+
+// [[24P--]]
+const u8 MkwPointsTab_LINEAR[12*12] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
+ 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0,
+ 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0,
+ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0,
+ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0,
+ 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// LINEAR_1 => Like LINEAR, but LAST := 1
+// - LAST := 1, INCREMENT := 1
+
+// [[24P--]]
+const u8 MkwPointsTab_LINEAR_1[12*12] =
+{
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
+ 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0,
+ 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0,
+ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0,
+ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0,
+ 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
+ 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// LINEAR_B => Like LINEAR, but with a bonus of 1 for a victory
+// - LAST := 0, INCREMENT := 1, BONUS := 1
+
+// [[24P--]]
+const u8 MkwPointsTab_LINEAR_B[12*12] =
+{
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 7, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
+ 8, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0,
+ 9, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0,
+ 10, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0,
+ 11, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0,
+ 12, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// LINEAR_B1 => Like LINEAR_B, but LAST := 1
+// - LAST := 1, INCREMENT := 1, BONUS := 1
+
+// [[24P--]]
+const u8 MkwPointsTab_LINEAR_B1[12*12] =
+{
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 7, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
+ 8, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0,
+ 9, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0,
+ 10, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0,
+ 11, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0,
+ 12, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
+ 13, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// Test
+
+#if MKW_POINTS_TEST_ENABLED
+ // [[24P--]]
+ const u8 MkwPointsTab_TEST[12*12] =
+ {
+ 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 18, 12, 6, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 20, 15, 10, 5, 0, 0, 0, 0, 0, 0, 0,
+ 25, 20, 16, 12, 8, 4, 0, 0, 0, 0, 0, 0,
+ 25, 21, 17, 13, 9, 6, 3, 0, 0, 0, 0, 0,
+ 25, 21, 18, 15, 12, 9, 6, 3, 0, 0, 0, 0,
+ 25, 22, 19, 16, 13, 10, 7, 4, 2, 0, 0, 0,
+ 25, 22, 19, 16, 13, 10, 8, 6, 4, 2, 0, 0,
+ 25, 22, 19, 16, 14, 12, 10, 8, 6, 4, 2, 0,
+ };
+#endif // MKW_POINTS_TEST_ENABLED
+
+///////////////////////////////////////////////////////////////////////////////
+// Test/one
+
+#if MKW_POINTS_TEST_ENABLED
+ // [[24P--]]
+ const u8 MkwPointsTab_TEST_1[12*12] =
+ {
+ 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 13, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 17, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 19, 13, 7, 1, 0, 0, 0, 0, 0, 0, 0,
+ 25, 20, 15, 10, 5, 1, 0, 0, 0, 0, 0, 0,
+ 25, 21, 17, 13, 9, 5, 1, 0, 0, 0, 0, 0,
+ 25, 21, 17, 13, 10, 7, 4, 1, 0, 0, 0, 0,
+ 25, 22, 19, 16, 13, 10, 7, 4, 1, 0, 0, 0,
+ 25, 22, 19, 16, 13, 10, 7, 5, 3, 1, 0, 0,
+ 25, 22, 19, 16, 13, 11, 9, 7, 5, 3, 1, 0,
+ 25, 22, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1,
+ };
+#endif // MKW_POINTS_TEST_ENABLED
+
+///////////////////////////////////////////////////////////////////////////////
+// Test/bonus
+
+#if MKW_POINTS_TEST_ENABLED
+ // [[24P--]]
+ const u8 MkwPointsTab_TEST_B[12*12] =
+ {
+ 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 18, 12, 6, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 19, 14, 9, 4, 0, 0, 0, 0, 0, 0, 0,
+ 25, 20, 16, 12, 8, 4, 0, 0, 0, 0, 0, 0,
+ 25, 20, 16, 12, 9, 6, 3, 0, 0, 0, 0, 0,
+ 25, 21, 18, 15, 12, 9, 6, 3, 0, 0, 0, 0,
+ 25, 21, 18, 15, 12, 9, 6, 4, 2, 0, 0, 0,
+ 25, 21, 18, 15, 12, 10, 8, 6, 4, 2, 0, 0,
+ 25, 21, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0,
+ };
+#endif // MKW_POINTS_TEST_ENABLED
+
+///////////////////////////////////////////////////////////////////////////////
+// Test/bonus/one
+
+#if MKW_POINTS_TEST_ENABLED
+ // [[24P--]]
+ const u8 MkwPointsTab_TEST_B1[12*12] =
+ {
+ 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 12, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 16, 8, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 18, 12, 6, 1, 0, 0, 0, 0, 0, 0, 0,
+ 25, 19, 14, 9, 5, 1, 0, 0, 0, 0, 0, 0,
+ 25, 20, 16, 12, 8, 4, 1, 0, 0, 0, 0, 0,
+ 25, 20, 16, 13, 10, 7, 4, 1, 0, 0, 0, 0,
+ 25, 21, 18, 15, 12, 9, 6, 3, 1, 0, 0, 0,
+ 25, 21, 18, 15, 12, 9, 7, 5, 3, 1, 0, 0,
+ 25, 21, 18, 15, 13, 11, 9, 7, 5, 3, 1, 0,
+ 25, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1,
+ };
+#endif // MKW_POINTS_TEST_ENABLED
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// Generic tables => More belanced than NINTENDO
+// * FIRST := X, LAST := 0|1
+// * linear in the middle with larger gaps at the beginning
+
+// [[24P--]]
+static u8 MkwPointsTab_WIN[12*12];
+static u8 MkwPointsTab_WIN_1[12*12];
+static u8 MkwPointsTab_WIN_B[12*12];
+static u8 MkwPointsTab_WIN_B1[12*12];
+
+// [[24P--]]
+static char MkwPointsInfo_WIN[20];
+static char MkwPointsInfo_WIN_1[20];
+static char MkwPointsInfo_WIN_B[20];
+static char MkwPointsInfo_WIN_B1[20];
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+// [[24P--]]
+const MkwPointInfo_t MkwPointInfo[MPI__N+1] =
+{
+ { MPI_UNKNOWN, 3, 0, "self defined",
+ { MPI_UNKNOWN, MPI_UNKNOWN, MPI_UNKNOWN, MPI_UNKNOWN }},
+
+ { MPI_NINTENDO, 0, MkwPointsTab_NINTENDO, "Nintendo",
+ { MPI_NINTENDO, MPI_NINTENDO, MPI_NINTENDO, MPI_NINTENDO }},
+
+ { MPI_LINEAR, 1, MkwPointsTab_LINEAR, "Linear",
+ { MPI_LINEAR, MPI_LINEAR_1, MPI_LINEAR_B, MPI_LINEAR_B1 }},
+ { MPI_LINEAR_1, 1, MkwPointsTab_LINEAR_1, "Linear, One",
+ { MPI_LINEAR, MPI_LINEAR_1, MPI_LINEAR_B, MPI_LINEAR_B1 }},
+ { MPI_LINEAR_B, 1, MkwPointsTab_LINEAR_B, "Linear, Bonus",
+ { MPI_LINEAR, MPI_LINEAR_1, MPI_LINEAR_B, MPI_LINEAR_B1 }},
+ { MPI_LINEAR_B1, 1, MkwPointsTab_LINEAR_B1, "Linear, Bonus, One",
+ { MPI_LINEAR, MPI_LINEAR_1, MPI_LINEAR_B, MPI_LINEAR_B1 }},
+
+ { MPI_WIN, 1, MkwPointsTab_WIN, MkwPointsInfo_WIN,
+ { MPI_WIN, MPI_WIN_1, MPI_WIN_B, MPI_WIN_B1 }},
+ { MPI_WIN_1, 1, MkwPointsTab_WIN_1, MkwPointsInfo_WIN_1,
+ { MPI_WIN, MPI_WIN_1, MPI_WIN_B, MPI_WIN_B1 }},
+ { MPI_WIN_B, 1, MkwPointsTab_WIN_B, MkwPointsInfo_WIN_B,
+ { MPI_WIN, MPI_WIN_1, MPI_WIN_B, MPI_WIN_B1 }},
+ { MPI_WIN_B1, 1, MkwPointsTab_WIN_B1, MkwPointsInfo_WIN_B1,
+ { MPI_WIN, MPI_WIN_1, MPI_WIN_B, MPI_WIN_B1 }},
+
+#if MKW_POINTS_TEST_ENABLED
+ { MPI_TEST, 2, MkwPointsTab_TEST, "Test",
+ { MPI_TEST, MPI_TEST_1, MPI_TEST_B, MPI_TEST_B1 }},
+ { MPI_TEST_1, 2, MkwPointsTab_TEST_1, "Test, One",
+ { MPI_TEST, MPI_TEST_1, MPI_TEST_B, MPI_TEST_B1 }},
+ { MPI_TEST_B, 2, MkwPointsTab_TEST_B, "Test, Bonus",
+ { MPI_TEST, MPI_TEST_1, MPI_TEST_B, MPI_TEST_B1 }},
+ { MPI_TEST_B1, 2, MkwPointsTab_TEST_B1, "Test, Bonus, One",
+ { MPI_TEST, MPI_TEST_1, MPI_TEST_B, MPI_TEST_B1 }},
+#endif
+
+ { MPI__N,0,0 }
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// assigned points: create generic ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+static inline void CalcSequence ( u8 *row, int np, int ni, int last_val )
+{
+ DASSERT(row);
+ DASSERT( np >=0 && np < 12 );
+ DASSERT( ni >= 0 );
+ DASSERT( last_val >= 0 && last_val < 0x100 );
+
+ int n_2do = np + 1 - ni;
+ if ( n_2do && ni )
+ {
+ int val = row[ni-1];
+ int diff = last_val - val;
+ int delta = diff / n_2do;
+ int bonus_count = diff - n_2do * delta, bonus_value = 1;
+ if ( bonus_count < 0 )
+ {
+ bonus_count = -bonus_count;
+ bonus_value = -1;
+ }
+ PRINT("CLOSE, 2do=%d, last=%u, delta=%d, bonus=%d+%d\n",
+ n_2do,last_val,delta,bonus_count,bonus_value);
+
+ while ( n_2do-- > 0 )
+ {
+ val += delta;
+ if ( bonus_count-- > 0 )
+ val += bonus_value;
+ row[ni++] = val;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+static void CreateGenericTable
+(
+ u8 *tab, // victory points table, size= 12*12
+ uint win_pts, // points of the winner
+ uint opt_bonus, // bonus for the first player
+ uint opt_last // points of the last player
+)
+{
+ DASSERT(tab);
+ memset(tab,0,12*12);
+
+ if ( opt_bonus > win_pts )
+ opt_bonus = win_pts;
+ win_pts -= opt_bonus;
+ if ( opt_last > win_pts )
+ opt_last = win_pts;
+
+ uint i;
+ for ( i = 0; i < 12; i++ )
+ {
+ tab[0] = win_pts;
+ if (i)
+ CalcSequence(tab,i,1,opt_last);
+ tab[0] += opt_bonus;
+ tab += 12;
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+static void CreateGenericWinTables ( uint win_pts )
+{
+ if ( win_pts > 255 )
+ win_pts = 255;
+
+ snprintf( MkwPointsInfo_WIN, sizeof(MkwPointsInfo_WIN),
+ "Win %u", win_pts );
+ snprintf( MkwPointsInfo_WIN_1, sizeof(MkwPointsInfo_WIN_1),
+ "Win %u+One", win_pts );
+ snprintf( MkwPointsInfo_WIN_B, sizeof(MkwPointsInfo_WIN_B),
+ "Win %u+Bonus", win_pts );
+ snprintf( MkwPointsInfo_WIN_B1, sizeof(MkwPointsInfo_WIN_B1),
+ "Win %u, Bonus, One", win_pts );
+
+ CreateGenericTable( MkwPointsTab_WIN, win_pts, 0, 0 );
+ CreateGenericTable( MkwPointsTab_WIN_1, win_pts, 0, 1 );
+ CreateGenericTable( MkwPointsTab_WIN_B, win_pts, 1, 0 );
+ CreateGenericTable( MkwPointsTab_WIN_B1, win_pts, 1, 1 );
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// assigned points: ScanOptMkwPoints() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static ccp SkipSep ( ccp arg )
+{
+ while ( *arg && ( *arg <= ' ' || *arg == ',' || *arg == '+' ))
+ arg++;
+ return arg;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool opt_points_used = false;
+
+// [[24P--]]
+int ScanOptMkwPoints ( ccp arg, bool silent )
+{
+ #if DEBUG
+ {
+ uint i;
+ for ( i = 0; i < MPI__N; i++ )
+ ASSERT_MSG( MkwPointInfo[i].id == i,
+ "MkwPointInfo[%d].id = %d\n",i,MkwPointInfo[i].id);
+ }
+ #endif
+
+ enum
+ {
+ MODE_MPI,
+ MODE_WIN,
+ MODE_ONE,
+ MODE_BONUS,
+ MODE_N,
+ };
+
+ static const KeywordTab_t key_tab[] =
+ {
+ { MODE_MPI, "NINTENDO", "N", MPI_NINTENDO },
+ { MODE_MPI, "LINEAR", "L", MPI_LINEAR },
+ #if MKW_POINTS_TEST_ENABLED
+ { MODE_MPI, "TEST", 0, MPI_TEST },
+ #endif
+
+ { MODE_WIN, "WIN", "W", 0 },
+ { MODE_WIN, "WIN15", "W15", 15 },
+ { MODE_WIN, "WIN25", "W25", 25 },
+ { MODE_WIN, "WIIMM", "WIIMM25", -25 },
+
+ { MODE_ONE, "ONE", "O", 1 },
+ { MODE_ONE, "NULL", 0, 0 },
+ { MODE_ONE, "NO-ONE", "NOONE", 0 },
+
+ { MODE_BONUS, "BONUS", "B", 1 },
+ { MODE_BONUS, "NO-BONUS", "NOBONUS", 0 },
+
+ { MODE_N, "N1", 0, /*row=*/ 0 },
+ { MODE_N, "N2", 0, 1 },
+ { MODE_N, "N3", 0, 2 },
+ { MODE_N, "N4", 0, 3 },
+ { MODE_N, "N5", 0, 4 },
+ { MODE_N, "N6", 0, 5 },
+ { MODE_N, "N7", 0, 6 },
+ { MODE_N, "N8", 0, 7 },
+ { MODE_N, "N9", 0, 8 },
+ { MODE_N, "N10", 0, 9 },
+ { MODE_N, "N11", 0, 10 },
+ { MODE_N, "N12", 0, 11 },
+
+ {0,0,0,0}
+ };
+
+ if ( !arg || !*arg )
+ {
+ memcpy(MkwPointsTab,MkwPointsTab_NINTENDO,sizeof(MkwPointsTab));
+ return 0;
+ }
+
+ u8 tab[12*12], row_defined[12];
+ memset(tab,0,sizeof(tab));
+ memset(row_defined,0,sizeof(row_defined));
+
+ int next_np = 10, np = 11, ni = 0;
+ u8 *row = tab + 12*np;
+
+ MkwPointID opt_mpi = MPI_UNKNOWN;
+ uint opt_bonus = 0; // >0: add 1 point for victory
+ uint opt_one = 0; // wanted points for last position
+
+ int last_val = -1; // >=0: points of last player
+ int max_val = -1;
+
+ uint close_sequence = 0;
+ for(;;)
+ {
+ arg = SkipSep(arg);
+ if ( !*arg || *arg == '/' )
+ close_sequence++;
+
+ if ( close_sequence )
+ {
+ if (ni)
+ {
+ if ( last_val < 0 )
+ last_val = opt_one;
+ CalcSequence(row,np,ni,last_val);
+ row_defined[np] = 1;
+ }
+
+ close_sequence = 0;
+ last_val = -1;
+ ni = 0;
+ np = next_np--;
+ row = tab + 12*np;
+ }
+
+ if (!*arg)
+ break;
+ if ( *arg == '/' )
+ {
+ arg++;
+ continue;
+ }
+
+ //--- scan number
+
+ u32 num;
+ ccp next = ScanU32(&num,arg,10);
+ if ( next > arg && !isalpha((int)*next) )
+ {
+ if ( num >= 0x100 )
+ {
+ num_err:
+ if (!silent)
+ ERROR0(ERR_SYNTAX,
+ "Option --point: Missing number less or equal 255: %s\n",arg);
+ return 1;
+ }
+
+ if ( max_val < (int)num )
+ max_val = num;
+ noPRINT("NUMBER: %u/%d |%s|\n",num,max_val,next);
+ if ( ni > np )
+ {
+ if (!silent)
+ ERROR0(ERR_SYNTAX,
+ "Option --point: Only %u numbers allowed for %u players: %s\n",
+ np+1, np+1, arg );
+ return 1;
+ }
+ row[ni++] = num;
+ arg = next;
+ while ( *arg > 0 && *arg <= ' ' )
+ arg++;
+
+ if ( arg[0] == '.' && arg[1] == '.' )
+ {
+ next = ScanU32(&num,arg+2,10);
+ if ( next == arg || num >= 0x100 || isalpha((int)*next) )
+ goto num_err;
+ if ( max_val < (int)num )
+ max_val = num;
+ noPRINT("..NUMBER: %3u |%s|\n",num,next);
+ arg = next;
+ last_val = num;
+ continue;
+ }
+ continue;
+ }
+
+
+ //--- scan keyword
+
+ char key_buf[KEYWORD_NAME_MAX];
+ char *dest = key_buf;
+ while ( ( isalnum((int)*arg) || *arg == '-' )
+ && dest < key_buf + sizeof(key_buf) -1 )
+ *dest++ = *arg++;
+ *dest = 0;
+ if ( dest == key_buf )
+ {
+ if (!silent)
+ ERROR0(ERR_SYNTAX,"Option --point: Syntax error: %s\n",arg);
+ return 1;
+ }
+
+ int abbrev_count;
+ const KeywordTab_t *key = ScanKeyword(&abbrev_count,key_buf,key_tab);
+ if (!key)
+ {
+ if (!silent)
+ PrintKeywordError(key_tab,key_buf,abbrev_count,
+ 0,"keyword for --point");
+ return 1;
+ }
+
+ switch(key->id)
+ {
+ case MODE_MPI:
+ opt_mpi = key->opt;
+ break;
+
+ case MODE_WIN:
+ if ( key->opt < 0 )
+ {
+ // WIIMM*
+ opt_one = 1;
+ opt_bonus = 1;
+ num = -key->opt;
+ }
+ else if (!key->opt)
+ {
+ arg = SkipSep(arg);
+ next = ScanU32(&num,arg,10);
+ if ( next == arg || num >= 0x100 || isalpha((int)*next) )
+ goto num_err;
+ arg = next;
+ }
+ else
+ num = key->opt;
+
+ CreateGenericWinTables(num);
+ opt_mpi = MPI_WIN;
+ break;
+
+ case MODE_ONE:
+ opt_one = key->opt;
+ break;
+
+ case MODE_BONUS:
+ opt_bonus = key->opt;
+ break;
+
+ case MODE_N:
+ next_np = key->opt;
+ close_sequence++;
+ break;
+
+ default:
+ printf("%3llu %3llu : %s, %s\n",key->id,key->opt,key->name1,key->name2);
+ }
+ }
+ opt_points_used = true;
+
+ const int alt_index = ( opt_one ? MPI_I_1 : 0 ) + ( opt_bonus ? MPI_I_B : 0 );
+ opt_mpi = MkwPointInfo[opt_mpi].alt[alt_index];
+ const u8 *src_tab = MkwPointInfo[opt_mpi].table;
+ if (!src_tab)
+ src_tab = MkwPointsTab_NINTENDO;
+
+ if ( max_val < 0 )
+ {
+ memcpy(MkwPointsTab,src_tab,sizeof(MkwPointsTab));
+ return 0;
+ }
+
+ uint max_src = 0;
+ for ( ni = 0; ni < 12*12; ni++ )
+ if ( max_src < src_tab[ni] )
+ max_src = src_tab[ni];
+
+ double factor = (double)max_val / (double)max_src;
+ for ( np = 0; np < 12; np++ )
+ {
+ if (row_defined[np])
+ continue;
+
+ const u8 *p1 = src_tab + 12*np;
+ u8 *p2 = tab + 12*np;
+ for ( ni = 0; ni < 12; ni++ )
+ *p2++ = double2int( *p1++ * factor );
+ }
+ memcpy(MkwPointsTab,tab,sizeof(MkwPointsTab));
+ return 0;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// assigned points: helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+const MkwPointInfo_t * GetMkwPointInfo ( const u8 * data )
+{
+ CreateGenericWinTables(data[11*12]);
+
+ const MkwPointInfo_t *ptr;
+ for ( ptr = MkwPointInfo; ptr->info; ptr++ )
+ if ( ptr->table && !memcmp(ptr->table,data,12*12) )
+ return ptr;
+
+ return MkwPointInfo + MPI_UNKNOWN;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// assigned points: printing ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+void PrintMkwPointsRAW ( FILE *f, uint indent, const u8 * data )
+{
+ DASSERT(f);
+ DASSERT(data);
+
+ uint i, np, max = 0;
+ for ( i = 0; i < 12*12; i++ )
+ if ( max < data[i] )
+ max = data[i];
+ const uint fw = max < 100 ? 2 : 3;
+
+ for ( np = 1; np <= 12; np++ )
+ {
+ fprintf(f,"%*u:",indent+2,np);
+ for ( i = 0; i < np; i++, data++ )
+ fprintf(f," %*u ",fw,*data);
+ for ( ; i < 12; i++, data++ )
+ if (*data )
+ fprintf(f," %*u!",fw,*data);
+ else
+ fprintf(f," %*c ",fw,'-');
+ fputc('\n',f);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+void PrintMkwPointsC ( FILE *f, uint indent, const u8 * data )
+{
+ DASSERT(f);
+ DASSERT(data);
+
+ uint i, np, max = 0;
+ for ( i = 0; i < 12*12; i++ )
+ if ( max < data[i] )
+ max = data[i];
+ const uint fw = max < 100 ? 2 : 3;
+
+ for ( np = 1; np <= 12; np++ )
+ {
+ if (indent > 0 )
+ fprintf(f,"%*s",indent-1,"");
+ for ( i = 0; i < 12; i++, data++ )
+ {
+ if ( i == np )
+ fprintf(f," %*c ",fw,' ');
+ fprintf(f," %*u,",fw,*data);
+ }
+ fputc('\n',f);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+void PrintMkwPointsCheat
+(
+ FILE *f, // output file
+ const u8 *data, // pointer to data table
+ char region_code, // region code: E|P|J|K
+ u32 cheat_base // value of first code
+)
+{
+ DASSERT(f);
+ DASSERT(data);
+
+ fprintf(f,
+ "RMC%c01\nMario Kart Wii\n\nVersus Points Modifier\n%08x 00000090\n",
+ region_code, cheat_base );
+
+ uint line;
+ for ( line = 0; line < 12*12/8; line++ )
+ {
+ fprintf(f,"%02x%02x%02x%02x %02x%02x%02x%02x\n",
+ data[0], data[1], data[2], data[3],
+ data[4], data[5], data[6], data[7] );
+ data += 8;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+uint PrintMkwPointsLIST
+(
+ // returns the used 'mode'
+
+ char *buf, // valid output buffer
+ uint buf_size, // size of 'buf', MKW_POINTS_DEF_SIZE recommended
+ const u8 *data, // 12*12 data table
+ uint mode // 0: pure number list
+ // 1: allow 'a..b'
+ // 2: allow names of permanent tables
+ // 3: allow names
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>2);
+ DASSERT(data);
+
+ if ( mode >= 2 )
+ {
+ const MkwPointInfo_t *mpi = GetMkwPointInfo(data);
+ if ( mpi->mode <= 1 || mpi->mode == 2 && mode >= 3 )
+ {
+ StringCopyS(buf,buf_size,mpi->info);
+ return mpi->mode <= 1 ? 2 : 3;
+ }
+ }
+
+
+ //--- fall back to pure numbers
+
+ uint ret_val = 0;
+ char *dest = buf;
+ char *end = buf + buf_size - 2;
+
+ int np;
+ for ( np = 11; np >= 0 && dest < end; np-- )
+ {
+ const u8 *row = data + 12*np;
+ if ( mode >= 1 && np > 1 )
+ {
+ // ranges allowed
+
+ int delta = (int)row[np-1] - (int)row[np];
+ if ( delta >= 0 )
+ {
+ int ni = np - 1;
+ while ( ni > 0 && (int)row[ni-1] - (int)row[ni] == delta )
+ ni--;
+ delta++;
+ while ( ni > 0 && (int)row[ni-1] - (int)row[ni] == delta )
+ ni--;
+ if ( np - ni > 2 )
+ {
+ int i;
+ for ( i = 0; i <= ni && dest < end; i++ )
+ dest += snprintf(dest,end-dest,"%u,",row[i]);
+ dest--;
+ if ( dest < end )
+ dest += snprintf(dest,end-dest,"..%u,",row[np]);
+ dest[-1] = '/';
+ ret_val = 1;
+ continue;
+ }
+ }
+ }
+
+ int i;
+ for ( i = 0; i <= np && dest < end; i++ )
+ dest += snprintf(dest,end-dest,"%u,",*row++);
+ dest[-1] = '/';
+ }
+ dest[-1] = 0;
+ return ret_val;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[24P--]]
+
+void PrintMkwPoints
+(
+ FILE *f, // destination file
+ uint indent, // indention of the output
+ const u8 *data, // data, 12*12 table
+ uint print_table, // print the table:
+ // 0: don't print
+ // 1: print table as RAW
+ // 2: print table as C listing
+ // A-Z: Print as cheat code with this region
+ u32 cheat_base, // not NULL: value of first param of cheat code
+ uint print_param // print a parameter string:
+ // 0: don't print
+ // 1: print single line
+ // 2: print all modes
+)
+{
+ switch (print_table)
+ {
+ case 1:
+ PrintMkwPointsRAW(f,indent,data);
+ fputc('\n',f);
+ break;
+
+ case 2:
+ PrintMkwPointsC(f,indent,data);
+ fputc('\n',f);
+ break;
+
+ default:
+ if ( print_table >= 'A' && print_table <= 'Z' && cheat_base )
+ {
+ PrintMkwPointsCheat(f,data,print_table,cheat_base);
+ fputc('\n',f);
+ }
+ break;
+ }
+
+ char buf[MKW_POINTS_DEF_SIZE];
+ if ( print_param == 1 )
+ {
+ PrintMkwPointsLIST(buf,sizeof(buf),data,2);
+ printf("%*s%s\n\n",indent,"",buf);
+ }
+ else if ( print_param > 1 )
+ {
+ int mode;
+ for ( mode = 3; mode >= 0; mode-- )
+ {
+ const uint res = PrintMkwPointsLIST(buf,sizeof(buf),data,mode);
+ if ( res == mode )
+ printf("%*s%s\n\n",indent,"",buf);
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// GetVrDiff*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// https://forum.wii-homebrew.com/index.php?page=Thread&postID=612772
+// http://wiki.tockdom.com/wiki/Player_Rating
+// https://www.desmos.com/calculator/gr17y7d944
+///////////////////////////////////////////////////////////////////////////////
+
+static const s16 vr_diff_tab[112] =
+{
+ 6598, 4999, 3963, 3233, 2671, 2212, 1819, 1476, // 0 .. 7
+ 1168, 889, 633, 395, 174, -34, -231, -418, // 8 .. 15
+ -596, -767, -931, -1090, -1243, -1392, -1536, -1676, // 16 .. 23
+ -1813, -1946, -2076, -2203, -2328, -2450, -2569, -2687, // 24 .. 31
+ -2802, -2915, -3026, -3136, -3243, -3349, -3454, -3557, // 32 .. 39
+ -3658, -3759, -3857, -3955, -4051, -4147, -4241, -4334, // 40 .. 47
+ -4425, -4516, -4606, -4695, -4783, -4870, -4957, -5042, // 48 .. 55
+ -5127, -5211, -5294, -5377, -5459, -5540, -5622, -5702, // 56 .. 63
+ -5783, -5863, -5942, -6022, -6101, -6180, -6259, -6338, // 64 .. 71
+ -6417, -6495, -6574, -6653, -6731, -6810, -6889, -6968, // 72 .. 79
+ -7048, -7127, -7207, -7287, -7367, -7448, -7530, -7611, // 80 .. 87
+ -7693, -7776, -7860, -7944, -8029, -8114, -8201, -8288, // 88 .. 95
+ -8377, -8466, -8557, -8649, -8743, -8838, -8935, -9034, // 96 .. 103
+ -9135, -9239, -9345, -9455, -9568, -9684, -9806, -9933, // 104 .. 111
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetVrDiffByTab ( int diff ) // diff = VR(winner) - VR(loser)
+{
+ if ( diff > 6598 )
+ return 0;
+ if ( diff < -9933 )
+ return 112;
+
+ const s16 search = diff;
+ int i = 0, j = sizeof(vr_diff_tab) / sizeof(*vr_diff_tab) - 1;
+
+ while ( i < j )
+ {
+ const int k = (i+j)/2;
+ if ( search > vr_diff_tab[k] )
+ j = k-1;
+ else if ( search <= vr_diff_tab[k+1] )
+ i = k+1;
+ else
+ return k+1;
+ }
+
+ return i+1;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// info tables ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetMkwTrackName3 ( int tid )
+{
+ static const char tab[][4] =
+ {
+ //-- tracks: 0..31 = 0x00..0x1f
+
+ "T21", "T12", "T13", "T34",
+ "T14", "T22", "T23", "T24",
+ "T11", "T31", "T42", "T33",
+ "T43", "T44", "T41", "T32",
+
+ "T51", "T74", "T64", "T83",
+ "T52", "T71", "T82", "T63",
+ "T81", "T53", "T54", "T61",
+ "T84", "T73", "T72", "T62",
+
+ //-- arenas: 32..41 = 0x20..0x29
+
+ "A12", "A11", "A14", "A13", "A15",
+ "A24", "A25", "A21", "A22", "A23",
+
+ //-- special: 42..67 = 0x2a..0x43
+
+ "", "", "", "", "", "",
+ "", "", "", "", "", "", "MIS", "WIN",
+ "LOS", "DRW", "END", "", "", "", "", "",
+ "", "", "inv", "non",
+ };
+
+ if ( (uint)tid < sizeof(tab)/sizeof(*tab) && tab[tid][0] )
+ return tab[tid];
+
+ if ( tid < 0 )
+ return "---";
+
+ if ( tid == 0xff )
+ return "rnd";
+
+ char buf[20];
+ const int len = snprintf(buf,sizeof(buf),"_%02x",tid) + 1;
+ char * dest = GetCircBuf(len);
+ memcpy(dest,buf,len);
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetMkwMusicName3 ( int mid )
+{
+ static const char tab[][4] =
+ {
+ "T11", "t11",
+ "T12", "t12",
+ "T13", "t13",
+ "T14", "t14",
+ "T21", "t21",
+ "T22", "t22",
+ "T23", "t23",
+ "T24", "t24",
+ "T32", "t32",
+ "T31", "t31",
+ "T41", "t41",
+ "T34", "t34",
+ "T42", "t42",
+ "T33", "t33",
+ "T43", "t43",
+ "T44", "t44",
+ "T62", "t62",
+ "T53", "t53",
+ "T81", "t81",
+ "T72", "t72",
+ "T61", "t61",
+ "T54", "t54",
+ "T73", "t73",
+ "T84", "t84",
+ "T51", "t51",
+ "T74", "t74",
+ "T64", "t64",
+ "T83", "t83",
+ "T52", "t52",
+ "T63", "t63",
+ "T71", "t71",
+ "T82", "t82",
+ "A12", "a12",
+ "A11", "a11",
+ "A13", "a13",
+ "A14", "a14",
+ "A15", "a15",
+ "A24", "a24",
+ "A25", "a25",
+ "A21", "a21",
+ "A22", "a22",
+ "A23", "a23",
+ };
+
+ if ( mid >= MKW_MUSIC_MIN_ID && mid < MKW_MUSIC_MIN_ID + sizeof(tab)/sizeof(*tab) )
+ return tab[mid-MKW_MUSIC_MIN_ID];
+
+ if ( mid < 0 )
+ return "---";
+
+ char buf[20];
+ const int len = snprintf(buf,sizeof(buf),"_%02x",mid) + 1;
+ char * dest = GetCircBuf(len);
+ memcpy(dest,buf,len);
+ return dest;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
diff --git a/project/dclib/lib-mkw.h b/project/dclib/lib-mkw.h
new file mode 100644
index 0000000..8a1c644
--- /dev/null
+++ b/project/dclib/lib-mkw.h
@@ -0,0 +1,308 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * Support for Mario Kart Wii *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#ifndef SZS_LIB_MKWII_H
+#define SZS_LIB_MKWII_H 1
+
+#define _GNU_SOURCE 1
+#include "dclib-basics.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CTW definitions / //////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define CTW_MAX_TRACK_ID 999999
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// base definitions ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enum mkw_consts_t // some constants
+{
+ //--- num of players
+
+ MKW_STD_MAX_PLAYER = 12,
+ MKW_EX_MAX_PLAYER = 24,
+
+
+ //--- tracks
+
+ MKW_N_TRACKS = 0x20,
+ MKW_TRACK_BEG = 0x00,
+ MKW_TRACK_END = MKW_TRACK_BEG + MKW_N_TRACKS,
+
+ MKW_TRACK_MID_1 = 0x2454,
+ MKW_TRACK_MID_2 = 0x2490,
+
+
+ //--- arenas
+
+ MKW_N_ARENAS = 0x0a,
+ MKW_ARENA_BEG = 0x20,
+ MKW_ARENA_END = MKW_ARENA_BEG + MKW_N_ARENAS,
+
+ MKW_ARENA_MID_1 = 0x24b8,
+ MKW_ARENA_MID_2 = 0x24cc,
+
+
+ //--- original track := tracks + arenas
+
+ MKW_N_ORIGINAL = MKW_N_TRACKS + MKW_N_ARENAS,
+ MKW_ORIGINAL_BEG = MKW_TRACK_BEG,
+ MKW_ORIGINAL_END = MKW_ARENA_END,
+
+
+ //--- music ids
+
+ MKW_MUSIC_MIN_ID = 0x75,
+ MKW_MUSIC_MAX_ID = 0xc8,
+ MKW_N_MUSIC = 42,
+};
+
+//-----------------------------------------------------------------------------
+
+static inline bool IsMkwTrack ( uint tid )
+ { return tid < MKW_TRACK_END; }
+
+static inline bool IsMkwArena ( uint tid )
+ { return tid >= MKW_ARENA_BEG && tid < MKW_ARENA_END; }
+
+static inline bool IsMkwOriginal ( uint tid )
+ { return tid < MKW_ARENA_END; }
+
+static inline bool IsMkwCustom ( uint tid )
+ { return tid >= MKW_ARENA_END; }
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// 24p support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// [[mkw_std_pbits1_t]] [[mkw_ex_pbits1_t]] [[mkw_pbits1_t]]
+// [[mkw_std_pbits2_t]] [[mkw_ex_pbits2_t]] [[mkw_pbits2_t]]
+
+#if !defined(SUPPORT_24P)
+ #define SUPPORT_24P 1
+#endif
+
+// unsigned types, that can hold 1 or 2 bits for each player
+typedef u16 mkw_std_pbits1_t; // 12 bits needed
+typedef u32 mkw_std_pbits2_t; // 24 bits needed
+typedef u32 mkw_ex_pbits1_t; // 24 bits needed
+typedef u64 mkw_ex_pbits2_t; // 48 bits needed
+
+#if SUPPORT_24P
+ typedef u32 mkw_pbits1_t;
+ typedef u64 mkw_pbits2_t;
+ #define MKW_MAX_PLAYER MKW_EX_MAX_PLAYER
+#else
+ typedef u16 mkw_pbits1_t;
+ typedef u32 mkw_pbits2_t;
+ #define MKW_MAX_PLAYER MKW_STD_MAX_PLAYER
+#endif
+
+#define MKW_STD_MAX_PLAYER2 ( MKW_STD_MAX_PLAYER * MKW_STD_MAX_PLAYER )
+#define MKW_EX_MAX_PLAYER2 ( MKW_EX_MAX_PLAYER * MKW_EX_MAX_PLAYER )
+#define MKW_MAX_PLAYER2 ( MKW_MAX_PLAYER * MKW_MAX_PLAYER )
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// assigned points for versus and rooms ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define MKW_POINTS_DEF_SIZE 320
+
+#if defined(DEBUG) || defined(TEST)
+ #define MKW_POINTS_TEST_ENABLED 1
+#else
+ #define MKW_POINTS_TEST_ENABLED 0
+#endif
+
+//-----------------------------------------------------------------------------
+
+typedef enum MkwPointID
+{
+ //--- tables
+ MPI_UNKNOWN, // no table assigend
+ MPI_NINTENDO, // original table
+
+ MPI_LINEAR, // 4 linear tables
+ MPI_LINEAR_1,
+ MPI_LINEAR_B,
+ MPI_LINEAR_B1,
+
+ MPI_WIN, // 4 generic tables tables with winner = X points
+ MPI_WIN_1,
+ MPI_WIN_B,
+ MPI_WIN_B1,
+
+ #if MKW_POINTS_TEST_ENABLED
+ MPI_TEST, // 4 test tables
+ MPI_TEST_1,
+ MPI_TEST_B,
+ MPI_TEST_B1,
+ #endif
+ MPI__N,
+
+ //--- index values
+ MPI_I_STD = 0,
+ MPI_I_1,
+ MPI_I_B,
+ MPI_I_B1,
+ MPI_I__N,
+}
+MkwPointID;
+
+typedef struct MkwPointInfo_t
+{
+ MkwPointID id; // ID of table
+ uint mode; // 0:permanent, 1:good, 2:temp, 3:UNKNOWN
+ const u8 *table; // related table
+ ccp info; // info about the table
+ u8 alt[MPI_I__N]; // index of related alternative tables
+}
+MkwPointInfo_t;
+
+//-----------------------------------------------------------------------------
+
+const MkwPointInfo_t MkwPointInfo[MPI__N+1];
+
+// [[24P--]] all tables
+
+extern u8 MkwPointsTab[MKW_STD_MAX_PLAYER2];
+extern const u8 MkwPointsTab_NINTENDO[MKW_STD_MAX_PLAYER2];
+extern const u8 MkwPointsTab_LINEAR[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_LINEAR_1[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_LINEAR_B[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_LINEAR_B1[MKW_STD_MAX_PLAYER2];
+extern const u8 MkwPointsTab_WIN25[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_WIN25_1[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_WIN25_B[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_WIN25_B1[MKW_STD_MAX_PLAYER2];
+extern const u8 MkwPointsTab_WIN15[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_WIN15_1[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_WIN15_B[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_WIN15_B1[MKW_STD_MAX_PLAYER2];
+
+#if MKW_POINTS_TEST_ENABLED
+ extern const u8 MkwPointsTab_TEST[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_TEST_1[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_TEST_B[MKW_STD_MAX_PLAYER2];
+ extern const u8 MkwPointsTab_TEST_B1[MKW_STD_MAX_PLAYER2];
+#endif
+
+extern bool opt_points_used;
+
+//-----------------------------------------------------------------------------
+
+static inline u8 GetMkwPoints ( uint n_player, uint idx_player )
+{
+// [[24P-]]
+ n_player--;
+ idx_player--;
+ return n_player < 12 && idx_player < 12
+ ? MkwPointsTab[ 12*n_player + idx_player ]
+ : 0;
+}
+
+static inline u8 GetDirectMkwPoints ( uint n_player, uint idx_player )
+{
+// [[24P-]]
+ return MkwPointsTab[ 12*n_player + idx_player - 13 ];
+}
+
+//-----------------------------------------------------------------------------
+
+int ScanOptMkwPoints ( ccp arg, bool silent );
+const MkwPointInfo_t * GetMkwPointInfo ( const u8 * data );
+
+void PrintMkwPointsRAW ( FILE *f, uint indent, const u8 * data );
+void PrintMkwPointsC ( FILE *f, uint indent, const u8 * data );
+
+// [[24P--]]
+uint PrintMkwPointsLIST
+(
+ // returns the used 'mode'
+
+ char *buf, // valid output buffer
+ uint buf_size, // size of 'buf', MKW_POINTS_DEF_SIZE recommended
+ const u8 *data, // 12*12 data table
+ uint mode // 0: pure number list
+ // 1: allow 'a..b'
+ // 2: allow names of permanent tables
+ // 3: allow names
+);
+
+// [[24P--]]
+void PrintMkwPoints
+(
+ FILE *f, // destination file
+ uint indent, // indention of the output
+ const u8 *data, // data, 12*12 table
+ uint print_table, // print the table:
+ // 0: don't print
+ // 1: print table as RAW
+ // 2: print table as C listing
+ // A-Z: Print as cheat code with this region
+ u32 cheat_base, // not NULL: value of first param of cheat code
+ uint print_param // print a parameter string:
+ // 0: don't print
+ // 1: print single line
+ // 2: print all modes
+);
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// GetVrDiff*() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int GetVrDiffByTab ( int diff ); // diff = VR(winner) - VR(loser)
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// info tables ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetMkwTrackName3 ( int tid );
+ccp GetMkwMusicName3 ( int mid );
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#endif // SZS_LIB_CTCODE_H
+