
If the console window has a full eight-digit handle my custom printf would get stuck in a loop, causing CMD to seemingly freeze. Do what I really should have done in the first place and make it more robust.
684 lines
14 KiB
C
684 lines
14 KiB
C
/*
|
|
util.c - Utility functions.
|
|
*/
|
|
|
|
#include "ansicon.h"
|
|
#include "version.h"
|
|
|
|
|
|
TCHAR prog_path[MAX_PATH];
|
|
LPTSTR prog;
|
|
|
|
int log_level;
|
|
|
|
TCHAR DllName[MAX_PATH]; // Dll file name
|
|
char ansi_dll[MAX_PATH];
|
|
DWORD ansi_len;
|
|
#ifdef _WIN64
|
|
char* ansi_bits;
|
|
#endif
|
|
|
|
|
|
// Get just the name of the program: "C:\path\program.exe" -> "program".
|
|
// Returns a pointer within program; it is modified to remove the extension.
|
|
LPTSTR get_program_name( LPTSTR program )
|
|
{
|
|
LPTSTR name, ext;
|
|
|
|
if (program == NULL)
|
|
{
|
|
GetModuleFileName( NULL, prog_path, lenof(prog_path) );
|
|
program = prog_path;
|
|
}
|
|
name = ac_wcsrchr( program, '\\' );
|
|
if (name != NULL)
|
|
++name;
|
|
else
|
|
name = program;
|
|
ext = ac_wcsrchr( name, '.' );
|
|
if (ext != NULL && ext != name)
|
|
*ext = '\0';
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
// Get the ANSI path of the DLL for the import. If it can't be converted,
|
|
// just use the name and hope it's on the PATH.
|
|
void set_ansi_dll( void )
|
|
{
|
|
BOOL bad;
|
|
|
|
ansi_len = WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, DllName, -1,
|
|
NULL, 0, NULL, &bad );
|
|
if (bad || ansi_len > MAX_PATH)
|
|
{
|
|
#ifdef _WIN64
|
|
ansi_bits = ansi_dll + 4;
|
|
if (*DllNameType == '6')
|
|
memcpy( ansi_dll, "ANSI64.dll\0", 12 );
|
|
else
|
|
#endif
|
|
memcpy( ansi_dll, "ANSI32.dll\0", 12 );
|
|
ansi_len = 12;
|
|
}
|
|
else
|
|
{
|
|
WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, DllName, -1,
|
|
ansi_dll, MAX_PATH, NULL, NULL );
|
|
#ifdef _WIN64
|
|
ansi_bits = ansi_dll + ansi_len - 7;
|
|
#endif
|
|
ansi_len = (ansi_len + 3) & ~3;
|
|
}
|
|
}
|
|
|
|
|
|
// GetVersion and GetVersionEx use Win32VersionValue from the header, which
|
|
// could be anything. Retrieve the OS version from NTDLL's header.
|
|
DWORD get_os_version( void )
|
|
{
|
|
PIMAGE_DOS_HEADER pDosHeader;
|
|
PIMAGE_NT_HEADERS pNTHeader;
|
|
|
|
pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle( L"ntdll.dll" );
|
|
pNTHeader = MakeVA( PIMAGE_NT_HEADERS, pDosHeader->e_lfanew );
|
|
return pNTHeader->OptionalHeader.MajorOperatingSystemVersion << 8 |
|
|
pNTHeader->OptionalHeader.MinorOperatingSystemVersion;
|
|
}
|
|
|
|
|
|
static LPSTR buf;
|
|
static DWORD buf_len;
|
|
static BOOL quote, alt;
|
|
|
|
static DWORD str_format( DWORD pos, BOOL wide, DWORD_PTR str, DWORD len )
|
|
{
|
|
static UINT cp;
|
|
static DWORD flags;
|
|
static BOOL def, *pDef, start_trail;
|
|
union
|
|
{
|
|
LPSTR a;
|
|
LPWSTR w;
|
|
} src;
|
|
int ch;
|
|
BOOL trail;
|
|
|
|
src.a = (LPSTR)str;
|
|
if (len == 0 && str != 0)
|
|
len = (DWORD)(wide ? lstrlen( src.w ) : strlen( src.a ));
|
|
|
|
if (pos + len * 6 + 8 >= buf_len)
|
|
{
|
|
LPVOID tmp = HeapReAlloc( hHeap, 0, buf, buf_len + len * 6 + 8 );
|
|
if (tmp == NULL)
|
|
return pos;
|
|
buf = tmp;
|
|
buf_len = (DWORD)HeapSize( hHeap, 0, buf );
|
|
}
|
|
|
|
if (len == 0)
|
|
{
|
|
if (str == 0)
|
|
{
|
|
memcpy( buf + pos, "<null>", 6 );
|
|
pos += 6;
|
|
}
|
|
else if (quote)
|
|
{
|
|
buf[pos++] = '"';
|
|
buf[pos++] = '"';
|
|
}
|
|
else if (alt)
|
|
{
|
|
memcpy( buf + pos, "<empty>", 7 );
|
|
pos += 7;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
if (cp != GetConsoleOutputCP())
|
|
{
|
|
cp = GetConsoleOutputCP();
|
|
if (wide)
|
|
{
|
|
wchar_t und = L'\xFFFF';
|
|
flags = WC_NO_BEST_FIT_CHARS;
|
|
pDef = &def;
|
|
// Some code pages don't support the default character.
|
|
if (!WideCharToMultiByte( cp, flags, &und, 1, buf + pos, 12, NULL, pDef ))
|
|
{
|
|
flags = 0;
|
|
pDef = NULL;
|
|
def = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (quote)
|
|
buf[pos++] = '"';
|
|
|
|
trail = FALSE;
|
|
while (len-- != 0)
|
|
{
|
|
if (wide)
|
|
ch = *src.w++;
|
|
else
|
|
ch = (BYTE)*src.a++;
|
|
|
|
if (ch < 32 || (quote && start_trail))
|
|
{
|
|
start_trail = FALSE;
|
|
if (quote)
|
|
{
|
|
buf[pos++] = '\\';
|
|
switch (ch)
|
|
{
|
|
case '\0': buf[pos++] = '0'; break;
|
|
case '\a': buf[pos++] = 'a'; break;
|
|
case '\b': buf[pos++] = 'b'; break;
|
|
case '\t': buf[pos++] = 't'; break;
|
|
case '\n': buf[pos++] = 'n'; break;
|
|
case '\v': buf[pos++] = 'v'; break;
|
|
case '\f': buf[pos++] = 'f'; break;
|
|
case '\r': buf[pos++] = 'r'; break;
|
|
case 27 : buf[pos++] = 'e'; break;
|
|
default:
|
|
pos += ac_sprintf( buf + pos, "x%2X", ch );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buf[pos++] = '^';
|
|
buf[pos++] = ch + '@';
|
|
}
|
|
}
|
|
else if (quote && ch == '"')
|
|
{
|
|
buf[pos++] = '\\';
|
|
buf[pos++] = ch;
|
|
}
|
|
else if (!wide)
|
|
{
|
|
if (quote && (cp == 932 || cp == 936 || cp == 949 || cp == 950))
|
|
{
|
|
if (trail)
|
|
trail = FALSE;
|
|
else if (IsDBCSLeadByteEx( cp, (char)ch ))
|
|
{
|
|
if (len == 0)
|
|
start_trail = TRUE;
|
|
else
|
|
trail = TRUE;
|
|
}
|
|
}
|
|
if (quote && start_trail)
|
|
pos += ac_sprintf( buf + pos, "\\x%2X", ch );
|
|
else
|
|
buf[pos++] = ch;
|
|
}
|
|
else
|
|
{
|
|
int mb = WideCharToMultiByte( cp, flags, src.w - 1, 1, buf + pos, 12,
|
|
NULL, pDef );
|
|
if (def)
|
|
{
|
|
buf[pos++] = (quote) ? '\\' : '^';
|
|
mb = ac_sprintf( buf + pos, ch < 0x100 ? "x%2X" : "u%4X", ch );
|
|
}
|
|
pos += mb;
|
|
}
|
|
}
|
|
|
|
if (quote)
|
|
buf[pos++] = '"';
|
|
|
|
return pos;
|
|
}
|
|
|
|
void DEBUGSTR( int level, LPCSTR szFormat, ... )
|
|
{
|
|
static int prefix_len;
|
|
static HANDLE mutex;
|
|
static DWORD size;
|
|
|
|
WCHAR temp[MAX_PATH];
|
|
HANDLE file;
|
|
va_list pArgList;
|
|
DWORD len, slen, written;
|
|
DWORD_PTR num;
|
|
|
|
if ((log_level & 3) < level && !(level & 4 & log_level))
|
|
return;
|
|
|
|
if (mutex == NULL)
|
|
{
|
|
mutex = CreateMutex( NULL, FALSE, L"ANSICON_debug_file" );
|
|
if (mutex == NULL)
|
|
{
|
|
log_level = 0;
|
|
return;
|
|
}
|
|
buf = HeapAlloc( hHeap, 0, 2048 );
|
|
buf_len = (DWORD)HeapSize( hHeap, 0, buf );
|
|
prefix_len = str_format( 0, TRUE, (DWORD_PTR)prog, 0 );
|
|
prefix_len += ac_sprintf(buf+prefix_len, " (%u): ", GetCurrentProcessId());
|
|
}
|
|
if (WaitForSingleObject( mutex, 500 ) == WAIT_TIMEOUT)
|
|
return;
|
|
|
|
ExpandEnvironmentStrings( L"%TEMP%\\ansicon.log", temp, lenof(temp) );
|
|
file = CreateFile( temp, GENERIC_WRITE, FILE_SHARE_READ, NULL,
|
|
(szFormat != NULL || (log_level & 8)) ? OPEN_ALWAYS
|
|
: CREATE_ALWAYS,
|
|
0, NULL );
|
|
if (file == INVALID_HANDLE_VALUE)
|
|
{
|
|
ReleaseMutex( mutex );
|
|
return;
|
|
}
|
|
|
|
len = SetFilePointer( file, 0, NULL, FILE_END );
|
|
if (len == 0 || szFormat == NULL)
|
|
{
|
|
char buf[128];
|
|
SYSTEMTIME now;
|
|
|
|
size = 0;
|
|
|
|
if (len != 0)
|
|
{
|
|
RtlFillMemory( buf + 2, 72, '=' );
|
|
buf[0] = buf[74] = buf[76] = '\r';
|
|
buf[1] = buf[75] = buf[77] = '\n';
|
|
WriteFile( file, buf, 78, &written, NULL );
|
|
}
|
|
|
|
GetLocalTime( &now );
|
|
len = ac_sprintf( buf, "ANSICON (" BITSA "-bit) v" PVERSA " log (%d)"
|
|
" started %d-%2d-%2d %d:%2d:%2d\r\n",
|
|
log_level,
|
|
now.wYear, now.wMonth, now.wDay,
|
|
now.wHour, now.wMinute, now.wSecond );
|
|
WriteFile( file, buf, len, &written, NULL );
|
|
if (szFormat == NULL)
|
|
{
|
|
CloseHandle( file );
|
|
ReleaseMutex( mutex );
|
|
return;
|
|
}
|
|
}
|
|
if (len != size)
|
|
WriteFile( file, "\r\n", 2, &written, NULL );
|
|
|
|
va_start( pArgList, szFormat );
|
|
|
|
// Customized printf, mainly to handle wide-character strings the way I want.
|
|
// It only supports:
|
|
// %u unsigned 32-bit decimal
|
|
// %X unsigned 32-bit upper case hexadecimal
|
|
// %p native pointer
|
|
// %q native pointer, display as 32 bits
|
|
// %P 32-bit pointer, display as 64 bits
|
|
// %s null-terminated byte characters
|
|
// %S null-terminated wide characters
|
|
//
|
|
// s & S may be prefixed with (in this order):
|
|
// " quote the string, using C-style escapes and <null> for NULL
|
|
// # use <null> for NULL and <empty> for ""
|
|
// < length of the string is the previous %u
|
|
// * length of the string is the parameter before the string
|
|
//
|
|
// Wide strings are converted according to the current code page; if a
|
|
// character could not be translated, hex is used.
|
|
//
|
|
// C-style escapes are the standard backslash sequences, plus '\e' for ESC,
|
|
// with '\x' used for two hex digits and '\u' for four. Otherwise, caret
|
|
// notation is used to represent controls, with '^x'/'^u' for hex.
|
|
|
|
num = 0;
|
|
len = prefix_len;
|
|
while (*szFormat != '\0')
|
|
{
|
|
if (*szFormat != '%')
|
|
buf[len++] = *szFormat++;
|
|
else
|
|
{
|
|
quote = alt = FALSE;
|
|
++szFormat;
|
|
if (*szFormat == '"')
|
|
{
|
|
quote = TRUE;
|
|
++szFormat;
|
|
}
|
|
if (*szFormat == '#')
|
|
{
|
|
alt = TRUE;
|
|
++szFormat;
|
|
}
|
|
slen = 0;
|
|
if (*szFormat == '<')
|
|
{
|
|
slen = (DWORD)num;
|
|
++szFormat;
|
|
}
|
|
if (*szFormat == '*')
|
|
{
|
|
slen = va_arg( pArgList, DWORD );
|
|
++szFormat;
|
|
}
|
|
num = va_arg( pArgList, DWORD_PTR );
|
|
switch (*szFormat++)
|
|
{
|
|
case 'u': len += ac_sprintf( buf + len, "%u", (DWORD)num ); break;
|
|
case 'X': len += ac_sprintf( buf + len, "%X", (DWORD)num ); break;
|
|
case 'p':
|
|
#ifdef _WIN64
|
|
len += ac_sprintf( buf + len, "%8X_%8X",
|
|
(DWORD)(num >> 32), (DWORD)num );
|
|
break;
|
|
#endif
|
|
case 'q': len += ac_sprintf( buf + len, "%8X", (DWORD)num ); break;
|
|
case 'P': len += ac_sprintf( buf + len, "00000000_%8X", (DWORD)num ); break;
|
|
case 's': len = str_format( len, FALSE, num, slen ); break;
|
|
case 'S': len = str_format( len, TRUE, num, slen ); break;
|
|
default:
|
|
buf[len++] = '%';
|
|
if (szFormat[-1] == '\0')
|
|
--szFormat;
|
|
else
|
|
buf[len++] = szFormat[-1];
|
|
}
|
|
if (len >= buf_len - 20)
|
|
{
|
|
LPVOID tmp = HeapReAlloc( hHeap, 0, buf, buf_len + 128 );
|
|
if (tmp == NULL)
|
|
break;
|
|
buf = tmp;
|
|
buf_len = (DWORD)HeapSize( hHeap, 0, buf );
|
|
}
|
|
}
|
|
}
|
|
buf[len++] = '\r';
|
|
buf[len++] = '\n';
|
|
|
|
WriteFile( file, buf, len, &written, NULL );
|
|
|
|
size = GetFileSize( file, NULL );
|
|
CloseHandle( file );
|
|
ReleaseMutex( mutex );
|
|
}
|
|
|
|
|
|
// Provide custom versions of used C runtime functions, to remove dependence on
|
|
// the runtime library.
|
|
|
|
// For my purposes (palette index and colors):
|
|
// * no leading space;
|
|
// * base is 10 or 16;
|
|
// * number doesn't overflow.
|
|
unsigned long ac_wcstoul( const wchar_t* str, wchar_t** end, int base )
|
|
{
|
|
unsigned c, n;
|
|
unsigned long num = 0;
|
|
|
|
for (;;)
|
|
{
|
|
n = -1;
|
|
c = *str;
|
|
if (c >= '0' && c <= '9')
|
|
n = c - '0';
|
|
else if (base == 16)
|
|
{
|
|
c |= 0x20;
|
|
if (c >= 'a' && c <= 'f')
|
|
n = c - 'a' + 10;
|
|
}
|
|
if (n == -1)
|
|
break;
|
|
|
|
num = num * base + n;
|
|
++str;
|
|
}
|
|
|
|
if (end != NULL)
|
|
*end = (wchar_t*)str;
|
|
return num;
|
|
}
|
|
|
|
|
|
// For my purposes (log level):
|
|
// * same as ac_wcstoul.
|
|
int ac_wtoi( const wchar_t* str )
|
|
{
|
|
return (int)ac_wcstoul( str, NULL, 10 );
|
|
}
|
|
|
|
|
|
// For my purposes (default attribute):
|
|
// * same as ac_wcstoul.
|
|
long ac_wcstol( const wchar_t* str, wchar_t** end, int base )
|
|
{
|
|
int neg = (*str == '-');
|
|
long num = ac_wcstoul( str + neg, end, base );
|
|
return neg ? -num : num;
|
|
}
|
|
|
|
|
|
// For my purposes (program separator):
|
|
// * set is only one or two characters.
|
|
wchar_t* ac_wcspbrk( const wchar_t* str, const wchar_t* set )
|
|
{
|
|
while (*str != '\0')
|
|
{
|
|
if (*str == set[0] || *str == set[1])
|
|
return (wchar_t*)str;
|
|
++str;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// For my purposes (path components):
|
|
// * c is not null.
|
|
wchar_t* ac_wcsrchr( const wchar_t* str, wchar_t c )
|
|
{
|
|
wchar_t* last = NULL;
|
|
|
|
while (*str != '\0')
|
|
{
|
|
if (*str == c)
|
|
last = (wchar_t*)str;
|
|
++str;
|
|
}
|
|
|
|
return last;
|
|
}
|
|
|
|
|
|
// For my purposes (import module matching):
|
|
// * A-Z becomes a-z;
|
|
// * s2 is lower case;
|
|
// * both strings are at least LEN long;
|
|
// * returns 0 for match, 1 for no match.
|
|
int ac_strnicmp( const char* s1, const char* s2, size_t len )
|
|
{
|
|
while (len--)
|
|
{
|
|
if (*s1 != *s2)
|
|
{
|
|
if (*s2 < 'a' || *s2 > 'z' || (*s1 | 0x20) != *s2)
|
|
return 1;
|
|
}
|
|
++s1;
|
|
++s2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const char hex[16] = { '0','1','2','3','4','5','6','7',
|
|
'8','9','A','B','C','D','E','F' };
|
|
|
|
// For my purposes:
|
|
// * BUF is big enough;
|
|
// * FMT is valid;
|
|
// * width implies zero fill and the number is not bigger than the width;
|
|
// * only types d, u & X are supported, all as 32-bit unsigned;
|
|
// * BUF is NOT NUL-terminated.
|
|
int ac_sprintf( char* buf, const char* fmt, ... )
|
|
{
|
|
va_list args;
|
|
DWORD num;
|
|
int t, width;
|
|
char* beg = buf;
|
|
|
|
va_start( args, fmt );
|
|
|
|
while (*fmt)
|
|
{
|
|
t = *fmt++;
|
|
if (t != '%')
|
|
*buf++ = t;
|
|
else
|
|
{
|
|
num = va_arg( args, DWORD );
|
|
t = *fmt++;
|
|
width = 0;
|
|
if (t == '2' || t == '4' || t == '8')
|
|
{
|
|
width = t - '0';
|
|
t = *fmt++;
|
|
}
|
|
if (t == 'X')
|
|
{
|
|
int bits;
|
|
if (width == 0)
|
|
{
|
|
if (num & 0xF0000000)
|
|
bits = 32;
|
|
else
|
|
{
|
|
bits = 4;
|
|
while (num >> bits)
|
|
bits += 4;
|
|
}
|
|
}
|
|
else
|
|
bits = width * 4;
|
|
do
|
|
{
|
|
bits -= 4;
|
|
*buf++ = hex[num >> bits & 0xF];
|
|
} while (bits);
|
|
}
|
|
else // (t == 'd' || t == 'u')
|
|
{
|
|
if (width == 2)
|
|
{
|
|
*buf++ = (int)(num / 10) + '0';
|
|
*buf++ = (int)(num % 10) + '0';
|
|
}
|
|
else
|
|
{
|
|
unsigned power;
|
|
if (num >= 1000000000)
|
|
power = 1000000000;
|
|
else
|
|
{
|
|
power = 1;
|
|
while (num / (power * 10))
|
|
power *= 10;
|
|
}
|
|
do
|
|
{
|
|
*buf++ = num / power % 10 + '0';
|
|
power /= 10;
|
|
} while (power);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (int)(buf - beg);
|
|
}
|
|
|
|
|
|
// For my purposes:
|
|
// * BUF is big enough;
|
|
// * FMT is valid;
|
|
// * width is only for X, is only 2 and the number is not bigger than that;
|
|
// * X, d & u are 32-bit unsigned decimal;
|
|
// * c is not output if NUL;
|
|
// * no other type is used;
|
|
// * return value is not used.
|
|
int ac_wprintf( wchar_t* buf, const char* fmt, ... )
|
|
{
|
|
va_list args;
|
|
DWORD num;
|
|
int t;
|
|
|
|
va_start( args, fmt );
|
|
|
|
while (*fmt)
|
|
{
|
|
t = *fmt++;
|
|
if (t != '%')
|
|
*buf++ = t;
|
|
else
|
|
{
|
|
num = va_arg( args, DWORD );
|
|
t = *fmt++;
|
|
if (t == '2')
|
|
{
|
|
++fmt;
|
|
*buf++ = hex[num >> 4];
|
|
*buf++ = hex[num & 0xF];
|
|
}
|
|
else if (t == 'X')
|
|
{
|
|
int bits;
|
|
if (num & 0xF0000000)
|
|
bits = 32;
|
|
else
|
|
{
|
|
bits = 4;
|
|
while (num >> bits)
|
|
bits += 4;
|
|
}
|
|
do
|
|
{
|
|
bits -= 4;
|
|
*buf++ = hex[num >> bits & 0xF];
|
|
} while (bits);
|
|
}
|
|
else if (t == 'c')
|
|
{
|
|
if (num)
|
|
*buf++ = (wchar_t)num;
|
|
}
|
|
else // (t == 'd' || t == 'u')
|
|
{
|
|
unsigned power;
|
|
if (num >= 1000000000)
|
|
power = 1000000000;
|
|
else
|
|
{
|
|
power = 1;
|
|
while (num / (power * 10))
|
|
power *= 10;
|
|
}
|
|
do
|
|
{
|
|
*buf++ = num / power % 10 + '0';
|
|
power /= 10;
|
|
} while (power);
|
|
}
|
|
}
|
|
}
|
|
*buf = '\0';
|
|
|
|
return 0;
|
|
}
|