/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    genxx.c

Abstract:

    This module implements a program which generates structure offset
    definitions for kernel structures that are accessed in assembly code.

Author:

    Forrest C. Foltz (forrestf) 20-Jan-98


To use:

    This program reads an OBJ file generated by the target platform's
    compiler.

    To generate such an OBJ, go to ke\up and do a "nmake UMAPPL=gen<plt>",
    where <plt> is a platform identifier like i386, etc.

    All you need from this latter step is the OBJ, the link phase will not
    succeed which is fine.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#define SKIP_M4
#include <genxx.h>

//
// Internal structure definitions, macros, constants
//

#define ARRAY_SIZE( x ) (sizeof( x ) / sizeof( (x)[0] ))

typedef struct _OUTPUT_FILE *POUTPUT_FILE;
typedef struct _OUTPUT_FILE {
    POUTPUT_FILE Next;
    ULONG EnableMask;
    BOOLEAN IncFormat;
    FILE *File;
} OUTPUT_FILE;

//
// Function prototypes follow
//

VOID
ApplyFixupsToImage( VOID );

VOID
BuildHeaderFiles(
    STRUC_ELEMENT UNALIGNED *StrucArray
    );

VOID
__cdecl
CheckCondition(
    int Condition,
    const char *format,
    ...
    );

VOID
AddNewOutputFile(
    PUCHAR RootRelativePath,
    ULONG Flags
    );

VOID
AddNewAbsoluteOutputFile(
    PUCHAR AbsolutePath,
    ULONG Flags
    );

VOID
CloseOutputFiles( VOID );

VOID
_cdecl
HeaderPrint(
    ULONG EnableFlags,
    ULONG Type,
    ...
    );

PSTRUC_ELEMENT
FindStructureElementArray(
    PCHAR Buffer,
    ULONG BufferSize
    );

VOID
GetEnvironment( VOID );

PSTRUC_ELEMENT
LoadObjImage(
    PUCHAR ImagePath
    );

PCHAR
StripWhiteSpace(
    PCHAR String
    );

VOID
Usage( VOID );

//
// Constant tables follow
//

const char *PreprocessedFormatStringArray[] = {

    // SEF_EQUATE
    "#define %s 0x%0x\n",

    // SEF_EQUATE64
    "#define %s 0x%016I64x\n",

    // SEF_COMMENT
    "\n"
    "//\n"
    "// %s\n"
    "//\n"
    "\n",

    // SEF_STRING
    "%s\n",

    // SEF_BITFLD
    "#define %s_MASK  0x%I64x\n"
    "#define %s 0x%0x\n",

    // SEF_BITALIAS
    "#define %s 0x%0x\n",

    // SEF_STRUCTURE
    "struct %s {\n"
    "    UCHAR fill[ %d ];\n"
    "}; // %s\n"

};

const char *Asm386FormatStringArray[] = {

    // SEF_EQUATE
    "%s equ 0%04XH\n",

    // SEF_EQUATE64
    "%s equ 0%016I64XH\n",

    // SEF_COMMENT
    "\n"
    ";\n"
    ";  %s\n"
    ";\n"
    "\n",

    // SEF_STRING
    "%s",

    // SEF_BITFLD
    "%s_MASK  equ 0%I64XH\n"
    "%s equ 0%0XH\n",

    // SEF_BITALIAS
    "%s equ 0%08XH\n",

    // SEF_STRUCTURE
    "%s  struc\n"
    "  db %d dup(0)\n"
    "%s  ends\n"
};

//
// Each platform contains a list of generated header files.
//

typedef struct {
    PCHAR HeaderPath;
    ULONG Flags;
} HEADERPATH, *PHEADERPATH;

HEADERPATH HeaderListi386[] = {
    { "public\\sdk\\inc\\ks386.inc", SEF_KERNEL | SEF_INC_FORMAT },
    { "base\\ntos\\inc\\hal386.inc", SEF_HAL | SEF_INC_FORMAT },
    { NULL, 0 }
};

HEADERPATH HeaderListIa64[] = {
    { "public\\sdk\\inc\\ksia64.h", SEF_KERNEL | SEF_H_FORMAT },
    { "base\\ntos\\inc\\halia64.h", SEF_HAL | SEF_H_FORMAT },
    { NULL, 0 }
};

HEADERPATH HeaderListVdm[] = {
    { "public\\internal\\base\\inc\\vdmtib.inc", SEF_INC_FORMAT },
    { NULL, 0 }
};

HEADERPATH HeaderListAmd64[] = {
    { "public\\sdk\\inc\\ksamd64.inc", SEF_KERNEL | SEF_INC_FORMAT },
    { "base\\ntos\\inc\\halamd64.inc", SEF_HAL | SEF_INC_FORMAT },
    { NULL, 0 }
};

typedef struct {
    PCHAR PlatformName;
    PCHAR ObjPath;
    PHEADERPATH HeaderPathList;
} PLATFORM, *PPLATFORM;

PLATFORM PlatformList[] = {

    { "i386",
      "base\\ntos\\ke\\up\\obj\\i386\\geni386.obj",
      HeaderListi386 },

    { "ia64",
      "base\\ntos\\ke\\up\\obj\\ia64\\genia64.obj",
      HeaderListIa64 },

    { "vdm",
      "base\\ntos\\vdm\\up\\obj\\i386\\genvdm.obj",
      HeaderListVdm },

    { "amd64",
      "base\\ntos\\ke\\up\\obj\\amd64\\genamd64.obj",
      HeaderListAmd64 },

    { NULL, NULL, NULL }
};

const char MarkerString[] = MARKER_STRING;

//
// Global vars follow
//

POUTPUT_FILE OutputFileList;

PCHAR ObjImage;
CHAR HalHeaderPath[ MAX_PATH ];
CHAR KernelHeaderPath[ MAX_PATH ];
CHAR HeaderPath[ MAX_PATH ];
CHAR ObjectPath[ MAX_PATH ];
CHAR NtRoot[ MAX_PATH ];
CHAR TempBuf[ MAX_PATH ];
BOOL fOutputSpecified;
BOOL fHalHeaderPath;
BOOL fKernelHeaderPath;
BOOL fIncFormat;

//
// The actual code...
//

int
__cdecl
main(
    int argc,
    char *argv[]
    )
{
    int argNum;
    char *arg;
    int platformIndex;
    int fileIndex;
    BOOL validSwitch;
    PSTRUC_ELEMENT strucArray;
    PPLATFORM platform;
    PHEADERPATH headerPath;

    GetEnvironment();

    //
    // Assume no platform specified, then see if we can find one.
    //

    ObjectPath[ 0 ] = '\0';
    for( argNum = 1; argNum < argc; argNum++ ){

        validSwitch = FALSE;

        arg = argv[ argNum ];
        if( *arg == '/' || *arg == '-' ){

            //
            // A switch was passed.  See what it is.
            //

            arg++;

            switch( *arg ){

            case 'o':

                //
                // Specified an output file
                //

                fOutputSpecified = TRUE;
                strcpy( HeaderPath, arg+1 );
                validSwitch = TRUE;
                break;

            case 's':

                //
                // Specified include file suffix (either 'h' or 'inc')
                //

                if( _stricmp( arg+1, "inc" ) == 0 ){

                    //
                    // We would like the "inc" format, thanks
                    //

                    fIncFormat = TRUE;

                } else {

                    CheckCondition( _stricmp( arg+1, "h" ) == 0,
                                    "Invalid suffix option: -s[inc|h]\n");
                }
                validSwitch = TRUE;
                break;

            case 'k':

                //
                // Kernel header path.  Save off.
                //
                fKernelHeaderPath = TRUE;
                strcpy( KernelHeaderPath, arg+1 );
                validSwitch = TRUE;
                break;

            case 'h':

                //
                // Hal header path.  Save off.
                //
                fHalHeaderPath = TRUE;
                strcpy( HalHeaderPath, arg+1 );
                validSwitch = TRUE;
                break;

            default:

                //
                // Check our platform list.
                //

                platform = PlatformList;
                while( platform->PlatformName != NULL ){

                    if( _stricmp( platform->PlatformName,
                                  arg ) == 0 ){

                        //
                        // Platform was specified, we will build the path to
                        // the obj.
                        //

                        sprintf( ObjectPath,
                                 "%s\\%s",
                                 NtRoot,
                                 platform->ObjPath );

                        //
                        // Add the header paths too.
                        //

                        headerPath = platform->HeaderPathList;
                        while( headerPath->HeaderPath != NULL ){

                            if (fHalHeaderPath && (headerPath->Flags & SEF_HAL)) {
                                strcpy(TempBuf, HalHeaderPath);
                                AddNewAbsoluteOutputFile( HalHeaderPath, headerPath->Flags );
                            } else
                            if (fKernelHeaderPath && (headerPath->Flags & SEF_KERNEL)) {
                                strcpy(TempBuf, KernelHeaderPath);
                                AddNewAbsoluteOutputFile( KernelHeaderPath, headerPath->Flags );
                            } else {
                                AddNewOutputFile( headerPath->HeaderPath,
                                                  headerPath->Flags );
                            }

                            headerPath++;
                        }

                        validSwitch = TRUE;
                        break;
                    }

                    platform++;
                }
                break;
            }

            if( validSwitch == FALSE ){
                Usage();
            }

        } else {

            //
            // We are dealing with something that is not a switch.  The only
            // possibility is the path to the object file.
            //

            strcpy( ObjectPath, arg );
        }
    }

    CheckCondition( ObjectPath[0] != '\0',
                    "Object path not specified\n" );

    if( fOutputSpecified != FALSE ){

        //
        // The output path was specified
        //

        AddNewAbsoluteOutputFile( HeaderPath,
                                  fIncFormat ? SEF_INC_FORMAT : SEF_H_FORMAT );
    }

    strucArray = LoadObjImage( ObjectPath );

    BuildHeaderFiles( strucArray );

    CloseOutputFiles();

    //
    // Indicate success.
    //

    return 0;
}

VOID
AddNewAbsoluteOutputFile(
    PUCHAR AbsolutePath,
    ULONG Flags
    )
{
    POUTPUT_FILE outputFile;

    outputFile = malloc( sizeof( OUTPUT_FILE ));
    CheckCondition( outputFile != NULL, "Out of memory\n" );

    outputFile->EnableMask = (ULONG)(Flags & SEF_ENABLE_MASK);

    if( (Flags & SEF_INC_FORMAT_MASK) == SEF_INC_FORMAT ){

        //
        // This file will be created in '.inc' format for the 386 assembler.
        //

        outputFile->IncFormat = TRUE;
    } else {

        //
        // This file will be created in '.h' format for the standard C
        // preprocessor.
        //

        outputFile->IncFormat = FALSE;
    }

    outputFile->File = fopen( AbsolutePath, "w" );
    CheckCondition( outputFile->File != NULL,
                    "Cannot open %s for writing.\n",
                    TempBuf );

    printf("%s -> %s\n", ObjectPath, TempBuf );

    //
    // Link this structure into the list of output files
    //

    outputFile->Next = OutputFileList;
    OutputFileList = outputFile;
}

VOID
AddNewOutputFile(
    PUCHAR RootRelativePath,
    ULONG Flags
    )
{
    //
    // Create the canonoical file path and open the file.
    //

    sprintf( TempBuf,
             "%s\\%s",
             NtRoot,
             RootRelativePath );

    AddNewAbsoluteOutputFile( TempBuf, Flags );
}

VOID
CloseOutputFiles( VOID )
{
    POUTPUT_FILE outputFile;

    outputFile = OutputFileList;
    while( outputFile != NULL ){

        fclose( outputFile->File );
        outputFile = outputFile->Next;
    }
}


PSTRUC_ELEMENT
LoadObjImage(
    PUCHAR ImagePath
    )
{
    long           objImageSize;
    int            result;
    PSTRUC_ELEMENT strucArray;
    FILE *         objFile;

    //
    // Open up and read the platform-specific .obj file.
    //

    objFile = fopen( ImagePath, "rb" );
    CheckCondition( objFile != NULL,
                    "Cannot open %s for reading.\n"
                    "This file must have been created by the compiler for the "
                    "target platform.\n",
                    ImagePath );

    //
    // Get the file size, allocate a buffer, read it in, and close.
    //

    result = fseek( objFile, 0, SEEK_END );
    CheckCondition( result == 0,
                    "fseek() failed, error %d\n",
                    errno );

    objImageSize = ftell( objFile );
    CheckCondition( objImageSize != -1L,
                    "ftell() failed, error %d\n",
                    errno );

    CheckCondition( objImageSize > 0,
                    "%s appears to be corrupt\n",
                    ImagePath );

    ObjImage = malloc( objImageSize );
    CheckCondition( ObjImage != NULL,
                    "Out of memory\n" );

    result = fseek( objFile, 0, SEEK_SET );
    CheckCondition( result == 0,
                    "fseek() failed, error %d\n",
                    errno );

    result = fread( ObjImage, 1, objImageSize, objFile );
    CheckCondition( result == objImageSize,
                    "Error reading from %s\n",
                    ImagePath );

    fclose( objFile );

    //
    // Even though this is just an .obj file, we want it "fixed up"
    //

    ApplyFixupsToImage();

    //
    // Got the image, find the beginning of the array.
    //

    strucArray = FindStructureElementArray( ObjImage,
                                            objImageSize );
    CheckCondition( strucArray != NULL,
                    "%s does not contain a structure description array.\n",
                    ImagePath );

    return strucArray;
}

VOID
BuildHeaderFiles(
    STRUC_ELEMENT UNALIGNED *StrucArray
    )
{
    STRUC_ELEMENT UNALIGNED *strucArray;
    ULONG          runningEnableMask;
    ULONG          enableMask;
    ULONG          sefType;
    const char    *formatString;
    ULONG          bitFieldStart;
    PUINT64        bitFieldPtr;
    UINT64         bitFieldData;
    PCHAR          name;
    BOOLEAN        finished;

    //
    // Process each element in the array.  The first element is the
    // marker string element, so it is skipped.
    //

    runningEnableMask = 0;
    finished = FALSE;
    strucArray = StrucArray;

    do{
        strucArray++;

        sefType = (ULONG)(strucArray->Flags & SEF_TYPE_MASK);

        if( sefType == SEF_BITFLD ){

            //
            // For bitfields, the enable mask is set explicitly
            //

            enableMask = (ULONG)(strucArray->Flags & SEF_ENABLE_MASK);

        } else {

            //
            // For everything else, we use the current runningEnableMask
            //

            enableMask = runningEnableMask;
        }

        switch( sefType ){

            case SEF_BITFLD:

                //
                // This kind of element is tricky.  "Equate" is actually a
                // pointer to a bitfield structure.  This structure has had
                // a portion of it (the bitfield) initialized to ones.
                //
                // It is the job of this case to poke around in that
                // structure in order to determine where the bitfield landed.
                //

                bitFieldPtr = (PINT64)(strucArray->Equate);
                bitFieldData = *bitFieldPtr;

                //
                // Determine the zero-based starting bitnumber of the field.
                //

                bitFieldStart = 0;
                while( (bitFieldData & ((UINT64)1 << bitFieldStart)) == 0 ){

                    bitFieldStart++;
                }

                name = StripWhiteSpace( strucArray->Name );

                if( *name != '\0'){

                    HeaderPrint( enableMask,
                                 sefType,
                                 name,
                                 bitFieldData,
                                 name,
                                 bitFieldStart );
                }

                //
                // A bitfield can be followed by any number of
                // SEF_BITALIAS entries.  These are alias names for the
                // bitmask that was just defined.
                //

                while( TRUE ){

                    sefType = (ULONG)((strucArray+1)->Flags & SEF_TYPE_MASK);
                    if( sefType != SEF_BITALIAS ){

                        //
                        // No more aliases.
                        //

                        break;
                    }

                    //
                    // This is a bitmask alias field, process it.
                    //

                    strucArray++;

                    name = StripWhiteSpace( strucArray->Name );

                    HeaderPrint( enableMask,
                                 sefType,
                                 name,
                                 bitFieldData );

                }
                break;

            case SEF_END:
                finished = TRUE;
                break;

            case SEF_EQUATE:

                if( (LONG64)strucArray->Equate < 0 ){

                    //
                    // Negative constant
                    //

                    if( (LONG64)strucArray->Equate < LONG_MIN ){

                        //
                        // More negative than can be represented in 32 bits
                        //

                        sefType = SEF_EQUATE64;

                    } else {

                        //
                        // Falls within [LONG_MIN..0], Leave as SEF_EQUATE32
                        //
                    }

                } else if( (ULONG64)strucArray->Equate > (ULONG_MAX) ){

                    //
                    // More positive than can be represented in 32 bits
                    //

                    sefType = SEF_EQUATE64;
                }

                //
                // Fall through
                //

            case SEF_EQUATE64:
            case SEF_COMMENT:
                HeaderPrint( enableMask,
                             sefType,
                             strucArray->Name,
                             (UINT64)strucArray->Equate );
                break;

            case SEF_STRING:
                HeaderPrint( enableMask,
                             sefType,
                             strucArray->Name,
                             strucArray->Equate );
                break;

            case SEF_STRUCTURE:
                HeaderPrint( enableMask,
                             sefType,
                             strucArray->Name,
                             (ULONG)strucArray->Equate,
                             strucArray->Name );
                break;

            case SEF_SETMASK:
                runningEnableMask |= strucArray->Equate;
                break;

            case SEF_CLRMASK:
                runningEnableMask &= ~strucArray->Equate;
                break;

            case SEF_PATH:

                //
                // Add another output file to our list.
                //

                CheckCondition( fOutputSpecified == FALSE,
                                "setPath() in %s incompatible with -o flag\n",
                                ObjectPath );

                AddNewOutputFile( strucArray->Name,
                                  (ULONG)strucArray->Flags );
                break;

            default:

                //
                // Found an SEF_TYPE we don't know about.  This is fatal.
                //

                CheckCondition( FALSE,
                                "Unknown structure type %d.  "
                                "Need an updated genxx.exe?\n",
                                sefType );
                break;

        }

    } while( finished == FALSE );
}

VOID
__cdecl
CheckCondition(
    int Condition,
    const char *FormatString,
    ...
    )
{
    va_list(arglist);

    va_start(arglist, FormatString);

    if( Condition == 0 ){

        //
        // A fatal error was encountered.  Bail.
        //

        vprintf( FormatString, arglist );
        perror( "genxx" );
        exit(1);
    }
}

VOID
_cdecl
HeaderPrint(
    ULONG EnableFlags,
    ULONG Type,
    ...
    )
{
    POUTPUT_FILE outputFile;
    char const *formatString;

    va_list arglist;

    //
    // Send the output to each output file as appropriate
    //

    outputFile = OutputFileList;
    while( outputFile != NULL ){

        va_start( arglist, Type );

        if( outputFile->EnableMask == 0 ||
            (outputFile->EnableMask & EnableFlags) != 0 ){

            //
            // Either this output file gets everything, or the mask
            // matches.  Figure out which format to use... '.h' or '.inc'
            // style.
            //

            if( Type == SEF_STRING ){

                //
                // For SEF_STRING, strucArray->Name *is* the format string.
                //

                formatString = va_arg( arglist, PUCHAR );

            } else if( outputFile->IncFormat != FALSE ){

                //
                // Use the ".inc" format
                //

                formatString = Asm386FormatStringArray[ Type ];

            } else {

                //
                // Use the ".h" format
                //

                formatString = PreprocessedFormatStringArray[ Type ];
            }

            //
            // Now send it
            //

            vfprintf( outputFile->File, formatString, arglist );
        }

        va_end( arglist );

        //
        // Process all current output files.
        //

        outputFile = outputFile->Next;
    }
}

VOID
GetEnvironment( VOID )
{
    char *ntDrive;
    char *ntRoot;

    //
    // Set NtRoot = %_NTDRIVE%\%_NTROOT%
    //

    ntDrive = getenv( "_NTDRIVE" );
    ntRoot = getenv( "_NTROOT" );
    if( ntDrive != NULL && ntRoot != NULL ){

        sprintf( NtRoot, "%s%s", ntDrive, ntRoot );

    } else {

        //
        // If either _NTDRIVE or _NTROOT were not found in the environment,
        // let's try with \nt.
        //

        strcpy( NtRoot, "\\nt" );
    }
}

PSTRUC_ELEMENT
FindStructureElementArray(
    PCHAR Buffer,
    ULONG BufferSize
    )
{
    PCHAR searchPoint;
    PCHAR searchEndPoint;
    PSTRUC_ELEMENT strucElement;

    //
    // Search Buffer for the beginning of a structure element array.
    // The first element in this array contains MARKER_STRING.
    //

    searchPoint = Buffer;
    searchEndPoint = Buffer + BufferSize - sizeof( MarkerString );

    do{
        //
        // We scan the buffer a character at a time until we find a character
        // that matches the first character in MarkerString.
        //

        if( *searchPoint != MarkerString[ 0 ] ){
            continue;
        }

        //
        // When a matching char is found, the rest of the string is compared.
        //

        if( strcmp( searchPoint, MarkerString ) == 0 ){

            //
            // It matched too, we're done.
            //

            strucElement = CONTAINING_RECORD( searchPoint,
                                              STRUC_ELEMENT,
                                              Name );
            return strucElement;
        }

    } while( searchPoint++ < searchEndPoint );

    //
    // Fell out of the loop, we couldn't find the string.
    //

    return NULL;
}

VOID
Usage( VOID ){

    int platformIndex;
    PPLATFORM platform;


    printf("genxx: [");
    platform = PlatformList;
    while( platform->PlatformName != NULL ){

        printf("-%s|", platform->PlatformName );
        platform++;
    }

    printf("<objpath>] [-s<h|inc>] [-o<outputpath>]\n");
    exit(1);
}

VOID
ApplyFixupsToImage( VOID )
{
    //
    // Applies fixups to the OBJ image loaded at ObjImage
    //

    PIMAGE_FILE_HEADER fileHeader;
    PIMAGE_SECTION_HEADER sectionHeader;
    PIMAGE_SECTION_HEADER sectionHeaderArray;
    PIMAGE_SYMBOL symbolTable;
    PIMAGE_SYMBOL symbol;
    PIMAGE_RELOCATION reloc;
    PIMAGE_RELOCATION relocArray;
    ULONG sectionNum;
    ULONG relocNum;
    ULONG_PTR targetVa;
    PULONG_PTR fixupVa;

    fileHeader = (PIMAGE_FILE_HEADER)ObjImage;

    //
    // We need the symbol table to apply the fixups
    //

    symbolTable = (PIMAGE_SYMBOL)(ObjImage + fileHeader->PointerToSymbolTable);

    //
    // Get a pointer to the first element in the section header
    //

    sectionHeaderArray = (PIMAGE_SECTION_HEADER)(ObjImage +
                              sizeof( IMAGE_FILE_HEADER ) +
                              fileHeader->SizeOfOptionalHeader);

    //
    // Apply the fixups for each section
    //

    for( sectionNum = 0;
         sectionNum < fileHeader->NumberOfSections;
         sectionNum++ ){

        sectionHeader = &sectionHeaderArray[ sectionNum ];

        if (memcmp(sectionHeader->Name, ".data", sizeof(".data")+1)) {
            // Not .data - don't bother with the fixup
            continue;
        }

        //
        // Apply each fixup in this section
        //

        relocArray = (PIMAGE_RELOCATION)(ObjImage +
                                         sectionHeader->PointerToRelocations);
        for( relocNum = 0;
             relocNum < sectionHeader->NumberOfRelocations;
             relocNum++ ){

            reloc = &relocArray[ relocNum ];

            //
            // The relocation gives us the position in the image of the
            // relocation modification (VirtualAddress).  To find out what
            // to put there, we have to look the symbol up in the symbol index.
            //

            symbol = &symbolTable[ reloc->SymbolTableIndex ];

            targetVa =
                sectionHeaderArray[ symbol->SectionNumber-1 ].PointerToRawData;

            targetVa += symbol->Value;
            targetVa += (ULONG_PTR)ObjImage;

            fixupVa = (PULONG_PTR)(ObjImage +
                                  reloc->VirtualAddress +
                                  sectionHeader->PointerToRawData );

            *fixupVa = targetVa;
        }
    }
}

BOOLEAN
IsWhiteSpace(
    CHAR Char
    )
{
    if( Char == '\t' ||
        Char == ' '  ||
        Char == '\r' ||
        Char == '\n' ){

        return TRUE;
    } else {
        return FALSE;
    }
}

PCHAR
StripWhiteSpace(
    PCHAR String
    )
{
    PCHAR chr;
    ULONG strLen;

    strLen = strlen( String );
    if( strLen == 0 ){
        return String;
    }

    //
    // Strip off trailing whitespace
    //

    chr = String + strLen - 1;
    while( IsWhiteSpace( *chr )){
        *chr = '\0';
        chr--;
    }

    //
    // Advance past leading whitespace
    //

    chr = String;
    while( IsWhiteSpace( *chr )){
        chr++;
    }

    return chr;
}