/*++

Copyright (c) 2004 - 2010, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials                          
are licensed and made available under the terms and conditions of the BSD License         
which accompanies this distribution.  The full text of the license may be found at        
http://opensource.org/licenses/bsd-license.php                                            
                                                                                          
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             

Module Name:

  StringDB.c

Abstract:

  String database implementation
  
--*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <Tiano.h>
#include <EfiUtilityMsgs.h>
#include <EfiHii.h>
#include "StrGather.h"
#include "StringDb.h"

static STRING_DB_DATA mDBData;

static const char     *mSourceFileHeader[] = {
  "//",
  "//  DO NOT EDIT -- auto-generated file",
  "//",
  "//  This file is generated by the string gather utility",
  "//",
  NULL
};

static
STRING_LIST           *
StringDBFindString (
  WCHAR                       *LanguageName,
  WCHAR                       *StringName,
  WCHAR                       *Scope,
  WCHAR_STRING_LIST           *LanguagesOfInterest,
  WCHAR_MATCHING_STRING_LIST  *IndirectionList
  );

static
STRING_IDENTIFIER     *
StringDBFindStringIdentifierByName (
  WCHAR *Name
  );

static
STRING_IDENTIFIER     *
StringDBFindStringIdentifierByIndex (
  UINT32    Index
  );

static
void
StringDBWriteStandardFileHeader (
  FILE *OutFptr
  );

static
WCHAR                 *
AsciiToWchar (
  INT8 *Str
  );

static
CHAR8 *
WcharToAscii (
  WCHAR *Str
  );

static
WCHAR                 *
DuplicateString (
  WCHAR   *Str
  );

static
WCHAR *
WstrCatenate (
  WCHAR *Dst,
  WCHAR *Src
  );

static
STATUS
StringDBWriteStringIdentifier (
  FILE                *DBFptr,
  UINT16              StringId,
  UINT16              Flags,
  WCHAR               *IdentifierName
  );

static
STATUS
StringDBReadStringIdentifier (
  FILE                *DBFptr
  );

static
STATUS
StringDBWriteLanguageDefinition (
  FILE            *DBFptr,
  WCHAR           *LanguageName,
  WCHAR           *PrintableLanguageName,
  WCHAR           *SecondaryLanguageList
  );

static
STATUS
StringDBReadLanguageDefinition (
  FILE            *DBFptr
  );

static
STATUS
StringDBWriteString (
  FILE            *DBFptr,
  UINT16          Flags,
  WCHAR           *Language,
  WCHAR           *StringName,
  WCHAR           *Scope,
  WCHAR           *Str
  );

static
STATUS
StringDBReadString (
  FILE            *DBFptr
  );

static
STATUS
StringDBReadGenericString (
  FILE      *DBFptr,
  UINT16    *Size,
  WCHAR     **Str
  );

static
STATUS
StringDBWriteGenericString (
  FILE      *DBFptr,
  WCHAR     *Str
  );

static
void
StringDBAssignStringIndexes (
  VOID
  );

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

/*++

Routine Description:
  Constructor function for the string database handler.

Arguments:
  None.

Returns:
  None.

--*/
void
StringDBConstructor (
  VOID
  )
{
  memset ((char *) &mDBData, 0, sizeof (STRING_DB_DATA));
  mDBData.CurrentScope = DuplicateString (L"NULL");
}

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

/*++

Routine Description:
  Destructor function for the string database handler.

Arguments:
  None.

Returns:
  None.

--*/
void
StringDBDestructor (
  VOID
  )
{
  LANGUAGE_LIST     *NextLang;
  STRING_LIST       *NextStr;
  STRING_IDENTIFIER *NextIdentifier;
  //
  // Close the database file if it's open
  //
  if (mDBData.StringDBFptr != NULL) {
    fclose (mDBData.StringDBFptr);
    mDBData.StringDBFptr = NULL;
  }
  //
  // If we've allocated any strings/languages, free them up
  //
  while (mDBData.LanguageList != NULL) {
    NextLang = mDBData.LanguageList->Next;
    //
    // Free up all strings for this language
    //
    while (mDBData.LanguageList->String != NULL) {
      NextStr = mDBData.LanguageList->String->Next;
      FREE (mDBData.LanguageList->String->Str);
      FREE (mDBData.LanguageList->String);
      mDBData.LanguageList->String = NextStr;
    }

    FREE (mDBData.LanguageList->SecondaryLanguageList);
    FREE (mDBData.LanguageList->PrintableLanguageName);
    FREE (mDBData.LanguageList);
    mDBData.LanguageList = NextLang;
  }
  //
  // Free up string identifiers
  //
  while (mDBData.StringIdentifier != NULL) {
    NextIdentifier = mDBData.StringIdentifier->Next;
    FREE (mDBData.StringIdentifier->StringName);
    FREE (mDBData.StringIdentifier);
    mDBData.StringIdentifier = NextIdentifier;
  }
  //
  // Free the filename
  //
  if (mDBData.StringDBFileName != NULL) {
    FREE (mDBData.StringDBFileName);
    mDBData.StringDBFileName = NULL;
  }
  //
  // We save a copy of the scope, so free it up if we
  // have one.
  //
  if (mDBData.CurrentScope != NULL) {
    FREE (mDBData.CurrentScope);
    mDBData.CurrentScope = NULL;
  }
}

/*****************************************************************************/
STATUS
StringDBDumpStringDefines (
  INT8 *FileName,
  INT8 *BaseName
  )
{
  FILE              *Fptr;
  STRING_IDENTIFIER *Identifier;
  INT8              CopyBaseName[100];
  UINT32            Index;
  const INT8        *StrDefHeader[] = {
    "#ifndef _%s_STRINGS_DEFINE_H_\n",
    "#define _%s_STRINGS_DEFINE_H_\n\n",
    NULL
  };

  if ((Fptr = fopen (FileName, "w")) == NULL) {
    Error (NULL, 0, 0, FileName, "failed to open output string defines file");
    return STATUS_ERROR;
  }
  //
  // Get the base source filename and convert to uppercase.
  //
  if (sizeof (CopyBaseName) <= strlen (BaseName) + 1) {
    Error (NULL, 0, 0, "application error", "StringDBDumpStringDefines() string length insufficient");
    return STATUS_ERROR;
  }

  strcpy (CopyBaseName, BaseName);
  for (Index = 0; CopyBaseName[Index] != 0; Index++) {
    if (islower (CopyBaseName[Index])) {
      CopyBaseName[Index] = (INT8) toupper (CopyBaseName[Index]);
    }
  }
  //
  // Assign index values to the string identifiers
  //
  StringDBAssignStringIndexes ();
  //
  // Write the standard header to the output file, and then the
  // protective #ifndef.
  //
  StringDBWriteStandardFileHeader (Fptr);
  for (Index = 0; StrDefHeader[Index] != NULL; Index++) {
    fprintf (Fptr, StrDefHeader[Index], CopyBaseName);
  }
  //
  // Print all the #defines for the string identifiers. Print identifiers
  // whose names start with '$' as comments. Add comments for string
  // identifiers not used as well.
  //
  Identifier = mDBData.StringIdentifier;
  while (Identifier != NULL) {
    if (Identifier->StringName[0] == L'$') {
      fprintf (Fptr, "// ");
    }

    if (Identifier->Flags & STRING_FLAGS_REFERENCED) {
      fprintf (Fptr, "#define %-40S 0x%04X\n", Identifier->StringName, Identifier->Index);
    } else {
      fprintf (Fptr, "//#define %-40S 0x%04X // not referenced\n", Identifier->StringName, Identifier->Index);
    }

    Identifier = Identifier->Next;
  }

  fprintf (Fptr, "\n#endif\n");
  fclose (Fptr);
  return STATUS_SUCCESS;
}

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

/*++

Routine Description:

  Add a string identifier to the database.

Arguments:

  StringName      - name of the string identifier. For example "STR_MY_STRING"
  NewId           - if an ID has been assigned
  Flags           - characteristics for the identifier

Returns:

  STATUS

--*/
STATUS
StringDBAddStringIdentifier (
  WCHAR     *StringName,
  UINT16    *NewId,
  UINT16    Flags
  )
{
  STRING_IDENTIFIER *StringIdentifier;
  STATUS            Status;
  //
  // If it was already used for some other language, then we don't
  // need to add it. But set it to the current string identifier.
  // The referenced bit is sticky.
  //
  Status            = STATUS_SUCCESS;
  StringIdentifier  = StringDBFindStringIdentifierByName (StringName);
  if (StringIdentifier != NULL) {
    if (Flags & STRING_FLAGS_REFERENCED) {
      StringIdentifier->Flags |= STRING_FLAGS_REFERENCED;
    }

    mDBData.CurrentStringIdentifier = StringIdentifier;
    *NewId                          = (UINT16) StringIdentifier->Index;
    return Status;
  }

  StringIdentifier = (STRING_IDENTIFIER *) MALLOC (sizeof (STRING_IDENTIFIER));
  if (StringIdentifier == NULL) {
    Error (NULL, 0, 0, NULL, "memory allocation error");
    return STATUS_ERROR;
  }

  memset ((char *) StringIdentifier, 0, sizeof (STRING_IDENTIFIER));
  StringIdentifier->StringName = (WCHAR *) malloc ((wcslen (StringName) + 1) * sizeof (WCHAR));
  if (StringIdentifier->StringName == NULL) {
    Error (NULL, 0, 0, NULL, "memory allocation error");
    return STATUS_ERROR;
  }

  wcscpy (StringIdentifier->StringName, StringName);
  if (*NewId != STRING_ID_INVALID) {
    StringIdentifier->Index = *NewId;
    StringIdentifier->Flags |= STRING_FLAGS_INDEX_ASSIGNED;
    if (mDBData.NumStringIdentifiers <= StringIdentifier->Index) {
      mDBData.NumStringIdentifiers = StringIdentifier->Index + 1;
    }
  } else {
    StringIdentifier->Index = mDBData.NumStringIdentifiers++;
  }

  StringIdentifier->Flags |= Flags;
  //
  // Add it to our list of string identifiers
  //
  if (mDBData.StringIdentifier == NULL) {
    mDBData.StringIdentifier = StringIdentifier;
  } else {
    mDBData.LastStringIdentifier->Next = StringIdentifier;
  }

  mDBData.LastStringIdentifier    = StringIdentifier;
  mDBData.CurrentStringIdentifier = StringIdentifier;
  *NewId                          = (UINT16) StringIdentifier->Index;
  return Status;
}

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

/*++

Routine Description:

  Add a new string to the database.

Arguments:

  LanguageName    - "eng" or "spa" language name
  StringName      - "STR_MY_TEXT" string name
  Scope           - from the #scope statements in the string file
  Format          - if we should format the string
  Flags           - characteristic flags for the string

Returns:

  STATUS

Notes:

  Several of the fields can be "inherited" from the previous calls to
  our database functions. For example, if scope is NULL here, then
  we'll use the previous setting.

--*/
STATUS
StringDBAddString (
  WCHAR   *LanguageName,
  WCHAR   *StringName,
  WCHAR   *Scope,
  WCHAR   *String,
  BOOLEAN Format,
  UINT16  Flags
  )
{
  LANGUAGE_LIST     *Lang;
  UINT32            Size;
  STRING_LIST       *Str;
  UINT16            StringIndex;
  STRING_IDENTIFIER *StringIdentifier;

  //
  // If they specified a language, make sure they've defined it already
  // via a #langdef statement. Otherwise use the current default language.
  //
  if (LanguageName != NULL) {
    Lang = StringDBFindLanguageList (LanguageName);
    if (Lang == NULL) {
      ParserError (0, "language not defined", "%S", LanguageName);
      return STATUS_ERROR;
    } else {
      StringDBSetCurrentLanguage (LanguageName);
    }
  } else {
    Lang = mDBData.CurrentLanguage;
    if (Lang == NULL) {
      //
      // Have to call SetLanguage() first
      //
      ParserError (0, "no language defined", "%S", StringName);
      return STATUS_ERROR;
    }
  }
  //
  // If they didn't define a string identifier, use the last string identifier
  // added.
  //
  if (StringName == NULL) {
    StringName = mDBData.CurrentStringIdentifier->StringName;
    if (StringName == NULL) {
      ParserError (0, "no string identifier previously specified", NULL);
      return STATUS_ERROR;
    }
  }
  //
  // If scope was not specified, use the default setting
  //
  if (Scope != NULL) {
    Scope = DuplicateString (Scope);
  } else {
    Scope = DuplicateString (mDBData.CurrentScope);
  }
  //
  // printf ("Adding string: %S.%S.%S\n", Lang->LanguageName, StringName, Scope);
  //
  // Check for duplicates for this Language.StringName.Scope. Allow multiple
  // definitions of the language name and printable language name, since the
  // user does not specifically define them.
  //
  if (StringDBFindString (Lang->LanguageName, StringName, Scope, NULL, NULL) != NULL) {
    if ((wcscmp (StringName, LANGUAGE_NAME_STRING_NAME) == 0) &&
        (wcscmp (StringName, PRINTABLE_LANGUAGE_NAME_STRING_NAME) == 0)
        ) {
      ParserError (
        0,
        "string multiply defined",
        "Language.Name.Scope = %S.%S.%S",
        Lang->LanguageName,
        StringName,
        Scope
        );
      return STATUS_ERROR;
    }
  }

  StringIndex = STRING_ID_INVALID;
  if (StringDBAddStringIdentifier (StringName, &StringIndex, Flags) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  StringIdentifier = StringDBFindStringIdentifierByName (StringName);
  //
  // Add this string to the end of the strings for this language.
  //
  Str = (STRING_LIST *) malloc (sizeof (STRING_LIST));
  if (Str == NULL) {
    Error (NULL, 0, 0, NULL, "memory allocation error");
    return STATUS_ERROR;
  }

  memset ((char *) Str, 0, sizeof (STRING_LIST));
  Size              = (wcslen (String) + 1) * sizeof (WCHAR);
  Str->Flags        = Flags;
  Str->Scope        = Scope;
  Str->StringName   = StringIdentifier->StringName;
  Str->LanguageName = DuplicateString (LanguageName);
  Str->Str          = (WCHAR *) MALLOC (Size);
  if (Str->Str == NULL) {
    Error (NULL, 0, 0, NULL, "memory allocation error");
    return STATUS_ERROR;
  }
  //
  // If not formatting, just copy the string.
  //
  wcscpy (Str->Str, String);
  if (Format) {
    StringDBFormatString (Str->Str);
  }
  //
  // Size may change after formatting. We set the size to
  // the actual size of the string, including the null for
  // easier processing later.
  //
  Str->Size = (wcslen (Str->Str) + 1) * sizeof (WCHAR);
  if (Lang->String == NULL) {
    Lang->String = Str;
  } else {
    Lang->LastString->Next = Str;
  }

  Lang->LastString = Str;
  return STATUS_SUCCESS;
}

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

/*++

Routine Description:

  Given a language name, see if a language list for it has been defined

Arguments:

  LanguageName    - like "eng"

Returns:

  A pointer to the language list

--*/
LANGUAGE_LIST *
StringDBFindLanguageList (
  WCHAR *LanguageName
  )
{
  LANGUAGE_LIST *Lang;

  Lang = mDBData.LanguageList;
  while (Lang != NULL) {
    if (wcscmp (LanguageName, Lang->LanguageName) == 0) {
      break;
    }

    Lang = Lang->Next;
  }

  return Lang;
}

/*****************************************************************************/
STATUS
StringDBSetCurrentLanguage (
  WCHAR *LanguageName
  )
{
  LANGUAGE_LIST *Lang;

  Lang = StringDBFindLanguageList (LanguageName);
  if (Lang == NULL) {
    ParserError (0, "language not previously defined", "%S", LanguageName);
    return STATUS_ERROR;
  }

  mDBData.CurrentLanguage = Lang;
  return STATUS_SUCCESS;
}

/*****************************************************************************/
STATUS
StringDBAddLanguage (
  WCHAR *LanguageName,
  WCHAR *PrintableLanguageName,
  WCHAR *SecondaryLanguageList
  )
{
  LANGUAGE_LIST *Lang;
  //
  // Check for redefinitions
  //
  Lang = StringDBFindLanguageList (LanguageName);
  if (Lang != NULL) {
    //
    // Better be the same printable name
    //
    if (wcscmp (PrintableLanguageName, Lang->PrintableLanguageName) != 0) {
      ParserError (
        0,
        "language redefinition",
        "%S:%S != %S:%S",
        Lang->LanguageName,
        Lang->PrintableLanguageName,
        LanguageName,
        PrintableLanguageName
        );
      return STATUS_ERROR;
      //
      //    } else {
      //      ParserWarning (0, "benign language redefinition", "%S", PrintableLanguageName);
      //      return STATUS_WARNING;
      //
    }
  } else {
    //
    // Allocate memory to keep track of this new language
    //
    Lang = (LANGUAGE_LIST *) malloc (sizeof (LANGUAGE_LIST));
    if (Lang == NULL) {
      Error (NULL, 0, 0, NULL, "memory allocation error");
      return STATUS_ERROR;
    }

    memset ((char *) Lang, 0, sizeof (LANGUAGE_LIST));
    //
    // Save the language name, then allocate memory to save the
    // printable language name
    //
    Lang->LanguageName = (WCHAR *) malloc ((wcslen (LanguageName) + 1) * 2);
	if (Lang->LanguageName == NULL) {
      Error (NULL, 0, 0, NULL, "memory allocation error");
      return STATUS_ERROR;
    }
    wcscpy (Lang->LanguageName, LanguageName);
    Lang->PrintableLanguageName = (WCHAR *) malloc ((wcslen (PrintableLanguageName) + 1) * sizeof (WCHAR));
    if (Lang->PrintableLanguageName == NULL) {
      Error (NULL, 0, 0, NULL, "memory allocation error");
      FREE (Lang->LanguageName);
      return STATUS_ERROR;
    }
    wcscpy (Lang->PrintableLanguageName, PrintableLanguageName);

	if (SecondaryLanguageList != NULL) {
      Lang->SecondaryLanguageList = (WCHAR *) malloc ((wcslen (SecondaryLanguageList) + 1) * sizeof (WCHAR));
      if (Lang->SecondaryLanguageList == NULL) {
        Error (NULL, 0, 0, NULL, "memory allocation error");
        FREE (Lang->PrintableLanguageName);
        FREE (Lang->LanguageName);
        return STATUS_ERROR;
      }
      wcscpy (Lang->SecondaryLanguageList, SecondaryLanguageList);
	} else {
      Lang->SecondaryLanguageList = NULL;
	}

    if (mDBData.LanguageList == NULL) {
      mDBData.LanguageList = Lang;
    } else {
      mDBData.LastLanguageList->Next = Lang;
    }

    mDBData.LastLanguageList = Lang;
  }
  //
  // Default is to make our active language this new one
  //
  StringDBSetCurrentLanguage (LanguageName);
  //
  // The first two strings for any language are the language name,
  // followed by the printable language name. Add them and set them
  // to referenced so they never get stripped out.
  //
  StringDBAddString (
    LanguageName,
    LANGUAGE_NAME_STRING_NAME,
    NULL,
    LanguageName,
    FALSE,
    STRING_FLAGS_REFERENCED
    );
  StringDBAddString (
    LanguageName,
    PRINTABLE_LANGUAGE_NAME_STRING_NAME,
    NULL,
    PrintableLanguageName,
    FALSE,
    STRING_FLAGS_REFERENCED
    );
  return STATUS_SUCCESS;
}

STATUS
StringDBAddSecondaryLanguage (
  WCHAR *LanguageName,
  WCHAR *SecondaryLanguageList
  )
{
  LANGUAGE_LIST *Lang;

  Lang = StringDBFindLanguageList (LanguageName);
  if (Lang == NULL) {
    return STATUS_ERROR;
  } else {
    Lang->SecondaryLanguageList = WstrCatenate(Lang->SecondaryLanguageList, SecondaryLanguageList);
    return STATUS_SUCCESS;
  }
}

/*****************************************************************************/
static
STRING_IDENTIFIER *
StringDBFindStringIdentifierByName (
  WCHAR *StringName
  )
{
  STRING_IDENTIFIER *Identifier;

  Identifier = mDBData.StringIdentifier;
  while (Identifier != NULL) {
    if (wcscmp (StringName, Identifier->StringName) == 0) {
      return Identifier;
    }

    Identifier = Identifier->Next;
  }

  return NULL;
}

static
STRING_IDENTIFIER *
StringDBFindStringIdentifierByIndex (
  UINT32    StringIndex
  )
{
  STRING_IDENTIFIER *Identifier;

  Identifier = mDBData.StringIdentifier;
  while (Identifier != NULL) {
    if (Identifier->Index == StringIndex) {
      return Identifier;
    }

    Identifier = Identifier->Next;
  }

  return NULL;
}

/*****************************************************************************/
static
void
StringDBWriteStandardFileHeader (
  FILE *OutFptr
  )
{
  UINT32  TempIndex;
  for (TempIndex = 0; mSourceFileHeader[TempIndex] != NULL; TempIndex++) {
    fprintf (OutFptr, "%s\n", mSourceFileHeader[TempIndex]);
  }
}

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

/*++

Routine Description:
  
  Given a Unicode string from an input file, reformat the string to replace
  backslash control sequences with the appropriate encoding.

Arguments:

  String        - pointer to string to reformat

Returns:

  Nothing

--*/
void
StringDBFormatString (
  WCHAR   *String
  )
{
  WCHAR *From;
  WCHAR *To;
  int   HexNibbles;
  WCHAR HexValue;
  //
  // Go through the string and process any formatting characters
  //
  From  = String;
  To    = String;
  while (*From) {
    if (*From == UNICODE_BACKSLASH) {
      //
      // First look for \wide and replace with the appropriate control character. Note that
      // when you have "define STR L"ABC"", then sizeof(ABC) is 8 because the null char is
      // counted. Make adjustments for this. We advance From below, so subtract 2 each time.
      //
      if (wcsncmp (From, UNICODE_WIDE_STRING, sizeof (UNICODE_WIDE_STRING) / sizeof (WCHAR) - 1) == 0) {
        *To = WIDE_CHAR;
        From += sizeof (UNICODE_WIDE_STRING) / sizeof (WCHAR) - 2;
      } else if (wcsncmp (From, UNICODE_NARROW_STRING, sizeof (UNICODE_NARROW_STRING) / sizeof (WCHAR) - 1) == 0) {
        //
        // Found: \narrow
        //
        *To = NARROW_CHAR;
        From += sizeof (UNICODE_NARROW_STRING) / sizeof (WCHAR) - 2;
      } else if (wcsncmp (From, UNICODE_NBR_STRING, sizeof (UNICODE_NBR_STRING) / sizeof (WCHAR) - 1) == 0) {
        //
        // Found: \nbr
        //
        *To = NON_BREAKING_CHAR;
        From += sizeof (UNICODE_NBR_STRING) / sizeof (WCHAR) - 2;
      } else if (wcsncmp (From, UNICODE_BR_STRING, sizeof (UNICODE_BR_STRING) / sizeof (WCHAR) - 1) == 0) {
        //
        // Found: \br -- pass through untouched
        //
        *To = *From;
      } else {
        //
        // Standard one-character control sequences such as \n, \r, \\, or \x
        //
        From++;
        switch (*From) {
        case ASCII_TO_UNICODE ('n'):
          *To = UNICODE_CR;
          To++;
          *To = UNICODE_LF;
          break;

        //
        // carriage return
        //
        case ASCII_TO_UNICODE ('r'):
          *To = UNICODE_CR;
          break;

        //
        // backslash
        //
        case UNICODE_BACKSLASH:
          *To = UNICODE_BACKSLASH;
          break;

        //
        // Tab
        //
        case ASCII_TO_UNICODE ('t'):
          *To = UNICODE_TAB;
          break;

        //
        // embedded double-quote
        //
        case UNICODE_DOUBLE_QUOTE:
          *To = UNICODE_DOUBLE_QUOTE;
          break;

        //
        // Hex Unicode character \x1234. We'll process up to 4 hex characters
        //
        case ASCII_TO_UNICODE ('x'):
          HexValue = 0;
          for (HexNibbles = 0; HexNibbles < 4; HexNibbles++) {
            if ((From[1] >= UNICODE_0) && (From[1] <= UNICODE_9)) {
              HexValue = (HexValue << 4) | (From[1] - UNICODE_0);
            } else if ((From[1] >= UNICODE_a) && (From[1] <= UNICODE_f)) {
              HexValue = (HexValue << 4) | (10 + From[1] - UNICODE_a);
            } else if ((From[1] >= UNICODE_A) && (From[1] <= UNICODE_F)) {
              HexValue = (HexValue << 4) | (10 + From[1] - UNICODE_A);
            } else {
              break;
            }

            From++;
          }

          if (HexNibbles == 0) {
            ParserWarning (
              0,
              "expected at least one valid hex digit with \\x escaped character in string",
              "\\%C",
              *From
              );
          } else {
            *To = HexValue;
          }
          break;

        default:
          *To = UNICODE_SPACE;
          ParserWarning (0, "invalid escaped character in string", "\\%C", *From);
          break;
        }
      }
    } else {
      *To = *From;
    }

    From++;
    To++;
  }

  *To = 0;
}

/*****************************************************************************/
STATUS
StringDBReadDatabase (
  INT8    *DBFileName,
  BOOLEAN IgnoreIfNotExist,
  BOOLEAN Verbose
  )
{
  STRING_DB_HEADER    DbHeader;
  STATUS              Status;
  FILE                *DBFptr;
  DB_DATA_ITEM_HEADER DataItemHeader;

  Status  = STATUS_SUCCESS;
  DBFptr  = NULL;
  //
  //  if (Verbose) {
  //    fprintf (stdout, "Reading database file %s\n", DBFileName);
  //  }
  //
  // Try to open the input file
  //
  if ((DBFptr = fopen (DBFileName, "rb")) == NULL) {
    if (IgnoreIfNotExist) {
      return STATUS_SUCCESS;
    }

    Error (NULL, 0, 0, DBFileName, "failed to open input database file for reading");
    return STATUS_ERROR;
  }
  //
  // Read and verify the database header
  //
  if (fread ((void *) &DbHeader, sizeof (STRING_DB_HEADER), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, DBFileName, "failed to read header from database file");
    Status = STATUS_ERROR;
    goto Finish;
  }

  if (DbHeader.Key != STRING_DB_KEY) {
    Error (NULL, 0, 0, DBFileName, "invalid header in database file");
    Status = STATUS_ERROR;
    goto Finish;
  }

  if ((DbHeader.Version & STRING_DB_MAJOR_VERSION_MASK) != (STRING_DB_VERSION & STRING_DB_MAJOR_VERSION_MASK)) {
    Error (NULL, 0, 0, DBFileName, "incompatible database file version -- rebuild clean");
    Status = STATUS_ERROR;
    goto Finish;
  }
  //
  // Read remaining items
  //
  while (fread (&DataItemHeader, sizeof (DataItemHeader), 1, DBFptr) == 1) {
    switch (DataItemHeader.DataType) {
    case DB_DATA_TYPE_STRING_IDENTIFIER:
      StringDBReadStringIdentifier (DBFptr);
      break;

    case DB_DATA_TYPE_LANGUAGE_DEFINITION:
      StringDBReadLanguageDefinition (DBFptr);
      break;

    case DB_DATA_TYPE_STRING_DEFINITION:
      StringDBReadString (DBFptr);
      break;

    default:
      Error (
        NULL,
        0,
        0,
        "database corrupted",
        "invalid data item type 0x%X at offset 0x%X",
        (UINT32) DataItemHeader.DataType,
        ftell (DBFptr) - sizeof (DataItemHeader)
        );
      Status = STATUS_ERROR;
      goto Finish;
    }
  }

Finish:
  if (DBFptr != NULL) {
    fclose (DBFptr);
  }

  return Status;
}

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

/*++

Routine Description:
  
  Write everything we know to the output database file. Write:

  Database header
  String identifiers[]
  StringPacks[]

Arguments:

  DBFileName    - name of the file to write to
  Verbose       - for debug purposes, print info messages along the way.

Returns:

  STATUS

--*/
STATUS
StringDBWriteDatabase (
  INT8    *DBFileName,
  BOOLEAN Verbose
  )
{
  STRING_DB_HEADER  DbHeader;
  UINT32            Counter;
  UINT32            StrLen;
  LANGUAGE_LIST     *Lang;
  STRING_IDENTIFIER *StringIdentifier;
  STRING_LIST       *StrList;
  FILE              *DBFptr;

  if (Verbose) {
    fprintf (stdout, "Writing database %s\n", DBFileName);
  }

  if ((DBFptr = fopen (DBFileName, "wb")) == NULL) {
    Error (NULL, 0, 0, DBFileName, "failed to open output database file for writing");
    return STATUS_ERROR;
  }
  //
  // Fill in and write the database header
  //
  memset (&DbHeader, 0, sizeof (STRING_DB_HEADER));
  DbHeader.HeaderSize = sizeof (STRING_DB_HEADER);
  DbHeader.Key        = STRING_DB_KEY;
  DbHeader.Version    = STRING_DB_VERSION;
  //
  // Count the number of languages we have
  //
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    DbHeader.NumLanguages++;
  }
  //
  // Count up how many string identifiers we have, and total up the
  // size of the names plus the size of the flags field we will
  // write out too.
  //
  DbHeader.NumStringIdenfiers = mDBData.NumStringIdentifiers;
  StringIdentifier            = mDBData.StringIdentifier;
  for (Counter = 0; Counter < mDBData.NumStringIdentifiers; Counter++) {
    StrLen = wcslen (StringIdentifier->StringName) + 1;
    DbHeader.StringIdentifiersSize += StrLen * sizeof (WCHAR) + sizeof (StringIdentifier->Flags);
    StringIdentifier = StringIdentifier->Next;
  }

  //
  // Write the header
  //
  fwrite (&DbHeader, sizeof (STRING_DB_HEADER), 1, DBFptr);
  if (Verbose) {
    fprintf (stdout, "  Number of string identifiers  0x%04X\n", DbHeader.NumStringIdenfiers);
    fprintf (stdout, "  Number of languages           %d\n", DbHeader.NumLanguages);
  }
  //
  // Write the string identifiers
  //
  for (StringIdentifier = mDBData.StringIdentifier; StringIdentifier != NULL; StringIdentifier = StringIdentifier->Next) {
    StringDBWriteStringIdentifier (
      DBFptr,
      (UINT16) StringIdentifier->Index,
      StringIdentifier->Flags,
      StringIdentifier->StringName
      );
  }
  //
  // Now write all the strings for each language
  //
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    StringDBWriteLanguageDefinition (DBFptr, Lang->LanguageName, Lang->PrintableLanguageName, Lang->SecondaryLanguageList);
    for (StrList = Lang->String; StrList != NULL; StrList = StrList->Next) {
      StringDBWriteString (
        DBFptr,
        StrList->Flags,
        Lang->LanguageName,
        StrList->StringName,
        StrList->Scope,
        StrList->Str
        );
    }
  }

  fclose (DBFptr);
  return STATUS_SUCCESS;
}

STATUS
StringDBSetStringReferenced (
  INT8      *StringIdentifierName,
  BOOLEAN   IgnoreNotFound
  )
{
  STRING_IDENTIFIER *Id;
  WCHAR             *WName;
  STATUS            Status;
  //
  // See if it's already been defined.
  //
  Status  = STATUS_SUCCESS;
  WName   = (WCHAR *) malloc ((strlen (StringIdentifierName) + 1) * sizeof (WCHAR));
#ifdef USE_VC8
  swprintf (WName, (strlen (StringIdentifierName) + 1) * sizeof (WCHAR), L"%S", StringIdentifierName);
#else
  swprintf (WName, L"%S", StringIdentifierName);
#endif
  Id = StringDBFindStringIdentifierByName (WName);
  if (Id != NULL) {
    Id->Flags |= STRING_FLAGS_REFERENCED;
  } else {
    if (IgnoreNotFound == 0) {
      ParserWarning (0, StringIdentifierName, "string identifier not found in database");
      Status = STATUS_WARNING;
    }
  }

  free (WName);
  return Status;
}

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

/*++

Routine Description:

  Dump the contents of a database to an output unicode file.

Arguments:

  DBFileName        - name of the pre-existing database file to read
  OutputFileName    - name of the file to dump the database contents to
  Verbose           - for printing of additional info useful for debugging

Returns:

  STATUS

Notes:

  There's some issue with the unicode printing routines. Therefore to 
  write to the output file properly, open it as binary and use fwrite.
  Ideally we could open it with just L"w" and use fwprintf().

--*/
STATUS
StringDBDumpDatabase (
  INT8                *DBFileName,
  INT8                *OutputFileName,
  BOOLEAN             Verbose
  )
{
  LANGUAGE_LIST     *Lang;
  STRING_IDENTIFIER *StringIdentifier;
  STRING_LIST       *StrList;
  FILE              *OutFptr;
  WCHAR             WChar;
  WCHAR             *WOutputFileName;
  WCHAR             CrLf[2];
  WCHAR             Line[200];
  WCHAR             *Scope;
  //
  // This function assumes the database has already been read, and
  // we're just dumping our internal data structures to a unicode file.
  //
  if (Verbose) {
    fprintf (stdout, "Dumping database file %s\n", DBFileName);
  }

  WOutputFileName = AsciiToWchar (OutputFileName);
  OutFptr         = _wfopen (WOutputFileName, L"wb");
  free (WOutputFileName);
  if (OutFptr == NULL) {
    Error (NULL, 0, 0, OutputFileName, "failed to open output file for writing");
    return STATUS_ERROR;
  }

  WChar = UNICODE_FILE_START;
  fwrite (&WChar, sizeof (WCHAR), 1, OutFptr);
  CrLf[1] = UNICODE_LF;
  CrLf[0] = UNICODE_CR;
  //
  // The default control character is '/'. Make it '#' by writing
  // "/=#" to the output file.
  //
#ifdef USE_VC8
  swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"/=#");
#else
  swprintf (Line, L"/=#");
#endif
  fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
  fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
  fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
  //
  // Dump all the string identifiers and their values
  //
  StringDBAssignStringIndexes ();
  for (StringIdentifier = mDBData.StringIdentifier; StringIdentifier != NULL; StringIdentifier = StringIdentifier->Next) {
    //
    // Write the "#define " string
    //
    if (StringIdentifier->Flags & STRING_FLAGS_REFERENCED) {
#ifdef USE_VC8
      swprintf (
        Line,
        wcslen(Line) * sizeof (WCHAR),
        L"%s %-60.60s 0x%04X",
        DEFINE_STR,
        StringIdentifier->StringName,
        StringIdentifier->Index
        );
#else
      swprintf (
        Line,
        L"%s %-60.60s 0x%04X",
        DEFINE_STR,
        StringIdentifier->StringName,
        StringIdentifier->Index
        );
#endif
    } else {
#ifdef USE_VC8
      swprintf (
        Line,
        wcslen(Line) * sizeof (WCHAR), 
        L"%s %-60.60s 0x%04X  // NOT REFERENCED",
        DEFINE_STR,
        StringIdentifier->StringName,
        StringIdentifier->Index
        );
#else
      swprintf (
        Line,
        L"%s %-60.60s 0x%04X  // NOT REFERENCED",
        DEFINE_STR,
        StringIdentifier->StringName,
        StringIdentifier->Index
        );
#endif
    }

    fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
    fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
  }

  fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
  //
  // Now write all the strings for each language.
  //
  WChar = UNICODE_DOUBLE_QUOTE;
  Scope = NULL;
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
#ifdef USE_VC8
    swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"#langdef %s \"%s\"", Lang->LanguageName, Lang->PrintableLanguageName);
#else
    swprintf (Line, L"#langdef %s \"%s\"", Lang->LanguageName, Lang->PrintableLanguageName);
#endif
    fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
    fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
    fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
    //
    // Now the strings (in double-quotes) for this language. Write
    // #string STR_NAME  #language eng "string"
    //
    for (StrList = Lang->String; StrList != NULL; StrList = StrList->Next) {
      //
      // Print the internal flags for debug
      //
#ifdef USE_VC8
      swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"// flags=0x%02X", (UINT32) StrList->Flags);
#else
      swprintf (Line, L"// flags=0x%02X", (UINT32) StrList->Flags);
#endif
      fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
      fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
      //
      // Print the scope if changed
      //
      if ((Scope == NULL) || (wcscmp (Scope, StrList->Scope) != 0)) {
#ifdef USE_VC8
        swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"#scope %s", StrList->Scope);
#else
        swprintf (Line, L"#scope %s", StrList->Scope);
#endif
        fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
        fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
        Scope = StrList->Scope;
      }

#ifdef USE_VC8
      swprintf (
        Line,
        wcslen(Line) * sizeof (WCHAR), 
        L"#string %-50.50s #language %s \"",
        StrList->StringName,
        Lang->LanguageName
        );
#else
      swprintf (
        Line,
        L"#string %-50.50s #language %s \"",
        StrList->StringName,
        Lang->LanguageName
        );
#endif
      fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
      fwrite (StrList->Str, StrList->Size - sizeof (WCHAR), 1, OutFptr);
#ifdef USE_VC8
      swprintf (Line, wcslen(Line) * sizeof (WCHAR), L"\"");
#else
      swprintf (Line, L"\"");
#endif
      fwrite (Line, wcslen (Line) * sizeof (WCHAR), 1, OutFptr);
      fwrite (&CrLf, sizeof (CrLf), 1, OutFptr);
    }
  }

  fclose (OutFptr);
  return STATUS_SUCCESS;
}

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

/*++

Routine Description:

  Given a primary language, a string identifier number, and a list of
  languages, find a secondary string.

Arguments:

  LanguageName      - primary language, like "spa"
  StringId          - string index value
  LanguageList      - linked list of "eng", "spa+cat",...

Returns:

  Pointer to a secondary string if found. NULL otherwise.

Notes:
 
  Given: LanguageName "spa"   and  LanguageList "spa+cat", match the
  "spa" and extract the "cat" and see if there is a string defined
  for "cat".StringId.

--*/
static
STATUS
StringDBWriteStringIdentifier (
  FILE                *DBFptr,
  UINT16              StringId,
  UINT16              Flags,
  WCHAR               *IdentifierName
  )
{
  DB_DATA_ITEM_HEADER Hdr;
  memset (&Hdr, 0, sizeof (DB_DATA_ITEM_HEADER));
  Hdr.DataType = DB_DATA_TYPE_STRING_IDENTIFIER;
  if (fwrite (&Hdr, sizeof (DB_DATA_ITEM_HEADER), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string to output database file", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (&StringId, sizeof (StringId), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write StringId to output database", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (&Flags, sizeof (Flags), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write StringId flags to output database", NULL);
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, IdentifierName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  return STATUS_SUCCESS;
}

static
STATUS
StringDBReadStringIdentifier (
  FILE                *DBFptr
  )
{
  WCHAR   *IdentifierName;
  UINT16  Flags;
  UINT16  StringId;
  UINT16  Size;

  if (fread (&StringId, sizeof (StringId), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read StringId from database", NULL);
    return STATUS_ERROR;
  }

  if (fread (&Flags, sizeof (Flags), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read StringId flags from database", NULL);
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &IdentifierName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  StringDBAddStringIdentifier (IdentifierName, &StringId, Flags);
  //
  // printf ("STRID:  0x%04X %S\n", (UINT32)StringId, IdentifierName);
  //
  FREE (IdentifierName);
  return STATUS_SUCCESS;
}

static
STATUS
StringDBWriteString (
  FILE            *DBFptr,
  UINT16          Flags,
  WCHAR           *Language,
  WCHAR           *StringName,
  WCHAR           *Scope,
  WCHAR           *Str
  )
{
  DB_DATA_ITEM_HEADER Hdr;
  memset (&Hdr, 0, sizeof (DB_DATA_ITEM_HEADER));
  Hdr.DataType = DB_DATA_TYPE_STRING_DEFINITION;
  if (fwrite (&Hdr, sizeof (DB_DATA_ITEM_HEADER), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string header to output database file", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (&Flags, sizeof (Flags), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string flags to output database", NULL);
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, Language) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, StringName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, Scope) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, Str) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }
  //
  // printf ("DBWriteString: %S.%S.%S\n", Language, StringName, Scope);
  //
  return STATUS_SUCCESS;
}

static
STATUS
StringDBReadString (
  FILE            *DBFptr
  )
{
  UINT16  Flags;
  UINT16  Size;
  WCHAR   *Language;
  WCHAR   *StringName;
  WCHAR   *Scope;
  WCHAR   *Str;

  if (fread (&Flags, sizeof (Flags), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read string flags from database", NULL);
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &Language) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &StringName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &Scope) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &Str) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }
  //
  // If the first or second string (language name and printable language name),
  // then skip them. They're added via language definitions data items in
  // the database.
  //
  if (StringName[0] != L'$') {
    StringDBAddString (Language, StringName, Scope, Str, FALSE, Flags);
  }
  //
  // printf ("DBReadString: %S.%S.%S\n", Language, StringName, Scope);
  //
  FREE (Language);
  FREE (StringName);
  if (Str != NULL) {
    FREE (Str);
  }

  if (Scope != NULL) {
    FREE (Scope);
  }

  return STATUS_SUCCESS;
}

static
STATUS
StringDBWriteLanguageDefinition (
  FILE            *DBFptr,
  WCHAR           *LanguageName,
  WCHAR           *PrintableLanguageName,
  WCHAR           *SecondaryLanguageList
  )
{
  DB_DATA_ITEM_HEADER Hdr;
  memset (&Hdr, 0, sizeof (DB_DATA_ITEM_HEADER));
  Hdr.DataType = DB_DATA_TYPE_LANGUAGE_DEFINITION;
  if (fwrite (&Hdr, sizeof (DB_DATA_ITEM_HEADER), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string to output database file", NULL);
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, LanguageName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, PrintableLanguageName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBWriteGenericString (DBFptr, SecondaryLanguageList) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  return STATUS_SUCCESS;
}

static
STATUS
StringDBReadLanguageDefinition (
  FILE            *DBFptr
  )
{
  WCHAR   *LanguageName = NULL;
  WCHAR   *PrintableLanguageName = NULL;
  WCHAR   *SecondaryLanguageList = NULL;
  UINT16  Size;
  STATUS  Status;

  if (StringDBReadGenericString (DBFptr, &Size, &LanguageName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &PrintableLanguageName) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  if (StringDBReadGenericString (DBFptr, &Size, &SecondaryLanguageList) != STATUS_SUCCESS) {
    return STATUS_ERROR;
  }

  //
  // printf("LANG: %S %S\n", LanguageName, PrintableLanguageName);
  //
  Status = StringDBAddLanguage (LanguageName, PrintableLanguageName, SecondaryLanguageList);
  FREE (LanguageName);
  FREE (PrintableLanguageName);
  FREE (SecondaryLanguageList);
  return Status;
}
//
// All unicode strings in the database consist of a UINT16 length
// field, followed by the string itself. This routine reads one
// of those and returns the info.
//
static
STATUS
StringDBReadGenericString (
  FILE      *DBFptr,
  UINT16    *Size,
  WCHAR     **Str
  )
{
  UINT16  LSize;
  UINT16  Flags;
  WCHAR   *LStr;

  if (fread (&LSize, sizeof (UINT16), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read a string length field from the database", NULL);
    return STATUS_ERROR;
  }

  if (fread (&Flags, sizeof (UINT16), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to read a string flags field from the database", NULL);
    return STATUS_ERROR;
  }

  LStr = MALLOC (LSize);
  if (LStr == NULL) {
    Error (__FILE__, __LINE__, 0, "memory allocation failed reading the database", NULL);
    return STATUS_ERROR;
  }

  if (fread (LStr, sizeof (WCHAR), (UINT32) LSize / sizeof (WCHAR), DBFptr) != (UINT32) LSize / sizeof (WCHAR)) {
    Error (NULL, 0, 0, "failed to read string from database", NULL);
    Error (NULL, 0, 0, "database read failure", "offset 0x%X", ftell (DBFptr));
    free (LStr);
    return STATUS_ERROR;
  }
  //
  // printf ("DBR: %S\n", LStr);
  //
  // If the flags field indicated we were asked to write a NULL string, then
  // return them a NULL pointer.
  //
  if (Flags & STRING_FLAGS_UNDEFINED) {
    *Size = 0;
    *Str  = NULL;
  } else {
    *Size = LSize;
    *Str  = LStr;
  }

  return STATUS_SUCCESS;
}

static
STATUS
StringDBWriteGenericString (
  FILE      *DBFptr,
  WCHAR     *Str
  )
{
  UINT16  Size;
  UINT16  Flags;
  WCHAR   ZeroString[1];
  //
  // Strings in the database consist of a size UINT16 followed
  // by the string itself.
  //
  if (Str == NULL) {
    ZeroString[0] = 0;
    Str           = ZeroString;
    Size          = sizeof (ZeroString);
    Flags         = STRING_FLAGS_UNDEFINED;
  } else {
    Flags = 0;
    Size  = (UINT16) ((wcslen (Str) + 1) * sizeof (WCHAR));
  }

  if (fwrite (&Size, sizeof (UINT16), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string size to database", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (&Flags, sizeof (UINT16), 1, DBFptr) != 1) {
    Error (NULL, 0, 0, "failed to write string flags to database", NULL);
    return STATUS_ERROR;
  }

  if (fwrite (Str, sizeof (WCHAR), Size / sizeof (WCHAR), DBFptr) != Size / sizeof (WCHAR)) {
    Error (NULL, 0, 0, "failed to write string to database", NULL);
    return STATUS_ERROR;
  }

  return STATUS_SUCCESS;
}

static
STRING_LIST *
StringDBFindString (
  WCHAR                       *LanguageName,
  WCHAR                       *StringName,
  WCHAR                       *Scope,
  WCHAR_STRING_LIST           *LanguagesOfInterest,
  WCHAR_MATCHING_STRING_LIST  *IndirectionList
  )
{
  LANGUAGE_LIST               *Lang;
  STRING_LIST                 *CurrString;
  WCHAR_MATCHING_STRING_LIST  *IndListPtr;
  WCHAR                       TempLangName[LANGUAGE_IDENTIFIER_NAME_LEN + 1];
  WCHAR                       *WCharPtr;

  //
  // If we were given an indirection list, then see if one was specified for this
  // string identifier. That is to say, if the indirection says "STR_ID_MY_FAVORITE MyScope",
  // then if this string name matches one in the list, then do a lookup with the
  // specified scope and return that value.
  //
  if (IndirectionList != NULL) {
    for (IndListPtr = IndirectionList; IndListPtr != NULL; IndListPtr = IndListPtr->Next) {
      if (wcscmp (StringName, IndListPtr->Str1) == 0) {
        CurrString = StringDBFindString (LanguageName, StringName, IndListPtr->Str2, LanguagesOfInterest, NULL);
        if (CurrString != NULL) {
          return CurrString;
        }
      }
    }
  }
  //
  // First look for exact match language.stringname
  //
  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    if (wcscmp (LanguageName, Lang->LanguageName) == 0) {
      //
      // Found language match. Try to find string name match
      //
      for (CurrString = Lang->String; CurrString != NULL; CurrString = CurrString->Next) {
        if (wcscmp (StringName, CurrString->StringName) == 0) {
          //
          // Found a string name match. See if we're supposed to find
          // a scope match.
          //
          if (Scope != NULL) {
            if (wcscmp (CurrString->Scope, Scope) == 0) {
              return CurrString;
            }
          } else {
            return CurrString;
          }
        }
      }
    }
  }
  //
  // If we got here, then we didn't find a match. Look for secondary string
  // matches. That is to say, if we're processing "spa", and they requested
  // "spa+cat", then recursively call with "cat"
  //
  while (LanguagesOfInterest != NULL) {
    //
    // If this is the language we're looking for, then process the
    // languages of interest list for it.
    //
    if (wcsncmp (LanguageName, LanguagesOfInterest->Str, LANGUAGE_IDENTIFIER_NAME_LEN) == 0) {
      WCharPtr = LanguagesOfInterest->Str + LANGUAGE_IDENTIFIER_NAME_LEN;
      while (*WCharPtr) {
        //
        // Double-check the length, though it should have been checked on the
        // command line.
        //
        if (wcslen (WCharPtr) < LANGUAGE_IDENTIFIER_NAME_LEN) {
          Error (NULL, 0, 0, "malformed alternate language list", "%S", LanguagesOfInterest->Str);
          return NULL;
        }

        wcsncpy (TempLangName, WCharPtr, LANGUAGE_IDENTIFIER_NAME_LEN);
        TempLangName[LANGUAGE_IDENTIFIER_NAME_LEN]  = 0;
        CurrString = StringDBFindString (TempLangName, StringName, NULL, NULL, IndirectionList);
        if (CurrString != NULL) {
          return CurrString;
        }

        WCharPtr += LANGUAGE_IDENTIFIER_NAME_LEN;
      }
    }

    LanguagesOfInterest = LanguagesOfInterest->Next;
  }

  return NULL;
}

STATUS
StringDBSetScope (
  WCHAR   *Scope
  )
{
  //
  // Free up existing scope memory.
  //
  if (mDBData.CurrentScope != NULL) {
    FREE (mDBData.CurrentScope);
  }

  mDBData.CurrentScope = DuplicateString (Scope);
  return STATUS_SUCCESS;
}
//
// We typically don't assign index values to string identifiers
// until we're ready to write out files. To reduce the size of
// the output file, re-order the string identifiers to move any
// unreferenced ones to the end. Then we'll walk the list
// again to assign string indexes, keeping track of the last
// one referenced.
//
static
void
StringDBAssignStringIndexes (
  VOID
  )
{
  STRING_IDENTIFIER *StrId;
  STRING_IDENTIFIER *FirstUsed;
  STRING_IDENTIFIER *LastUsed;
  STRING_IDENTIFIER *FirstUnused;
  STRING_IDENTIFIER *LastUnused;
  UINT32            Index;
  UINT32            MaxReferenced;

  //
  // Create two lists -- used and unused. Then put them together with
  // the unused ones on the end.
  //
  FirstUsed   = NULL;
  LastUsed    = NULL;
  FirstUnused = NULL;
  LastUnused  = NULL;
  StrId       = mDBData.StringIdentifier;
  while (StrId != NULL) {
    if ((StrId->Flags & STRING_FLAGS_REFERENCED) == 0) {
      //
      // Put it on the unused list
      //
      if (FirstUnused == NULL) {
        FirstUnused = StrId;
      } else {
        LastUnused->Next = StrId;
      }

      LastUnused        = StrId;
      StrId             = StrId->Next;
      LastUnused->Next  = NULL;
    } else {
      //
      // Put it on the used list
      //
      if (FirstUsed == NULL) {
        FirstUsed = StrId;
      } else {
        LastUsed->Next = StrId;
      }

      LastUsed        = StrId;
      StrId           = StrId->Next;
      LastUsed->Next  = NULL;
    }
  }
  //
  // Join the lists
  //
  if (FirstUsed != NULL) {
    mDBData.StringIdentifier  = FirstUsed;
    LastUsed->Next            = FirstUnused;
  } else {
    mDBData.StringIdentifier = FirstUnused;
  }

  MaxReferenced = 0;
  Index         = 0;
  for (StrId = mDBData.StringIdentifier; StrId != NULL; StrId = StrId->Next) {
    StrId->Index = Index;
    Index++;
    if (StrId->Flags & STRING_FLAGS_REFERENCED) {
      mDBData.NumStringIdentifiersReferenced = Index;
    }
  }

  mDBData.NumStringIdentifiers = Index;
}

static
WCHAR *
DuplicateString (
  WCHAR   *Str
  )
{
  WCHAR *NewStr;
  if (Str == NULL) {
    return NULL;
  }

  NewStr = MALLOC ((wcslen (Str) + 1) * sizeof (WCHAR));
  if (NewStr == NULL) {
    Error (NULL, 0, 0, "memory allocation failure", NULL);
    return NULL;
  }

  wcscpy (NewStr, Str);
  return NewStr;
}

static
WCHAR *
WstrCatenate (
  WCHAR *Dst,
  WCHAR *Src
  )
{
  UINT32 Len  = 0;
  WCHAR  *Bak = Dst;

  if (Src == NULL) {
    return Dst;
  }

  if (Dst != NULL) {
    Len = wcslen (Dst);
  }
  Len += wcslen (Src);
  Dst = (WCHAR *) malloc ((Len + 1) * 2);
  if (Dst == NULL) {
    return NULL;
  }

  Dst[0] = L'\0';
  if (Bak != NULL) {
    wcscpy (Dst, Bak);
    FREE (Bak);
  }
  wcscat (Dst, Src);
  return Dst;
}

static
WCHAR *
AsciiToWchar (
  INT8 *Str
  )
{
  UINT32  Len;
  WCHAR   *NewStr;
  WCHAR   *Ptr;

  Len     = strlen (Str) + 1;
  NewStr  = (WCHAR *) malloc (Len * sizeof (WCHAR));
  for (Ptr = NewStr; *Str != 0; Str++, Ptr++) {
    *Ptr = (UINT16) (UINT8) *Str;
  }

  *Ptr = 0;
  return NewStr;
}

static
CHAR8 *
WcharToAscii (
  WCHAR *Str
  )
{
  UINT32  Len;
  CHAR8   *NewStr;
  CHAR8   *Ptr;

  Len     = wcslen (Str) + 1;
  NewStr  = (CHAR8 *) malloc (Len * sizeof (CHAR8));
  for (Ptr = NewStr; *Str != L'\0'; Str++, Ptr++) {
    *Ptr = (CHAR8) *Str;
  }

  *Ptr = '\0';
  return NewStr;
}

/*****************************************************************************/
CHAR8 *
unicode2ascii (
  WCHAR *UnicodeStr
  )
{
  CHAR8     *RetStr   = (CHAR8 *)UnicodeStr;
  CHAR8     *AsciiStr = (CHAR8 *)UnicodeStr;

  while (*UnicodeStr != '\0') {
    *AsciiStr = (CHAR8) *(UnicodeStr++);
    AsciiStr++;
  }
  *AsciiStr = '\0';

  return RetStr;
}

STATUS
BuildStringPkgHdr (
  IN  WCHAR                       *PrimaryLangName,
  IN  WCHAR                       *SecondaryLangList,
  IN  UINT32                      Type,
  IN  UINT32                      PkgBlkSize,
  OUT EFI_HII_STRING_PACKAGE_HDR  **StrPkgHdr
  )
{
  UINT32  LangNameLen;

  LangNameLen = wcslen (PrimaryLangName);
  if (SecondaryLangList != NULL) {
    LangNameLen += wcslen (SecondaryLangList) + 1;
  }

  *StrPkgHdr = (EFI_HII_STRING_PACKAGE_HDR *) malloc(sizeof (EFI_HII_STRING_PACKAGE_HDR) + LangNameLen);
  if (*StrPkgHdr == NULL) {
    return STATUS_ERROR;
  }
  memset (*StrPkgHdr, 0, sizeof (EFI_HII_STRING_PACKAGE_HDR) + LangNameLen);

  (*StrPkgHdr)->Header.Type       = Type;
  (*StrPkgHdr)->Header.Length     = PkgBlkSize + sizeof (EFI_HII_STRING_PACKAGE_HDR) + LangNameLen;
  (*StrPkgHdr)->HdrSize           = sizeof (EFI_HII_STRING_PACKAGE_HDR) + LangNameLen;
  (*StrPkgHdr)->StringInfoOffset  = sizeof (EFI_HII_STRING_PACKAGE_HDR) + LangNameLen;
  (*StrPkgHdr)->LanguageWindow[0] = L'\0';
  (*StrPkgHdr)->LanguageName      = (EFI_STRING_ID)1;

  strcpy ((*StrPkgHdr)->Language, unicode2ascii(PrimaryLangName));
  if (SecondaryLangList != NULL) {
    strcat ((*StrPkgHdr)->Language, ";");
    strcat ((*StrPkgHdr)->Language, unicode2ascii(SecondaryLangList));
  }

#ifdef DEBUG_STRGATHER
  printf ("STR HDR\t %s\n", (*StrPkgHdr)->Language);
#endif
  return STATUS_SUCCESS;
}

STATUS
BuildStringPkgUCS2Blk (
  IN  EFI_STRING_ID                   StringId,
  IN  WCHAR                           *LangName,
  IN  WCHAR                           *StrName,
  OUT EFI_HII_SIBT_STRING_UCS2_BLOCK  **StrBlk,
  OUT UINT32                          *BlkSize
  )
{
  UINT32      StrLen      = 0;
  STRING_LIST *CurrString = NULL;

  if ((LangName == NULL) || (StrName == NULL) || (StrBlk == NULL)) {
    return STATUS_ERROR;
  }

  *StrBlk  = NULL;
  *BlkSize = 0;

  CurrString = StringDBFindString (LangName, StrName, NULL, NULL, NULL);
  if (CurrString == NULL) {
  	return STATUS_WARNING;
  }

  StrLen = wcslen (CurrString->Str);
  *BlkSize = sizeof (EFI_HII_SIBT_STRING_UCS2_BLOCK) + StrLen * 2;
  *StrBlk  = (EFI_HII_SIBT_STRING_UCS2_BLOCK *) malloc (*BlkSize);
  if (*StrBlk == NULL) {
    *StrBlk  = NULL;
    *BlkSize = 0;
    return STATUS_ERROR;
  }
  (*StrBlk)->Header.BlockType = EFI_HII_SIBT_STRING_UCS2;
  wcscpy((*StrBlk)->StringText, CurrString->Str);

  return STATUS_SUCCESS;
}

STATUS
BuildStringPkgSKIP2Blk (
  IN  EFI_STRING_ID                   SkipIdCount,
  OUT EFI_HII_SIBT_SKIP2_BLOCK        **StrBlk
  )
{
  if (StrBlk == NULL) {
    return STATUS_ERROR;
  }

  *StrBlk  = NULL;

  *StrBlk  = (EFI_HII_SIBT_SKIP2_BLOCK *) malloc (sizeof (EFI_HII_SIBT_SKIP2_BLOCK));
  if (*StrBlk == NULL) {
    *StrBlk  = NULL;
    return STATUS_ERROR;
  }
  (*StrBlk)->Header.BlockType = EFI_HII_SIBT_SKIP2;
  (*StrBlk)->SkipCount        = SkipIdCount;

  return STATUS_SUCCESS;
}

STATUS
BuildStringPkgEndBlk (
  OUT EFI_HII_SIBT_END_BLOCK **End
  )
{
  *End = (EFI_HII_SIBT_END_BLOCK *) malloc (sizeof (EFI_HII_SIBT_END_BLOCK));
  if (*End == NULL) {
    return STATUS_ERROR;
  }

  (*End)->Header.BlockType = EFI_HII_SIBT_END;
  return STATUS_SUCCESS;
}

/*++

Routine Description:

  Create an HII export string pack for the strings in our database.

Arguments:

  FileName        - name of the output file to write 

Returns:

  STATUS


--*/
STATUS
StrPkgBlkBufferListAddTail (
  IN EFI_STRING_ID      StringId,
  IN WCHAR              *StrName,
  IN SPkgBlkBuffer      **PkgBufferListHead,
  IN SPkgBlkBuffer      **PkgBufferListTail,
  IN VOID               *Buffer,
  IN UINT32             Size 
  )
{
  SPkgBlkBuffer         *pNew = NULL;
#ifdef DEBUG_STRGATHER
  EFI_HII_STRING_BLOCK  *SBlk = (EFI_HII_STRING_BLOCK *)Buffer;
#endif

  if ((PkgBufferListHead == NULL) || (PkgBufferListTail == NULL)) {
    return STATUS_ERROR;
  }

  pNew = (SPkgBlkBuffer *) malloc (sizeof (SPkgBlkBuffer));
  if (pNew == NULL) {
    return STATUS_ERROR;
  }

  pNew->mBlkBuffer = Buffer;
  pNew->mBlkSize   = Size;
  if ((*PkgBufferListTail) == NULL) {
    (*PkgBufferListHead) = (*PkgBufferListTail) = pNew;
  } else {
    (*PkgBufferListTail)->mNext = pNew;
    (*PkgBufferListTail) = pNew;
    pNew->mNext = NULL;
  }

#ifdef DEBUG_STRGATHER
  switch (SBlk->BlockType) {
  case EFI_HII_SIBT_STRING_UCS2 :
    printf ("\tID: [%x] TYPE: [UCS2]\t NAME: %S \t STR: %S\n", StringId, StrName, ((EFI_HII_SIBT_STRING_UCS2_BLOCK *)SBlk)->StringText);
    break;
  case EFI_HII_SIBT_SKIP2 :
    printf ("\tID: [NULL] TYPE: [SKIP2] SKIPCOUNT: [%x]\n", ((EFI_HII_SIBT_SKIP2_BLOCK *)SBlk)->SkipCount);
    break;
  case EFI_HII_SIBT_END :
    printf ("\tID: [%x] TYPE: [END]\n", StringId);
    break;
  default :
    printf ("!!!!UNKNOWN STRING TYPE!!!\n");
  }
#endif

  return STATUS_SUCCESS;
}

VOID
StrPkgHdrFree (
  IN EFI_HII_STRING_PACKAGE_HDR *StrPkgHdr
  )
{
  if (StrPkgHdr != NULL) {
    free (StrPkgHdr);
  }
}

VOID
StrPkgBlkBufferListFree (
  IN SPkgBlkBuffer *PkgBlkList
  )
{
  SPkgBlkBuffer  *Buffer;

  while (PkgBlkList != NULL) {
    Buffer      = PkgBlkList;
    PkgBlkList = PkgBlkList->mNext;

    if (Buffer->mBlkBuffer != NULL) {
      free (Buffer->mBlkBuffer);
    }
    free (Buffer);
  }
}

VOID
WriteBlockLine (
  IN FILE   *pFile,
  IN UINT32 LineBytes,
  IN INT8   *LineHeader,
  IN INT8   *BlkBuf,
  IN UINT32 BlkSize
  )
{
  UINT32    Index;

  if ((pFile == NULL) || (LineHeader == NULL) || (BlkBuf == NULL)) {
    return;
  }

  for (Index = 0; Index < BlkSize; Index++) {
    if ((Index % LineBytes) == 0) {
      fprintf (pFile, "\n%s", LineHeader);
    }
    fprintf (pFile, "0x%02X,  ", (UINT8)BlkBuf[Index]);
  }
}

VOID
WriteBlockEnd (
  IN FILE   *pFile,
  IN UINT32 LineBytes,
  IN INT8   *LineHeader,
  IN INT8   *BlkBuf,
  IN UINT32 BlkSize
  )
{
  UINT32    Index;

  if ((BlkSize == 0) || (pFile == NULL) || (LineHeader == NULL) || (BlkBuf == NULL)) {
    return;
  }

  for (Index = 0; Index < BlkSize - 1; Index++) {
    if ((Index % LineBytes) == 0) {
      fprintf (pFile, "\n%s", LineHeader);
    }
    fprintf (pFile, "0x%02X,  ", (UINT8)BlkBuf[Index]);
  }

  if ((Index % LineBytes) == 0) {
    fprintf (pFile, "\n%s", LineHeader);
  }
  fprintf (pFile, "0x%02X\n", (UINT8)BlkBuf[Index]);
}

#define BYTES_PRE_LINE 0x10

VOID
StrPkgWriteHdrCFile (
  IN FILE                       *File,
  IN EFI_HII_STRING_PACKAGE_HDR *StrPkgHdr
  )
{
  if (StrPkgHdr != NULL) {
    fprintf (File, "\n  // PACKAGE HEADER\n");
    WriteBlockLine(File, BYTES_PRE_LINE, "  ", (INT8 *)StrPkgHdr, StrPkgHdr->HdrSize);
  }
}

VOID
StrPkgWirteArrayLength (
  IN FILE                       *File,
  IN UINT32                     PkgNumber,
  IN EFI_HII_STRING_PACKAGE_HDR **PkgHdr
  )
{
  UINT32                        Index;
  UINT32                        ArrayLen;

  ArrayLen = sizeof (UINT32);
  for (Index = 0; Index < PkgNumber; Index++) {
    if (PkgHdr[Index] != NULL) {
      ArrayLen += PkgHdr[Index]->Header.Length;
    }
  }

  fprintf (File, "\n  // STRING ARRAY LENGTH\n");
  WriteBlockLine(File, BYTES_PRE_LINE, "  ", (UINT8 *)&ArrayLen, sizeof (UINT32));
}

VOID
StrPkgWriteBlkListCFile (
  IN FILE                       *File,
  IN SPkgBlkBuffer              *BlkList,
  IN BOOLEAN                    WriteEnd
  )
{
  SPkgBlkBuffer  *Buffer;

  fprintf (File, "\n\n  // PACKAGE DATA\n");

  while (BlkList != NULL) {
    Buffer   = BlkList;
    BlkList = BlkList->mNext;

    if ((Buffer->mNext == NULL) && (WriteEnd == TRUE)) {
      if (Buffer->mBlkBuffer != NULL) {
        WriteBlockEnd (File, BYTES_PRE_LINE, "  ", Buffer->mBlkBuffer, Buffer->mBlkSize);
      }
    } else {
      if (Buffer->mBlkBuffer != NULL) {
        WriteBlockLine(File, BYTES_PRE_LINE, "  ", Buffer->mBlkBuffer, Buffer->mBlkSize);
      }
    }
  }
}

VOID
StrPkgWriteHdrBinary (
  IN FILE                       *File,
  IN EFI_HII_STRING_PACKAGE_HDR *StrPkgHdr
  )
{
  fwrite (StrPkgHdr, StrPkgHdr->HdrSize, 1, File);
}

VOID
StrPkgWriteBlkListBinary (
  IN FILE                       *File,
  IN SPkgBlkBuffer              *BlkList
  )
{
  SPkgBlkBuffer  *Buffer;

  while (BlkList != NULL) {
    Buffer   = BlkList;
    BlkList = BlkList->mNext;

    if (Buffer->mBlkBuffer != NULL) {
      fwrite (Buffer->mBlkBuffer, Buffer->mBlkSize, 1, File);
    }
  }
}

STATUS
StringDBGenStrPkgHdrAndBlkList (
  IN  LANGUAGE_LIST              *Lang,
  OUT EFI_HII_STRING_PACKAGE_HDR **StrPkgHdr,
  OUT SPkgBlkBuffer              **BlkList
  )
{
  STATUS                          Status;
  UINT32                          StringIndex;
  EFI_STRING_ID                   StringIdCurrent;
  EFI_STRING_ID                   SkipIdCount;
  UINT32                          BlkSize = 0;
  EFI_HII_SIBT_STRING_UCS2_BLOCK  *StrUCS2Blk  = NULL;
  EFI_HII_SIBT_SKIP2_BLOCK        *StrSKIP2Blk = NULL;
  STRING_IDENTIFIER               *StringIdentifier = NULL;
  EFI_HII_SIBT_END_BLOCK          *EndBlk = NULL;
  UINT32                          PkgBlkSize = 0;
  SPkgBlkBuffer                   *PkgBufferListHead = NULL;
  SPkgBlkBuffer                   *PkgBufferListTail = NULL;

  if ((Lang == NULL) || (StrPkgHdr == NULL) || (BlkList == NULL)) {
    return STATUS_ERROR;
  }

  //
  // Assign index values to the string identifiers
  //
  StringDBAssignStringIndexes ();
  StringIdCurrent = EFI_STRING_ID_BEGIN;
  SkipIdCount     = 0;

  for (StringIndex = STRING_ID_PRINTABLE_LANGUAGE_NAME; StringIndex < mDBData.NumStringIdentifiersReferenced; StringIndex++) {
    if ((StringIdentifier = StringDBFindStringIdentifierByIndex (StringIndex)) == NULL) {
      Error (NULL, 0, 0, "internal error", "invalid string index 0x%X", StringIndex);
      goto ExportPackOut;
    }

    if (StringIdentifier->Flags & STRING_FLAGS_REFERENCED) {
      Status = BuildStringPkgUCS2Blk (StringIdCurrent, Lang->LanguageName, StringIdentifier->StringName, &StrUCS2Blk, &BlkSize);
      switch (Status) {
      case STATUS_ERROR: 
        goto ExportPackOut; 
        break;
      case STATUS_WARNING :
        SkipIdCount++;
        break;
      case STATUS_SUCCESS :
        if (SkipIdCount == 0) {
          if (StrPkgBlkBufferListAddTail (
                StringIdCurrent, 
                StringIdentifier->StringName, 
                &PkgBufferListHead, 
                &PkgBufferListTail, 
                StrUCS2Blk, 
                BlkSize
                ) != STATUS_SUCCESS) {
            goto ExportPackOut;
          }
          PkgBlkSize += BlkSize;
        } else {
          if (BuildStringPkgSKIP2Blk (SkipIdCount, &StrSKIP2Blk) != STATUS_SUCCESS) {
            goto ExportPackOut;
          } else {
            if (StrPkgBlkBufferListAddTail (
                StringIdCurrent, 
                NULL, 
                &PkgBufferListHead, 
                &PkgBufferListTail, 
                StrSKIP2Blk, 
                sizeof (EFI_HII_SIBT_SKIP2_BLOCK)
                ) != STATUS_SUCCESS) {
              goto ExportPackOut;
            }
            PkgBlkSize += sizeof (EFI_HII_SIBT_SKIP2_BLOCK);
            SkipIdCount = 0;
          }

          if (StrPkgBlkBufferListAddTail (
                StringIdCurrent, 
                StringIdentifier->StringName, 
                &PkgBufferListHead, 
                &PkgBufferListTail, 
                StrUCS2Blk, 
                BlkSize
                ) != STATUS_SUCCESS) {
            goto ExportPackOut;
          }
          PkgBlkSize += BlkSize;
        }
      }
    }

    StringIdCurrent++;
  }

  if (SkipIdCount != 0) {
    if (BuildStringPkgSKIP2Blk (SkipIdCount, &StrSKIP2Blk) != STATUS_SUCCESS) {
      goto ExportPackOut;
    } else {
      if (StrPkgBlkBufferListAddTail (
            StringIdCurrent, 
            NULL, 
            &PkgBufferListHead, 
            &PkgBufferListTail, 
            StrSKIP2Blk, 
            sizeof (EFI_HII_SIBT_SKIP2_BLOCK)
            ) != STATUS_SUCCESS) {
        goto ExportPackOut;
      }
      PkgBlkSize += sizeof (EFI_HII_SIBT_SKIP2_BLOCK);
      SkipIdCount = 0;
    }
  }

  if (BuildStringPkgEndBlk (&EndBlk) != STATUS_SUCCESS) {
    goto ExportPackOut;
  } else if (StrPkgBlkBufferListAddTail (
               StringIdCurrent, 
               NULL, 
               &PkgBufferListHead, 
               &PkgBufferListTail, 
               EndBlk, 
               sizeof (EFI_HII_SIBT_END_BLOCK)
               ) != STATUS_SUCCESS) {
    goto ExportPackOut;
  }
  StringIdCurrent++;
  PkgBlkSize += sizeof (EFI_HII_SIBT_END_BLOCK);

  if (BuildStringPkgHdr(
        Lang->LanguageName, 
        Lang->SecondaryLanguageList,
        EFI_HII_PACKAGE_STRINGS, 
        PkgBlkSize, 
        StrPkgHdr
        ) != STATUS_SUCCESS) {
    goto ExportPackOut;
  }

  *BlkList   = PkgBufferListHead;

  return STATUS_SUCCESS;

ExportPackOut:
  StrPkgBlkBufferListFree(PkgBufferListHead);
  *BlkList   = NULL;
  *StrPkgHdr = NULL;
  return STATUS_ERROR;
}

STATUS
StringDBCreateHiiExportPack (
  INT8                        *FileName,
  WCHAR_STRING_LIST           *LanguagesOfInterest
  )
{
  FILE                            *File;
  LANGUAGE_LIST                   *Lang;
  EFI_HII_STRING_PACKAGE_HDR      *StrPkgHdr;
  SPkgBlkBuffer                   *BlkList;
  WCHAR_STRING_LIST               *LOIPtr;

  if (FileName == NULL) {
    return STATUS_ERROR;
  }

  if ((File = fopen (FileName, "wb")) == NULL) {
    Error (NULL, 0, 0, FileName, "failed to open output HII export file");
    return STATUS_ERROR;
  }

  for (Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    for (LOIPtr = LanguagesOfInterest; LOIPtr != NULL; LOIPtr = LOIPtr->Next) {
      if (wcscmp (LOIPtr->Str, Lang->LanguageName) == 0) {
        break;
      }
    }

    if ((LanguagesOfInterest == NULL) ||
        (LanguagesOfInterest != NULL && LOIPtr != NULL)) {
      if (StringDBGenStrPkgHdrAndBlkList(Lang, &StrPkgHdr, &BlkList) != STATUS_SUCCESS) {
        fclose (File);
        return STATUS_SUCCESS;
      }

      StrPkgWriteHdrBinary (File, StrPkgHdr);
      StrPkgWriteBlkListBinary (File, BlkList);

      StrPkgHdrFree (StrPkgHdr);
      StrPkgBlkBufferListFree (BlkList);
    }
  }
  fclose (File);
  return STATUS_SUCCESS;
}

static const char *gSourceFileHeader[] = {
  "//",
  "//  DO NOT EDIT -- auto-generated file",
  "//",
  "//  This file is generated by the StrGather utility",
  "//",
  NULL
};

STATUS
StringDBDumpCStrings (
  INT8                            *BaseName,
  INT8                            *FileName,
  WCHAR_STRING_LIST               *LanguagesOfInterest
  )
{
  EFI_STATUS                      Status;
  FILE                            *File;
  LANGUAGE_LIST                   *Lang;
  EFI_HII_STRING_PACKAGE_HDR      **StrPkgHdr;
  SPkgBlkBuffer                   **BlkList;
  UINT32                          Index;
  UINT32                          LangNumber;
  WCHAR_STRING_LIST               *LOIPtr;

  if ((BaseName == NULL) || (FileName == NULL)) {
    return STATUS_ERROR;
  }

  if (mDBData.LanguageList == NULL) {
    return STATUS_SUCCESS;
  }

  for (LangNumber = 0, Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next, LangNumber++)
    ;

  StrPkgHdr = (EFI_HII_STRING_PACKAGE_HDR **) malloc (sizeof (EFI_HII_STRING_PACKAGE_HDR *) * LangNumber);
  BlkList = (SPkgBlkBuffer **) malloc (sizeof (SPkgBlkBuffer *) * LangNumber);
  for (Index = 0; Index < LangNumber; Index++) {
    StrPkgHdr[Index] = NULL;
    BlkList[Index]   = NULL;
  }

  for (Index = 0, Lang = mDBData.LanguageList; Lang != NULL; Lang = Lang->Next) {
    for (LOIPtr = LanguagesOfInterest; LOIPtr != NULL; LOIPtr = LOIPtr->Next) {
      if (wcscmp (LOIPtr->Str, Lang->LanguageName) == 0) {
        break;
      }
    }
    if ((LanguagesOfInterest == NULL) ||
        (LanguagesOfInterest != NULL && LOIPtr != NULL)) {
      Status = StringDBGenStrPkgHdrAndBlkList(Lang, &StrPkgHdr[Index], &BlkList[Index]);
      Index++;
      if (EFI_ERROR(Status)) {
        free (StrPkgHdr);
        free (BlkList);
        return STATUS_ERROR;
      }
    }
  }

  //
  // Update LangNumber after filter
  //
  LangNumber = Index;

  if (LangNumber == 0) {
    free (StrPkgHdr);
    free (BlkList);
    return STATUS_SUCCESS;
  }

  if ((File = fopen (FileName, "w")) == NULL) {
    Error (NULL, 0, 0, FileName, "failed to open output C file - %s", FileName);
    return STATUS_ERROR;
  }

  for (Index = 0; gSourceFileHeader[Index] != NULL; Index++) {
    fprintf (File, "%s\n", gSourceFileHeader[Index]);
  }

  fprintf (File, "\nunsigned char %s[] = {\n", BaseName);

  //
  // Save the length of the string package array.
  //
  StrPkgWirteArrayLength (File, LangNumber, StrPkgHdr);

  for (Index = 0; Index < LangNumber; Index++) {
    StrPkgWriteHdrCFile (File, StrPkgHdr[Index]);
    StrPkgWriteBlkListCFile (File, BlkList[Index], (Index == LangNumber - 1) ? TRUE : FALSE);

    StrPkgHdrFree (StrPkgHdr[Index]);
    StrPkgBlkBufferListFree (BlkList[Index]);
  }

  fprintf (File, "\n};\n");

  fclose (File);
  free (StrPkgHdr);
  free (BlkList);
  return STATUS_SUCCESS;
}
