From 1192c4575ea7afdf38e3fa0df4587a08620af11e Mon Sep 17 00:00:00 2001 From: root Date: Tue, 21 Jul 2020 10:00:46 +0200 Subject: SZS v2.18a --- project/dclib/Makefile.inc | 67 + project/dclib/dclib-basics.c | 8065 +++++++++++++++++++++++++++++++++++ project/dclib/dclib-basics.h | 5252 +++++++++++++++++++++++ project/dclib/dclib-color.c | 4351 +++++++++++++++++++ project/dclib/dclib-color.h | 828 ++++ project/dclib/dclib-debug.c | 1891 ++++++++ project/dclib/dclib-debug.h | 714 ++++ project/dclib/dclib-file.c | 5360 +++++++++++++++++++++++ project/dclib/dclib-file.h | 1278 ++++++ project/dclib/dclib-gen-ui.h | 162 + project/dclib/dclib-gen-ui.inc | 1303 ++++++ project/dclib/dclib-mysql.c | 1560 +++++++ project/dclib/dclib-mysql.h | 544 +++ project/dclib/dclib-network-linux.c | 461 ++ project/dclib/dclib-network-linux.h | 171 + project/dclib/dclib-network.c | 4546 ++++++++++++++++++++ project/dclib/dclib-network.h | 1486 +++++++ project/dclib/dclib-numeric.c | 6102 ++++++++++++++++++++++++++ project/dclib/dclib-numeric.h | 839 ++++ project/dclib/dclib-option.c | 1012 +++++ project/dclib/dclib-option.h | 383 ++ project/dclib/dclib-regex.c | 465 ++ project/dclib/dclib-regex.h | 131 + project/dclib/dclib-shift-jis.c | 1408 ++++++ project/dclib/dclib-system.h | 101 + project/dclib/dclib-tables.c | 417 ++ project/dclib/dclib-types.h | 403 ++ project/dclib/dclib-ui.c | 950 +++++ project/dclib/dclib-ui.h | 215 + project/dclib/dclib-utf8.c | 1910 +++++++++ project/dclib/dclib-utf8.h | 130 + project/dclib/dclib-vector.c | 2582 +++++++++++ project/dclib/dclib-xdump.c | 2214 ++++++++++ project/dclib/dclib-xdump.h | 348 ++ project/dclib/lib-bmg.c | 6346 +++++++++++++++++++++++++++ project/dclib/lib-bmg.h | 1125 +++++ project/dclib/lib-dol.c | 1208 ++++++ project/dclib/lib-dol.h | 517 +++ project/dclib/lib-mkw.c | 1107 +++++ project/dclib/lib-mkw.h | 308 ++ 40 files changed, 68260 insertions(+) create mode 100644 project/dclib/Makefile.inc create mode 100644 project/dclib/dclib-basics.c create mode 100644 project/dclib/dclib-basics.h create mode 100644 project/dclib/dclib-color.c create mode 100644 project/dclib/dclib-color.h create mode 100644 project/dclib/dclib-debug.c create mode 100644 project/dclib/dclib-debug.h create mode 100644 project/dclib/dclib-file.c create mode 100644 project/dclib/dclib-file.h create mode 100644 project/dclib/dclib-gen-ui.h create mode 100644 project/dclib/dclib-gen-ui.inc create mode 100644 project/dclib/dclib-mysql.c create mode 100644 project/dclib/dclib-mysql.h create mode 100644 project/dclib/dclib-network-linux.c create mode 100644 project/dclib/dclib-network-linux.h create mode 100644 project/dclib/dclib-network.c create mode 100644 project/dclib/dclib-network.h create mode 100644 project/dclib/dclib-numeric.c create mode 100644 project/dclib/dclib-numeric.h create mode 100644 project/dclib/dclib-option.c create mode 100644 project/dclib/dclib-option.h create mode 100644 project/dclib/dclib-regex.c create mode 100644 project/dclib/dclib-regex.h create mode 100644 project/dclib/dclib-shift-jis.c create mode 100644 project/dclib/dclib-system.h create mode 100644 project/dclib/dclib-tables.c create mode 100644 project/dclib/dclib-types.h create mode 100644 project/dclib/dclib-ui.c create mode 100644 project/dclib/dclib-ui.h create mode 100644 project/dclib/dclib-utf8.c create mode 100644 project/dclib/dclib-utf8.h create mode 100644 project/dclib/dclib-vector.c create mode 100644 project/dclib/dclib-xdump.c create mode 100644 project/dclib/dclib-xdump.h create mode 100755 project/dclib/lib-bmg.c create mode 100755 project/dclib/lib-bmg.h create mode 100644 project/dclib/lib-dol.c create mode 100644 project/dclib/lib-dol.h create mode 100644 project/dclib/lib-mkw.c create mode 100644 project/dclib/lib-mkw.h (limited to 'project/dclib') 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 * + * * + *************************************************************************** + * * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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= 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 ) + return (char*)buf_end - 1; + + DASSERT(buf); + DASSERT(buf= buf_end ) + return (char*)buf_end - 1; + + DASSERT(buf); + DASSERT(buf 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("",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: + // ": Unknown keyword: " + 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 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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#ifndef WIN_DCLIB + #include +#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> 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: + // ": Unknown keyword: " + 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: + // ": Unknown keyword: " + 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<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<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 =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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include +#include +//#include +//#include + +#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 * + * * + *************************************************************************** + * * + * 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 + +#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;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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include +#include +#include + +#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 * + * * + *************************************************************************** + * * + * 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 +#include + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// 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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + ccp file_name, // NULL or + ccp prog_ext // , if NULL use '.' + +) +{ + 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 * + * * + *************************************************************************** + * * + * 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 +#include +#include + +#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, // ~/./ + FCF_REL_SHARE = 0x002, // /../share// + FCF_LOC_SHARE = 0x004, // /usr/local/share// + FCF_USR_SHARE = 0x008, // /usr/share// + FCF_PROG_SHARE = 0x010, // /share/ + FCF_PROG = 0x020, // / + + 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 + ccp file_name, // NULL or + ccp prog_ext // , if NULL use '.' + +); + +//----------------------------------------------------------------------------- + +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 * + * * + *************************************************************************** + * * + * 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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include + +#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 \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 * + * * + *************************************************************************** + * * + * 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 +#include +#include + +#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 * + * * + *************************************************************************** + * * + * 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 + +#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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include +#include + +#ifdef SYSTEM_LINUX + #include +#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 * + * * + *************************************************************************** + * * + * 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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 * + * * + *************************************************************************** + * * + * 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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include +#include +#include + +#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<> 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 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 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< 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< 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< ~(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 * + * * + *************************************************************************** + * * + * 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 + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// 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 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 * + * * + *************************************************************************** + * * + * 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 + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// 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 * + * * + *************************************************************************** + * * + * 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 * + * * + *************************************************************************** + * * + * 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 * + * * + *************************************************************************** + * * + * 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 + #define DC_REGEX_TYPE pcre +#else + #ifndef DCLIB_USE_REGEX + #define DCLIB_USE_REGEX 1 + #endif + #if DCLIB_USE_REGEX + #include + #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 * + * * + *************************************************************************** + * * + * 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 +//#include + +#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 * + * * + *************************************************************************** + * * + * 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 + +/////////////////////////////////////////////////////////////////////////////// + +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 * + * * + *************************************************************************** + * * + * 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 +//#include +//#include +//#include +//#include +//#include + +#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 * + * * + *************************************************************************** + * * + * 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 +#include +#include +#include +#include + +#ifndef WIN_DCLIB + #include +#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 * + * * + *************************************************************************** + * * + * 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 +#include +#include + +#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 * + * * + *************************************************************************** + * * + * 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 + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// 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 * + * * + *************************************************************************** + * * + * 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 +#include +#include + +#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 * + * * + *************************************************************************** + * * + * 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 * + * * + *************************************************************************** + * * + * 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 + +#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 * + * * + *************************************************************************** + * * + * 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 +#include +#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 * + * * + *************************************************************************** + * * + * 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 * + * * + *************************************************************************** + * * + * 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 + +#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 * + * * + *************************************************************************** + * * + * 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 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 * + * * + *************************************************************************** + * * + * 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,§ion,&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 * + * * + *************************************************************************** + * * + * 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 * + * * + *************************************************************************** + * * + * 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 +#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 * + * * + *************************************************************************** + * * + * 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 + -- cgit v1.3.1