//--------------------------------------------------------------
//
// File:        bothchar
//
// Contents:    Functions that need to be compiled as ascii for
//              scanstate and unicode for loadstate.
//
//---------------------------------------------------------------

#include "bothchar.hxx"


//---------------------------------------------------------------
// Constants

UCHAR       EMPTY_STRING[] = "";

const UCHAR NEWLINE_SET[256] =
{
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

const DWORD VERBOSE_BIT                 = 0x01;   // used with -v flag
const DWORD DEBUGOUTPUT_BIT             = 0x02;   // used with -v flag
const DWORD VERBOSEREG_BIT              = 0x04;   // used with -v flag

#define LOADSTATE_KEY TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Loadstate")

//---------------------------------------------------------------
// Globals.
TCHAR  *DomainName    = NULL;
TCHAR  *UserName      = NULL;
TCHAR  *UserPath      = NULL;

//---------------------------------------------------------------
void CStringList::Add( CStringList *pslMore )
{
    CStringList *b;
    CStringList *c;
    CStringList *d;

    // Do nothing if there is no list.
    if (pslMore == NULL)
        return;

    // Determine some nodes to work with.
    b = pslMore->_pslNext;
    c = pslMore;
    d = _pslNext;

    // Relink the list so head points to b is a list to c points to d is
    // a list back to head.
    _pslNext    = b;
    c->_pslNext = d;
}


//---------------------------------------------------------------
CStringList::CStringList( DWORD dwLen )
{
    _pslNext = this;
    if (dwLen == 0)
    {
        _ptsString = NULL;
        _fHead = TRUE;
    }
    else
    {
        _fHead = FALSE;
        _ptsString = (TCHAR *) malloc( dwLen*sizeof(TCHAR) );
    }
}

//---------------------------------------------------------------
CStringList::~CStringList()
{
    CStringList *pslCurrent = _pslNext;
    CStringList *pslEnd;

    // Non header nodes just free their string.
    if (_ptsString != NULL)
        free( _ptsString );
    
    // Header nodes free the list.
    if (_fHead)
    {
        while (pslCurrent != this)
        {
            pslEnd = pslCurrent->_pslNext;
            delete pslCurrent;
            pslCurrent = pslEnd;
        }
    }
}

//---------------------------------------------------------------
DWORD ParseSectionList( INFCONTEXT *pic,
                        TCHAR **pptsLabel,
                        CStringList **pslList )
{
    DWORD        len;
    BOOL         fSuccess;
    DWORD        dwFields;
    DWORD        i;
    DWORD        dwResult = ERROR_SUCCESS;
    CStringList *pslCurrent;

    // Initialize output
    *pslList = NULL;

    // Query the length of the label name
    fSuccess = SetupGetStringField( pic, 0, NULL, 0, &len );
    LOG_ASSERT_GLE( fSuccess, dwResult );

    // Allocate space
    *pptsLabel = (TCHAR *) malloc( len * sizeof(TCHAR) );    
    LOG_ASSERT_EXPR( *pptsLabel != NULL, IDS_NOT_ENOUGH_MEMORY, dwResult,
                     ERROR_NOT_ENOUGH_MEMORY );

    // Read the label name
    fSuccess = SetupGetStringField( pic, 0, *pptsLabel, len, NULL );
    LOG_ASSERT_GLE( fSuccess, dwResult );

    // Find out how many fields are on the line.
    dwFields = SetupGetFieldCount( pic );
    LOG_ASSERT_GLE( dwFields != 0, dwResult );

    // Read each field.
    for (i = 1; i <= dwFields; i++)
    {
        // Query the length of the field
        fSuccess = SetupGetStringField( pic, i, NULL, 0, &len );
        LOG_ASSERT_GLE( fSuccess, dwResult );

        // Allocate a new node.
        pslCurrent = new CStringList( len );
        LOG_ASSERT_EXPR( pslCurrent != NULL,
                         IDS_NOT_ENOUGH_MEMORY,
                         dwResult,
                         ERROR_NOT_ENOUGH_MEMORY);
        
        LOG_ASSERT_EXPR( pslCurrent->String() != NULL,
                         IDS_NOT_ENOUGH_MEMORY,
                         dwResult,
                         ERROR_NOT_ENOUGH_MEMORY );

        // Copy the field into the node.
        fSuccess = SetupGetStringField( pic, i, pslCurrent->String(), len, NULL );
        LOG_ASSERT_GLE( fSuccess, dwResult );

        // Link the node in the list.
        if (*pslList == NULL)
            *pslList = pslCurrent;
        else
            (*pslList)->Add( pslCurrent );
    }

cleanup:
    return dwResult;
}

/***************************************************************************

        ParseRegPath

     Read a string field from a line in the rules inf file and parse it
into a root, key, and value.  All components of the reg path are optional.

        root\key [value]

***************************************************************************/

DWORD ParseRegPath( INFCONTEXT *pic,
                    DWORD dwField,
                    TCHAR **pptsRoot,
                    TCHAR **pptsKey,
                    TCHAR **pptsValue )
{
    TCHAR *ptsBuffer = NULL;
    TCHAR *ptsStop;
    TCHAR *ptsStart;
    TCHAR *bracket;
    BOOL   fSuccess;
    DWORD  dwLen    = 0;
    DWORD  dwResult = ERROR_SUCCESS;

    //Null out return values in case of later error.
    *pptsRoot  = NULL;
    *pptsKey   = NULL;
    *pptsValue = NULL;
    
    // Compute the length of the reg path field.
    SetupGetStringField( pic, dwField, NULL, 0, &dwLen );

    // Allocate a buffer.
    ptsBuffer = (TCHAR *) malloc( dwLen*sizeof(TCHAR) );
    LOG_ASSERT_EXPR( ptsBuffer != NULL, IDS_NOT_ENOUGH_MEMORY, dwResult,
                     ERROR_NOT_ENOUGH_MEMORY );

    // Get the whole reg path.  The function fails if the field is empty.
    fSuccess = SetupGetStringField( pic, dwField, ptsBuffer, dwLen, &dwLen );
    if (!fSuccess)
    {
        free( ptsBuffer );
        return ERROR_SUCCESS;
    }

    // Look for a backslash.
    ptsStop = _tcschr( ptsBuffer, TEXT('\\') );

    // If there wasn't one, there is no root.
    if (ptsStop == NULL)
    {
        *pptsRoot = NULL;
        ptsStart = ptsBuffer;
    }

    // If there was one, copy the root name.
    else
    {
        *pptsRoot = (TCHAR *) malloc( ((ptsStop - ptsBuffer) + 1) *
                                      sizeof(TCHAR) );
    
        LOG_ASSERT_EXPR( *pptsRoot != NULL,
                         IDS_NOT_ENOUGH_MEMORY,
                         dwResult,
                         ERROR_NOT_ENOUGH_MEMORY );
        _tcsncpy( *pptsRoot, ptsBuffer, ptsStop - ptsBuffer );
        (*pptsRoot)[ptsStop - ptsBuffer] = 0;
        ptsStart = ptsStop + 1;
    }

    // Look for an opening square bracket.
    ptsStop = _tcschr( ptsStart, TEXT('[') );

    // If there wasn't one, copy the rest of the string to the key name.
    if (ptsStop == NULL)
    {
        if (ptsStart[0] == 0)
            *pptsKey = NULL;
        else
        {
            *pptsKey   = (TCHAR *) malloc( (dwLen - (ptsStart - ptsBuffer)) *
                                           sizeof(TCHAR) );
            *pptsValue = NULL;
            LOG_ASSERT_EXPR( *pptsKey != NULL,
                             IDS_NOT_ENOUGH_MEMORY,
                             dwResult,
                             ERROR_NOT_ENOUGH_MEMORY );
            _tcscpy( *pptsKey, ptsStart );
        }
    }

    // Handle an optional key and a value.
    else
    {
        // Back up past any intervening white space.
        bracket = ptsStop + 1;
        while (ptsStop != ptsStart &&
               (ptsStop[0] == TEXT(' ') || ptsStop[0] == TEXT('[')))
            ptsStop -= 1;

        // If there are any characters left, copy them to the key.
        if (ptsStop != ptsStart)
        {
            ptsStop += 1;
            *pptsKey   = (TCHAR *) malloc( ((ptsStop - ptsStart) + 1) *
                                           sizeof(TCHAR) );
      
            LOG_ASSERT_EXPR( *pptsKey != NULL,
                             IDS_NOT_ENOUGH_MEMORY,
                             dwResult,
                             ERROR_NOT_ENOUGH_MEMORY );
            _tcsncpy( *pptsKey, ptsStart, ptsStop - ptsStart );
            (*pptsKey)[ptsStop - ptsStart] = 0;
        }
        else
            *pptsKey = NULL;

        // Find the closing square bracket.
        ptsStart = bracket;
        bracket = _tcschr( ptsStart, TEXT(']') );
        LOG_ASSERT_EXPR( bracket != NULL,
                         IDS_INF_ERROR,
                         dwResult,
                         SPAPI_E_GENERAL_SYNTAX );

        // Copy the value name.
        *pptsValue = (TCHAR *) malloc( ((bracket - ptsStart) + 1) *
                                       sizeof(TCHAR) );
    
        LOG_ASSERT_EXPR( *pptsValue != NULL,
                         IDS_NOT_ENOUGH_MEMORY,
                         dwResult,
                         ERROR_NOT_ENOUGH_MEMORY );
        _tcsncpy( *pptsValue, ptsStart, bracket - ptsStart );
        (*pptsValue)[bracket - ptsStart] = 0;
    }

cleanup:
    if (ptsBuffer != NULL)
        free( ptsBuffer );
    return dwResult;
}

/***************************************************************************

        ParseRule

     Read a line from the rules inf file in one of the following formats.
When partial paths are specified, compute the full path. Create a rule
record from the line.

     reg_path2 is optional and reg_path is parsed by the function
ParseRegPath into a root, key, and value.
        reg-path1 = reg_path2

     If reg_path2 contains just a leaf name, generate a full path using
the path from reg_path1 and replacing its leaf with the leaf from reg_path2.

***************************************************************************/

DWORD ParseRule( INFCONTEXT *pic, HASH_NODE **pphnRule )
{
    TCHAR     *ptsBuffer;
    DWORD      dwReqLen;
    DWORD      dwResult = ERROR_SUCCESS;
    BOOL       fSuccess;
    TCHAR     *ptsLast;
    TCHAR     *ptsTemp;

    // Allocate a new rule.
    *pphnRule = (HASH_NODE *) malloc( sizeof(HASH_NODE) );
    LOG_ASSERT_EXPR( *pphnRule != NULL, IDS_NOT_ENOUGH_MEMORY, dwResult,
                     ERROR_NOT_ENOUGH_MEMORY );
    (*pphnRule)->dwAction    = 0;
    (*pphnRule)->phnNext     = 0;
    (*pphnRule)->ptsNewValue = NULL;
    (*pphnRule)->ptsNewKey   = NULL;
    (*pphnRule)->ptsFunction = NULL;
    (*pphnRule)->ptsFileDest = NULL;

    // Get the first reg path.
    dwResult = ParseRegPath( pic,
                             0,
                             &(*pphnRule)->ptsRoot,
                             &(*pphnRule)->ptsKey,
                             &(*pphnRule)->ptsValue );
    FAIL_ON_ERROR( dwResult );

    // Get the second reg path.
    dwResult = ParseRegPath( pic,
                             1,
                             &ptsBuffer,
                             &(*pphnRule)->ptsNewKey,
                             &(*pphnRule)->ptsNewValue );
    FAIL_ON_ERROR( dwResult );

    // Get the optional regfile destination
    fSuccess = SetupGetStringField( pic, 2, NULL, 0, &dwReqLen );
    if ((TRUE == fSuccess) && (dwReqLen > 0))
    {
        (*pphnRule)->ptsFileDest = (TCHAR *)malloc(dwReqLen * sizeof(TCHAR));
		LOG_ASSERT_EXPR( (*pphnRule)->ptsFileDest != NULL, IDS_NOT_ENOUGH_MEMORY, dwResult,
				         ERROR_NOT_ENOUGH_MEMORY );

        fSuccess = SetupGetStringField(pic, 2, (*pphnRule)->ptsFileDest, dwReqLen, NULL);
		LOG_ASSERT_GLE(fSuccess, dwResult);
    }

    // If the original key ends with a star, remove it and set the recursive
    // flag.
    if ((*pphnRule)->ptsKey != NULL)
    {
        dwReqLen = _tcslen( (*pphnRule)->ptsKey );
        if (dwReqLen > 2 &&
            (*pphnRule)->ptsKey[dwReqLen-1] == TEXT('*') &&
            (*pphnRule)->ptsKey[dwReqLen-2] == TEXT('\\'))
        {
            (*pphnRule)->dwAction |= recursive_fa;
            (*pphnRule)->ptsKey[dwReqLen-2] = 0;
        }
      
        // If there is a new key, set the rename_leaf or rename_path flag.
        if ((*pphnRule)->ptsNewKey != NULL)
            if ((*pphnRule)->dwAction & recursive_fa)
                (*pphnRule)->dwAction |= rename_path_fa;
            else
                (*pphnRule)->dwAction |= rename_leaf_fa;
    }

    // If there is a new value, set the rename_value flag.
    if ((*pphnRule)->ptsNewValue != NULL)
        (*pphnRule)->dwAction |= rename_value_fa;

    // If the new key did not contain a root, compute a full path for it.
    if (ptsBuffer == NULL &&
        (*pphnRule)->ptsKey != NULL &&
        (*pphnRule)->ptsNewKey != NULL)
    {
        // Find the last backslash in the key.
        ptsLast = (*pphnRule)->ptsKey;
        do
        {
            ptsTemp = _tcschr( ptsLast, TEXT('\\') );
            if (ptsTemp != NULL)
                ptsLast = ptsTemp+1;
        } while (ptsTemp != NULL);

        // Don't do anything if the original key contains just a leaf.
        if (ptsLast != (*pphnRule)->ptsKey)
        {

            // Allocate a ptsBuffer to hold the old path and the new leaf.
            free( ptsBuffer );
            dwReqLen = _tcslen( (*pphnRule)->ptsNewKey ) +
                (ptsLast - (*pphnRule)->ptsKey) + 2;
            ptsBuffer = (TCHAR *) malloc( dwReqLen*sizeof(TCHAR) );
            LOG_ASSERT_EXPR( ptsBuffer != NULL,
                             IDS_NOT_ENOUGH_MEMORY,
                             dwResult,
                             ERROR_NOT_ENOUGH_MEMORY );

            // Copy in the old path and the new leaf.
            _tcsncpy( ptsBuffer,
                      (*pphnRule)->ptsKey,
                      (ptsLast - (*pphnRule)->ptsKey) );
            _tcscpy( &ptsBuffer[ptsLast-(*pphnRule)->ptsKey],
                     (*pphnRule)->ptsNewKey );
            free( (*pphnRule)->ptsNewKey );
            (*pphnRule)->ptsNewKey = ptsBuffer;
        }
    }
    else
        free(ptsBuffer);

    // Consider freeing strings on error.
cleanup:
    if (dwResult != ERROR_SUCCESS)
    {
        if (*pphnRule != NULL)
        {
            free((*pphnRule)->ptsRoot);
            free((*pphnRule)->ptsKey);
            free((*pphnRule)->ptsValue);
            free((*pphnRule)->ptsNewKey);
            free((*pphnRule)->ptsNewValue);
            free((*pphnRule)->ptsFunction);
            free((*pphnRule)->ptsFileDest);
            free(*pphnRule);
            *pphnRule = NULL;
        }
    }
            
    return dwResult;
}


//---------------------------------------------------------------
// This function prints from an ascii format string to a unicode
// win32 file handle.  It is not thread safe.
DWORD Win32Printf( HANDLE file, char *format, ... )
{
    va_list     va;
    DWORD       dwWritten;
    DWORD       dwLen;
    DWORD       dwWideLength;
    WCHAR      *pwsBuffer = NULL;
    const ULONG LINEBUFSIZE = 4096;
#ifdef UNICODE  
    char *pszBuffer;
    int iCharLen;
    char szOutputBuffer[LINEBUFSIZE];
#endif  
    BOOL        fSuccess;
    TCHAR   tcsPrintBuffer[LINEBUFSIZE];
    TCHAR   *ptsFormat;


#ifdef UNICODE
    WCHAR wcsFormat[LINEBUFSIZE];

  
    dwWideLength = MultiByteToWideChar(CP_ACP, 0, format, -1, NULL, 0);
    if (dwWideLength >= LINEBUFSIZE)
    {
        pwsBuffer = (WCHAR *)_alloca( dwWideLength * sizeof(WCHAR));
        if (pwsBuffer == NULL)
            return ERROR_NOT_ENOUGH_MEMORY;
      
        ptsFormat = pwsBuffer;
        if (!MultiByteToWideChar( CP_ACP,
                                  0,
                                  format,
                                  -1,
                                  pwsBuffer,
                                  dwWideLength ))
            return GetLastError();
    }
    else
    {
        if (!MultiByteToWideChar(CP_ACP,
                                 0,
                                 format,
                                 -1,
                                 wcsFormat,
                                 LINEBUFSIZE))
        {
            return GetLastError();
        }
        ptsFormat = wcsFormat;
    }
#else
    ptsFormat = format;
#endif
  
    va_start( va, format );

    // The doc says if wvsprintf fails, return value is less than
    // the length of the expected output.  Since its hard to know the
    // correct output length, always clear the last error and always
    // check it.
    SetLastError(ERROR_SUCCESS);
    wvsprintf( tcsPrintBuffer, ptsFormat, va );
    va_end(va);
    if (GetLastError() != ERROR_SUCCESS)
        return GetLastError();

    // When printing to the console or logfile use ascii.
    // When printing to the migration file use Unicode.
    dwLen     = _tcslen(tcsPrintBuffer);
    if ((file != OutputFile) || OutputAnsi)
    {
#ifdef UNICODE

        //Convert to ANSI for output
      
        iCharLen = WideCharToMultiByte(CP_ACP,
                                   0,
                                   tcsPrintBuffer,
                                   -1,
                                   NULL,
                                   0,
                                   NULL,
                                   NULL);
        if (iCharLen >= LINEBUFSIZE)
        {
            pszBuffer = (char *)_alloca( iCharLen * sizeof(char));
            if (pszBuffer == NULL)
                return ERROR_OUTOFMEMORY;
          
            if (!WideCharToMultiByte(CP_ACP,
                                     0,
                                     tcsPrintBuffer,
                                     -1,
                                     pszBuffer,
                                     iCharLen,
                                     NULL,
                                     NULL))
                return GetLastError();
              
            pwsBuffer = (WCHAR *)pszBuffer;
        }
        else
        {
            if (!WideCharToMultiByte(CP_ACP,
                                     0,
                                     tcsPrintBuffer,
                                     -1,
                                     szOutputBuffer,
                                     LINEBUFSIZE,
                                     NULL,
                                     NULL))
                return GetLastError();
            pwsBuffer = (WCHAR *)szOutputBuffer;
        }
        dwWideLength = dwLen;
#else
        pwsBuffer = (WCHAR *) tcsPrintBuffer;
        dwWideLength    = dwLen;
#endif      
    }
    else
    {
#ifdef UNICODE
        pwsBuffer = tcsPrintBuffer;
        dwWideLength = dwLen * sizeof(WCHAR);
#else
        // Allocate a buffer to hold the unicode string.
        DEBUG_ASSERT( dwLen < LINEBUFSIZE );
        dwWideLength    = MultiByteToWideChar( CP_ACP,
                                               0,
                                               tcsPrintBuffer,
                                               dwLen,
                                               NULL,
                                               0 );
        pwsBuffer = (WCHAR *) _alloca( dwWideLength*sizeof(WCHAR) );
        if (pwsBuffer == NULL)
            return ERROR_NOT_ENOUGH_MEMORY;

        // Convert the buffer to unicode.
        dwWideLength = MultiByteToWideChar( CP_ACP,
                                            0,
                                            tcsPrintBuffer,
                                            dwLen,
                                            pwsBuffer,
                                            dwWideLength );
        if (dwWideLength == 0)
            return GetLastError();
        dwWideLength *= sizeof(WCHAR);
#endif    
    }

    // Write the unicode string.
    fSuccess = WriteFile( file, pwsBuffer, dwWideLength,  &dwWritten, NULL );
    if (!fSuccess || dwWideLength != dwWritten)
        return GetLastError();

    if (file == STDERR)
    {
        //Also write to the log file for these
        fSuccess = WriteFile( LogFile,
                              pwsBuffer,
                              dwWideLength,
                              &dwWritten,
                              NULL );
        if (!fSuccess || dwWideLength != dwWritten)
        {
            return GetLastError();
        }
    }

    return ERROR_SUCCESS;
}


//*****************************************************************
//
//  Synopsis:       Recursive function to make the command line. We have to do
//                  this because CStringList stores the parameter in reverse
//                  order.
//
//  Parameters:     h
//                      We need to know when we reach the end of the
//                      CStringList, which is denoted by pointing back to the
//                      head of the chain. But in a recursive function we lose
//                      the head of the chain so it is passed in.
//
//  History:        11/8/1999   Created by WeiruC.
//
//  Return Value:   Win32 error code.
//
//*****************************************************************

void MakeCommandLine(CStringList* h, CStringList* command, TCHAR* commandLine)
{
    if (h==NULL || command == NULL || commandLine == NULL)
    {
        if (DebugOutput)
        {
            Win32Printf(LogFile, "Error: NULL pointer passed to MakeCommandLine\r\n");
        }
        _tcscpy(commandLine, TEXT(""));
        return;
    }
    if(command->Next() != h)
    {
        MakeCommandLine(h, command->Next(), commandLine);
    }

    // No sizes of these buffers are passed in, so we must assume that there is enough space
    _tcscat(commandLine, TEXT("\""));
    _tcscat(commandLine, command->String());
    _tcscat(commandLine, TEXT("\""));
    _tcscat(commandLine, TEXT(" "));
}

//---------------------------------------------------------------
// This is a variation of strpbrk.  It searchs a string that may
// contain nulls for any character in the set.  It returns a pointer
// to the first character in the set or null if the string does
// not contain any characters in the set.  Since the function
// searchs past nulls in the str parameter, the len parameter
// indicates the actual length of str.  The set is an array of
// booleans.
UCHAR *mempbrk( UCHAR *str, DWORD len, const UCHAR set[256] )
{
  DWORD i;

  for (i = 0; i < len; i++)
    if (set[str[i]] != 0)
      return &str[i];
  return NULL;
}

//---------------------------------------------------------------
DWORD WriteKey( HANDLE outfile, DWORD type, TCHAR *rootname, TCHAR *key, TCHAR *value_name,
                UCHAR *data, DWORD data_len )
{
  DWORD  result = ERROR_SUCCESS;
  DWORD  j;
  UCHAR *curr;
  DWORD  orig   = 0;

  // If a string contains an embedded carriage return or linefeed, save
  // it as binary and convert it back to a string on read.
  if (type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ)
  {
    curr = mempbrk( data, data_len, NEWLINE_SET );
    if (curr != NULL)
    {
      if (type == REG_SZ)
        orig = 0x400000;
      else if (type == REG_MULTI_SZ)
        orig = 0x100000;
      else
        orig = 0x200000;
      type = REG_BINARY;
    }
  }

  if (NULL == rootname)
     rootname = TEXT("");
  if (NULL == key)
     key = TEXT("");
  if (NULL == value_name)
     value_name = TEXT("");

  switch (type)
  {
    case REG_DWORD:
      if (data == NULL)
         *data = 0;
      result = Win32Printf( outfile, "%s, \"%s\", \"%s\", 0x10001, 0x%x\r\n", rootname, key,
                            value_name, *((DWORD *) data) );
      FAIL_ON_ERROR( result );
      break;

    case REG_EXPAND_SZ:
    case REG_SZ:
      if (data == NULL)
        data = EMPTY_STRING;
      result = Win32Printf( outfile, "%s, \"%s\", \"%s\", 0x%x, \"%s\"\r\n", rootname,
                            key, value_name,
                            type == REG_SZ ? FLG_ADDREG_TYPE_SZ : FLG_ADDREG_TYPE_EXPAND_SZ,
                            data );
      FAIL_ON_ERROR( result );
      break;

    case REG_MULTI_SZ:

      // Print the start of the line and the first string.
      if (data == NULL)
        data = EMPTY_STRING;
      result = Win32Printf( outfile, "%s, \"%s\", \"%s\", 0x10000, \"%s\"", rootname,
                            key, value_name, data );
      FAIL_ON_ERROR( result );

      // Print each remaining string.
      curr = data+strlen((char *) data)+1;
      do
      {
        // Print a comma and the current string.
        Win32Printf( outfile, ", \"%s\"", curr );

        // Skip passed the current string and its null.
        curr += strlen((char *) curr)+1;
      } while ((DWORD) (curr - data) < data_len);

      // Print the trailing newline.
      result = Win32Printf( outfile, "\r\n" );
      FAIL_ON_ERROR( result );
      break;

    case REG_BINARY:
    case REG_NONE:
    default:   // Unknown types, just copy the type and treat the data as binary
      // Print the start of the line.
      result = Win32Printf( outfile, "%s, \"%s\", \"%s\", 0x%x", 
                            rootname, key, value_name,
                            ((type == REG_NONE ? 0x800000 : type ) | orig) );
      FAIL_ON_ERROR( result );

      // Print each byte in hex without the 0x prefix.
      for (j = 0; j < data_len; j++)
      {
        result = Win32Printf( outfile, ",%x", data[j] );
        FAIL_ON_ERROR( result );
        if ( (j+1) % 20 == 0)
        {
          result = Win32Printf( outfile, "\\\r\n" );
          FAIL_ON_ERROR( result );
        }
      }

      result = Win32Printf( outfile, "\r\n" );
      FAIL_ON_ERROR( result );
      break;
  }

cleanup:
  return result;
}

//---------------------------------------------------------------
//
DWORD LogReadRule( HASH_NODE *phnNode )
{
    DWORD dwRetval   = ERROR_SUCCESS;
    BOOL  fRuleFound = FALSE;

    dwRetval = Win32Printf(LogFile, "Read rule: ");
    FAIL_ON_ERROR( dwRetval );

    if (phnNode->dwAction & function_fa )
    { 
        dwRetval = Win32Printf(LogFile, "function");
        FAIL_ON_ERROR( dwRetval );
        fRuleFound = TRUE;
    }
    if (phnNode->dwAction & (rename_leaf_fa | rename_path_fa | rename_value_fa))
    {
        dwRetval = Win32Printf(LogFile, "rename" );
        FAIL_ON_ERROR( dwRetval );
        fRuleFound = TRUE;
    }
    if (phnNode->dwAction & file_fa)
    {
        dwRetval = Win32Printf(LogFile, "copy file");
        FAIL_ON_ERROR( dwRetval );
        fRuleFound = TRUE;
    }
    if (phnNode->dwAction & delete_fa ||
        phnNode->dwAction & suppress_fa)
    {
        dwRetval = Win32Printf(LogFile, "delete" );
        FAIL_ON_ERROR( dwRetval );
        fRuleFound = TRUE;
    }
    // If none of the above, then it must be an addreg
    if ( fRuleFound == FALSE )
    {
        dwRetval = Win32Printf(LogFile, "addreg" );
        FAIL_ON_ERROR( dwRetval );
    }

    dwRetval = Win32Printf(LogFile,
                           " %s\\%s ",
                           phnNode->ptsRoot,
                           phnNode->ptsKey);
    FAIL_ON_ERROR( dwRetval );

    if ( phnNode->ptsValue != NULL )
    {
        dwRetval = Win32Printf(LogFile,
                               "[%s] ",
                               phnNode->ptsValue );
        FAIL_ON_ERROR( dwRetval );
    }

    if ( phnNode->ptsNewKey != NULL || phnNode->ptsNewValue != NULL )
    {
        dwRetval = Win32Printf(LogFile, "to ");
        FAIL_ON_ERROR( dwRetval );
        if (phnNode->ptsNewKey != NULL)
        {
            dwRetval = Win32Printf(LogFile, 
                                   "%s ", 
                                   phnNode->ptsNewKey);
            FAIL_ON_ERROR( dwRetval );
        }
        if (phnNode->ptsNewValue != NULL)
        {
            dwRetval = Win32Printf(LogFile,
                                   "[%s]",
                                   phnNode->ptsNewValue);
            FAIL_ON_ERROR( dwRetval );
        }
    }

    dwRetval = Win32Printf(LogFile, "\r\n");
    FAIL_ON_ERROR( dwRetval );

cleanup:
    return (dwRetval);
}
//---------------------------------------------------------------
char *GetValueFromRegistry(const char *lpValue)
{
    HKEY  hKey;
    char  *buffer    = NULL;
    DWORD dwDataSize = 0;
    DWORD result;

    result = RegOpenKeyEx( HKEY_CURRENT_USER, LOADSTATE_KEY, 0, KEY_READ, &hKey );
    FAIL_ON_ERROR( result );

    // Determine size needed
    dwDataSize = 0;
    result = RegQueryValueExA( hKey, lpValue,  NULL, NULL, NULL, &dwDataSize);
    FAIL_ON_ERROR( result );
    buffer = (char *)malloc((dwDataSize + 1) * sizeof(char));
    if (NULL == buffer)
    {
        Win32PrintfResource(Console, IDS_NOT_ENOUGH_MEMORY);
        goto cleanup;
    }
    result = RegQueryValueExA( hKey, lpValue,  NULL, NULL, 
                               (LPBYTE)buffer, &dwDataSize);

cleanup:
    if ((ERROR_SUCCESS != result) && (NULL != buffer))
    {
        free(buffer);
        buffer = NULL;
    }
    RegCloseKey(hKey);

    return buffer;
}

#define MAX_VALUE_LENGTH 255
//---------------------------------------------------------------
DWORD OpenInfsFromRegistry()
{
    HKEY  hKey;
    DWORD dwIndex = 0;
    char  szData[MAX_PATH + 1];
    char  szValueName[MAX_VALUE_LENGTH];
    DWORD dwValueSize;
    DWORD dwDataSize;
    DWORD result = ERROR_SUCCESS;

    result = RegOpenKeyEx( HKEY_CURRENT_USER, LOADSTATE_KEY, 0, KEY_READ, &hKey );
    FAIL_ON_ERROR( result );

    do
    {
        dwDataSize  = MAX_PATH;
        dwValueSize = MAX_VALUE_LENGTH;
        result = RegEnumValueA(hKey, dwIndex, 
                               szValueName, &dwValueSize,
                               NULL, NULL,
                               (UCHAR *)szData, &dwDataSize);

        if (ERROR_NO_MORE_ITEMS != result)
        {
            FAIL_ON_ERROR( result );

            // If this is an Inf* key, then open the Inf file
            if (0 == strncmp(szValueName, "Inf", 3))
            {
                result = OpenInf( szData );
                FAIL_ON_ERROR( result );
            }

            dwIndex++;
        }
    } while (ERROR_SUCCESS == result);

cleanup:
    RegCloseKey(hKey);
    return result;
}

//---------------------------------------------------------------
DWORD ParseParams( int argc, char *argv[], BOOL scan, TCHAR *pszFullLogFilename )
{
  int i;
  BOOL  cleared_flags = FALSE;
  DWORD result;
  char *error;
  int iarg;
  BOOL fAppendLog = FALSE;
  BOOL fLogFile   = FALSE;
  TCHAR *logfile = NULL;
  TCHAR szArgv[MAX_PATH + 1];
  char *lpData;

  // Save the OS version.
  Win9x = (GetVersion() & 0x80000000);

  // Check all the parameters.
  for (i = 1; i < argc; i++)
  {
      if (argv[i][0] == '/' ||
          argv[i][0] == '-')
      {
          switch (tolower(argv[i][1]))
          {
          case 'a':
              OutputAnsi = TRUE;
              break;
          case 'f':
              CopyFiles = TRUE;
              if (!cleared_flags)
              {
                  CopyUser      = FALSE;
                  CopySystem    = FALSE;
                  SchedSystem   = FALSE;
                  cleared_flags = TRUE;
              }
              break;
          case 'i':
              // Verify that there is a file name.
              if (i == argc-1)
              {
                  Win32PrintfResourceA( Console, IDS_INF_REQUIRED );
                  PrintHelp( scan );
                  return ERROR_INVALID_PARAMETER;
              }

              // Open the inf file.
              i += 1;
              result = OpenInf( argv[i] );
              if (result != ERROR_SUCCESS)
                  return result;
              break;
          case 'l':
              // Verify that there is a file name.
              if (i == argc-1)
              {
                  Win32PrintfResourceA( Console, IDS_LOG_REQUIRED );
                  PrintHelp( scan );
                  return ERROR_INVALID_PARAMETER;
              }

              // Fail if the log file was already specified.
              if (fLogFile == TRUE)
              {
                  Win32PrintfResourceA( Console, IDS_LOG_ONCE );
                  PrintHelp( scan );
                  return ERROR_INVALID_PARAMETER;
              }

              i += 1;
#ifdef _UNICODE
              if (0 == MultiByteToWideChar (GetACP(), 0, argv[i], -1, szArgv, MAX_PATH))
              {
                  result = GetLastError();
                  Win32PrintfResourceA( Console, IDS_INVALID_PARAMETER, argv[i] );
                  return result;
              }
              logfile = szArgv;
#else
              logfile = argv[i];
#endif
              fLogFile = TRUE;
              break;
          case 'm':
              ReallyCopyFiles = FALSE;
              break;
          case 'p':
              UserPortion = TRUE;
              fAppendLog = TRUE;

              if (!scan)
              {
                  if (fLogFile == TRUE)
                  {
                      Win32PrintfResourceA( Console, IDS_LOG_ONCE );
                      PrintHelp( scan );
                      return ERROR_INVALID_PARAMETER;
                  }
                  lpData = GetValueFromRegistry("Logfile");
#ifdef _UNICODE
                  if (0 == MultiByteToWideChar (GetACP(), 0, lpData, -1, szArgv, MAX_PATH))
                  {
                      result = GetLastError();
                      Win32PrintfResourceA( Console, IDS_INVALID_PARAMETER, lpData);
                      return result;
                  }
                  logfile = szArgv;
#else
                  logfile = lpData;
#endif
                  fLogFile = TRUE;

                  MigrationPath = GetValueFromRegistry("Store");
                  if (MigrationPath != NULL)
                  {
                      MultiByteToWideChar(CP_ACP, 0,  MigrationPath, -1,
                                          wcsMigrationPath, MAX_PATH + 1);
                  }

                  OpenInfsFromRegistry();
              }
              break;
          case 'q':
              // TestMode will:
              // - skip version checking the OS 
              // - not create a user hive with /f (still will with /u)
              TestMode = TRUE;
              break;
          case 'r':  // run once
              SchedSystem = TRUE;
              if (!cleared_flags)
              {
                  CopyFiles     = FALSE;
                  CopySystem    = FALSE;
                  CopyUser      = FALSE;
                  cleared_flags = TRUE;
              }
              break;
          case 's':
              CopySystem = TRUE;
              if (!cleared_flags)
              {
                  CopyFiles     = FALSE;
                  CopyUser      = FALSE;
                  SchedSystem   = FALSE;
                  cleared_flags = TRUE;
              }
              break;
          case 'u':
              CopyUser = TRUE;
              if (!cleared_flags)
              {
                  CopyFiles     = FALSE;
                  CopySystem    = FALSE;
                  SchedSystem   = FALSE;
                  cleared_flags = TRUE;
              }
              break;
          case 'v':
              // Verify that there is a verbosity argument
              i += 1;
              if ((i == argc) || (1 != sscanf(argv[i], "%d", &iarg)))
              {
                  Win32PrintfResourceA( Console, IDS_VERBOSE_FLAG_REQUIRED );
                  PrintHelp( scan );
                  return ERROR_INVALID_PARAMETER;
              }

              if ( ( iarg & VERBOSE_BIT ) == VERBOSE_BIT )
              {
                  Verbose = TRUE;
              }
              if ( ( iarg & DEBUGOUTPUT_BIT ) == DEBUGOUTPUT_BIT )
              {
                  DebugOutput = TRUE;
              }
              if ( ( iarg & VERBOSEREG_BIT ) == VERBOSEREG_BIT )
              {
                  VerboseReg = TRUE;
              }
              break;
          case 'x':
              if (!cleared_flags)
              {
                  CopyFiles     = FALSE;
                  CopySystem    = FALSE;
                  CopyUser      = FALSE;
                  SchedSystem   = FALSE;
                  cleared_flags = TRUE;
              }
              break;
          case '9':
              Win9x = TRUE;
              break;

          default:

              // There should be no other switches defined.

              Win32PrintfResourceA( Console, IDS_INVALID_PARAMETER, argv[i] );
              PrintHelp( scan );
              return ERROR_INVALID_PARAMETER;
          }
      }
      else if (MigrationPath != NULL)
      {
        // The path to the server should be specified exactly once.
        Win32PrintfResourceA( Console, IDS_INVALID_PARAMETER, argv[i] );
        PrintHelp( scan );
        return ERROR_INVALID_PARAMETER;
      }
      else
      {
        // Save the migration path.
        MigrationPath = argv[i];
        DWORD ccMigPath;
        if (!(ccMigPath = MultiByteToWideChar(CP_ACP,
                                              0,
                                              MigrationPath,
                                              -1,
                                              wcsMigrationPath,
                                              MAX_PATH + 1)))
        {
            Win32PrintfResourceA( Console,
                                  IDS_INVALID_PARAMETER,
                                  MigrationPath );
            PrintHelp( scan );
            return ERROR_INVALID_PARAMETER;
        }
      }
  }

  // Verify that a path was specified.
  if (MigrationPath == NULL)
  {
    Win32PrintfResourceA( Console, IDS_MISSING_MIGRATION );
    PrintHelp( scan );
    return ERROR_INVALID_PARAMETER;
  }

  // Open LogFile
  if ( fLogFile == FALSE )
  {
      if (scan)
          logfile = TEXT("scanstate.log");
      else
          logfile = TEXT("loadstate.log");
  }
  if (fAppendLog == FALSE)
  {
     // Delete any previous log
     DeleteFile( logfile );
  }
  LogFile = CreateFile( logfile,
                        GENERIC_WRITE, 0, NULL,
                        fAppendLog ? OPEN_ALWAYS : CREATE_NEW,
                        FILE_ATTRIBUTE_NORMAL, NULL );
  if (LogFile == INVALID_HANDLE_VALUE)
  {
      result = GetLastError();
      Win32PrintfResourceA( Console, IDS_OPEN_LOG_ERROR, logfile );
      error = NULL;
      FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER |
                      FORMAT_MESSAGE_FROM_SYSTEM,
                      0,
                      result,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                     (char *) &error,
                      0,
                      NULL );
    if (error != NULL)
    {
        Win32Printf( Console, error );
        LocalFree( error );
    }
    return result;
  }
  else if (fAppendLog == TRUE)
  {
      // Move file pointer to the end of the file,
      // so we won't overwrite previous entries
      result = SetFilePointer( LogFile, 0, NULL, FILE_END);
      if ( result == INVALID_SET_FILE_POINTER )
      {
          result = GetLastError();
          Win32PrintfResourceA( Console, IDS_OPEN_LOG_ERROR, logfile );
          error = NULL;
          FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER |
                          FORMAT_MESSAGE_FROM_SYSTEM,
                          0,
                          result,
                          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                         (char *) &error,
                          0,
                          NULL );
          if (error != NULL)
          {
              Win32Printf( Console, error );
              LocalFree( error );
          }
          return result;
      }
  }

  TCHAR *ptsFileNamePart;
  result = GetFullPathName( logfile, MAX_PATH, pszFullLogFilename, &ptsFileNamePart);
  if (0 == result)
  {
      return GetLastError();
  }

  return ERROR_SUCCESS; 
}