
Just copying the history from the source: recognize the standard handle defines in WriteFile; minor speed improvement by caching GetConsoleMode; keep track of three handles (ostensibly stdout, stderr and a file); test a DOS header exists before writing to e_oemid; more flexible/robust handling of data directories; files writing to the console will always succeed; log: use API file functions and a custom printf; add a blank line between processes; set function name for MyWriteConsoleA; scan imports from "kernel32" (without extension); added dynamic environment variable CLICOLOR; removed _hwrite (it's the same address as _lwrite); join multibyte characters split across separate writes; remove wcstok, avoiding potential interference with the host; similarly, use a private heap instead of malloc.
390 lines
8.7 KiB
C
390 lines
8.7 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 = wcsrchr( program, '\\' );
|
|
if (name != NULL)
|
|
++name;
|
|
else
|
|
name = program;
|
|
ext = 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;
|
|
}
|
|
}
|
|
|
|
|
|
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 ? wcslen( 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 0;
|
|
buf = tmp;
|
|
buf_len = (DWORD)HeapSize( hHeap, 0, buf );
|
|
}
|
|
|
|
if (len == 0)
|
|
{
|
|
if (str == 0)
|
|
pos += wsprintfA( buf + pos, "<null>" );
|
|
else if (quote)
|
|
{
|
|
buf[pos++] = '"';
|
|
buf[pos++] = '"';
|
|
}
|
|
else if (alt)
|
|
pos += wsprintfA( buf + pos, "<empty>" );
|
|
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 += wsprintfA( 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 += wsprintfA( 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)
|
|
mb = wsprintfA( buf + pos, ch < 0x100 ? "%cx%.2X" : "%cu%.4X",
|
|
(quote) ? '\\' : '^', 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)
|
|
{
|
|
file = INVALID_HANDLE_VALUE;
|
|
return;
|
|
}
|
|
buf = HeapAlloc( hHeap, 0, 2048 );
|
|
buf_len = (DWORD)HeapSize( hHeap, 0, buf );
|
|
prefix_len = wsprintfA( buf, "%S (%lu): ", prog, 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 );
|
|
CloseHandle( mutex );
|
|
return;
|
|
}
|
|
|
|
len = SetFilePointer( file, 0, NULL, FILE_END );
|
|
if (len == 0 || szFormat == NULL)
|
|
{
|
|
char buf[128];
|
|
SYSTEMTIME now;
|
|
|
|
size = 0;
|
|
|
|
if (len != 0)
|
|
{
|
|
memset( 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 = wsprintfA( 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 += wsprintfA( buf + len, "%u", (DWORD)num ); break;
|
|
case 'X': len += wsprintfA( buf + len, "%X", (DWORD)num ); break;
|
|
case 'p':
|
|
#ifdef _WIN64
|
|
len += wsprintfA( buf + len, "%.8X_%.8X",
|
|
(DWORD)(num >> 32), (DWORD)num );
|
|
break;
|
|
#endif
|
|
case 'q': len += wsprintfA( buf + len, "%.8X", (DWORD)num ); break;
|
|
case 'P': len += wsprintfA( 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 );
|
|
}
|