/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    init.c

Abstract:

    Utility routines for sac driver

Author:

    Andrew Ritz (andrewr) - 15 June, 2000

Revision History:

--*/

#include "sac.h"

VOID
AppendMessage(
    PWSTR       OutPutBuffer,
    ULONG       MessageId,
    PWSTR       ValueBuffer OPTIONAL
    );

NTSTATUS
InsertRegistrySzIntoMachineInfoBuffer(
    PWSTR       KeyName,
    PWSTR       ValueName,
    ULONG       MessageId
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, PreloadGlobalMessageTable )
#pragma alloc_text( INIT, AppendMessage )
#pragma alloc_text( INIT, InsertRegistrySzIntoMachineInfoBuffer )
#pragma alloc_text( INIT, InitializeMachineInformation )
#endif


//
// Message Table routines.  We load all of our message table entries into a 
// global non-paged structure so that we can send text to HeadlessDispatch at
// any time.
//

typedef struct _MESSAGE_TABLE_ENTRY {
    ULONG             MessageId;
    PCWSTR             MessageText;
} MESSAGE_TABLE_ENTRY, *PMESSAGE_TABLE_ENTRY;

PMESSAGE_TABLE_ENTRY GlobalMessageTable;
ULONG          GlobalMessageTableCount;
PUCHAR         Utf8ConversionBuffer;
ULONG          Utf8ConversionBufferSize = MEMORY_INCREMENT;

#define MESSAGE_INITIAL 1
#define MESSAGE_FINAL 200


//
// Machine Information table and routines.
//
#define INIT_OBJA(Obja,UnicodeString,UnicodeText)           \
                                                            \
    RtlInitUnicodeString((UnicodeString),(UnicodeText));    \
                                                            \
    InitializeObjectAttributes(                             \
        (Obja),                                             \
        (UnicodeString),                                    \
        OBJ_CASE_INSENSITIVE,                               \
        NULL,                                               \
        NULL                                                \
        )

extern char *GlobalBuffer;

PWSTR MachineInformationBuffer = NULL;


extern
BOOLEAN
ExVerifySuite(
    SUITE_TYPE SuiteType
    );

VOID
SacFormatMessage(
    PWSTR       OutputString,
    PWSTR       InputString,
    ULONG       InputStringLength
    )
/*++

Routine Description:

    This routine parses the InputString for any control characters in the
    message, then converts those control characters.
    
Arguments:

    OutputString      - holds formatted string.

    InputString       - original unformatted string.

    InputStringLength - length of unformatted string.


Return Value:

    NONE

--*/
{

    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC SacFormatMessage: Entering.\n")));


    if( (InputString == NULL) ||
        (OutputString == NULL) ||
        (InputStringLength == 0) ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC SacFormatMessage: Exiting with invalid parameters.\n")));

        return;
    }



    while( (*InputString != L'\0') &&
           (InputStringLength) ) {
        if( *InputString == L'%' ) {
            
            //
            // Possibly a control sequence.
            //
            if( *(InputString+1) == L'0' ) {

                *OutputString = L'\0';
                OutputString++;
                goto SacFormatMessage_Done;

            } else if( *(InputString+1) == L'%' ) {

                *OutputString = L'%';
                OutputString++;
                InputString += 2;

            } else if( *(InputString+1) == L'\\' ) {

                *OutputString = L'\r';
                OutputString++;
                *OutputString = L'\n';
                OutputString++;
                InputString += 2;

            } else if( *(InputString+1) == L'r' ) {

                *OutputString = L'\r';
                InputString += 2;
                OutputString++;

            } else if( *(InputString+1) == L'b' ) {

                *OutputString = L' ';
                InputString += 2;
                OutputString++;

            } else if( *(InputString+1) == L'.' ) {

                *OutputString = L'.';
                InputString += 2;
                OutputString++;

            } else if( *(InputString+1) == L'!' ) {

                *OutputString = L'!';
                InputString += 2;
                OutputString++;

            } else {

                //
                // Don't know what this is.  eat the '%' character.
                //
                InputString += 1;
            }
    
        } else {

            *OutputString++ = *InputString++;
        }

        InputStringLength--;

    }


    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC SacFormatMessage: Exiting.\n")));

SacFormatMessage_Done:

    return;
}


NTSTATUS
PreloadGlobalMessageTable(
    PVOID ImageBase
    )
/*++

Routine Description:

    This routine loads all of our message table entries into a global
    structure and 
    
Arguments:

    ImageBase - pointer to image base for locating resources

Return Value:

    NTSTATUS code indicating outcome.

--*/
{
    ULONG Count,EntryCount;
    SIZE_T TotalSizeInBytes = 0;
    NTSTATUS Status;
    PMESSAGE_RESOURCE_ENTRY messageEntry;
    PWSTR pStringBuffer;
    
    PAGED_CODE( );

    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC PreloadGlobalMessageTable: Entering.\n")));


    // 
    // if it already exists, then just return success
    //
    if (GlobalMessageTable != NULL) {
        Status = STATUS_SUCCESS;
        goto exit;
    }

    ASSERT( MESSAGE_FINAL > MESSAGE_INITIAL );

    //
    // get the total required size for the table.
    //
    for (Count = MESSAGE_INITIAL; Count != MESSAGE_FINAL ; Count++) {
        
        Status = RtlFindMessage(ImageBase,
                                11, // RT_MESSAGETABLE
                                LANG_NEUTRAL,
                                Count,
                                &messageEntry
                               );

        if (NT_SUCCESS(Status)) {
            //
            // add it on to our total size.
            //
            // the messageEntry size contains the structure size + the size of the text.
            //
            ASSERT(messageEntry->Flags & MESSAGE_RESOURCE_UNICODE);
            TotalSizeInBytes += sizeof(MESSAGE_TABLE_ENTRY) + 
                                (messageEntry->Length - FIELD_OFFSET(MESSAGE_RESOURCE_ENTRY, Text));
            GlobalMessageTableCount +=1;        
        }
            
    }


    if (TotalSizeInBytes == 0) {
        IF_SAC_DEBUG(
            SAC_DEBUG_FAILS,
            KdPrint(("SAC PreloadGlobalMessageTable: No Messages.\n")));
        Status = STATUS_INVALID_PARAMETER;
        goto exit;
    }

    //
    // Allocate space for the table.
    //
    GlobalMessageTable = (PMESSAGE_TABLE_ENTRY) ALLOCATE_POOL( TotalSizeInBytes, GENERAL_POOL_TAG);
    if (!GlobalMessageTable) {
        Status = STATUS_NO_MEMORY;
        goto exit;
    }

    //
    // go through again, this time filling out the table with actual data
    //
    pStringBuffer = (PWSTR)((ULONG_PTR)GlobalMessageTable + 
                        (ULONG_PTR)(sizeof(MESSAGE_TABLE_ENTRY)*GlobalMessageTableCount));
    EntryCount = 0;
    for (Count = MESSAGE_INITIAL ; Count != MESSAGE_FINAL ; Count++) {
        Status = RtlFindMessage(ImageBase,
                                11, // RT_MESSAGETABLE
                                LANG_NEUTRAL,
                                Count,
                                &messageEntry
                               );

        if (NT_SUCCESS(Status)) {
            ULONG TextSize = messageEntry->Length - FIELD_OFFSET(MESSAGE_RESOURCE_ENTRY, Text);
            GlobalMessageTable[EntryCount].MessageId = Count;
            GlobalMessageTable[EntryCount].MessageText = pStringBuffer;

            //
            // Send the message through our Formatting filter as it passes
            // into our global message structure.
            //
            SacFormatMessage( pStringBuffer, (PWSTR)messageEntry->Text, TextSize );

            ASSERT( (wcslen(pStringBuffer)*sizeof(WCHAR)) <= TextSize );

            pStringBuffer = (PWSTR)((ULONG_PTR)pStringBuffer + (ULONG_PTR)(TextSize));
            EntryCount += 1;
        }
    }

    Status = STATUS_SUCCESS;
                    
exit:
    IF_SAC_DEBUG(
        SAC_DEBUG_FUNC_TRACE, 
        KdPrint(("SAC PreloadGlobalMessageTable: Exiting with status 0x%0x.\n",
                Status)));

    return(Status);

}

NTSTATUS
TearDownGlobalMessageTable(
    VOID
    ) 
{
    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC PreloadGlobalMessageTable: Entering.\n")));
    
    if (GlobalMessageTable) {
        FREE_POOL( &GlobalMessageTable );
    }

    IF_SAC_DEBUG(
        SAC_DEBUG_FUNC_TRACE, 
        KdPrint(("SAC TearDownGlobalMessageTable: Exiting\n")));

    return(STATUS_SUCCESS);
}

PCWSTR
GetMessage(
    ULONG MessageId
    )
{
    PMESSAGE_TABLE_ENTRY pMessageTable;
    ULONG Count;
    
    if (!GlobalMessageTable) {
        return(NULL);
    }

    for (Count = 0; Count < GlobalMessageTableCount; Count++) {
        pMessageTable = &GlobalMessageTable[Count];
        if (pMessageTable->MessageId == MessageId) {

            
            return(pMessageTable->MessageText);
        }
    }


    ASSERT( FALSE );
    return(NULL);

}

BOOLEAN
SacTranslateUnicodeToUtf8(
    PCWSTR SourceBuffer,
    UCHAR  *DestinationBuffer
    )
{
    ULONG Count = 0;

    //
    // convert into UTF8 for actual transmission
    //
    // UTF-8 encodes 2-byte Unicode characters as follows:
    // If the first nine bits are zero (00000000 0xxxxxxx), encode it as one byte 0xxxxxxx
    // If the first five bits are zero (00000yyy yyxxxxxx), encode it as two bytes 110yyyyy 10xxxxxx
    // Otherwise (zzzzyyyy yyxxxxxx), encode it as three bytes 1110zzzz 10yyyyyy 10xxxxxx
    //
    DestinationBuffer[Count] = (UCHAR)'\0';
    while (*SourceBuffer) {

        if( (*SourceBuffer & 0xFF80) == 0 ) {
            //
            // if the top 9 bits are zero, then just
            // encode as 1 byte.  (ASCII passes through unchanged).
            //
            DestinationBuffer[Count++] = (UCHAR)(*SourceBuffer & 0x7F);
        } else if( (*SourceBuffer & 0xF700) == 0 ) {
            //
            // if the top 5 bits are zero, then encode as 2 bytes
            //
            DestinationBuffer[Count++] = (UCHAR)((*SourceBuffer >> 6) & 0x1F) | 0xC0;
            DestinationBuffer[Count++] = (UCHAR)(*SourceBuffer & 0xBF) | 0x80;
        } else {
            //
            // encode as 3 bytes
            //
            DestinationBuffer[Count++] = (UCHAR)((*SourceBuffer >> 12) & 0xF) | 0xE0;
            DestinationBuffer[Count++] = (UCHAR)((*SourceBuffer >> 6) & 0x3F) | 0x80;
            DestinationBuffer[Count++] = (UCHAR)(*SourceBuffer & 0xBF) | 0x80;
        }
        SourceBuffer += 1;
    }

    DestinationBuffer[Count] = (UCHAR)'\0';

    return(TRUE);

}

BOOLEAN
SacPutUnicodeString(
    PCWSTR String
    )
{
    if (!Utf8ConversionBuffer) {
        return(FALSE);
    }

    ASSERT( (wcslen(String)+1)*sizeof(WCHAR) < Utf8ConversionBufferSize );

    SacTranslateUnicodeToUtf8(String,Utf8ConversionBuffer);
    SacPutString(Utf8ConversionBuffer);

    return(TRUE);
}

BOOLEAN
SacPutSimpleMessage(
    ULONG MessageId
    )
{
    PCWSTR p;

    p = GetMessage(MessageId);

    if (p) {
        SacPutUnicodeString(p);        
        return(TRUE);
    }
    
    return(FALSE);

}

VOID
SacPutString(
    PUCHAR String
    )

/*++

Routine Description:

    This routine takes a string and packages it into a command structure for the
    HeadlessDispatch routine.

Arguments:

    String - The string to display.

Return Value:

        None.

--*/
{
    SIZE_T Length;

    ASSERT(FIELD_OFFSET(HEADLESS_CMD_PUT_STRING, String) == 0);  // ASSERT if anyone changes this structure.
    
    Length = strlen((LPSTR)String) + sizeof('\0');

    HeadlessDispatch(HeadlessCmdPutString,
                     (PHEADLESS_CMD_PUT_STRING)String,
                     Length,
                     NULL,
                     NULL
                    );
}




VOID
AppendMessage(
    PWSTR       OutPutBuffer,
    ULONG       MessageId,
    PWSTR       ValueBuffer OPTIONAL
    )
/*++

Routine Description:

    This function will insert the valuestring into the specified message, then
    concatenate the resulting string onto the OutPutBuffer.

Arguments:
    
    OutPutBuffer    The resulting String.

    MessageId       ID of the formatting message to use.

    ValueBUffer     Value string to be inserted into the message.

Return Value:

    NONE

--*/
{
PWSTR       MyTemporaryBuffer = NULL;
PCWSTR      p;

    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC AppendMessage: Entering.\n")));

    p = GetMessage(MessageId);

    if( p == NULL ) {
        return;
    }

    if( ValueBuffer == NULL ) {

        wcscat( OutPutBuffer, p );

    } else {

        MyTemporaryBuffer = (PWSTR)(wcschr(OutPutBuffer, L'\0'));
        if( MyTemporaryBuffer == NULL ) {
            MyTemporaryBuffer = OutPutBuffer;
        }

        swprintf( MyTemporaryBuffer, p, ValueBuffer );
    }

    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC AppendMessage: Entering.\n")));

    return;
}


NTSTATUS
InsertRegistrySzIntoMachineInfoBuffer(
    PWSTR       KeyName,
    PWSTR       ValueName,
    ULONG       MessageId
    )
/*++

Routine Description:

    This function will go query the registry and pull the specified Value.
    We will then insert this value into the specified message, then concatenate
    the resulting string into our MachineInformationBuffer.

Arguments:
    
    KeyName         Name of the registry key we'll be querying.

    ValueName       Name of the registry value we'll be querying.

    MessageId       ID of the formatting message to use.

Return Value:

    NTSTATUS.

--*/
{
NTSTATUS        Status = STATUS_SUCCESS;
ULONG           KeyValueLength = 0;
PKEY_VALUE_PARTIAL_INFORMATION ValueBuffer = NULL;
OBJECT_ATTRIBUTES Obja;
UNICODE_STRING  UnicodeString;
HANDLE          KeyHandle;



    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC InsertRegistrySzIntoMachineInfoBuffer: Entering.\n")));

    INIT_OBJA( &Obja, &UnicodeString, KeyName );
    Status = ZwOpenKey( &KeyHandle,
                        KEY_READ | KEY_WRITE,
                        &Obja );

    if( !NT_SUCCESS(Status) ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InsertRegistrySzIntoMachineInfoBuffer: Exiting (1).\n")));
        return Status;
    }

    RtlInitUnicodeString( &UnicodeString, ValueName );
    KeyValueLength = 0;
    Status = ZwQueryValueKey( KeyHandle,
                              &UnicodeString,
                              KeyValuePartialInformation,
                              (PVOID)NULL,
                              0,
                              &KeyValueLength );

    if( KeyValueLength == 0 ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InsertRegistrySzIntoMachineInfoBuffer: Exiting (2).\n")));
        return Status;
    }


    KeyValueLength += 4;
    ValueBuffer = (PKEY_VALUE_PARTIAL_INFORMATION)ALLOCATE_POOL( KeyValueLength, GENERAL_POOL_TAG );

    if( ValueBuffer == NULL ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InsertRegistrySzIntoMachineInfoBuffer: Exiting (3).\n")));
        return Status;
    }

    Status = ZwQueryValueKey( KeyHandle,
                              &UnicodeString,
                              KeyValuePartialInformation,
                              ValueBuffer,
                              KeyValueLength,
                              &KeyValueLength );

    if( !NT_SUCCESS(Status) ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InsertRegistrySzIntoMachineInfoBuffer: Exiting (4).\n")));
        FREE_POOL( &ValueBuffer );
        return Status;
    }

    AppendMessage( MachineInformationBuffer,
                   MessageId,
                   (PWSTR)ValueBuffer->Data );

    FREE_POOL( &ValueBuffer );

    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC InsertRegistrySzIntoMachineInfoBuffer: Exiting.\n")));

    return Status;

}

VOID
InitializeMachineInformation(
    VOID
    )
/*++

Routine Description:

    This function initializes the global variable MachineInformationBuffer.

    We'll gather a whole bunch of information about the machine and fill
    in the buffer.

Arguments:
    
    None.

Return Value:

    None.

--*/
{
#define         MACHINEINFORMATIONBUFFER_SIZE (512 * sizeof(WCHAR))
PWSTR           COMPUTERNAME_KEY_NAME  = L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\ComputerName\\ComputerName";
PWSTR           COMPUTERNAME_VALUE_NAME  = L"ComputerName";
PWSTR           PROCESSOR_ARCHITECTURE_KEY_NAME  = L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Session Manager\\Environment";
PWSTR           PROCESSOR_ARCHITECTURE_VALUE_NAME  = L"PROCESSOR_ARCHITECTURE";

PSTR            XML_TAG = "MACHINEINFO";
PSTR            XML_HEADER0 = "\r\n<PROPERTY.ARRAY NAME=\"MACHINEINFO\" TYPE=\"string\">\r\n";
PSTR            XML_HEADER1 = "<VALUE.ARRAY>\r\n";
PSTR            XML_HEADER2 = "<VALUE>\"%ws\"</VALUE>\r\n";
PSTR            XML_FOOTER0 = "</VALUE.ARRAY>\r\n</PROPERTY.ARRAY>";

NTSTATUS        Status = STATUS_SUCCESS;
SIZE_T          i;
RTL_OSVERSIONINFOEXW VersionInfo;
PWSTR           MyTemporaryBufferW = NULL;
PHEADLESS_CMD_SET_BLUE_SCREEN_DATA BSBuffer;
PUCHAR          pch;
ULONG           len;
GUID            MyGUID;
PCWSTR          pwStr = NULL;




    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC Initialize Machine Information: Entering.\n")));


    if( MachineInformationBuffer != NULL ) {

        //
        // someone called us again!
        //
        IF_SAC_DEBUG( SAC_DEBUG_FUNC_TRACE_LOUD, 
                      KdPrint(("SAC Initialize Machine Information:: MachineInformationBuffer already initialzied.\n")));

        return;
    } else {

        MachineInformationBuffer = (PWCHAR)ALLOCATE_POOL( MACHINEINFORMATIONBUFFER_SIZE, GENERAL_POOL_TAG );

        if( MachineInformationBuffer == NULL ) {

            goto InitializeMachineInformation_Failure;
        }
    }

    RtlZeroMemory( MachineInformationBuffer, MACHINEINFORMATIONBUFFER_SIZE );



    //
    // We're real early in the boot process, so we're going to take for granted that the machine hasn't
    // bugchecked.  This means that we can safely call some kernel functions to go figure out what
    // platform we're running on.
    //
    RtlZeroMemory( &VersionInfo, sizeof(VersionInfo));
    Status = RtlGetVersion( (POSVERSIONINFOW)&VersionInfo );

    if( !NT_SUCCESS(Status) ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InitializeMachineInformation: Exiting (2).\n")));
        goto InitializeMachineInformation_Failure;
    }


    //
    // ========
    // Machine name.
    // ========
    //
    Status = InsertRegistrySzIntoMachineInfoBuffer( COMPUTERNAME_KEY_NAME,
                                                    COMPUTERNAME_VALUE_NAME,
                                                    SAC_MACHINEINFO_COMPUTERNAME );

    if( !NT_SUCCESS(Status) ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InitializeMachineInformation: Exiting (20).\n")));
        goto InitializeMachineInformation_Failure;
    }



    //
    // ========
    // Machine GUID.
    // ========
    //

    // make sure.
    RtlZeroMemory( &MyGUID, sizeof(GUID) );
    i = sizeof(GUID);
    Status = HeadlessDispatch( HeadlessCmdQueryGUID,
                               NULL,
                               0,
                               &MyGUID,
                               &i );

    if( !NT_SUCCESS(Status) ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InitializeMachineInformation: Exiting (30).\n")));
        goto InitializeMachineInformation_Failure;
    }

    //
    // Allocate enough memory for the formatting message, plus the size of a
    // GUID-turned-text, which is 2x the number of bytes required to hold the binary,
    // plus 8 bytes of slop.
    //
    pwStr = GetMessage(SAC_MACHINEINFO_GUID);

    if( pwStr ) {

        MyTemporaryBufferW = (PWSTR)ALLOCATE_POOL( (sizeof(GUID)*2) + (wcslen(pwStr) + 8) * sizeof(WCHAR) , GENERAL_POOL_TAG );
        if( MyTemporaryBufferW == NULL ) {

            IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InitializeMachineInformation: Exiting (31).\n")));
            goto InitializeMachineInformation_Failure;
        }

        swprintf( MyTemporaryBufferW,
                  GetMessage(SAC_MACHINEINFO_GUID),
                  MyGUID.Data1,
                  MyGUID.Data2,
                  MyGUID.Data3,
                  MyGUID.Data4[0],
                  MyGUID.Data4[1],
                  MyGUID.Data4[2],
                  MyGUID.Data4[3],
                  MyGUID.Data4[4],
                  MyGUID.Data4[5],
                  MyGUID.Data4[6],
                  MyGUID.Data4[7] );

        wcscat( MachineInformationBuffer, MyTemporaryBufferW );

        FREE_POOL( &MyTemporaryBufferW );
    }


    //
    // ========
    // Processor Architecture.
    // ========
    //
    Status = InsertRegistrySzIntoMachineInfoBuffer( PROCESSOR_ARCHITECTURE_KEY_NAME,
                                                    PROCESSOR_ARCHITECTURE_VALUE_NAME,
                                                    SAC_MACHINEINFO_PROCESSOR_ARCHITECTURE );

    if( !NT_SUCCESS(Status) ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InitializeMachineInformation: Exiting (40).\n")));
        goto InitializeMachineInformation_Failure;
    }



    //
    // ========
    // OS Name.
    // ========
    //

    //
    // Allocate enough memory for the formatting message, plus the size of 2 digits.
    // Currently, our versioning info is of the type "5.1", so we don't need much space
    // here, but let's be conservative and assume both major and minor version numbers
    // are 4 digits in size.  That's 9 characters.
    //
    MyTemporaryBufferW = (PWSTR)ALLOCATE_POOL( (wcslen(GetMessage(SAC_MACHINEINFO_OS_VERSION)) + 9) * sizeof(WCHAR), GENERAL_POOL_TAG );
    if( MyTemporaryBufferW == NULL ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InitializeMachineInformation: Exiting (50).\n")));
        goto InitializeMachineInformation_Failure;
    }

    swprintf( MyTemporaryBufferW,
              GetMessage(SAC_MACHINEINFO_OS_VERSION),
              VersionInfo.dwMajorVersion,
              VersionInfo.dwMinorVersion );

    wcscat( MachineInformationBuffer, MyTemporaryBufferW );

    FREE_POOL( &MyTemporaryBufferW );



    //
    // ========
    // Build Number.
    // ========
    //

    //
    // Allocate enough memory for the formatting message, plus the size of our build number.
    // Currently that's well below the 5-digit mark, but let's build some headroom here for
    // build numbers up to 99000 (5-digits).
    //
    MyTemporaryBufferW = (PWSTR)ALLOCATE_POOL( (wcslen(GetMessage(SAC_MACHINEINFO_OS_BUILD)) + 5) * sizeof(WCHAR), GENERAL_POOL_TAG );
    if( MyTemporaryBufferW == NULL ) {

        IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InitializeMachineInformation: Exiting (60).\n")));
        goto InitializeMachineInformation_Failure;
    }

    swprintf( MyTemporaryBufferW,
              GetMessage(SAC_MACHINEINFO_OS_BUILD),
              VersionInfo.dwBuildNumber );

    wcscat( MachineInformationBuffer, MyTemporaryBufferW );

    FREE_POOL( &MyTemporaryBufferW );



    //
    // ========
    // Product Type (and Suite).
    // ========
    //
    if( ExVerifySuite(DataCenter) ) {

        AppendMessage( MachineInformationBuffer,
                       SAC_MACHINEINFO_OS_PRODUCTTYPE,
                       (PWSTR)GetMessage(SAC_MACHINEINFO_DATACENTER) );

    } else if( ExVerifySuite(EmbeddedNT) ) {

        AppendMessage( MachineInformationBuffer,
                       SAC_MACHINEINFO_OS_PRODUCTTYPE,
                       (PWSTR)GetMessage(SAC_MACHINEINFO_EMBEDDED) );

    } else if( ExVerifySuite(Enterprise) ) {

        AppendMessage( MachineInformationBuffer,
                       SAC_MACHINEINFO_OS_PRODUCTTYPE,
                       (PWSTR)GetMessage(SAC_MACHINEINFO_ADVSERVER) );

    } else {

        //
        // We found no product suite that we recognized or cared about.
        // Assume we're running on a generic server.
        //
        AppendMessage( MachineInformationBuffer,
                       SAC_MACHINEINFO_OS_PRODUCTTYPE,
                       (PWSTR)GetMessage(SAC_MACHINEINFO_SERVER) );
    }



    //
    // ========
    // Service Pack Information.
    // ========
    //
    if( VersionInfo.wServicePackMajor != 0 ) {

        //
        // There's been a service pack applied.  Better tell the user.
        //


        //
        // Allocate enough memory for the formatting message, plus the size of our servicepack number.
        // Currently that's well below the 5-digit mark, but let's build some headroom here for
        // service pack numbers up to 99000 (5-digits).
        //
        MyTemporaryBufferW = (PWSTR)ALLOCATE_POOL( (wcslen(GetMessage(SAC_MACHINEINFO_SERVICE_PACK)) + 5) * sizeof(WCHAR), GENERAL_POOL_TAG );
        if( MyTemporaryBufferW == NULL ) {

            IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, KdPrint(("SAC InitializeMachineInformation: Exiting (80).\n")));
            goto InitializeMachineInformation_Failure;
        }

        swprintf( MyTemporaryBufferW,
                  GetMessage(SAC_MACHINEINFO_SERVICE_PACK),
                  VersionInfo.wServicePackMajor,
                  VersionInfo.wServicePackMinor );

        wcscat( MachineInformationBuffer, MyTemporaryBufferW );

        FREE_POOL( &MyTemporaryBufferW );
    } else {

        AppendMessage( MachineInformationBuffer, SAC_MACHINEINFO_NO_SERVICE_PACK, NULL );
    }




    //
    // ========
    // Insert it all into the BLUESCREEN data.
    // ========
    //


    //
    // How big is this going to be?
    //
    i = wcslen(MachineInformationBuffer);
    i += strlen(XML_TAG);
    i += strlen(XML_HEADER0);
    i += strlen(XML_HEADER1);
    i += strlen(XML_HEADER2);
    i += strlen(XML_FOOTER0);
    i += 8;

    BSBuffer = (PHEADLESS_CMD_SET_BLUE_SCREEN_DATA)ALLOCATE_POOL( i,
                                                                  GENERAL_POOL_TAG );

    if( BSBuffer == NULL ) {

        goto InitializeMachineInformation_Failure;
    }

    pch = &(BSBuffer->Data[0]);
    len = sprintf( (LPSTR)pch, XML_TAG );
    BSBuffer->ValueIndex = len+1;
    pch = pch+len+1;
    len = sprintf( (LPSTR)pch, "%s%s", XML_HEADER0, XML_HEADER1 );
    pch = pch + len;
    len = sprintf( (LPSTR)pch, XML_HEADER2, MachineInformationBuffer );
    strcat( (LPSTR)pch, XML_FOOTER0 );

    HeadlessDispatch( HeadlessCmdSetBlueScreenData,
                      BSBuffer,
                      wcslen(MachineInformationBuffer)+256,
                      NULL,
                      0 );

    FREE_POOL( &BSBuffer );

    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                      KdPrint(("SAC Initialize Machine Information: Exiting.\n")));

    return;




InitializeMachineInformation_Failure:
    if( MachineInformationBuffer != NULL ) {
        FREE_POOL(&MachineInformationBuffer);
        MachineInformationBuffer = NULL;
    }

    IF_SAC_DEBUG(SAC_DEBUG_FUNC_TRACE, 
                    KdPrint(("SAC Initialize Machine Information: Exiting with error.\n")));
    return;

}