Many changes, bad programmer!

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.
This commit is contained in:
Jason Hood 2017-07-25 18:18:34 +10:00
parent 2f18f10719
commit 40f59c543c
10 changed files with 943 additions and 437 deletions

553
ANSI.c

File diff suppressed because it is too large Load Diff

View File

@ -87,7 +87,7 @@
add error codes to some message.
*/
#define PDATE L"26 February, 2014"
#define PDATE L"24 December, 2015"
#include "ansicon.h"
#include "version.h"
@ -169,6 +169,7 @@ int my_fputws( const wchar_t* s, FILE* f )
#define _putws( s ) my_fputws( s L"\n", stdout )
HANDLE hHeap;
#if defined(_WIN64)
LPTSTR DllNameType;
#endif
@ -183,7 +184,7 @@ BOOL Inject( LPPROCESS_INFORMATION ppi, BOOL* gui, LPCTSTR app )
#ifdef _WIN64
if (app != NULL)
#endif
DEBUGSTR( 1, L"%s (%lu)", app, ppi->dwProcessId );
DEBUGSTR( 1, "%S (%u)", app, ppi->dwProcessId );
type = ProcessType( ppi, &base, gui );
if (type <= 0)
{
@ -230,7 +231,7 @@ void RemoteLoad( LPPROCESS_INFORMATION ppi, LPCTSTR app )
int type;
#endif
DEBUGSTR( 1, L"%s (%lu)", app, ppi->dwProcessId );
DEBUGSTR( 1, "%S (%u)", app, ppi->dwProcessId );
// Find the base address of kernel32.dll.
ticks = GetTickCount();
@ -242,7 +243,7 @@ void RemoteLoad( LPPROCESS_INFORMATION ppi, LPCTSTR app )
#ifndef _WIN64
if (err == ERROR_PARTIAL_COPY)
{
DEBUGSTR( 1, L" Ignoring 64-bit process (use x64\\ansicon)" );
DEBUGSTR( 1, " Ignoring 64-bit process (use x64\\ansicon)" );
fputws( L"ANSICON: parent is 64-bit (use x64\\ansicon).\n", stderr );
return;
}
@ -251,10 +252,10 @@ void RemoteLoad( LPPROCESS_INFORMATION ppi, LPCTSTR app )
// two seconds to avoid a potentially infinite loop.
if (err == ERROR_BAD_LENGTH && GetTickCount() - ticks < 2000)
{
Sleep( 0 );
Sleep( 1 );
continue;
}
DEBUGSTR( 1, L" Unable to create snapshot (%lu)", err );
DEBUGSTR( 1, " Unable to create snapshot (%u)", err );
no_go:
fputws( L"ANSICON: unable to inject into parent.\n", stderr );
return;
@ -272,7 +273,7 @@ void RemoteLoad( LPPROCESS_INFORMATION ppi, LPCTSTR app )
CloseHandle( hSnap );
if (LLW == NULL)
{
DEBUGSTR( 1, L" Unable to locate kernel32.dll (%lu)", GetLastError() );
DEBUGSTR( 1, " Unable to locate kernel32.dll" );
goto no_go;
}
@ -292,7 +293,7 @@ void RemoteLoad( LPPROCESS_INFORMATION ppi, LPCTSTR app )
mem = VirtualAllocEx( ppi->hProcess, NULL, len, MEM_COMMIT, PAGE_READWRITE );
if (mem == NULL)
{
DEBUGSTR( 1, L" Unable to allocate virtual memory (%lu)", GetLastError() );
DEBUGSTR(1, " Failed to allocate virtual memory (%u)", GetLastError());
goto no_go;
}
WriteProcMem( mem, DllName, TSIZE(len + 11) );
@ -357,6 +358,8 @@ int main( void )
}
}
hHeap = HeapCreate( 0, 0, 65 * 1024 );
prog = get_program_name( NULL );
*buf = '\0';
GetEnvironmentVariable( L"ANSICON_LOG", buf, lenof(buf) );
@ -369,7 +372,7 @@ int main( void )
pi.hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId );
if (pi.hProcess == NULL)
{
DEBUGSTR( 1, L" Unable to open process %lu (%lu)",
DEBUGSTR( 1, " Unable to open process %u (%u)",
pi.dwProcessId, GetLastError() );
}
else
@ -738,7 +741,7 @@ BOOL GetParentProcessInfo( LPPROCESS_INFORMATION ppi, LPTSTR name )
hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
if (hSnap == INVALID_HANDLE_VALUE)
{
DEBUGSTR( 1, L"Failed to create snapshot (%lu)", GetLastError() );
DEBUGSTR( 1, "Failed to create snapshot (%u)", GetLastError() );
return FALSE;
}
@ -747,7 +750,7 @@ BOOL GetParentProcessInfo( LPPROCESS_INFORMATION ppi, LPTSTR name )
CloseHandle( hSnap );
if (!fOk)
{
DEBUGSTR( 1, L"Failed to locate parent" );
DEBUGSTR( 1, "Failed to locate parent" );
return FALSE;
}

View File

@ -35,6 +35,7 @@
// Macro for adding pointers/DWORDs together without C arithmetic interfering
#define MakeVA( cast, offset ) (cast)((DWORD_PTR)pDosHeader + (DWORD)(offset))
#define DATADIRS OptionalHeader.NumberOfRvaAndSizes
#define EXPORTDIR OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]
#define IMPORTDIR OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
#define BOUNDDIR OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]
@ -61,6 +62,7 @@ DWORD GetProcRVA( LPCTSTR, LPCSTR, int );
DWORD GetProcRVA( LPCTSTR, LPCSTR );
#endif
extern HANDLE hHeap;
extern TCHAR prog_path[MAX_PATH];
extern LPTSTR prog;
@ -74,6 +76,6 @@ extern char* ansi_bits;
void set_ansi_dll( void );
extern int log_level;
void DEBUGSTR( int level, LPTSTR szFormat, ... );
void DEBUGSTR( int level, LPCSTR szFormat, ... );
#endif

106
injdll.c
View File

@ -38,11 +38,35 @@ static PVOID FindMem( HANDLE hProcess, PBYTE base, DWORD len )
}
// Count the imports directly (including the terminator), since the size of the
// import directory is not necessarily correct (Windows doesn't use it at all).
static DWORD sizeof_imports( LPPROCESS_INFORMATION ppi,
PBYTE pBase, DWORD rImports )
{
IMAGE_IMPORT_DESCRIPTOR import;
PIMAGE_IMPORT_DESCRIPTOR pImports;
DWORD cnt;
if (rImports == 0)
return 0;
pImports = (PIMAGE_IMPORT_DESCRIPTOR)(pBase + rImports);
cnt = 0;
do
{
++cnt;
ReadProcVar( pImports++, &import );
} while (import.Name != 0);
return cnt * sizeof(import);
}
void InjectDLL( LPPROCESS_INFORMATION ppi, PBYTE pBase )
{
DWORD rva;
PVOID pMem;
DWORD len;
DWORD len, import_size;
DWORD pr;
IMAGE_DOS_HEADER DosHeader;
IMAGE_NT_HEADERS NTHeader, *pNTHeader;
@ -59,18 +83,19 @@ void InjectDLL( LPPROCESS_INFORMATION ppi, PBYTE pBase )
pNTHeader = (PIMAGE_NT_HEADERS)(pBase + DosHeader.e_lfanew);
ReadProcVar( pNTHeader, &NTHeader );
len = 4 * PTRSZ + ansi_len + sizeof(*pImports) + NTHeader.IMPORTDIR.Size;
pImports = malloc( len );
import_size = sizeof_imports( ppi, pBase, NTHeader.IMPORTDIR.VirtualAddress );
len = 2 * PTRSZ + ansi_len + sizeof(*pImports) + import_size;
pImports = HeapAlloc( hHeap, 0, len );
if (pImports == NULL)
{
DEBUGSTR( 1, L" Failed to allocate memory." );
DEBUGSTR( 1, " Failed to allocate memory" );
return;
}
pMem = FindMem( ppi->hProcess, pBase, len );
if (pMem == NULL)
{
DEBUGSTR( 1, L" Failed to allocate virtual memory." );
free( pImports );
DEBUGSTR( 1, " Failed to allocate virtual memory (%u)", GetLastError() );
HeapFree( hHeap, 0, pImports );
return;
}
rva = (DWORD)((PBYTE)pMem - pBase);
@ -78,37 +103,39 @@ void InjectDLL( LPPROCESS_INFORMATION ppi, PBYTE pBase )
ip.pL = (PLONG_PTR)pImports;
*ip.pL++ = IMAGE_ORDINAL_FLAG + 1;
*ip.pL++ = 0;
*ip.pL++ = IMAGE_ORDINAL_FLAG + 1;
*ip.pL++ = 0;
memcpy( ip.pB, ansi_dll, ansi_len );
ip.pB += ansi_len;
ip.pI->OriginalFirstThunk = rva + 2 * PTRSZ;
ip.pI->OriginalFirstThunk = 0;
ip.pI->TimeDateStamp = 0;
ip.pI->ForwarderChain = 0;
ip.pI->Name = rva + 4 * PTRSZ;
ip.pI->Name = rva + 2 * PTRSZ;
ip.pI->FirstThunk = rva;
ReadProcMem( pBase + NTHeader.IMPORTDIR.VirtualAddress,
ip.pI + 1, NTHeader.IMPORTDIR.Size );
ReadProcMem( pBase+NTHeader.IMPORTDIR.VirtualAddress, ip.pI+1, import_size );
WriteProcMem( pMem, pImports, len );
free( pImports );
HeapFree( hHeap, 0, pImports );
// If there's no IAT, copy the original IDT (to allow writable ".idata").
if (NTHeader.IATDIR.VirtualAddress == 0)
if (NTHeader.DATADIRS > IMAGE_DIRECTORY_ENTRY_IAT &&
NTHeader.IATDIR.VirtualAddress == 0)
NTHeader.IATDIR = NTHeader.IMPORTDIR;
NTHeader.IMPORTDIR.VirtualAddress = rva + 4 * PTRSZ + ansi_len;
NTHeader.IMPORTDIR.Size += sizeof(*pImports);
NTHeader.IMPORTDIR.VirtualAddress = rva + 2 * PTRSZ + ansi_len;
//NTHeader.IMPORTDIR.Size += sizeof(*pImports);
// Remove bound imports, so the updated import table is used.
if (NTHeader.DATADIRS > IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT)
{
NTHeader.BOUNDDIR.VirtualAddress = 0;
NTHeader.BOUNDDIR.Size = 0;
//NTHeader.BOUNDDIR.Size = 0;
}
VirtProtVar( pNTHeader, PAGE_READWRITE );
WriteProcVar( pNTHeader, &NTHeader );
VirtProtVar( pNTHeader, pr );
// Remove the IL-only flag on a managed process.
if (NTHeader.COMDIR.VirtualAddress != 0 && NTHeader.COMDIR.Size != 0)
if (NTHeader.DATADIRS > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR &&
NTHeader.COMDIR.VirtualAddress != 0)
{
pComHeader = (PIMAGE_COR20_HEADER)(pBase + NTHeader.COMDIR.VirtualAddress);
ReadProcVar( pComHeader, &ComHeader );
@ -128,7 +155,7 @@ void InjectDLL32( LPPROCESS_INFORMATION ppi, PBYTE pBase )
{
DWORD rva;
PVOID pMem;
DWORD len;
DWORD len, import_size;
DWORD pr;
IMAGE_DOS_HEADER DosHeader;
IMAGE_NT_HEADERS32 NTHeader, *pNTHeader;
@ -145,18 +172,19 @@ void InjectDLL32( LPPROCESS_INFORMATION ppi, PBYTE pBase )
pNTHeader = (PIMAGE_NT_HEADERS32)(pBase + DosHeader.e_lfanew);
ReadProcVar( pNTHeader, &NTHeader );
len = 16 + ansi_len + sizeof(*pImports) + NTHeader.IMPORTDIR.Size;
pImports = malloc( len );
import_size = sizeof_imports( ppi, pBase, NTHeader.IMPORTDIR.VirtualAddress );
len = 8 + ansi_len + sizeof(*pImports) + import_size;
pImports = HeapAlloc( hHeap, 0, len );
if (pImports == NULL)
{
DEBUGSTR( 1, L" Failed to allocate memory." );
DEBUGSTR( 1, " Failed to allocate memory" );
return;
}
pMem = FindMem( ppi->hProcess, pBase, len );
if (pMem == NULL)
{
DEBUGSTR( 1, L" Failed to allocate virtual memory." );
free( pImports );
DEBUGSTR( 1, " Failed to allocate virtual memory" );
HeapFree( hHeap, 0, pImports );
return;
}
rva = (DWORD)((PBYTE)pMem - pBase);
@ -164,31 +192,33 @@ void InjectDLL32( LPPROCESS_INFORMATION ppi, PBYTE pBase )
ip.pL = (PLONG)pImports;
*ip.pL++ = IMAGE_ORDINAL_FLAG32 + 1;
*ip.pL++ = 0;
*ip.pL++ = IMAGE_ORDINAL_FLAG32 + 1;
*ip.pL++ = 0;
memcpy( ip.pB, ansi_dll, ansi_len );
ip.pB += ansi_len;
ip.pI->OriginalFirstThunk = rva + 8;
ip.pI->OriginalFirstThunk = 0;
ip.pI->TimeDateStamp = 0;
ip.pI->ForwarderChain = 0;
ip.pI->Name = rva + 16;
ip.pI->Name = rva + 8;
ip.pI->FirstThunk = rva;
ReadProcMem( pBase + NTHeader.IMPORTDIR.VirtualAddress,
ip.pI + 1, NTHeader.IMPORTDIR.Size );
ReadProcMem( pBase+NTHeader.IMPORTDIR.VirtualAddress, ip.pI+1, import_size );
WriteProcMem( pMem, pImports, len );
free( pImports );
HeapFree( hHeap, 0, pImports );
if (NTHeader.IATDIR.VirtualAddress == 0)
if (NTHeader.DATADIRS > IMAGE_DIRECTORY_ENTRY_IAT &&
NTHeader.IATDIR.VirtualAddress == 0)
NTHeader.IATDIR = NTHeader.IMPORTDIR;
NTHeader.IMPORTDIR.VirtualAddress = rva + 16 + ansi_len;
NTHeader.IMPORTDIR.Size += sizeof(*pImports);
NTHeader.IMPORTDIR.VirtualAddress = rva + 8 + ansi_len;
//NTHeader.IMPORTDIR.Size += sizeof(*pImports);
if (NTHeader.DATADIRS > IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT)
{
NTHeader.BOUNDDIR.VirtualAddress = 0;
NTHeader.BOUNDDIR.Size = 0;
//NTHeader.BOUNDDIR.Size = 0;
}
VirtProtVar( pNTHeader, PAGE_READWRITE );
WriteProcVar( pNTHeader, &NTHeader );
VirtProtVar( pNTHeader, pr );
if (NTHeader.COMDIR.VirtualAddress != 0 && NTHeader.COMDIR.Size != 0)
if (NTHeader.DATADIRS > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR &&
NTHeader.COMDIR.VirtualAddress != 0)
{
pComHeader = (PIMAGE_COR20_HEADER)(pBase + NTHeader.COMDIR.VirtualAddress);
ReadProcVar( pComHeader, &ComHeader );
@ -233,7 +263,7 @@ static PBYTE get_ntdll( LPPROCESS_INFORMATION ppi )
}
}
DEBUGSTR( 1, L" Failed to find ntdll.dll!" );
DEBUGSTR( 1, " Failed to find ntdll.dll!" );
return NULL;
}
@ -266,7 +296,7 @@ void InjectDLL64( LPPROCESS_INFORMATION ppi )
PAGE_EXECUTE_READ );
if (pMem == NULL)
{
DEBUGSTR( 1, L" Failed to allocate virtual memory (%lu)", GetLastError() );
DEBUGSTR(1, " Failed to allocate virtual memory (%u)", GetLastError());
return;
}

View File

@ -43,10 +43,10 @@ DWORD GetProcRVA( LPCTSTR module, LPCSTR func )
if (hMod == NULL)
{
#ifdef _WIN64
DEBUGSTR( 1, L"Unable to load %d-bit %s (%lu)!",
DEBUGSTR( 1, "Unable to load %u-bit %S (%u)!",
bits, module, GetLastError() );
#else
DEBUGSTR( 1, L"Unable to load %s (%lu)!", module, GetLastError() );
DEBUGSTR( 1, "Unable to load %S (%u)!", module, GetLastError() );
#endif
return 0;
}
@ -70,9 +70,9 @@ DWORD GetProcRVA( LPCTSTR module, LPCSTR func )
if (pFunc == NULL)
{
#ifdef _WIN64
DEBUGSTR( 1, L"Could not find %d-bit %s!", bits, func );
DEBUGSTR( 1, "Could not find %u-bit %s!", bits, func );
#else
DEBUGSTR( 1, L"Could not find %s!", func );
DEBUGSTR( 1, "Could not find %s!", func );
#endif
rva = 0;
}

View File

@ -76,8 +76,8 @@ int ProcessType( LPPROCESS_INFORMATION ppi, PBYTE* pBase, BOOL* gui )
if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
{
PIMAGE_NT_HEADERS32 pNTHeader = (PIMAGE_NT_HEADERS32)&nt_header;
if (pNTHeader->COMDIR.VirtualAddress != 0 &&
pNTHeader->COMDIR.Size != 0)
if (pNTHeader->DATADIRS > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR &&
pNTHeader->COMDIR.VirtualAddress != 0)
{
IMAGE_COR20_HEADER ComHeader, *pComHeader;
pComHeader = (PIMAGE_COR20_HEADER)((PBYTE)minfo.BaseAddress
@ -87,56 +87,50 @@ int ProcessType( LPPROCESS_INFORMATION ppi, PBYTE* pBase, BOOL* gui )
!(ComHeader.Flags & COMIMAGE_FLAGS_32BITREQUIRED))
{
#if defined(_WIN64) || !defined(W32ON64) // W32ON64 will log due to -P
DEBUGSTR( 1, L" AnyCPU %s (base = %.8X)",
(*gui) ? L"GUI" : L"console",
PtrToUint( minfo.BaseAddress ) );
DEBUGSTR( 1, " AnyCPU %s (base = %q)",
(*gui) ? "GUI" : "console", minfo.BaseAddress );
#endif
#if defined(_WIN64) || defined(W32ON64)
return 48;
#else
if (ProcessIs64( ppi->hProcess ))
{
DEBUGSTR( 1, L" Unsupported (use x64\\ansicon)" );
DEBUGSTR( 1, " Unsupported (use x64\\ansicon)" );
return 0;
}
return 32;
#endif
}
}
DEBUGSTR( 1, L" 32-bit %s (base = %.8X)",
(*gui) ? L"GUI" : L"console",
PtrToUint( minfo.BaseAddress ) );
DEBUGSTR( 1, " 32-bit %s (base = %q)",
(*gui) ? "GUI" : "console", minfo.BaseAddress );
return 32;
}
if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
{
#ifdef _WIN64
DEBUGSTR( 1, L" 64-bit %s (base = %.8X_%.8X)",
(*gui) ? L"GUI" : L"console",
(DWORD)((DWORD_PTR)minfo.BaseAddress >> 32),
PtrToUint( minfo.BaseAddress ) );
DEBUGSTR( 1, " 64-bit %s (base = %p)",
(*gui) ? "GUI" : "console", minfo.BaseAddress );
return 64;
#elif defined(W32ON64)
// Console will log due to -P, but GUI may be ignored (if not,
// this'll show up twice).
if (*gui)
DEBUGSTR( 1, L" 64-bit GUI (base = 00000000_%.8X)",
PtrToUint( minfo.BaseAddress ) );
DEBUGSTR( 1, " 64-bit GUI (base = %P)", minfo.BaseAddress );
return 64;
#else
DEBUGSTR( 1, L" 64-bit %s (base = 00000000_%.8X)",
(*gui) ? L"GUI" : L"console",
PtrToUint( minfo.BaseAddress ) );
DEBUGSTR( 1, L" Unsupported (use x64\\ansicon)" );
DEBUGSTR( 1, " 64-bit %s (base = %P)",
(*gui) ? "GUI" : "console", minfo.BaseAddress );
DEBUGSTR( 1, " Unsupported (use x64\\ansicon)" );
return 0;
#endif
}
DEBUGSTR( 1, L" Ignoring unsupported machine (0x%X)",
DEBUGSTR( 1, " Ignoring unsupported machine (0x%X)",
nt_header.FileHeader.Machine );
}
else
{
DEBUGSTR( 1, L" Ignoring unsupported subsystem (%u)",
DEBUGSTR( 1, " Ignoring unsupported subsystem (%u)",
nt_header.OptionalHeader.Subsystem );
}
return 0;
@ -147,16 +141,16 @@ int ProcessType( LPPROCESS_INFORMATION ppi, PBYTE* pBase, BOOL* gui )
if (((DWORD)ptr >> 12) + ((DWORD)minfo.RegionSize >> 12) >= 0x100000)
{
#ifdef W32ON64
DEBUGSTR( 1, L" Pointer overflow: assuming 64-bit" );
DEBUGSTR( 1, " Pointer overflow: assuming 64-bit" );
return 64;
#else
DEBUGSTR( 1, L" Ignoring apparent 64-bit process (use x64\\ansicon)" );
DEBUGSTR( 1, " Ignoring apparent 64-bit process (use x64\\ansicon)" );
return 0;
#endif
}
#endif
}
DEBUGSTR( 1, L" Ignoring non-Windows process" );
DEBUGSTR( 1, " Ignoring non-Windows process" );
return 0;
}

View File

@ -5,8 +5,8 @@ provides much the same functionality as `ANSI.SYS` does for MS-DOS.
## Requirements
* 32-bit: Windows 2000 Professional or later (it won't work with NT or 9X).
* 64-bit: Vista or later (it won't work with XP64).
* 32-bit: Windows 2000 Professional and later (it won't work with NT or 9X).
* 64-bit: AMD64 (it won't work with IA64).
## Installation
@ -111,15 +111,17 @@ Using `ansicon` after install will always start with the default attributes,
restoring the originals on exit; all other programs will use the current
attributes. The shift state is always reset for a new process.
The Windows API `WriteFile` and `WriteConsoleA` functions will set the number of
characters written, not the number of bytes. When using a multibyte character
set, this results in a smaller number (since multiple bytes are used to
represent a single character). Some programs recognise this as a reduced write
and will inadvertently repeat previous characters. If you discover such a
program, use the `ANSICON_API` environment variable to record it and override
the API, returning the original byte count. Ruby (prior to 1.9.3) is an example
of such a program, so use `set ANSICON_API=ruby` to avoid the repitition. The
full syntax is:
My version of `WriteConsoleA` will always set the number of characters written,
not the number of bytes. This means writing a double-byte character as two
bytes will set 0 the first write (nothing was written) and 1 the second (when
the character was actually written); Windows normally sets 1 for both writes.
Similarly, writing the individual bytes of a multibyte character will set 0 for
all but the last byte, then 1 on the last; Windows normally sets 1 for each
byte, writing the undefined character. However, my `WriteFile` (and
`_lwrite`/`_hwrite`) will always set what was received; Windows, using a
multibyte character set (but not DBCS), would set the characters. You can have
`WriteConsoleA` return the original byte count by using the `ANSICON_API`
environment variable:
ANSICON_API=[!]program;program;program...
@ -127,14 +129,16 @@ PROGRAM is the name of the program, with no path and extension. The leading
exclamation inverts the usage, meaning the API will always be overridden, unless
the program is in the list. The variable can be made permanent by going to
_System Properties_, selecting the _Advanced_ tab (with Vista onwards, this can
be done by running _"SystemPropertiesAdvanced"_) and clicking _Environment
be done by running `SystemPropertiesAdvanced`) and clicking _Environment
Variables_.
## Limitations
- The entire console buffer is used, not just the visible window.
- There's a conflict with NVIDIA's drivers, requiring the setting of the
- Line sequences use the window; column sequences use the buffer.
- An application using multiple screen buffers will not have separate
attributes in each buffer.
- There may be a conflict with NVIDIA's drivers, requiring the setting of the
Environment Variable:
ANSICON_EXC=nvd3d9wrap.dll;nvd3d9wrapx.dll
@ -146,19 +150,26 @@ Variables_.
The following escape sequences are recognised.
\e]0;titleBEL Set (xterm) window's title (and icon)
\e[21t Report (xterm) window's title
\e[s Save Cursor
\e[u Restore Cursor
\e]0;titleBEL xterm: Set window's title (and icon, ignored)
\e]2;titleBEL xterm: Set window's title
\e[21t xterm: Report window's title
\e[s ANSI.SYS: Save Cursor Position
\e[u ANSI.SYS: Restore Cursor Position
\e[#Z CBT Cursor Backward Tabulation
\e[#G CHA Cursor Character Absolute
\e[#I CHT Cursor Forward Tabulation
\e[#E CNL Cursor Next Line
\e[#F CPL Cursor Preceding Line
\e[3h CRM Control Representation Mode (display controls)
\e[3l CRM Control Representation Mode (perform controls)
\e[#D CUB Cursor Left
\e[#B CUD Cursor Down
\e[#C CUF Cursor Right
\e[#;#H CUP Cursor Position
\e[#A CUU Cursor Up
\e[#P DCH Delete Character
\e[?7h DECAWM DEC Autowrap Mode (autowrap)
\e[?7l DECAWM DEC Autowrap Mode (no autowrap)
\e[?25h DECTCEM DEC Text Cursor Enable Mode (show cursor)
\e[?25l DECTCEM DEC Text Cursor Enable Mode (hide cursor)
\e[#M DL Delete Line
@ -174,6 +185,7 @@ The following escape sequences are recognised.
\e[#L IL Insert Line
SI LS0 Locking-shift Zero (see below)
SO LS1 Locking-shift One
\e[#b REP Repeat
\e[#;#;#m SGR Select Graphic Rendition
\e[#d VPA Line Position Absolute
\e[#k VPB Line Position Backward
@ -193,6 +205,12 @@ the latter will explicitly reset them. The environment variable `ANSICON_DEF`
can be used to change the default colors (same value as `-m`; setting the
variable does not change the current colors).
The first time a program clears the screen (`\e[2J`) will actually scroll in a
new window (assuming the buffer is bigger than the window, of course).
Subsequent clears will then blank the window. However, if the window has
scrolled, or the cursor is on the last line of the buffer, it will again scroll
in a new window.
### Ignored Sequences
@ -214,7 +232,7 @@ http://vt100.net/docs/vt220-rm/table2-4.html.
Char Unicode Code Point & Name
---- -------------------------
_ U+0020 Space (blank)
_ U+00A0 No-Break Space (blank)
` U+2666 Black Diamond Suit
a U+2592 Medium Shade
b U+2409 Symbol For Horizontal Tabulation
@ -299,4 +317,4 @@ In particular, the supplied binaries are freely redistributable.
A formal license (zlib) is available in `LICENSE.txt`.
---
Copyright 2005-2014 Jason Hood
Copyright 2005-2015 Jason Hood

View File

@ -1,9 +1,9 @@
ANSICON
Copyright 2005-2014 Jason Hood
Copyright 2005-2015 Jason Hood
Version 1.71. Freeware
Version 1.72. Freeware
Description
@ -110,9 +110,10 @@ Usage
The variable is updated whenever a program reads it directly (i.e. as an
individual request, not as part of the entire environment block). For
example, 'set an' will not update it, but 'echo %ansicon%' will. Also
created is ANSICON_VER, which contains the version without the point (1.50
becomes "150"). This variable does not exist as part of the environment
block ('set an' will not show it).
created are ANSICON_VER, which contains the version without the point (1.50
becomes "150"), and CLICOLOR (see http://bixense.com/clicolors/), which
contains "1". These variables do not exist as part of the environment
block (e.g. 'set an' will not show ANSICON_VER).
If installed, GUI programs will not be hooked. Either start the program
directly with 'ansicon', or add it to the ANSICON_GUI variable (see
@ -122,15 +123,17 @@ Usage
utes, restoring the originals on exit; all other programs will use the cur-
rent attributes. The shift state is always reset for a new process.
The Windows API WriteFile and WriteConsoleA functions will set the number
of characters written, not the number of bytes. When using a multibyte
character set, this results in a smaller number (since multiple bytes are
used to represent a single character). Some programs recognise this as a
reduced write and will inadvertently repeat previous characters. If you
discover such a program, use the ANSICON_API environment variable to record
it and override the API, returning the original byte count. Ruby (prior to
1.9.3) is an example of such a program, so use 'set ANSICON_API=ruby' to
avoid the repitition. The full syntax is:
My version of WriteConsoleA will always set the number of characters writt-
en, not the number of bytes. This means writing a double-byte character as
two bytes will set 0 the first write (nothing was written) and 1 the second
(when the character was actually written); Windows normally sets 1 for both
writes. Similarly, writing the individual bytes of a multibyte character
will set 0 for all but the last byte, then 1 on the last; Windows normally
sets 1 for each byte, writing the undefined character. However, my
WriteFile (and _lwrite/_hwrite) will always set what was received; Windows,
using a multibyte character set (but not DBCS), would set the characters.
You can have WriteConsoleA return the original byte count by using the
ANSICON_API environment variable:
ANSICON_API=[!]program;program;program...
@ -277,7 +280,7 @@ Limitations
Line sequences use the window; column sequences use the buffer.
Tabs are fixed at eight columns.
There's a conflict with NVIDIA's drivers, requiring the setting of the
There may be a conflict with NVIDIA's drivers, requiring the setting of the
Environment Variable:
ANSICON_EXC=nvd3d9wrap.dll;nvd3d9wrapx.dll
@ -291,8 +294,24 @@ Version History
Legend: + added, - bug-fixed, * changed.
1.72 - 24 December, 2015:
- handle STD_OUTPUT_HANDLE & STD_ERROR_HANDLE in WriteFile;
- better handling of unusual PE files;
* cache GetConsoleMode for an improvement in speed;
* files writing to the console always succeed (should mostly remove the
need for ANSICON_API);
* log: add a blank line between processes;
remove the separate line for WriteFile & _lwrite;
write byte strings as-is, wide strings using the current code page;
use caret notation for control characters, with hexadecimal "^xNN"
and "^uNNNN" for characters not in the code page (custom printf);
* join multibyte characters split across separate writes;
* remove wcstok, avoiding potential interference with the host program;
* similarly, remove malloc & friends, using a private heap;
+ add CLICOLOR dynamic environment variable.
1.71 - 23 October, 2015:
+ add _CRT_NON_CONFORMING_WCSTOK define
+ add _CRT_NON_CONFORMING_WCSTOK define for VS2015.
1.70 - 26 February, 2014:
- don't hook again if using LoadLibrary or LoadLibraryEx;
@ -501,4 +520,4 @@ Distribution
==============================
Jason Hood, 26 February, 2014.
Jason Hood, 24 December, 2015.

357
util.c
View File

@ -74,99 +74,316 @@ void set_ansi_dll( void )
}
void DEBUGSTR( int level, LPTSTR szFormat, ... )
{
static char tempfile[MAX_PATH];
static DWORD pid;
static LPSTR buf;
static DWORD buf_len;
static BOOL quote, alt;
TCHAR szBuffer[1024], szEscape[1024];
va_list pArgList;
HANDLE mutex;
DWORD wait;
FILE* file;
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;
if ((log_level & 3) < level && !(level & 4 & log_level))
return;
src.a = (LPSTR)str;
if (len == 0 && str != 0)
len = (DWORD)(wide ? wcslen( src.w ) : strlen( src.a ));
if (*tempfile == '\0')
if (pos + len * 6 + 8 >= buf_len)
{
_snprintf( tempfile, MAX_PATH, "%s\\ansicon.log", getenv( "TEMP" ) );
pid = GetCurrentProcessId();
}
if (szFormat == NULL)
{
// Explicitly use 't', as _fmode might be binary.
file = fopen( tempfile, (log_level & 8) ? "at" : "wt" );
if (file != NULL)
{
SYSTEMTIME now;
GetLocalTime( &now );
fseek( file, 0, SEEK_END );
if (ftell( file ) != 0)
putc( '\n', file );
fprintf( file, "ANSICON (" BITSA "-bit) v" PVERSA " log (%d) started "
"%d-%.2d-%.2d %d:%.2d:%.2d\n",
log_level,
now.wYear, now.wMonth, now.wDay,
now.wHour, now.wMinute, now.wSecond );
fclose( file );
}
return;
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 );
}
va_start( pArgList, szFormat );
_vsnwprintf( szBuffer, lenof(szBuffer), szFormat, pArgList );
va_end( pArgList );
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;
}
szFormat = szBuffer;
if (*szFormat == '\33')
if (cp != GetConsoleOutputCP())
{
BOOL first = TRUE;
LPTSTR pos = szEscape;
while (*++szFormat != '\0' && pos < szEscape + lenof(szEscape) - 4)
cp = GetConsoleOutputCP();
if (wide)
{
if (*szFormat < 32)
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 ))
{
*pos++ = '\\';
switch (*szFormat)
flags = 0;
pDef = NULL;
def = FALSE;
}
}
}
if (quote)
buf[pos++] = '"';
trail = FALSE;
while (len-- != 0)
{
case '\a': *pos++ = 'a'; break;
case '\b': *pos++ = 'b'; break;
case '\t': *pos++ = 't'; break;
case '\r': *pos++ = 'r'; break;
case '\n': *pos++ = 'n'; break;
case 27 : *pos++ = 'e'; break;
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 += _snwprintf( pos, 32, L"%.*o",
(szFormat[1] >= '0' && szFormat[1] <= '7') ? 3 : 1,
*szFormat );
pos += wsprintfA( buf + pos, "x%.2X", ch );
}
}
else
{
if (*szFormat == '"')
buf[pos++] = '^';
buf[pos++] = ch + '@';
}
}
else if (quote && ch == '"')
{
if (first)
first = FALSE;
else if (szFormat[1] != '\0')
*pos++ = '\\';
buf[pos++] = '\\';
buf[pos++] = ch;
}
*pos++ = *szFormat;
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;
}
}
*pos = '\0';
szFormat = szEscape;
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;
}
}
mutex = CreateMutex( NULL, FALSE, L"ANSICON_debug_file" );
wait = WaitForSingleObject( mutex, 500 );
file = fopen( tempfile, "at" );
if (file != NULL)
{
fwprintf( file, L"%s (%lu): %s\n", prog, pid, szFormat );
fclose( file );
if (quote)
buf[pos++] = '"';
return pos;
}
if (wait == WAIT_OBJECT_0)
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 );
}

View File

@ -2,11 +2,11 @@
version.h - Version defines.
*/
#define PVERS L"1.71" // wide string
#define PVERSA "1.71" // ANSI string (windres 2.16.91 didn't like L)
#define PVERE L"171" // wide environment string
#define PVEREA "171" // ANSI environment string
#define PVERB 1,7,1,0 // binary (resource)
#define PVERS L"1.72" // wide string
#define PVERSA "1.72" // ANSI string (windres 2.16.91 didn't like L)
#define PVERE L"172" // wide environment string
#define PVEREA "172" // ANSI environment string
#define PVERB 1,7,2,0 // binary (resource)
#ifdef _WIN64
# define BITS L"64"