/*++

Copyright (c) 1991-2000,  Microsoft Corporation  All rights reserved.

Module Name:

    string.c

Abstract:

    This file contains functions that deal with characters and strings.

    APIs found in this file:
      CompareStringW
      GetStringTypeExW
      GetStringTypeW

Revision History:

    05-31-91    JulieB    Created.

--*/



//
//  Include Files.
//

#include "nls.h"
#include "jamo.h"




//
//  Constant Declarations.
//

//
//  State Table.
//
#define STATE_DW                  1    // normal diacritic weight state
#define STATE_REVERSE_DW          2    // reverse diacritic weight state
#define STATE_CW                  4    // case weight state
#define STATE_JAMO_WEIGHT         8    // jamo weight state


//
//  Invalid weight value.
//
#define CMP_INVALID_WEIGHT        0xffffffff
#define CMP_INVALID_FAREAST       0xffff0000
#define CMP_INVALID_UW            0xffff




//
//  Forward Declarations.
//

int
LongCompareStringW(
    PLOC_HASH pHashN,
    DWORD dwCmpFlags,
    LPCWSTR lpString1,
    int cchCount1,
    LPCWSTR lpString2,
    int cchCount2,
    BOOL fModify);

int
FindJamoDifference(
    PLOC_HASH pHashN,
    LPCWSTR* ppString1,
    int* ctr1,
    int cchCount1,
    DWORD* pWeight1,
    LPCWSTR* ppString2,
    int* ctr2,
    int cchCount2,
    DWORD* pWeight2,
    LPCWSTR* pLastJamo,
    WORD* uw1,
    WORD* uw2,
    int* pState,
    int* WhichJamo,
    BOOL fModify);





//-------------------------------------------------------------------------//
//                           INTERNAL MACROS                               //
//-------------------------------------------------------------------------//


////////////////////////////////////////////////////////////////////////////
//
//  NOT_END_STRING
//
//  Checks to see if the search has reached the end of the string.
//  It returns TRUE if the counter is not at zero (counting backwards) and
//  the null termination has not been reached (if -1 was passed in the count
//  parameter.
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

#define NOT_END_STRING(ct, ptr, cchIn)                                     \
    ((ct != 0) && (!((*(ptr) == 0) && (cchIn == -2))))


////////////////////////////////////////////////////////////////////////////
//
//  AT_STRING_END
//
//  Checks to see if the pointer is at the end of the string.
//  It returns TRUE if the counter is zero or if the null termination
//  has been reached (if -2 was passed in the count parameter).
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

#define AT_STRING_END(ct, ptr, cchIn)                                      \
    ((ct == 0) || ((*(ptr) == 0) && (cchIn == -2)))


////////////////////////////////////////////////////////////////////////////
//
//  REMOVE_STATE
//
//  Removes the current state from the state table.  This should only be
//  called when the current state should not be entered for the remainder
//  of the comparison.  It decrements the counter going through the state
//  table and decrements the number of states in the table.
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

#define REMOVE_STATE(value)            (State &= ~value)


////////////////////////////////////////////////////////////////////////////
//
//  POINTER_FIXUP
//
//  Fixup the string pointers if expansion characters were found.
//  Then, advance the string pointers and decrement the string counters.
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

#define POINTER_FIXUP()                                                    \
{                                                                          \
    /*                                                                     \
     *  Fixup the pointers (if necessary).                                 \
     */                                                                    \
    if (pSave1 && (--cExpChar1 == 0))                                      \
    {                                                                      \
        /*                                                                 \
         *  Done using expansion temporary buffer.                         \
         */                                                                \
        pString1 = pSave1;                                                 \
        pSave1 = NULL;                                                     \
    }                                                                      \
                                                                           \
    if (pSave2 && (--cExpChar2 == 0))                                      \
    {                                                                      \
        /*                                                                 \
         *  Done using expansion temporary buffer.                         \
         */                                                                \
        pString2 = pSave2;                                                 \
        pSave2 = NULL;                                                     \
    }                                                                      \
                                                                           \
    /*                                                                     \
     *  Advance the string pointers.                                       \
     */                                                                    \
    pString1++;                                                            \
    pString2++;                                                            \
}


////////////////////////////////////////////////////////////////////////////
//
//  SCAN_LONGER_STRING
//
//  Scans the longer string for diacritic, case, and special weights.
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

#define SCAN_LONGER_STRING( ct,                                            \
                            ptr,                                           \
                            cchIn,                                         \
                            ret )                                          \
{                                                                          \
    /*                                                                     \
     *  Search through the rest of the longer string to make sure          \
     *  all characters are not to be ignored.  If find a character that    \
     *  should not be ignored, return the given return value immediately.  \
     *                                                                     \
     *  The only exception to this is when a nonspace mark is found.  If   \
     *  another DW difference has been found earlier, then use that.       \
     */                                                                    \
    while (NOT_END_STRING(ct, ptr, cchIn))                                 \
    {                                                                      \
        Weight1 = GET_DWORD_WEIGHT(pHashN, *ptr);                          \
        switch (GET_SCRIPT_MEMBER(&Weight1))                               \
        {                                                                  \
            case ( UNSORTABLE ):                                           \
            {                                                              \
                break;                                                     \
            }                                                              \
            case ( NONSPACE_MARK ):                                        \
            {                                                              \
                if ((!fIgnoreDiacritic) && (!WhichDiacritic))              \
                {                                                          \
                    return (ret);                                          \
                }                                                          \
                break;                                                     \
            }                                                              \
            case ( PUNCTUATION ) :                                         \
            case ( SYMBOL_1 ) :                                            \
            case ( SYMBOL_2 ) :                                            \
            case ( SYMBOL_3 ) :                                            \
            case ( SYMBOL_4 ) :                                            \
            case ( SYMBOL_5 ) :                                            \
            {                                                              \
                if (!fIgnoreSymbol)                                        \
                {                                                          \
                    return (ret);                                          \
                }                                                          \
                break;                                                     \
            }                                                              \
            case ( EXPANSION ) :                                           \
            case ( FAREAST_SPECIAL ) :                                     \
            case ( JAMO_SPECIAL ) :                                        \
            case ( EXTENSION_A ) :                                         \
            default :                                                      \
            {                                                              \
                return (ret);                                              \
            }                                                              \
        }                                                                  \
                                                                           \
        /*                                                                 \
         *  Advance pointer and decrement counter.                         \
         */                                                                \
        ptr++;                                                             \
        ct--;                                                              \
    }                                                                      \
                                                                           \
    /*                                                                     \
     *  Need to check diacritic, case, extra, and special weights for      \
     *  final return value.  Still could be equal if the longer part of    \
     *  the string contained only characters to be ignored.                \
     *                                                                     \
     *  NOTE:  The following checks MUST REMAIN IN THIS ORDER:             \
     *            Diacritic, Case, Extra, Punctuation.                     \
     */                                                                    \
    if (WhichDiacritic)                                                    \
    {                                                                      \
        return (WhichDiacritic);                                           \
    }                                                                      \
    if (WhichCase)                                                         \
    {                                                                      \
        return (WhichCase);                                                \
    }                                                                      \
    if (WhichExtra)                                                        \
    {                                                                      \
        if (!fIgnoreDiacritic)                                             \
        {                                                                  \
            if (GET_WT_FOUR(&WhichExtra))                                  \
            {                                                              \
                return (GET_WT_FOUR(&WhichExtra));                         \
            }                                                              \
            if (GET_WT_FIVE(&WhichExtra))                                  \
            {                                                              \
                return (GET_WT_FIVE(&WhichExtra));                         \
            }                                                              \
        }                                                                  \
        if (GET_WT_SIX(&WhichExtra))                                       \
        {                                                                  \
            return (GET_WT_SIX(&WhichExtra));                              \
        }                                                                  \
        if (GET_WT_SEVEN(&WhichExtra))                                     \
        {                                                                  \
            return (GET_WT_SEVEN(&WhichExtra));                            \
        }                                                                  \
    }                                                                      \
    if (WhichJamo)                                                         \
    {                                                                      \
        return (WhichJamo);                                                \
    }                                                                      \
    if (WhichPunct1)                                                       \
    {                                                                      \
        return (WhichPunct1);                                              \
    }                                                                      \
    if (WhichPunct2)                                                       \
    {                                                                      \
        return (WhichPunct2);                                              \
    }                                                                      \
                                                                           \
    return (CSTR_EQUAL);                                                   \
}


////////////////////////////////////////////////////////////////////////////
//
//  QUICK_SCAN_LONGER_STRING
//
//  Scans the longer string for diacritic, case, and special weights.
//  Assumes that both strings are null-terminated.
//
//  11-04-92    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

#define QUICK_SCAN_LONGER_STRING( ptr,                                     \
                                  ret )                                    \
{                                                                          \
    /*                                                                     \
     *  Search through the rest of the longer string to make sure          \
     *  all characters are not to be ignored.  If find a character that    \
     *  should not be ignored, return the given return value immediately.  \
     *                                                                     \
     *  The only exception to this is when a nonspace mark is found.  If   \
     *  another DW difference has been found earlier, then use that.       \
     */                                                                    \
    while (*ptr != 0)                                                      \
    {                                                                      \
        switch (GET_SCRIPT_MEMBER(&(pHashN->pSortkey[*ptr])))              \
        {                                                                  \
            case ( UNSORTABLE ):                                           \
            {                                                              \
                break;                                                     \
            }                                                              \
            case ( NONSPACE_MARK ):                                        \
            {                                                              \
                if (!WhichDiacritic)                                       \
                {                                                          \
                    return (ret);                                          \
                }                                                          \
                break;                                                     \
            }                                                              \
            default :                                                      \
            {                                                              \
                return (ret);                                              \
            }                                                              \
        }                                                                  \
                                                                           \
        /*                                                                 \
         *  Advance pointer.                                               \
         */                                                                \
        ptr++;                                                             \
    }                                                                      \
                                                                           \
    /*                                                                     \
     *  Need to check diacritic, case, extra, and special weights for      \
     *  final return value.  Still could be equal if the longer part of    \
     *  the string contained only unsortable characters.                   \
     *                                                                     \
     *  NOTE:  The following checks MUST REMAIN IN THIS ORDER:             \
     *            Diacritic, Case, Extra, Punctuation.                     \
     */                                                                    \
    if (WhichDiacritic)                                                    \
    {                                                                      \
        return (WhichDiacritic);                                           \
    }                                                                      \
    if (WhichCase)                                                         \
    {                                                                      \
        return (WhichCase);                                                \
    }                                                                      \
    if (WhichExtra)                                                        \
    {                                                                      \
        if (GET_WT_FOUR(&WhichExtra))                                      \
        {                                                                  \
            return (GET_WT_FOUR(&WhichExtra));                             \
        }                                                                  \
        if (GET_WT_FIVE(&WhichExtra))                                      \
        {                                                                  \
            return (GET_WT_FIVE(&WhichExtra));                             \
        }                                                                  \
        if (GET_WT_SIX(&WhichExtra))                                       \
        {                                                                  \
            return (GET_WT_SIX(&WhichExtra));                              \
        }                                                                  \
        if (GET_WT_SEVEN(&WhichExtra))                                     \
        {                                                                  \
            return (GET_WT_SEVEN(&WhichExtra));                            \
        }                                                                  \
    }                                                                      \
    if (WhichJamo)                                                         \
    {                                                                      \
        return (WhichJamo);                                                \
    }                                                                      \
    if (WhichPunct1)                                                       \
    {                                                                      \
        return (WhichPunct1);                                              \
    }                                                                      \
    if (WhichPunct2)                                                       \
    {                                                                      \
        return (WhichPunct2);                                              \
    }                                                                      \
                                                                           \
    return (CSTR_EQUAL);                                                   \
}


////////////////////////////////////////////////////////////////////////////
//
//  GET_FAREAST_WEIGHT
//
//  Returns the weight for the far east special case in "wt".  This currently
//  includes the Cho-on, the Repeat, and the Kana characters.
//
//  08-19-93    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

#define GET_FAREAST_WEIGHT( wt,                                            \
                            uw,                                            \
                            mask,                                          \
                            pBegin,                                        \
                            pCur,                                          \
                            ExtraWt,                                       \
                            fModify )                                      \
{                                                                          \
    int ct;                       /* loop counter */                       \
    BYTE PrevSM;                  /* previous script member value */       \
    BYTE PrevAW;                  /* previous alphanumeric value */        \
    BYTE PrevCW;                  /* previous case value */                \
    BYTE AW;                      /* alphanumeric value */                 \
    BYTE CW;                      /* case value */                         \
    DWORD PrevWt;                 /* previous weight */                    \
                                                                           \
                                                                           \
    /*                                                                     \
     *  Get the alphanumeric weight and the case weight of the             \
     *  current code point.                                                \
     */                                                                    \
    AW = GET_ALPHA_NUMERIC(&wt);                                           \
    CW = GET_CASE(&wt);                                                    \
    ExtraWt = (DWORD)0;                                                    \
                                                                           \
    /*                                                                     \
     *  Special case Repeat and Cho-On.                                    \
     *    AW = 0  =>  Repeat                                               \
     *    AW = 1  =>  Cho-On                                               \
     *    AW = 2+ =>  Kana                                                 \
     */                                                                    \
    if (AW <= MAX_SPECIAL_AW)                                              \
    {                                                                      \
        /*                                                                 \
         *  If the script member of the previous character is              \
         *  invalid, then give the special character an                    \
         *  invalid weight (highest possible weight) so that it            \
         *  will sort AFTER everything else.                               \
         */                                                                \
        ct = 1;                                                            \
        PrevWt = CMP_INVALID_FAREAST;                                      \
        while ((pCur - ct) >= pBegin)                                      \
        {                                                                  \
            PrevWt = GET_DWORD_WEIGHT(pHashN, *(pCur - ct));               \
            PrevWt &= mask;                                                \
            PrevSM = GET_SCRIPT_MEMBER(&PrevWt);                           \
            if (PrevSM < FAREAST_SPECIAL)                                  \
            {                                                              \
                if (PrevSM == EXPANSION)                                   \
                {                                                          \
                    PrevWt = CMP_INVALID_FAREAST;                          \
                }                                                          \
                else                                                       \
                {                                                          \
                    /*                                                     \
                     *  UNSORTABLE or NONSPACE_MARK.                       \
                     *                                                     \
                     *  Just ignore these, since we only care about the    \
                     *  previous UW value.                                 \
                     */                                                    \
                    PrevWt = CMP_INVALID_FAREAST;                          \
                    ct++;                                                  \
                    continue;                                              \
                }                                                          \
            }                                                              \
            else if (PrevSM == FAREAST_SPECIAL)                            \
            {                                                              \
                PrevAW = GET_ALPHA_NUMERIC(&PrevWt);                       \
                if (PrevAW <= MAX_SPECIAL_AW)                              \
                {                                                          \
                    /*                                                     \
                     *  Handle case where two special chars follow         \
                     *  each other.  Keep going back in the string.        \
                     */                                                    \
                    PrevWt = CMP_INVALID_FAREAST;                          \
                    ct++;                                                  \
                    continue;                                              \
                }                                                          \
                                                                           \
                UNICODE_WT(&PrevWt) =                                      \
                    MAKE_UNICODE_WT(KANA, PrevAW, fModify);                \
                                                                           \
                /*                                                         \
                 *  Only build weights 4, 5, 6, and 7 if the               \
                 *  previous character is KANA.                            \
                 *                                                         \
                 *  Always:                                                \
                 *    4W = previous CW  &  ISOLATE_SMALL                   \
                 *    6W = previous CW  &  ISOLATE_KANA                    \
                 *                                                         \
                 */                                                        \
                PrevCW = GET_CASE(&PrevWt);                                \
                GET_WT_FOUR(&ExtraWt) = PrevCW & ISOLATE_SMALL;            \
                GET_WT_SIX(&ExtraWt)  = PrevCW & ISOLATE_KANA;             \
                                                                           \
                if (AW == AW_REPEAT)                                       \
                {                                                          \
                    /*                                                     \
                     *  Repeat:                                            \
                     *    UW = previous UW                                 \
                     *    5W = WT_FIVE_REPEAT                              \
                     *    7W = previous CW  &  ISOLATE_WIDTH               \
                     */                                                    \
                    uw = UNICODE_WT(&PrevWt);                              \
                    GET_WT_FIVE(&ExtraWt)  = WT_FIVE_REPEAT;               \
                    GET_WT_SEVEN(&ExtraWt) = PrevCW & ISOLATE_WIDTH;       \
                }                                                          \
                else                                                       \
                {                                                          \
                    /*                                                     \
                     *  Cho-On:                                            \
                     *    UW = previous UW  &  CHO_ON_UW_MASK              \
                     *    5W = WT_FIVE_CHO_ON                              \
                     *    7W = current  CW  &  ISOLATE_WIDTH               \
                     */                                                    \
                    uw = UNICODE_WT(&PrevWt) & CHO_ON_UW_MASK;             \
                    GET_WT_FIVE(&ExtraWt)  = WT_FIVE_CHO_ON;               \
                    GET_WT_SEVEN(&ExtraWt) = CW & ISOLATE_WIDTH;           \
                }                                                          \
            }                                                              \
            else                                                           \
            {                                                              \
                uw = GET_UNICODE_MOD(&PrevWt, fModify);                    \
            }                                                              \
                                                                           \
            break;                                                         \
        }                                                                  \
    }                                                                      \
    else                                                                   \
    {                                                                      \
        /*                                                                 \
         *  Kana:                                                          \
         *    SM = KANA                                                    \
         *    AW = current AW                                              \
         *    4W = current CW  &  ISOLATE_SMALL                            \
         *    5W = WT_FIVE_KANA                                            \
         *    6W = current CW  &  ISOLATE_KANA                             \
         *    7W = current CW  &  ISOLATE_WIDTH                            \
         */                                                                \
        uw = MAKE_UNICODE_WT(KANA, AW, fModify);                           \
        GET_WT_FOUR(&ExtraWt)  = CW & ISOLATE_SMALL;                       \
        GET_WT_FIVE(&ExtraWt)  = WT_FIVE_KANA;                             \
        GET_WT_SIX(&ExtraWt)   = CW & ISOLATE_KANA;                        \
        GET_WT_SEVEN(&ExtraWt) = CW & ISOLATE_WIDTH;                       \
    }                                                                      \
                                                                           \
    /*                                                                     \
     *  Get the weight for the far east special case and store it in wt.   \
     */                                                                    \
    if ((AW > MAX_SPECIAL_AW) || (PrevWt != CMP_INVALID_FAREAST))          \
    {                                                                      \
        /*                                                                 \
         *  Always:                                                        \
         *    DW = current DW                                              \
         *    CW = minimum CW                                              \
         */                                                                \
        UNICODE_WT(&wt) = uw;                                              \
        CASE_WT(&wt) = MIN_CW;                                             \
    }                                                                      \
    else                                                                   \
    {                                                                      \
        uw = CMP_INVALID_UW;                                               \
        wt = CMP_INVALID_FAREAST;                                          \
        ExtraWt = 0;                                                       \
    }                                                                      \
}




//-------------------------------------------------------------------------//
//                             API ROUTINES                                //
//-------------------------------------------------------------------------//


////////////////////////////////////////////////////////////////////////////
//
//  CompareStringW
//
//  Compares two wide character strings of the same locale according to the
//  supplied locale handle.
//
//  05-31-91    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int WINAPI CompareStringW(
    LCID Locale,
    DWORD dwCmpFlags,
    LPCWSTR lpString1,
    int cchCount1,
    LPCWSTR lpString2,
    int cchCount2)
{
    register LPWSTR pString1;     // ptr to go thru string 1
    register LPWSTR pString2;     // ptr to go thru string 2
    PLOC_HASH pHashN;             // ptr to LOC hash node
    BOOL fIgnorePunct;            // flag to ignore punctuation (not symbol)
    BOOL fModify;                 // flag to use modified script member weights
    DWORD State;                  // state table
    DWORD Mask;                   // mask for weights
    DWORD Weight1;                // full weight of char - string 1
    DWORD Weight2;                // full weight of char - string 2

    int JamoFlag = FALSE;
    LPCWSTR pLastJamo = lpString1;

    int WhichDiacritic;           // DW => 1 = str1 smaller, 3 = str2 smaller
    int WhichCase;                // CW => 1 = str1 smaller, 3 = str2 smaller
    int WhichJamo;                // XW for Jamo
    int WhichPunct1;              // SW => 1 = str1 smaller, 3 = str2 smaller
    int WhichPunct2;              // SW => 1 = str1 smaller, 3 = str2 smaller
    LPWSTR pSave1;                // ptr to saved pString1
    LPWSTR pSave2;                // ptr to saved pString2
    int cExpChar1, cExpChar2;     // ct of expansions in tmp

    DWORD ExtraWt1, ExtraWt2;     // extra weight values (for far east)
    DWORD WhichExtra;             // XW => wts 4, 5, 6, 7 (for far east)

    //
    //  Invalid Parameter Check:
    //    - validate LCID
    //    - either string is null
    //
    VALIDATE_LANGUAGE(Locale, pHashN, 0, TRUE);
    if ((pHashN == NULL) ||
        (lpString1 == NULL) || (lpString2 == NULL))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return (0);
    }

    //
    //  Make sure the appropriate sorting tables are available.  If not,
    //  return an error.
    //
    if ((pHashN->pSortkey == NULL) ||
        (pHashN->IfIdeographFailure == TRUE))
    {
        KdPrint(("NLSAPI: Appropriate Sorting Tables Not Loaded.\n"));
        SetLastError(ERROR_FILE_NOT_FOUND);
        return (0);
    }

    //
    //  Call longer compare string if any of the following is true:
    //     - compression locale
    //     - either count is not -1
    //     - dwCmpFlags is not 0 or ignore case   (see NOTE below)
    //     - locale is Korean - script member weight adjustment needed
    //
    //  NOTE:  If the value of NORM_IGNORECASE ever changes, this
    //         code should check for:
    //            ( (dwCmpFlags != 0)  &&  (dwCmpFlags != NORM_IGNORECASE) )
    //         Since NORM_IGNORECASE is equal to 1, we can optimize this
    //         by checking for > 1.
    //
    dwCmpFlags &= (~LOCALE_USE_CP_ACP);
    fModify = IS_KOREAN(Locale);
    if ( (pHashN->IfCompression) ||
         (cchCount1 > -1) || (cchCount2 > -1) ||
         (dwCmpFlags > NORM_IGNORECASE) ||
         (fModify == TRUE) )
    {
        return (LongCompareStringW( pHashN,
                                    dwCmpFlags,
                                    lpString1,
                                    ((cchCount1 <= -1) ? -2 : cchCount1),
                                    lpString2,
                                    ((cchCount2 <= -1) ? -2 : cchCount2),
                                    fModify ));
    }

    //
    //  Initialize string pointers.
    //
    pString1 = (LPWSTR)lpString1;
    pString2 = (LPWSTR)lpString2;

    //
    //  Do a wchar by wchar compare.
    //
    while (TRUE)
    {
        //
        //  See if characters are equal.
        //  If characters are equal, increment pointers and continue
        //  string compare.
        //
        //  NOTE: Loop is unrolled 8 times for performance.
        //
        if ((*pString1 != *pString2) || (*pString1 == 0))
        {
            break;
        }
        pString1++;
        pString2++;

        if ((*pString1 != *pString2) || (*pString1 == 0))
        {
            break;
        }
        pString1++;
        pString2++;

        if ((*pString1 != *pString2) || (*pString1 == 0))
        {
            break;
        }
        pString1++;
        pString2++;

        if ((*pString1 != *pString2) || (*pString1 == 0))
        {
            break;
        }
        pString1++;
        pString2++;

        if ((*pString1 != *pString2) || (*pString1 == 0))
        {
            break;
        }
        pString1++;
        pString2++;

        if ((*pString1 != *pString2) || (*pString1 == 0))
        {
            break;
        }
        pString1++;
        pString2++;

        if ((*pString1 != *pString2) || (*pString1 == 0))
        {
            break;
        }
        pString1++;
        pString2++;

        if ((*pString1 != *pString2) || (*pString1 == 0))
        {
            break;
        }
        pString1++;
        pString2++;
    }

    //
    //  If strings are both at null terminators, return equal.
    //
    if (*pString1 == *pString2)
    {
        return (CSTR_EQUAL);
    }

    //
    //  Initialize flags, pointers, and counters.
    //
    fIgnorePunct = FALSE;
    WhichDiacritic = 0;
    WhichCase = 0;
    WhichJamo = 0;
    WhichPunct1 = 0;
    WhichPunct2 = 0;
    pSave1 = NULL;
    pSave2 = NULL;
    ExtraWt1 = (DWORD)0;
    WhichExtra = (DWORD)0;

    //
    //  Switch on the different flag options.  This will speed up
    //  the comparisons of two strings that are different.
    //
    //  The only two possibilities in this optimized section are
    //  no flags and the ignore case flag.
    //
    if (dwCmpFlags == 0)
    {
        Mask = CMP_MASKOFF_NONE;
    }
    else
    {
        Mask = CMP_MASKOFF_CW;
    }
    State = (pHashN->IfReverseDW) ? STATE_REVERSE_DW : STATE_DW;
    State |= (STATE_CW | STATE_JAMO_WEIGHT);

    //
    //  Compare each character's sortkey weight in the two strings.
    //
    while ((*pString1 != 0) && (*pString2 != 0))
    {
        Weight1 = GET_DWORD_WEIGHT(pHashN, *pString1);
        Weight2 = GET_DWORD_WEIGHT(pHashN, *pString2);
        Weight1 &= Mask;
        Weight2 &= Mask;

        if (Weight1 != Weight2)
        {
            BYTE sm1 = GET_SCRIPT_MEMBER(&Weight1);     // script member 1
            BYTE sm2 = GET_SCRIPT_MEMBER(&Weight2);     // script member 2
            WORD uw1 = GET_UNICODE_SM(&Weight1, sm1);   // unicode weight 1
            WORD uw2 = GET_UNICODE_SM(&Weight2, sm2);   // unicode weight 2
            BYTE dw1;                                   // diacritic weight 1
            BYTE dw2;                                   // diacritic weight 2
            BOOL fContinue;                             // flag to continue loop
            DWORD Wt;                                   // temp weight holder
            WCHAR pTmpBuf1[MAX_TBL_EXPANSION];          // temp buffer for exp 1
            WCHAR pTmpBuf2[MAX_TBL_EXPANSION];          // temp buffer for exp 2


            //
            //  If Unicode Weights are different and no special cases,
            //  then we're done.  Otherwise, we need to do extra checking.
            //
            //  Must check ENTIRE string for any possibility of Unicode Weight
            //  differences.  As soon as a Unicode Weight difference is found,
            //  then we're done.  If no UW difference is found, then the
            //  first Diacritic Weight difference is used.  If no DW difference
            //  is found, then use the first Case Difference.  If no CW
            //  difference is found, then use the first Extra Weight
            //  difference.  If no XW difference is found, then use the first
            //  Special Weight difference.
            //
            if ((uw1 != uw2) ||
                (sm1 == FAREAST_SPECIAL) ||
                (sm1 == EXTENSION_A))
            {
                //
                //  Initialize the continue flag.
                //
                fContinue = FALSE;

                //
                //  Check for Unsortable characters and skip them.
                //  This needs to be outside the switch statement.  If EITHER
                //  character is unsortable, must skip it and start over.
                //
                if (sm1 == UNSORTABLE)
                {
                    pString1++;
                    fContinue = TRUE;
                }
                if (sm2 == UNSORTABLE)
                {
                    pString2++;
                    fContinue = TRUE;
                }
                if (fContinue)
                {
                    continue;
                }

                //
                //  Switch on the script member of string 1 and take care
                //  of any special cases.
                //
                switch (sm1)
                {
                    case ( NONSPACE_MARK ) :
                    {
                        //
                        //  Nonspace only - look at diacritic weight only.
                        //
                        if ((WhichDiacritic == 0) ||
                            (State & STATE_REVERSE_DW))
                        {
                            WhichDiacritic = CSTR_GREATER_THAN;

                            //
                            //  Remove state from state machine.
                            //
                            REMOVE_STATE(STATE_DW);
                        }

                        //
                        //  Adjust pointer and set flags.
                        //
                        pString1++;
                        fContinue = TRUE;

                        break;
                    }
                    case ( PUNCTUATION ) :
                    {
                        //
                        //  If the ignore punctuation flag is set, then skip
                        //  over the punctuation.
                        //
                        if (fIgnorePunct)
                        {
                            pString1++;
                            fContinue = TRUE;
                        }
                        else if (sm2 != PUNCTUATION)
                        {
                            //
                            //  The character in the second string is
                            //  NOT punctuation.
                            //
                            if (WhichPunct2)
                            {
                                //
                                //  Set WP 2 to show that string 2 is smaller,
                                //  since a punctuation char had already been
                                //  found at an earlier position in string 2.
                                //
                                //  Set the Ignore Punctuation flag so we just
                                //  skip over any other punctuation chars in
                                //  the string.
                                //
                                WhichPunct2 = CSTR_GREATER_THAN;
                                fIgnorePunct = TRUE;
                            }
                            else
                            {
                                //
                                //  Set WP 1 to show that string 2 is smaller,
                                //  and that string 1 has had a punctuation
                                //  char - since no punctuation chars have
                                //  been found in string 2.
                                //
                                WhichPunct1 = CSTR_GREATER_THAN;
                            }

                            //
                            //  Advance pointer 1, and set flag to true.
                            //
                            pString1++;
                            fContinue = TRUE;
                        }

                        //
                        //  Do NOT want to advance the pointer in string 1 if
                        //  string 2 is also a punctuation char.  This will
                        //  be done later.
                        //
                        break;
                    }
                    case ( EXPANSION ) :
                    {
                        //
                        //  Save pointer in pString1 so that it can be
                        //  restored.
                        //
                        if (pSave1 == NULL)
                        {
                            pSave1 = pString1;
                        }
                        pString1 = pTmpBuf1;

                        //
                        //  Expand character into temporary buffer.
                        //
                        pTmpBuf1[0] = GET_EXPANSION_1(&Weight1);
                        pTmpBuf1[1] = GET_EXPANSION_2(&Weight1);

                        //
                        //  Set cExpChar1 to the number of expansion characters
                        //  stored.
                        //
                        cExpChar1 = MAX_TBL_EXPANSION;

                        fContinue = TRUE;

                        break;
                    }
                    case ( FAREAST_SPECIAL ) :
                    {
                        if (sm2 != EXPANSION) 
                        {
                            //
                            //  Get the weight for the far east special case
                            //  and store it in Weight1.
                            //
                            GET_FAREAST_WEIGHT( Weight1,
                                                uw1,
                                                Mask,
                                                lpString1,
                                                pString1,
                                                ExtraWt1,
                                                FALSE );

                            if (sm2 != FAREAST_SPECIAL)
                            {
                                //
                                //  The character in the second string is
                                //  NOT a fareast special char.
                                //
                                //  Set each of weights 4, 5, 6, and 7 to show
                                //  that string 2 is smaller (if not already set).
                                //
                                if ((GET_WT_FOUR(&WhichExtra) == 0) &&
                                    (GET_WT_FOUR(&ExtraWt1) != 0))
                                {
                                    GET_WT_FOUR(&WhichExtra) = CSTR_GREATER_THAN;
                                }
                                if ((GET_WT_FIVE(&WhichExtra) == 0) &&
                                    (GET_WT_FIVE(&ExtraWt1) != 0))
                                {
                                    GET_WT_FIVE(&WhichExtra) = CSTR_GREATER_THAN;
                                }
                                if ((GET_WT_SIX(&WhichExtra) == 0) &&
                                    (GET_WT_SIX(&ExtraWt1) != 0))
                                {
                                    GET_WT_SIX(&WhichExtra) = CSTR_GREATER_THAN;
                                }
                                if ((GET_WT_SEVEN(&WhichExtra) == 0) &&
                                    (GET_WT_SEVEN(&ExtraWt1) != 0))
                                {
                                    GET_WT_SEVEN(&WhichExtra) = CSTR_GREATER_THAN;
                                }
                            }
                        }
                        break;
                    }
                    case ( JAMO_SPECIAL ) :
                    {
                        int ctr1;     // dummy variables for FindJamoDifference
                        LPWSTR pStr1 = pString1;
                        LPWSTR pStr2 = pString2;

                        //
                        //  Set the JamoFlag so we don't handle it again.
                        //
                        JamoFlag = TRUE;
                        fContinue = FindJamoDifference(
                            pHashN,
                            &pStr1, &ctr1, -2, &Weight1,
                            &pStr2, &ctr1, -2, &Weight2,
                            &pLastJamo,
                            &uw1, &uw2,
                            &State,
                            &WhichJamo,
                            fModify );
                        if (WhichJamo)
                        {
                            return (WhichJamo);
                        }    

                        pString1 = pStr1;
                        pString2 = pStr2;

                        break;
                    }
                    case ( EXTENSION_A ) :
                    {
                        //
                        //  Compare the weights.
                        //
                        if (Weight1 == Weight2)
                        {
                            //
                            //  Adjust pointers and set flag.
                            //
                            pString1++;  pString2++;
                            fContinue = TRUE;
                        }
                        else
                        {
                            //
                            //  Get the actual UW to compare.
                            //
                            if (sm2 == EXTENSION_A)
                            {
                                //
                                //  Set the UW values to be the AW and DW since
                                //  both strings contain an extension A char.
                                //
                                uw1 = MAKE_UNICODE_WT( GET_ALPHA_NUMERIC(&Weight1),
                                                       GET_DIACRITIC(&Weight1),
                                                       FALSE );
                                uw2 = MAKE_UNICODE_WT( GET_ALPHA_NUMERIC(&Weight2),
                                                       GET_DIACRITIC(&Weight2),
                                                       FALSE );
                            }
                            else
                            {
                                //
                                //  Only string1 contains an extension A char,
                                //  so set the UW value to be the first UW
                                //  value for extension A (default values):
                                //    SM_EXT_A, AW_EXT_A
                                //
                                uw1 = MAKE_UNICODE_WT(SM_EXT_A, AW_EXT_A, fModify);
                            }
                        }

                        break;
                    }
                    case ( UNSORTABLE ) :
                    {
                        //
                        //  Fill out the case statement so the compiler
                        //  will use a jump table.
                        //
                        break;
                    }
                }

                //
                //  Switch on the script member of string 2 and take care
                //  of any special cases.
                //
                switch (sm2)
                {
                    case ( NONSPACE_MARK ) :
                    {
                        //
                        //  Nonspace only - look at diacritic weight only.
                        //
                        if ((WhichDiacritic == 0) ||
                            (State & STATE_REVERSE_DW))
                        {
                            WhichDiacritic = CSTR_LESS_THAN;

                            //
                            //  Remove state from state machine.
                            //
                            REMOVE_STATE(STATE_DW);
                        }

                        //
                        //  Adjust pointer and set flags.
                        //
                        pString2++;
                        fContinue = TRUE;

                        break;
                    }
                    case ( PUNCTUATION ) :
                    {
                        //
                        //  If the ignore punctuation flag is set, then skip
                        //  over the punctuation.
                        //
                        if (fIgnorePunct)
                        {
                            //
                            //  Pointer 2 will be advanced after if-else
                            //  statement.
                            //
                            ;
                        }
                        else if (sm1 != PUNCTUATION)
                        {
                            //
                            //  The character in the first string is
                            //  NOT punctuation.
                            //
                            if (WhichPunct1)
                            {
                                //
                                //  Set WP 1 to show that string 1 is smaller,
                                //  since a punctuation char had already
                                //  been found at an earlier position in
                                //  string 1.
                                //
                                //  Set the Ignore Punctuation flag so we just
                                //  skip over any other punctuation in the
                                //  string.
                                //
                                WhichPunct1 = CSTR_LESS_THAN;
                                fIgnorePunct = TRUE;
                            }
                            else
                            {
                                //
                                //  Set WP 2 to show that string 1 is smaller,
                                //  and that string 2 has had a punctuation
                                //  char - since no punctuation chars have
                                //  been found in string 1.
                                //
                                WhichPunct2 = CSTR_LESS_THAN;
                            }

                            //
                            //  Pointer 2 will be advanced after if-else
                            //  statement.
                            //
                        }
                        else
                        {
                            //
                            //  Both code points are punctuation.
                            //
                            //  See if either of the strings has encountered
                            //  punctuation chars previous to this.
                            //
                            if (WhichPunct1)
                            {
                                //
                                //  String 1 has had a punctuation char, so
                                //  it should be the smaller string (since
                                //  both have punctuation chars).
                                //
                                WhichPunct1 = CSTR_LESS_THAN;
                            }
                            else if (WhichPunct2)
                            {
                                //
                                //  String 2 has had a punctuation char, so
                                //  it should be the smaller string (since
                                //  both have punctuation chars).
                                //
                                WhichPunct2 = CSTR_GREATER_THAN;
                            }
                            else
                            {
                                //
                                //  Position is the same, so compare the
                                //  special weights.  Set WhichPunct1 to
                                //  the smaller special weight.
                                //
                                WhichPunct1 = (((GET_ALPHA_NUMERIC(&Weight1) <
                                                 GET_ALPHA_NUMERIC(&Weight2)))
                                                 ? CSTR_LESS_THAN
                                                 : CSTR_GREATER_THAN);
                            }

                            //
                            //  Set the Ignore Punctuation flag so we just
                            //  skip over any other punctuation in the string.
                            //
                            fIgnorePunct = TRUE;

                            //
                            //  Advance pointer 1.  Pointer 2 will be
                            //  advanced after if-else statement.
                            //
                            pString1++;
                        }

                        //
                        //  Advance pointer 2 and set flag to true.
                        //
                        pString2++;
                        fContinue = TRUE;

                        break;
                    }
                    case ( EXPANSION ) :
                    {
                        //
                        //  Save pointer in pString1 so that it can be
                        //  restored.
                        //
                        if (pSave2 == NULL)
                        {
                            pSave2 = pString2;
                        }
                        pString2 = pTmpBuf2;

                        //
                        //  Expand character into temporary buffer.
                        //
                        pTmpBuf2[0] = GET_EXPANSION_1(&Weight2);
                        pTmpBuf2[1] = GET_EXPANSION_2(&Weight2);

                        //
                        //  Set cExpChar2 to the number of expansion characters
                        //  stored.
                        //
                        cExpChar2 = MAX_TBL_EXPANSION;

                        fContinue = TRUE;

                        break;
                    }
                    case ( FAREAST_SPECIAL ) :
                    {
                        if (sm1 != EXPANSION) 
                        {
                            //
                            //  Get the weight for the far east special case
                            //  and store it in Weight2.
                            //
                            GET_FAREAST_WEIGHT( Weight2,
                                                uw2,
                                                Mask,
                                                lpString2,
                                                pString2,
                                                ExtraWt2,
                                                FALSE );

                            if (sm1 != FAREAST_SPECIAL)
                            {
                                //
                                //  The character in the first string is
                                //  NOT a fareast special char.
                                //
                                //  Set each of weights 4, 5, 6, and 7 to show
                                //  that string 1 is smaller (if not already set).
                                //
                                if ((GET_WT_FOUR(&WhichExtra) == 0) &&
                                    (GET_WT_FOUR(&ExtraWt2) != 0))
                                {
                                    GET_WT_FOUR(&WhichExtra) = CSTR_LESS_THAN;
                                }
                                if ((GET_WT_FIVE(&WhichExtra) == 0) &&
                                    (GET_WT_FIVE(&ExtraWt2) != 0))
                                {
                                    GET_WT_FIVE(&WhichExtra) = CSTR_LESS_THAN;
                                }
                                if ((GET_WT_SIX(&WhichExtra) == 0) &&
                                    (GET_WT_SIX(&ExtraWt2) != 0))
                                {
                                    GET_WT_SIX(&WhichExtra) = CSTR_LESS_THAN;
                                }
                                if ((GET_WT_SEVEN(&WhichExtra) == 0) &&
                                    (GET_WT_SEVEN(&ExtraWt2) != 0))
                                {
                                    GET_WT_SEVEN(&WhichExtra) = CSTR_LESS_THAN;
                                }
                            }
                            else
                            {
                                //
                                //  Characters in both strings are fareast
                                //  special chars.
                                //
                                //  Set each of weights 4, 5, 6, and 7
                                //  appropriately (if not already set).
                                //
                                if ( (GET_WT_FOUR(&WhichExtra) == 0) &&
                                     ( GET_WT_FOUR(&ExtraWt1) !=
                                       GET_WT_FOUR(&ExtraWt2) ) )
                                {
                                    GET_WT_FOUR(&WhichExtra) =
                                      ( GET_WT_FOUR(&ExtraWt1) <
                                        GET_WT_FOUR(&ExtraWt2) )
                                      ? CSTR_LESS_THAN
                                      : CSTR_GREATER_THAN;
                                }
                                if ( (GET_WT_FIVE(&WhichExtra) == 0) &&
                                     ( GET_WT_FIVE(&ExtraWt1) !=
                                       GET_WT_FIVE(&ExtraWt2) ) )
                                {
                                    GET_WT_FIVE(&WhichExtra) =
                                      ( GET_WT_FIVE(&ExtraWt1) <
                                        GET_WT_FIVE(&ExtraWt2) )
                                      ? CSTR_LESS_THAN
                                      : CSTR_GREATER_THAN;
                                }
                                if ( (GET_WT_SIX(&WhichExtra) == 0) &&
                                     ( GET_WT_SIX(&ExtraWt1) !=
                                       GET_WT_SIX(&ExtraWt2) ) )
                                {
                                    GET_WT_SIX(&WhichExtra) =
                                      ( GET_WT_SIX(&ExtraWt1) <
                                        GET_WT_SIX(&ExtraWt2) )
                                      ? CSTR_LESS_THAN
                                      : CSTR_GREATER_THAN;
                                }
                                if ( (GET_WT_SEVEN(&WhichExtra) == 0) &&
                                     ( GET_WT_SEVEN(&ExtraWt1) !=
                                       GET_WT_SEVEN(&ExtraWt2) ) )
                                {
                                    GET_WT_SEVEN(&WhichExtra) =
                                      ( GET_WT_SEVEN(&ExtraWt1) <
                                        GET_WT_SEVEN(&ExtraWt2) )
                                      ? CSTR_LESS_THAN
                                      : CSTR_GREATER_THAN;
                                }
                            }
                        }
                        break;
                    }
                    case ( JAMO_SPECIAL ) :
                    {
                        if (!JamoFlag)
                        {
                            int ctr1, ctr2;   // dummy variables for FindJamoDifference
                            LPWSTR pStr1 = pString1;
                            LPWSTR pStr2 = pString2;

                            //
                            //  Set the JamoFlag so we don't handle it again.
                            //
                            JamoFlag = TRUE;
                            fContinue = FindJamoDifference(
                                pHashN,
                                &pStr1, &ctr1, -2, &Weight1,
                                &pStr2, &ctr2, -2, &Weight2,
                                &pLastJamo,
                                &uw1, &uw2,
                                &State,
                                &WhichJamo,
                                fModify );
                            if (WhichJamo)
                            {
                                return (WhichJamo);
                            }
                            pString1 = pStr1;
                            pString2 = pStr2;
                        }
                        else
                        {
                            JamoFlag = FALSE;
                        }

                        break;
                    }
                    case ( EXTENSION_A ) :
                    {
                        //
                        //  If sm1 is an extension A character, then
                        //  both sm1 and sm2 have been handled.  We should
                        //  only get here when either sm1 is not an
                        //  extension A character or the two extension A
                        //  characters are different.
                        //
                        if (sm1 != EXTENSION_A)
                        {
                            //
                            //  Get the actual UW to compare.
                            //
                            //  Only string2 contains an extension A char,
                            //  so set the UW value to be the first UW
                            //  value for extension A (default values):
                            //    SM_EXT_A, AW_EXT_A
                            //
                            uw2 = MAKE_UNICODE_WT(SM_EXT_A, AW_EXT_A, fModify);
                        }

                        //
                        //  We should then fall through to the comparison
                        //  of the Unicode weights.
                        //

                        break;
                    }
                    case ( UNSORTABLE ) :
                    {
                        //
                        //  Fill out the case statement so the compiler
                        //  will use a jump table.
                        //
                        break;
                    }
                }

                //
                //  See if the comparison should start again.
                //
                if (fContinue)
                {
                    continue;
                }

                //
                //  We're not supposed to drop down into the state table if
                //  unicode weights are different, so stop comparison and
                //  return result of unicode weight comparison.
                //
                if (uw1 != uw2)
                {
                    return ((uw1 < uw2) ? CSTR_LESS_THAN : CSTR_GREATER_THAN);
                }
            }

            //
            //  For each state in the state table, do the appropriate
            //  comparisons.     (UW1 == UW2)
            //
            if (State & (STATE_DW | STATE_REVERSE_DW))
            {
                //
                //  Get the diacritic weights.
                //
                dw1 = GET_DIACRITIC(&Weight1);
                dw2 = GET_DIACRITIC(&Weight2);

                if (dw1 != dw2)
                {
                    //
                    //  Look ahead to see if diacritic follows a
                    //  minimum diacritic weight.  If so, get the
                    //  diacritic weight of the nonspace mark.
                    //
                    while (*(pString1 + 1) != 0)
                    {
                        Wt = GET_DWORD_WEIGHT(pHashN, *(pString1 + 1));
                        if (GET_SCRIPT_MEMBER(&Wt) == NONSPACE_MARK)
                        {
                            dw1 += GET_DIACRITIC(&Wt);
                            pString1++;
                        }
                        else
                        {
                            break;
                        }
                    }

                    while (*(pString2 + 1) != 0)
                    {
                        Wt = GET_DWORD_WEIGHT(pHashN, *(pString2 + 1));
                        if (GET_SCRIPT_MEMBER(&Wt) == NONSPACE_MARK)
                        {
                            dw2 += GET_DIACRITIC(&Wt);
                            pString2++;
                        }
                        else
                        {
                            break;
                        }
                    }

                    //
                    //  Save which string has the smaller diacritic
                    //  weight if the diacritic weights are still
                    //  different.
                    //
                    if (dw1 != dw2)
                    {
                        WhichDiacritic = (dw1 < dw2)
                                           ? CSTR_LESS_THAN
                                           : CSTR_GREATER_THAN;

                        //
                        //  Remove state from state machine.
                        //
                        REMOVE_STATE(STATE_DW);
                    }
                }
            }
            if (State & STATE_CW)
            {
                //
                //  Get the case weights.
                //
                if (GET_CASE(&Weight1) != GET_CASE(&Weight2))
                {
                    //
                    //  Save which string has the smaller case weight.
                    //
                    WhichCase = (GET_CASE(&Weight1) < GET_CASE(&Weight2))
                                  ? CSTR_LESS_THAN
                                  : CSTR_GREATER_THAN;

                    //
                    //  Remove state from state machine.
                    //
                    REMOVE_STATE(STATE_CW);
                }
            }
        }

        //
        //  Fixup the pointers.
        //
        POINTER_FIXUP();
    }

    //
    //  If the end of BOTH strings has been reached, then the unicode
    //  weights match exactly.  Check the diacritic, case and special
    //  weights.  If all are zero, then return success.  Otherwise,
    //  return the result of the weight difference.
    //
    //  NOTE:  The following checks MUST REMAIN IN THIS ORDER:
    //            Diacritic, Case, Punctuation.
    //
    if (*pString1 == 0)
    {
        if (*pString2 == 0)
        {
            if (WhichDiacritic)
            {
                return (WhichDiacritic);
            }
            if (WhichCase)
            {
                return (WhichCase);
            }
            if (WhichExtra)
            {
                if (GET_WT_FOUR(&WhichExtra))
                {
                    return (GET_WT_FOUR(&WhichExtra));
                }
                if (GET_WT_FIVE(&WhichExtra))
                {
                    return (GET_WT_FIVE(&WhichExtra));
                }
                if (GET_WT_SIX(&WhichExtra))
                {
                    return (GET_WT_SIX(&WhichExtra));
                }
                if (GET_WT_SEVEN(&WhichExtra))
                {
                    return (GET_WT_SEVEN(&WhichExtra));
                }
            }
            if (WhichPunct1)
            {
                return (WhichPunct1);
            }
            if (WhichPunct2)
            {
                return (WhichPunct2);
            }

            return (CSTR_EQUAL);
        }
        else
        {
            //
            //  String 2 is longer.
            //
            pString1 = pString2;
        }
    }

    //
    //  Scan to the end of the longer string.
    //
    QUICK_SCAN_LONGER_STRING( pString1,
                              ((*pString2 == 0)
                                ? CSTR_GREATER_THAN
                                : CSTR_LESS_THAN) );
}


////////////////////////////////////////////////////////////////////////////
//
//  GetStringTypeExW
//
//  Returns character type information about a particular Unicode string.
//
//  01-18-94    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

BOOL WINAPI GetStringTypeExW(
    LCID Locale,
    DWORD dwInfoType,
    LPCWSTR lpSrcStr,
    int cchSrc,
    LPWORD lpCharType)
{
    PLOC_HASH pHashN;             // ptr to LOC hash node


    //
    //  Invalid Parameter Check:
    //    - Validate LCID
    //
    VALIDATE_LOCALE(Locale, pHashN, FALSE);
    if (pHashN == NULL)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return (0);
    }

    //
    //  Return the result of GetStringTypeW.
    //
    return (GetStringTypeW( dwInfoType,
                            lpSrcStr,
                            cchSrc,
                            lpCharType ));
}


////////////////////////////////////////////////////////////////////////////
//
//  GetStringTypeW
//
//  Returns character type information about a particular Unicode string.
//
//  NOTE:  The number of parameters is different from GetStringTypeA.
//         The 16-bit OLE product shipped GetStringTypeA with the wrong
//         parameters (ported from Chicago) and now we must support it.
//
//         Use GetStringTypeEx to get the same set of parameters between
//         the A and W version.
//
//  05-31-91    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

BOOL WINAPI GetStringTypeW(
    DWORD dwInfoType,
    LPCWSTR lpSrcStr,
    int cchSrc,
    LPWORD lpCharType)
{
    int Ctr;                      // loop counter


    //
    //  Invalid Parameter Check:
    //    - lpSrcStr NULL
    //    - cchSrc is 0
    //    - lpCharType NULL
    //    - same buffer - src and destination
    //    - (flags will be checked in switch statement below)
    //
    if ( (lpSrcStr == NULL) || (cchSrc == 0) ||
         (lpCharType == NULL) || (lpSrcStr == lpCharType) )
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return (FALSE);
    }

    //
    //  If cchSrc is -1, then the source string is null terminated and we
    //  need to get the length of the source string.  Add one to the
    //  length to include the null termination.
    //  (This will always be at least 1.)
    //
    if (cchSrc <= -1)
    {
        cchSrc = NlsStrLenW(lpSrcStr) + 1;
    }

    //
    //  Make sure the ctype table is mapped in.
    //
    if (GetCTypeFileInfo())
    {
        SetLastError(ERROR_FILE_NOT_FOUND);
        return (FALSE);
    }

    //
    //  Return the appropriate information in the lpCharType parameter
    //  based on the dwInfoType parameter.
    //
    switch (dwInfoType)
    {
        case ( CT_CTYPE1 ) :
        {
            //
            //  Return the ctype 1 information for the string.
            //
            for (Ctr = 0; Ctr < cchSrc; Ctr++)
            {
                lpCharType[Ctr] = GET_CTYPE(lpSrcStr[Ctr], CType1);
            }
            break;
        }
        case ( CT_CTYPE2 ) :
        {
            //
            //  Return the ctype 2 information.
            //
            for (Ctr = 0; Ctr < cchSrc; Ctr++)
            {
                lpCharType[Ctr] = GET_CTYPE(lpSrcStr[Ctr], CType2);
            }
            break;
        }
        case ( CT_CTYPE3 ) :
        {
            //
            //  Return the ctype 3 information.
            //
            for (Ctr = 0; Ctr < cchSrc; Ctr++)
            {
                lpCharType[Ctr] = GET_CTYPE(lpSrcStr[Ctr], CType3);
            }
            break;
        }
        default :
        {
            //
            //  Invalid flag parameter, so return failure.
            //
            SetLastError(ERROR_INVALID_FLAGS);
            return (FALSE);
        }
    }

    //
    //  Return success.
    //
    return (TRUE);
}




//-------------------------------------------------------------------------//
//                           INTERNAL ROUTINES                             //
//-------------------------------------------------------------------------//


////////////////////////////////////////////////////////////////////////////
//
//  LongCompareStringW
//
//  Compares two wide character strings of the same locale according to the
//  supplied locale handle.
//
//  05-31-91    JulieB    Created.
////////////////////////////////////////////////////////////////////////////

int LongCompareStringW(
    PLOC_HASH pHashN,
    DWORD dwCmpFlags,
    LPCWSTR lpString1,
    int cchCount1,
    LPCWSTR lpString2,
    int cchCount2,
    BOOL fModify)
{
    int ctr1 = cchCount1;         // loop counter for string 1
    int ctr2 = cchCount2;         // loop counter for string 2
    register LPWSTR pString1;     // ptr to go thru string 1
    register LPWSTR pString2;     // ptr to go thru string 2
    BOOL IfCompress;              // if compression in locale
    BOOL IfDblCompress1;          // if double compression in string 1
    BOOL IfDblCompress2;          // if double compression in string 2
    BOOL fEnd1;                   // if at end of string 1
    BOOL fIgnorePunct;            // flag to ignore punctuation (not symbol)
    BOOL fIgnoreDiacritic;        // flag to ignore diacritics
    BOOL fIgnoreSymbol;           // flag to ignore symbols
    BOOL fStringSort;             // flag to use string sort
    DWORD State;                  // state table
    DWORD Mask;                   // mask for weights
    DWORD Weight1;                // full weight of char - string 1
    DWORD Weight2;                // full weight of char - string 2

    int JamoFlag = FALSE;
    LPCWSTR pLastJamo = lpString1;

    int WhichDiacritic;           // DW => 1 = str1 smaller, 3 = str2 smaller
    int WhichCase;                // CW => 1 = str1 smaller, 3 = str2 smaller
    int WhichJamo;                // XW for Jamo
    int WhichPunct1;              // SW => 1 = str1 smaller, 3 = str2 smaller
    int WhichPunct2;              // SW => 1 = str1 smaller, 3 = str2 smaller
    LPWSTR pSave1;                // ptr to saved pString1
    LPWSTR pSave2;                // ptr to saved pString2
    int cExpChar1, cExpChar2;     // ct of expansions in tmp

    DWORD ExtraWt1, ExtraWt2;     // extra weight values (for far east)
    DWORD WhichExtra;             // XW => wts 4, 5, 6, 7 (for far east)

    //
    //  Initialize string pointers.
    //
    pString1 = (LPWSTR)lpString1;
    pString2 = (LPWSTR)lpString2;

    //
    //  Invalid Flags Check:
    //    - invalid flags
    //
    if (dwCmpFlags & CS_INVALID_FLAG)
    {
        SetLastError(ERROR_INVALID_FLAGS);
        return (0);
    }

    //
    //  See if we should stop on the null terminator regardless of the
    //  count values.  The original count values are stored in ctr1 and ctr2
    //  above, so it's ok to set these here.
    //
    if (dwCmpFlags & NORM_STOP_ON_NULL)
    {
        cchCount1 = cchCount2 = -2;
    }

    //
    //  Check if compression in the given locale.  If not, then
    //  try a wchar by wchar compare.  If strings are equal, this
    //  will be quick.
    //
    if ((IfCompress = pHashN->IfCompression) == FALSE)
    {
        //
        //  Compare each wide character in the two strings.
        //
        while ( NOT_END_STRING(ctr1, pString1, cchCount1) &&
                NOT_END_STRING(ctr2, pString2, cchCount2) )
        {
            //
            //  See if characters are equal.
            //
            if (*pString1 == *pString2)
            {
                //
                //  Characters are equal, so increment pointers,
                //  decrement counters, and continue string compare.
                //
                pString1++;
                pString2++;
                ctr1--;
                ctr2--;
            }
            else
            {
                //
                //  Difference was found.  Fall into the sortkey
                //  check below.
                //
                break;
            }
        }

        //
        //  If the end of BOTH strings has been reached, then the strings
        //  match exactly.  Return success.
        //
        if ( AT_STRING_END(ctr1, pString1, cchCount1) &&
             AT_STRING_END(ctr2, pString2, cchCount2) )
        {
            return (CSTR_EQUAL);
        }
    }

    //
    //  Initialize flags, pointers, and counters.
    //
    fIgnorePunct = dwCmpFlags & NORM_IGNORESYMBOLS;
    fIgnoreDiacritic = dwCmpFlags & NORM_IGNORENONSPACE;
    fIgnoreSymbol = fIgnorePunct;
    fStringSort = dwCmpFlags & SORT_STRINGSORT;
    WhichDiacritic = 0;
    WhichCase = 0;
    WhichJamo = 0;
    WhichPunct1 = 0;
    WhichPunct2 = 0;
    pSave1 = NULL;
    pSave2 = NULL;
    ExtraWt1 = (DWORD)0;
    WhichExtra = (DWORD)0;

    //
    //  Set the weights to be invalid.  This flags whether or not to
    //  recompute the weights next time through the loop.  It also flags
    //  whether or not to start over (continue) in the loop.
    //
    Weight1 = CMP_INVALID_WEIGHT;
    Weight2 = CMP_INVALID_WEIGHT;

    //
    //  Switch on the different flag options.  This will speed up
    //  the comparisons of two strings that are different.
    //
    State = STATE_CW | STATE_JAMO_WEIGHT;
    switch (dwCmpFlags & (NORM_IGNORECASE | NORM_IGNORENONSPACE))
    {
        case ( 0 ) :
        {
            Mask = CMP_MASKOFF_NONE;
            State |= (pHashN->IfReverseDW) ? STATE_REVERSE_DW : STATE_DW;

            break;
        }

        case ( NORM_IGNORECASE ) :
        {
            Mask = CMP_MASKOFF_CW;
            State |= (pHashN->IfReverseDW) ? STATE_REVERSE_DW : STATE_DW;

            break;
        }

        case ( NORM_IGNORENONSPACE ) :
        {
            Mask = CMP_MASKOFF_DW;

            break;
        }

        case ( NORM_IGNORECASE | NORM_IGNORENONSPACE ) :
        {
            Mask = CMP_MASKOFF_DW_CW;

            break;
        }
    }

    switch (dwCmpFlags & (NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH))
    {
        case ( 0 ) :
        {
            break;
        }

        case ( NORM_IGNOREKANATYPE ) :
        {
            Mask &= CMP_MASKOFF_KANA;

            break;
        }

        case ( NORM_IGNOREWIDTH ) :
        {
            Mask &= CMP_MASKOFF_WIDTH;

            if (dwCmpFlags & NORM_IGNORECASE)
            {
                REMOVE_STATE(STATE_CW);
            }

            break;
        }

        case ( NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH ) :
        {
            Mask &= CMP_MASKOFF_KANA_WIDTH;

            if (dwCmpFlags & NORM_IGNORECASE)
            {
                REMOVE_STATE(STATE_CW);
            }

            break;
        }
    }

    //
    //  Compare each character's sortkey weight in the two strings.
    //
    while ( NOT_END_STRING(ctr1, pString1, cchCount1) &&
            NOT_END_STRING(ctr2, pString2, cchCount2) )
    {
        if (Weight1 == CMP_INVALID_WEIGHT)
        {
            Weight1 = GET_DWORD_WEIGHT(pHashN, *pString1);
            Weight1 &= Mask;
        }
        if (Weight2 == CMP_INVALID_WEIGHT)
        {
            Weight2 = GET_DWORD_WEIGHT(pHashN, *pString2);
            Weight2 &= Mask;
        }

        //
        //  If compression locale, then need to check for compression
        //  characters even if the weights are equal.  If it's not a
        //  compression locale, then we don't need to check anything
        //  if the weights are equal.
        //
        if ( (IfCompress) &&
             (GET_COMPRESSION(&Weight1) || GET_COMPRESSION(&Weight2)) )
        {
            int ctr;                   // loop counter
            PCOMPRESS_3 pComp3;        // ptr to compress 3 table
            PCOMPRESS_2 pComp2;        // ptr to compress 2 table
            int If1;                   // if compression found in string 1
            int If2;                   // if compression found in string 2
            int CompVal;               // compression value
            int IfEnd1;                // if exists 1 more char in string 1
            int IfEnd2;                // if exists 1 more char in string 2


            //
            //  Check for compression in the weights.
            //
            If1 = GET_COMPRESSION(&Weight1);
            If2 = GET_COMPRESSION(&Weight2);
            CompVal = ((If1 > If2) ? If1 : If2);

            IfEnd1 = AT_STRING_END(ctr1 - 1, pString1 + 1, cchCount1);
            IfEnd2 = AT_STRING_END(ctr2 - 1, pString2 + 1, cchCount2);

            if (pHashN->IfDblCompression == FALSE)
            {
                //
                //  NO double compression, so don't check for it.
                //
                switch (CompVal)
                {
                    //
                    //  Check for 3 characters compressing to 1.
                    //
                    case ( COMPRESS_3_MASK ) :
                    {
                        //
                        //  Check character in string 1 and string 2.
                        //
                        if ( ((If1) && (!IfEnd1) &&
                              !AT_STRING_END(ctr1 - 2, pString1 + 2, cchCount1)) ||
                             ((If2) && (!IfEnd2) &&
                              !AT_STRING_END(ctr2 - 2, pString2 + 2, cchCount2)) )
                        {
                            ctr = pHashN->pCompHdr->Num3;
                            pComp3 = pHashN->pCompress3;
                            for (; ctr > 0; ctr--, pComp3++)
                            {
                                //
                                //  Check character in string 1.
                                //
                                if ( (If1) && (!IfEnd1) &&
                                     !AT_STRING_END(ctr1 - 2, pString1 + 2, cchCount1) &&
                                     (pComp3->UCP1 == *pString1) &&
                                     (pComp3->UCP2 == *(pString1 + 1)) &&
                                     (pComp3->UCP3 == *(pString1 + 2)) )
                                {
                                    //
                                    //  Found compression for string 1.
                                    //  Get new weight and mask it.
                                    //  Increment pointer and decrement counter.
                                    //
                                    Weight1 = MAKE_SORTKEY_DWORD(pComp3->Weights);
                                    Weight1 &= Mask;
                                    pString1 += 2;
                                    ctr1 -= 2;

                                    //
                                    //  Set boolean for string 1 - search is
                                    //  complete.
                                    //
                                    If1 = 0;

                                    //
                                    //  Break out of loop if both searches are
                                    //  done.
                                    //
                                    if (If2 == 0)
                                    {
                                        break;
                                    }
                                }

                                //
                                //  Check character in string 2.
                                //
                                if ( (If2) && (!IfEnd2) &&
                                     !AT_STRING_END(ctr2 - 2, pString2 + 2, cchCount2) &&
                                     (pComp3->UCP1 == *pString2) &&
                                     (pComp3->UCP2 == *(pString2 + 1)) &&
                                     (pComp3->UCP3 == *(pString2 + 2)) )
                                {
                                    //
                                    //  Found compression for string 2.
                                    //  Get new weight and mask it.
                                    //  Increment pointer and decrement counter.
                                    //
                                    Weight2 = MAKE_SORTKEY_DWORD(pComp3->Weights);
                                    Weight2 &= Mask;
                                    pString2 += 2;
                                    ctr2 -= 2;

                                    //
                                    //  Set boolean for string 2 - search is
                                    //  complete.
                                    //
                                    If2 = 0;

                                    //
                                    //  Break out of loop if both searches are
                                    //  done.
                                    //
                                    if (If1 == 0)
                                    {
                                        break;
                                    }
                                }
                            }
                            if (ctr > 0)
                            {
                                break;
                            }
                        }
                        //
                        //  Fall through if not found.
                        //
                    }

                    //
                    //  Check for 2 characters compressing to 1.
                    //
                    case ( COMPRESS_2_MASK ) :
                    {
                        //
                        //  Check character in string 1 and string 2.
                        //
                        if ( ((If1) && (!IfEnd1)) ||
                             ((If2) && (!IfEnd2)) )
                        {
                            ctr = pHashN->pCompHdr->Num2;
                            pComp2 = pHashN->pCompress2;
                            for (; ((ctr > 0) && (If1 || If2)); ctr--, pComp2++)
                            {
                                //
                                //  Check character in string 1.
                                //
                                if ( (If1) &&
                                     (!IfEnd1) &&
                                     (pComp2->UCP1 == *pString1) &&
                                     (pComp2->UCP2 == *(pString1 + 1)) )
                                {
                                    //
                                    //  Found compression for string 1.
                                    //  Get new weight and mask it.
                                    //  Increment pointer and decrement counter.
                                    //
                                    Weight1 = MAKE_SORTKEY_DWORD(pComp2->Weights);
                                    Weight1 &= Mask;
                                    pString1++;
                                    ctr1--;

                                    //
                                    //  Set boolean for string 1 - search is
                                    //  complete.
                                    //
                                    If1 = 0;

                                    //
                                    //  Break out of loop if both searches are
                                    //  done.
                                    //
                                    if (If2 == 0)
                                    {
                                        break;
                                    }
                                }

                                //
                                //  Check character in string 2.
                                //
                                if ( (If2) &&
                                     (!IfEnd2) &&
                                     (pComp2->UCP1 == *pString2) &&
                                     (pComp2->UCP2 == *(pString2 + 1)) )
                                {
                                    //
                                    //  Found compression for string 2.
                                    //  Get new weight and mask it.
                                    //  Increment pointer and decrement counter.
                                    //
                                    Weight2 = MAKE_SORTKEY_DWORD(pComp2->Weights);
                                    Weight2 &= Mask;
                                    pString2++;
                                    ctr2--;

                                    //
                                    //  Set boolean for string 2 - search is
                                    //  complete.
                                    //
                                    If2 = 0;

                                    //
                                    //  Break out of loop if both searches are
                                    //  done.
                                    //
                                    if (If1 == 0)
                                    {
                                        break;
                                    }
                                }
                            }
                            if (ctr > 0)
                            {
                                break;
                            }
                        }
                    }
                }
            }
            else if (!IfEnd1 && !IfEnd2)
            {
                //
                //  Double Compression exists, so must check for it.
                //
                if (IfDblCompress1 =
                       ((GET_DWORD_WEIGHT(pHashN, *pString1) & CMP_MASKOFF_CW) ==
                        (GET_DWORD_WEIGHT(pHashN, *(pString1 + 1)) & CMP_MASKOFF_CW)))
                {
                    //
                    //  Advance past the first code point to get to the
                    //  compression character.
                    //
                    pString1++;
                    ctr1--;
                    IfEnd1 = AT_STRING_END(ctr1 - 1, pString1 + 1, cchCount1);
                }

                if (IfDblCompress2 =
                       ((GET_DWORD_WEIGHT(pHashN, *pString2) & CMP_MASKOFF_CW) ==
                        (GET_DWORD_WEIGHT(pHashN, *(pString2 + 1)) & CMP_MASKOFF_CW)))
                {
                    //
                    //  Advance past the first code point to get to the
                    //  compression character.
                    //
                    pString2++;
                    ctr2--;
                    IfEnd2 = AT_STRING_END(ctr2 - 1, pString2 + 1, cchCount2);
                }

                switch (CompVal)
                {
                    //
                    //  Check for 3 characters compressing to 1.
                    //
                    case ( COMPRESS_3_MASK ) :
                    {
                        //
                        //  Check character in string 1.
                        //
                        if ( (If1) && (!IfEnd1) &&
                             !AT_STRING_END(ctr1 - 2, pString1 + 2, cchCount1) )
                        {
                            ctr = pHashN->pCompHdr->Num3;
                            pComp3 = pHashN->pCompress3;
                            for (; ctr > 0; ctr--, pComp3++)
                            {
                                //
                                //  Check character in string 1.
                                //
                                if ( (pComp3->UCP1 == *pString1) &&
                                     (pComp3->UCP2 == *(pString1 + 1)) &&
                                     (pComp3->UCP3 == *(pString1 + 2)) )
                                {
                                    //
                                    //  Found compression for string 1.
                                    //  Get new weight and mask it.
                                    //  Increment pointer and decrement counter.
                                    //
                                    Weight1 = MAKE_SORTKEY_DWORD(pComp3->Weights);
                                    Weight1 &= Mask;
                                    if (!IfDblCompress1)
                                    {
                                        pString1 += 2;
                                        ctr1 -= 2;
                                    }

                                    //
                                    //  Set boolean for string 1 - search is
                                    //  complete.
                                    //
                                    If1 = 0;

                                    break;
                                }
                            }
                        }

                        //
                        //  Check character in string 2.
                        //
                        if ( (If2) && (!IfEnd2) &&
                             !AT_STRING_END(ctr2 - 2, pString2 + 2, cchCount2) )
                        {
                            ctr = pHashN->pCompHdr->Num3;
                            pComp3 = pHashN->pCompress3;
                            for (; ctr > 0; ctr--, pComp3++)
                            {
                                //
                                //  Check character in string 2.
                                //
                                if ( (pComp3->UCP1 == *pString2) &&
                                     (pComp3->UCP2 == *(pString2 + 1)) &&
                                     (pComp3->UCP3 == *(pString2 + 2)) )
                                {
                                    //
                                    //  Found compression for string 2.
                                    //  Get new weight and mask it.
                                    //  Increment pointer and decrement counter.
                                    //
                                    Weight2 = MAKE_SORTKEY_DWORD(pComp3->Weights);
                                    Weight2 &= Mask;
                                    if (!IfDblCompress2)
                                    {
                                        pString2 += 2;
                                        ctr2 -= 2;
                                    }

                                    //
                                    //  Set boolean for string 2 - search is
                                    //  complete.
                                    //
                                    If2 = 0;

                                    break;
                                }
                            }
                        }

                        //
                        //  Fall through if not found.
                        //
                        if ((If1 == 0) && (If2 == 0))
                        {
                            break;
                        }
                    }

                    //
                    //  Check for 2 characters compressing to 1.
                    //
                    case ( COMPRESS_2_MASK ) :
                    {
                        //
                        //  Check character in string 1.
                        //
                        if ((If1) && (!IfEnd1))
                        {
                            ctr = pHashN->pCompHdr->Num2;
                            pComp2 = pHashN->pCompress2;
                            for (; ctr > 0; ctr--, pComp2++)
                            {
                                //
                                //  Check character in string 1.
                                //
                                if ((pComp2->UCP1 == *pString1) &&
                                    (pComp2->UCP2 == *(pString1 + 1)))
                                {
                                    //
                                    //  Found compression for string 1.
                                    //  Get new weight and mask it.
                                    //  Increment pointer and decrement counter.
                                    //
                                    Weight1 = MAKE_SORTKEY_DWORD(pComp2->Weights);
                                    Weight1 &= Mask;
                                    if (!IfDblCompress1)
                                    {
                                        pString1++;
                                        ctr1--;
                                    }

                                    //
                                    //  Set boolean for string 1 - search is
                                    //  complete.
                                    //
                                    If1 = 0;

                                    break;
                                }
                            }
                        }

                        //
                        //  Check character in string 2.
                        //
                        if ((If2) && (!IfEnd2))
                        {
                            ctr = pHashN->pCompHdr->Num2;
                            pComp2 = pHashN->pCompress2;
                            for (; ctr > 0; ctr--, pComp2++)
                            {
                                //
                                //  Check character in string 2.
                                //
                                if ((pComp2->UCP1 == *pString2) &&
                                    (pComp2->UCP2 == *(pString2 + 1)))
                                {
                                    //
                                    //  Found compression for string 2.
                                    //  Get new weight and mask it.
                                    //  Increment pointer and decrement counter.
                                    //
                                    Weight2 = MAKE_SORTKEY_DWORD(pComp2->Weights);
                                    Weight2 &= Mask;
                                    if (!IfDblCompress2)
                                    {
                                        pString2++;
                                        ctr2--;
                                    }

                                    //
                                    //  Set boolean for string 2 - search is
                                    //  complete.
                                    //
                                    If2 = 0;

                                    break;
                                }
                            }
                        }
                    }
                }

                //
                //  Reset the pointer back to the beginning of the double
                //  compression.  Pointer fixup at the end will advance
                //  them correctly.
                //
                //  If double compression, we advanced the pointer at
                //  the beginning of the switch statement.  If double
                //  compression character was actually found, the pointer
                //  was NOT advanced.  We now want to decrement the pointer
                //  to put it back to where it was.
                //
                //  The next time through, the pointer will be pointing to
                //  the regular compression part of the string.
                //
                if (IfDblCompress1)
                {
                    pString1--;
                    ctr1++;
                }
                if (IfDblCompress2)
                {
                    pString2--;
                    ctr2++;
                }
            }
        }

        //
        //  Check the weights again.
        //
        if ((Weight1 != Weight2) ||
            (GET_SCRIPT_MEMBER(&Weight1) == EXTENSION_A))
        {
            //
            //  Weights are still not equal, even after compression
            //  check, so compare the different weights.
            //
            BYTE sm1 = GET_SCRIPT_MEMBER(&Weight1);                // script member 1
            BYTE sm2 = GET_SCRIPT_MEMBER(&Weight2);                // script member 2
            WORD uw1 = GET_UNICODE_SM_MOD(&Weight1, sm1, fModify); // unicode weight 1
            WORD uw2 = GET_UNICODE_SM_MOD(&Weight2, sm2, fModify); // unicode weight 2
            BYTE dw1;                                              // diacritic weight 1
            BYTE dw2;                                              // diacritic weight 2
            DWORD Wt;                                              // temp weight holder
            WCHAR pTmpBuf1[MAX_TBL_EXPANSION];                     // temp buffer for exp 1
            WCHAR pTmpBuf2[MAX_TBL_EXPANSION];                     // temp buffer for exp 2


            //
            //  If Unicode Weights are different and no special cases,
            //  then we're done.  Otherwise, we need to do extra checking.
            //
            //  Must check ENTIRE string for any possibility of Unicode Weight
            //  differences.  As soon as a Unicode Weight difference is found,
            //  then we're done.  If no UW difference is found, then the
            //  first Diacritic Weight difference is used.  If no DW difference
            //  is found, then use the first Case Difference.  If no CW
            //  difference is found, then use the first Extra Weight
            //  difference.  If no XW difference is found, then use the first
            //  Special Weight difference.
            //
            if ((uw1 != uw2) ||
                ((sm1 <= SYMBOL_5) && (sm1 >= FAREAST_SPECIAL)))
            {
                //
                //  Check for Unsortable characters and skip them.
                //  This needs to be outside the switch statement.  If EITHER
                //  character is unsortable, must skip it and start over.
                //
                if (sm1 == UNSORTABLE)
                {
                    pString1++;
                    ctr1--;
                    Weight1 = CMP_INVALID_WEIGHT;
                }
                if (sm2 == UNSORTABLE)
                {
                    pString2++;
                    ctr2--;
                    Weight2 = CMP_INVALID_WEIGHT;
                }

                //
                //  Check for Ignore Nonspace and Ignore Symbol.  If
                //  Ignore Nonspace is set and either character is a
                //  nonspace mark only, then we need to advance the
                //  pointer to skip over the character and continue.
                //  If Ignore Symbol is set and either character is a
                //  punctuation char, then we need to advance the
                //  pointer to skip over the character and continue.
                //
                //  This step is necessary so that a string with a
                //  nonspace mark and a punctuation char following one
                //  another are properly ignored when one or both of
                //  the ignore flags is set.
                //
                if (fIgnoreDiacritic)
                {
                    if (sm1 == NONSPACE_MARK)
                    {
                        pString1++;
                        ctr1--;
                        Weight1 = CMP_INVALID_WEIGHT;
                    }
                    if (sm2 == NONSPACE_MARK)
                    {
                        pString2++;
                        ctr2--;
                        Weight2 = CMP_INVALID_WEIGHT;
                    }
                }
                if (fIgnoreSymbol)
                {
                    if (sm1 == PUNCTUATION)
                    {
                        pString1++;
                        ctr1--;
                        Weight1 = CMP_INVALID_WEIGHT;
                    }
                    if (sm2 == PUNCTUATION)
                    {
                        pString2++;
                        ctr2--;
                        Weight2 = CMP_INVALID_WEIGHT;
                    }
                }
                if ((Weight1 == CMP_INVALID_WEIGHT) || (Weight2 == CMP_INVALID_WEIGHT))
                {
                    continue;
                }

                //
                //  Switch on the script member of string 1 and take care
                //  of any special cases.
                //
                switch (sm1)
                {
                    case ( NONSPACE_MARK ) :
                    {
                        //
                        //  Nonspace only - look at diacritic weight only.
                        //
                        if (!fIgnoreDiacritic)
                        {
                            if ((WhichDiacritic == 0) ||
                                (State & STATE_REVERSE_DW))
                            {
                                WhichDiacritic = CSTR_GREATER_THAN;

                                //
                                //  Remove state from state machine.
                                //
                                REMOVE_STATE(STATE_DW);
                            }
                        }

                        //
                        //  Adjust pointer and counter and set flags.
                        //
                        pString1++;
                        ctr1--;
                        Weight1 = CMP_INVALID_WEIGHT;

                        break;
                    }
                    case ( SYMBOL_1 ) :
                    case ( SYMBOL_2 ) :
                    case ( SYMBOL_3 ) :
                    case ( SYMBOL_4 ) :
                    case ( SYMBOL_5 ) :
                    {
                        //
                        //  If the ignore symbol flag is set, then skip over
                        //  the symbol.
                        //
                        if (fIgnoreSymbol)
                        {
                            pString1++;
                            ctr1--;
                            Weight1 = CMP_INVALID_WEIGHT;
                        }

                        break;
                    }
                    case ( PUNCTUATION ) :
                    {
                        //
                        //  If the ignore punctuation flag is set, then skip
                        //  over the punctuation char.
                        //
                        if (fIgnorePunct)
                        {
                            pString1++;
                            ctr1--;
                            Weight1 = CMP_INVALID_WEIGHT;
                        }
                        else if (!fStringSort)
                        {
                            //
                            //  Use WORD sort method.
                            //
                            if (sm2 != PUNCTUATION)
                            {
                                //
                                //  The character in the second string is
                                //  NOT punctuation.
                                //
                                if (WhichPunct2)
                                {
                                    //
                                    //  Set WP 2 to show that string 2 is
                                    //  smaller, since a punctuation char had
                                    //  already been found at an earlier
                                    //  position in string 2.
                                    //
                                    //  Set the Ignore Punctuation flag so we
                                    //  just skip over any other punctuation
                                    //  chars in the string.
                                    //
                                    WhichPunct2 = CSTR_GREATER_THAN;
                                    fIgnorePunct = TRUE;
                                }
                                else
                                {
                                    //
                                    //  Set WP 1 to show that string 2 is
                                    //  smaller, and that string 1 has had
                                    //  a punctuation char - since no
                                    //  punctuation chars have been found
                                    //  in string 2.
                                    //
                                    WhichPunct1 = CSTR_GREATER_THAN;
                                }

                                //
                                //  Advance pointer 1 and decrement counter 1.
                                //
                                pString1++;
                                ctr1--;
                                Weight1 = CMP_INVALID_WEIGHT;
                            }

                            //
                            //  Do NOT want to advance the pointer in string 1
                            //  if string 2 is also a punctuation char.  This
                            //  will be done later.
                            //
                        }

                        break;
                    }
                    case ( EXPANSION ) :
                    {
                        //
                        //  Save pointer in pString1 so that it can be
                        //  restored.
                        //
                        if (pSave1 == NULL)
                        {
                            pSave1 = pString1;
                        }
                        pString1 = pTmpBuf1;

                        //
                        //  Add one to counter so that subtraction doesn't end
                        //  comparison prematurely.
                        //
                        ctr1++;

                        //
                        //  Expand character into temporary buffer.
                        //
                        pTmpBuf1[0] = GET_EXPANSION_1(&Weight1);
                        pTmpBuf1[1] = GET_EXPANSION_2(&Weight1);

                        //
                        //  Set cExpChar1 to the number of expansion characters
                        //  stored.
                        //
                        cExpChar1 = MAX_TBL_EXPANSION;

                        Weight1 = CMP_INVALID_WEIGHT;

                        break;
                    }
                    case ( FAREAST_SPECIAL ) :
                    {
                        if (sm2 != EXPANSION) 
                        {
                            //
                            //  Get the weight for the far east special case
                            //  and store it in Weight1.
                            //
                            GET_FAREAST_WEIGHT( Weight1,
                                                uw1,
                                                Mask,
                                                lpString1,
                                                pString1,
                                                ExtraWt1,
                                                fModify );

                            if (sm2 != FAREAST_SPECIAL)
                            {
                                //
                                //  The character in the second string is
                                //  NOT a fareast special char.
                                //
                                //  Set each of weights 4, 5, 6, and 7 to show
                                //  that string 2 is smaller (if not already set).
                                //
                                if ((GET_WT_FOUR(&WhichExtra) == 0) &&
                                    (GET_WT_FOUR(&ExtraWt1) != 0))
                                {
                                    GET_WT_FOUR(&WhichExtra) = CSTR_GREATER_THAN;
                                }
                                if ((GET_WT_FIVE(&WhichExtra) == 0) &&
                                    (GET_WT_FIVE(&ExtraWt1) != 0))
                                {
                                    GET_WT_FIVE(&WhichExtra) = CSTR_GREATER_THAN;
                                }
                                if ((GET_WT_SIX(&WhichExtra) == 0) &&
                                    (GET_WT_SIX(&ExtraWt1) != 0))
                                {
                                    GET_WT_SIX(&WhichExtra) = CSTR_GREATER_THAN;
                                }
                                if ((GET_WT_SEVEN(&WhichExtra) == 0) &&
                                    (GET_WT_SEVEN(&ExtraWt1) != 0))
                                {
                                    GET_WT_SEVEN(&WhichExtra) = CSTR_GREATER_THAN;
                                }
                            }
                        }
                        break;
                    }
                    case ( JAMO_SPECIAL ) :
                    {
                        LPWSTR pStr1 = pString1;
                        LPWSTR pStr2 = pString2;

                        //
                        //  Set the JamoFlag so we don't handle it again.
                        //
                        JamoFlag = TRUE;
                        FindJamoDifference(
                            pHashN,
                            &pStr1, &ctr1, cchCount1, &Weight1,
                            &pStr2, &ctr2, cchCount2, &Weight2,
                            &pLastJamo,
                            &uw1, &uw2,
                            &State,
                            &WhichJamo,
                            fModify );

                        if (WhichJamo) 
                        {
                            return (WhichJamo);
                        }                            
                        pString1 = pStr1;
                        pString2 = pStr2;

                        break;
                    }
                    case ( EXTENSION_A ) :
                    {
                        //
                        //  Get the full weight in case DW got masked.
                        //
                        Weight1 = GET_DWORD_WEIGHT(pHashN, *pString1);
                        if (sm2 == EXTENSION_A)
                        {
                            Weight2 = GET_DWORD_WEIGHT(pHashN, *pString2);
                        }

                        //
                        //  Compare the weights.
                        //
                        if (Weight1 == Weight2)
                        {
                            //
                            //  Adjust pointers and counters and set flags.
                            //
                            pString1++;  pString2++;
                            ctr1--;  ctr2--;
                            Weight1 = CMP_INVALID_WEIGHT;
                            Weight2 = CMP_INVALID_WEIGHT;
                        }
                        else
                        {
                            //
                            //  Get the actual UW to compare.
                            //
                            if (sm2 == EXTENSION_A)
                            {
                                //
                                //  Set the UW values to be the AW and DW since
                                //  both strings contain an extension A char.
                                //
                                uw1 = MAKE_UNICODE_WT( GET_ALPHA_NUMERIC(&Weight1),
                                                       GET_DIACRITIC(&Weight1),
                                                       FALSE );
                                uw2 = MAKE_UNICODE_WT( GET_ALPHA_NUMERIC(&Weight2),
                                                       GET_DIACRITIC(&Weight2),
                                                       FALSE );
                            }
                            else
                            {
                                //
                                //  Only string1 contains an extension A char,
                                //  so set the UW value to be the first UW
                                //  value for extension A (default values):
                                //    SM_EXT_A, AW_EXT_A
                                //
                                uw1 = MAKE_UNICODE_WT(SM_EXT_A, AW_EXT_A, fModify);
                            }
                        }

                        break;
                    }
                    case ( UNSORTABLE ) :
                    {
                        //
                        //  Fill out the case statement so the compiler
                        //  will use a jump table.
                        //
                        break;
                    }
                }

                //
                //  Switch on the script member of string 2 and take care
                //  of any special cases.
                //
                switch (sm2)
                {
                    case ( NONSPACE_MARK ) :
                    {
                        //
                        //  Nonspace only - look at diacritic weight only.
                        //
                        if (!fIgnoreDiacritic)
                        {
                            if ((WhichDiacritic == 0) ||
                                (State & STATE_REVERSE_DW))

                            {
                                WhichDiacritic = CSTR_LESS_THAN;

                                //
                                //  Remove state from state machine.
                                //
                                REMOVE_STATE(STATE_DW);
                            }
                        }

                        //
                        //  Adjust pointer and counter and set flags.
                        //
                        pString2++;
                        ctr2--;
                        Weight2 = CMP_INVALID_WEIGHT;

                        break;
                    }
                    case ( SYMBOL_1 ) :
                    case ( SYMBOL_2 ) :
                    case ( SYMBOL_3 ) :
                    case ( SYMBOL_4 ) :
                    case ( SYMBOL_5 ) :
                    {
                        //
                        //  If the ignore symbol flag is set, then skip over
                        //  the symbol.
                        //
                        if (fIgnoreSymbol)
                        {
                            pString2++;
                            ctr2--;
                            Weight2 = CMP_INVALID_WEIGHT;
                        }

                        break;
                    }
                    case ( PUNCTUATION ) :
                    {
                        //
                        //  If the ignore punctuation flag is set, then
                        //  skip over the punctuation char.
                        //
                        if (fIgnorePunct)
                        {
                            //
                            //  Advance pointer 2 and decrement counter 2.
                            //
                            pString2++;
                            ctr2--;
                            Weight2 = CMP_INVALID_WEIGHT;
                        }
                        else if (!fStringSort)
                        {
                            //
                            //  Use WORD sort method.
                            //
                            if (sm1 != PUNCTUATION)
                            {
                                //
                                //  The character in the first string is
                                //  NOT punctuation.
                                //
                                if (WhichPunct1)
                                {
                                    //
                                    //  Set WP 1 to show that string 1 is
                                    //  smaller, since a punctuation char had
                                    //  already been found at an earlier
                                    //  position in string 1.
                                    //
                                    //  Set the Ignore Punctuation flag so we
                                    //  just skip over any other punctuation
                                    //  chars in the string.
                                    //
                                    WhichPunct1 = CSTR_LESS_THAN;
                                    fIgnorePunct = TRUE;
                                }
                                else
                                {
                                    //
                                    //  Set WP 2 to show that string 1 is
                                    //  smaller, and that string 2 has had
                                    //  a punctuation char - since no
                                    //  punctuation chars have been found
                                    //  in string 1.
                                    //
                                    WhichPunct2 = CSTR_LESS_THAN;
                                }

                                //
                                //  Pointer 2 and counter 2 will be updated
                                //  after if-else statement.
                                //
                            }
                            else
                            {
                                //
                                //  Both code points are punctuation chars.
                                //
                                //  See if either of the strings has encountered
                                //  punctuation chars previous to this.
                                //
                                if (WhichPunct1)
                                {
                                    //
                                    //  String 1 has had a punctuation char, so
                                    //  it should be the smaller string (since
                                    //  both have punctuation chars).
                                    //
                                    WhichPunct1 = CSTR_LESS_THAN;
                                }
                                else if (WhichPunct2)
                                {
                                    //
                                    //  String 2 has had a punctuation char, so
                                    //  it should be the smaller string (since
                                    //  both have punctuation chars).
                                    //
                                    WhichPunct2 = CSTR_GREATER_THAN;
                                }
                                else
                                {
                                    BYTE aw1 = GET_ALPHA_NUMERIC(&Weight1);
                                    BYTE aw2 = GET_ALPHA_NUMERIC(&Weight2);

                                    if (aw1 == aw2) 
                                    {
                                        BYTE cw1 = GET_CASE(&Weight1);
                                        BYTE cw2 = GET_CASE(&Weight2);
                                        if (cw1 < cw2) 
                                        {
                                            WhichPunct1 = CSTR_LESS_THAN;
                                        } else if (cw1 > cw2)
                                        {
                                            WhichPunct1 = CSTR_GREATER_THAN;
                                        }
                                    } else 
                                    {                                
                                        //
                                        //  Position is the same, so compare the
                                        //  special weights.   Set WhichPunct1 to
                                        //  the smaller special weight.
                                        //
                                        WhichPunct1 = (aw1 < aw2
                                                        ? CSTR_LESS_THAN
                                                        : CSTR_GREATER_THAN);
                                    }
                                }

                                //
                                //  Set the Ignore Punctuation flag.
                                //
                                fIgnorePunct = TRUE;

                                //
                                //  Advance pointer 1 and decrement counter 1.
                                //  Pointer 2 and counter 2 will be updated
                                //  after if-else statement.
                                //
                                pString1++;
                                ctr1--;
                                Weight1 = CMP_INVALID_WEIGHT;
                            }

                            //
                            //  Advance pointer 2 and decrement counter 2.
                            //
                            pString2++;
                            ctr2--;
                            Weight2 = CMP_INVALID_WEIGHT;
                        }

                        break;
                    }
                    case ( EXPANSION ) :
                    {
                        //
                        //  Save pointer in pString1 so that it can be restored.
                        //
                        if (pSave2 == NULL)
                        {
                            pSave2 = pString2;
                        }
                        pString2 = pTmpBuf2;

                        //
                        //  Add one to counter so that subtraction doesn't end
                        //  comparison prematurely.
                        //
                        ctr2++;

                        //
                        //  Expand character into temporary buffer.
                        //
                        pTmpBuf2[0] = GET_EXPANSION_1(&Weight2);
                        pTmpBuf2[1] = GET_EXPANSION_2(&Weight2);

                        //
                        //  Set cExpChar2 to the number of expansion characters
                        //  stored.
                        //
                        cExpChar2 = MAX_TBL_EXPANSION;

                        Weight2 = CMP_INVALID_WEIGHT;

                        break;
                    }
                    case ( FAREAST_SPECIAL ) :
                    {
                        if (sm1 != EXPANSION) 
                        {                        
                            //
                            //  Get the weight for the far east special case
                            //  and store it in Weight2.
                            //
                            GET_FAREAST_WEIGHT( Weight2,
                                                uw2,
                                                Mask,
                                                lpString2,
                                                pString2,
                                                ExtraWt2,
                                                fModify );

                            if (sm1 != FAREAST_SPECIAL)
                            {
                                //
                                //  The character in the first string is
                                //  NOT a fareast special char.
                                //
                                //  Set each of weights 4, 5, 6, and 7 to show
                                //  that string 1 is smaller (if not already set).
                                //
                                if ((GET_WT_FOUR(&WhichExtra) == 0) &&
                                    (GET_WT_FOUR(&ExtraWt2) != 0))
                                {
                                    GET_WT_FOUR(&WhichExtra) = CSTR_LESS_THAN;
                                }
                                if ((GET_WT_FIVE(&WhichExtra) == 0) &&
                                    (GET_WT_FIVE(&ExtraWt2) != 0))
                                {
                                    GET_WT_FIVE(&WhichExtra) = CSTR_LESS_THAN;
                                }
                                if ((GET_WT_SIX(&WhichExtra) == 0) &&
                                    (GET_WT_SIX(&ExtraWt2) != 0))
                                {
                                    GET_WT_SIX(&WhichExtra) = CSTR_LESS_THAN;
                                }
                                if ((GET_WT_SEVEN(&WhichExtra) == 0) &&
                                    (GET_WT_SEVEN(&ExtraWt2) != 0))
                                {
                                    GET_WT_SEVEN(&WhichExtra) = CSTR_LESS_THAN;
                                }
                            }
                            else
                            {
                                //
                                //  Characters in both strings are fareast
                                //  special chars.
                                //
                                //  Set each of weights 4, 5, 6, and 7
                                //  appropriately (if not already set).
                                //
                                if ( (GET_WT_FOUR(&WhichExtra) == 0) &&
                                     ( GET_WT_FOUR(&ExtraWt1) !=
                                       GET_WT_FOUR(&ExtraWt2) ) )
                                {
                                    GET_WT_FOUR(&WhichExtra) =
                                      ( GET_WT_FOUR(&ExtraWt1) <
                                        GET_WT_FOUR(&ExtraWt2) )
                                      ? CSTR_LESS_THAN
                                      : CSTR_GREATER_THAN;
                                }
                                if ( (GET_WT_FIVE(&WhichExtra) == 0) &&
                                     ( GET_WT_FIVE(&ExtraWt1) !=
                                       GET_WT_FIVE(&ExtraWt2) ) )
                                {
                                    GET_WT_FIVE(&WhichExtra) =
                                      ( GET_WT_FIVE(&ExtraWt1) <
                                        GET_WT_FIVE(&ExtraWt2) )
                                      ? CSTR_LESS_THAN
                                      : CSTR_GREATER_THAN;
                                }
                                if ( (GET_WT_SIX(&WhichExtra) == 0) &&
                                     ( GET_WT_SIX(&ExtraWt1) !=
                                       GET_WT_SIX(&ExtraWt2) ) )
                                {
                                    GET_WT_SIX(&WhichExtra) =
                                      ( GET_WT_SIX(&ExtraWt1) <
                                        GET_WT_SIX(&ExtraWt2) )
                                      ? CSTR_LESS_THAN
                                      : CSTR_GREATER_THAN;
                                }
                                if ( (GET_WT_SEVEN(&WhichExtra) == 0) &&
                                     ( GET_WT_SEVEN(&ExtraWt1) !=
                                       GET_WT_SEVEN(&ExtraWt2) ) )
                                {
                                    GET_WT_SEVEN(&WhichExtra) =
                                      ( GET_WT_SEVEN(&ExtraWt1) <
                                        GET_WT_SEVEN(&ExtraWt2) )
                                      ? CSTR_LESS_THAN
                                      : CSTR_GREATER_THAN;
                                }
                            }
                        }
                        break;
                    }
                    case ( JAMO_SPECIAL ) :
                    {
                        if (!JamoFlag)
                        {
                            LPWSTR pStr1 = pString1;
                            LPWSTR pStr2 = pString2;

                            FindJamoDifference(
                                pHashN,
                                &pStr1, &ctr1, cchCount1, &Weight1,
                                &pStr2, &ctr2, cchCount2, &Weight2,
                                &pLastJamo,
                                &uw1, &uw2,
                                &State,
                                &WhichJamo,
                                fModify );
                            if (WhichJamo) 
                            {
                                return (WhichJamo);
                            }                                                            
                            pString1 = pStr1;
                            pString2 = pStr2;
                        }
                        else
                        {
                            //
                            //  Reset the Jamo flag.
                            //
                            JamoFlag = FALSE;
                        }

                        break;
                    }
                    case ( EXTENSION_A ) :
                    {
                        //
                        //  If sm1 is an extension A character, then
                        //  both sm1 and sm2 have been handled.  We should
                        //  only get here when either sm1 is not an
                        //  extension A character or the two extension A
                        //  characters are different.
                        //
                        if (sm1 != EXTENSION_A)
                        {
                            //
                            //  Get the full weight in case DW got masked.
                            //  Also, get the actual UW to compare.
                            //
                            //  Only string2 contains an extension A char,
                            //  so set the UW value to be the first UW
                            //  value for extension A (default values):
                            //    SM_EXT_A, AW_EXT_A
                            //
                            Weight2 = GET_DWORD_WEIGHT(pHashN, *pString2);
                            uw2 = MAKE_UNICODE_WT(SM_EXT_A, AW_EXT_A, fModify);
                        }

                        //
                        //  We should then fall through to the comparison
                        //  of the Unicode weights.
                        //

                        break;
                    }
                    case ( UNSORTABLE ) :
                    {
                        //
                        //  Fill out the case statement so the compiler
                        //  will use a jump table.
                        //
                        break;
                    }
                }

                //
                //  See if the comparison should start again.
                //
                if ((Weight1 == CMP_INVALID_WEIGHT) || (Weight2 == CMP_INVALID_WEIGHT))
                {
                    //
                    //  Check to see if we're modifying the script value.
                    //  If so, then we need to reset the fareast weight
                    //  (if applicable) so that it doesn't get modified
                    //  again.
                    //
                    if (fModify == TRUE)
                    {
                        if (sm1 == FAREAST_SPECIAL)
                        {
                            Weight1 = CMP_INVALID_WEIGHT;
                        }
                        else if (sm2 == FAREAST_SPECIAL)
                        {
                            Weight2 = CMP_INVALID_WEIGHT;
                        }
                    }
                    continue;
                }

                //
                //  We're not supposed to drop down into the state table if
                //  the unicode weights are different, so stop comparison
                //  and return result of unicode weight comparison.
                //
                if (uw1 != uw2)
                {
                    return ((uw1 < uw2) ? CSTR_LESS_THAN : CSTR_GREATER_THAN);
                }
            }

            //
            //  For each state in the state table, do the appropriate
            //  comparisons.
            //
            if (State & (STATE_DW | STATE_REVERSE_DW))
            {
                //
                //  Get the diacritic weights.
                //
                dw1 = GET_DIACRITIC(&Weight1);
                dw2 = GET_DIACRITIC(&Weight2);

                if (dw1 != dw2)
                {
                    //
                    //  Look ahead to see if diacritic follows a
                    //  minimum diacritic weight.  If so, get the
                    //  diacritic weight of the nonspace mark.
                    //
                    while (!AT_STRING_END(ctr1 - 1, pString1 + 1, cchCount1))
                    {
                        Wt = GET_DWORD_WEIGHT(pHashN, *(pString1 + 1));
                        if (GET_SCRIPT_MEMBER(&Wt) == NONSPACE_MARK)
                        {
                            dw1 += GET_DIACRITIC(&Wt);
                            pString1++;
                            ctr1--;
                        }
                        else
                        {
                            break;
                        }
                    }

                    while (!AT_STRING_END(ctr2 - 1, pString2 + 1, cchCount2))
                    {
                        Wt = GET_DWORD_WEIGHT(pHashN, *(pString2 + 1));
                        if (GET_SCRIPT_MEMBER(&Wt) == NONSPACE_MARK)
                        {
                            dw2 += GET_DIACRITIC(&Wt);
                            pString2++;
                            ctr2--;
                        }
                        else
                        {
                            break;
                        }
                    }

                    //
                    //  Save which string has the smaller diacritic
                    //  weight if the diacritic weights are still
                    //  different.
                    //
                    if (dw1 != dw2)
                    {
                        WhichDiacritic = (dw1 < dw2)
                                           ? CSTR_LESS_THAN
                                           : CSTR_GREATER_THAN;

                        //
                        //  Remove state from state machine.
                        //
                        REMOVE_STATE(STATE_DW);
                    }
                }
            }
            if (State & STATE_CW)
            {
                //
                //  Get the case weights.
                //
                if (GET_CASE(&Weight1) != GET_CASE(&Weight2))
                {
                    //
                    //  Save which string has the smaller case weight.
                    //
                    WhichCase = (GET_CASE(&Weight1) < GET_CASE(&Weight2))
                                  ? CSTR_LESS_THAN
                                  : CSTR_GREATER_THAN;

                    //
                    //  Remove state from state machine.
                    //
                    REMOVE_STATE(STATE_CW);
                }
            }
        }

        //
        //  Fixup the pointers and counters.
        //
        POINTER_FIXUP();
        ctr1--;
        ctr2--;

        //
        //  Reset the weights to be invalid.
        //
        Weight1 = CMP_INVALID_WEIGHT;
        Weight2 = CMP_INVALID_WEIGHT;
    }

    //
    //  If the end of BOTH strings has been reached, then the unicode
    //  weights match exactly.  Check the diacritic, case and special
    //  weights.  If all are zero, then return success.  Otherwise,
    //  return the result of the weight difference.
    //
    //  NOTE:  The following checks MUST REMAIN IN THIS ORDER:
    //            Diacritic, Case, Punctuation.
    //
    if (AT_STRING_END(ctr1, pString1, cchCount1))
    {
        if (AT_STRING_END(ctr2, pString2, cchCount2))
        {
            if (WhichDiacritic)
            {
                return (WhichDiacritic);
            }
            if (WhichCase)
            {
                return (WhichCase);
            }
            if (WhichExtra)
            {
                if (!fIgnoreDiacritic)
                {
                    if (GET_WT_FOUR(&WhichExtra))
                    {
                        return (GET_WT_FOUR(&WhichExtra));
                    }
                    if (GET_WT_FIVE(&WhichExtra))
                    {
                        return (GET_WT_FIVE(&WhichExtra));
                    }
                }
                if (GET_WT_SIX(&WhichExtra))
                {
                    return (GET_WT_SIX(&WhichExtra));
                }
                if (GET_WT_SEVEN(&WhichExtra))
                {
                    return (GET_WT_SEVEN(&WhichExtra));
                }
            }
            if (WhichPunct1)
            {
                return (WhichPunct1);
            }
            if (WhichPunct2)
            {
                return (WhichPunct2);
            }

            return (CSTR_EQUAL);
        }
        else
        {
            //
            //  String 2 is longer.
            //
            pString1 = pString2;
            ctr1 = ctr2;
            cchCount1 = cchCount2;
            fEnd1 = CSTR_LESS_THAN;
        }
    }
    else
    {
        fEnd1 = CSTR_GREATER_THAN;
    }

    //
    //  Scan to the end of the longer string.
    //
    SCAN_LONGER_STRING( ctr1,
                        pString1,
                        cchCount1,
                        fEnd1 );
}


////////////////////////////////////////////////////////////////////////////
//
//  FindJamoDifference
//
////////////////////////////////////////////////////////////////////////////

int FindJamoDifference(
    PLOC_HASH pHashN,
    LPCWSTR* ppString1, int* ctr1, int cchCount1, DWORD* pWeight1,
    LPCWSTR* ppString2, int* ctr2, int cchCount2, DWORD* pWeight2,
    LPCWSTR* pLastJamo,
    WORD* uw1,
    WORD* uw2,
    int* pState,
    int* WhichJamo,
    BOOL fModify)
{
    int bRestart = 0;            // if string compare should restart again
    int oldHangulsFound1 = 0;    // # of valid old Hangul Jamo compositions found
    int oldHangulsFound2 = 0;    // # of valid old Hangul Jamo compositions found
    WORD UW;
    BYTE JamoWeight1[3];         // extra weight for first old Hangul composition
    BYTE JamoWeight2[3];         // extra weight for second old Hangul composition

    //
    //  Roll back to the first Jamo.  We know that these Jamos in both strings
    //  should be equal, so we can decrement both strings at once.
    //
    while ((*ppString1 > *pLastJamo) && IsJamo(*(*ppString1 - 1)))
    {
        (*ppString1)--; (*ppString2)--; (*ctr1)++; (*ctr2)++;
    }

    //
    //  Now we are at the beginning of two groups of Jamo characters.
    //  Compare Jamo unit (either a single Jamo or a valid old Hangul Jamo
    //  composition) until we run out Jamo units in either strings.
    //  We also exit when we reach the ends of either string.
    //
    //  while (NOT_END_STRING(*ctr1, *ppString1, cchCount1) &&
    //         NOT_END_STRING(*ctr2, *ppString2, cchCount2))
    //
    for (;;)
    {
        if (IsJamo(**ppString1))
        {
            if (IsLeadingJamo(**ppString1))
            {
                if ((oldHangulsFound1 = MapOldHangulSortKey( pHashN,
                                                             *ppString1,
                                                             *ctr1,
                                                             &UW,
                                                             JamoWeight1,
                                                             fModify )) > 0)
                {
                    *uw1 = UW;

                    //
                    //  Mark *pWeight1 so that it is not CMP_INVALID_WEIGHT.
                    //  0202 is the DW/CW.
                    //
                    *pWeight1 = ((DWORD)UW | 0x02020000);

                    //
                    //  We always increment ppString1/ctr1 at the end of the
                    //  loop, so we need to subtract 1 here.
                    //
                    *ppString1 += (oldHangulsFound1 - 1);
                    *ctr1 -= (oldHangulsFound1 - 1);
                }
            }
            if (oldHangulsFound1 == 0)
            {
                //
                //  No valid old Hangul compositions are found.  Get the UW
                //  for the Jamo instead.
                //
                *pWeight1 = GET_DWORD_WEIGHT(pHashN, **ppString1);

                //
                //  The SMs in PSORTKEY for Jamos are not really SMs. They
                //  are all 4 (for JAMO_SPECIAL).
                //  Here we get the real Jamo Unicode weight. The actual SM
                //  is stored in DW.
                //
                *uw1 = MAKE_UNICODE_WT( GET_DIACRITIC(pWeight1),
                                        GET_ALPHA_NUMERIC(pWeight1),
                                        fModify );
                ((PSORTKEY)pWeight1)->Diacritic = MIN_DW;
            }
        }

        if (IsJamo(**ppString2))
        {
            if (IsLeadingJamo(**ppString2))
            {
                if ((oldHangulsFound2 = MapOldHangulSortKey( pHashN,
                                                             *ppString2,
                                                             *ctr2,
                                                             &UW,
                                                             JamoWeight2,
                                                             fModify )) > 0)
                {
                    *uw2 = UW;
                    *pWeight2 = ((DWORD)UW | 0x02020000);
                    *ppString2 += (oldHangulsFound2 - 1);
                    *ctr2 -= (oldHangulsFound2 - 1);
                }
            }
            if (oldHangulsFound2 == 0)
            {
                *pWeight2 = GET_DWORD_WEIGHT(pHashN, **ppString2);
                *uw2 = MAKE_UNICODE_WT( GET_DIACRITIC(pWeight2),
                                        GET_ALPHA_NUMERIC(pWeight2),
                                        fModify );
                ((PSORTKEY)pWeight2)->Diacritic = MIN_DW;                                        
            }
        }

        //
        //  See if either weight is invalid.
        // A weight can be invalid when the character is not a Jamo.
        //
        if (*pWeight1 == CMP_INVALID_WEIGHT)
        {
            //
            //  The current character is not a Jamo.  Set the Weight to
            //  be CMP_INVALID_WEIGHT, so that the string comparision can
            //  restart within the loop of CompareString().
            //
            *pWeight1 = CMP_INVALID_WEIGHT;
            bRestart = 1;
            goto FindJamoDifferenceExit;
        }
        if (*pWeight2 == CMP_INVALID_WEIGHT)
        {
            //
            //  The current character is not a Jamo.  Set the Weight to
            //  be CMP_INVALID_WEIGHT, so that the string comparision can
            //  restart within the loop of CompareString().
            //
            *pWeight2 = CMP_INVALID_WEIGHT;
            bRestart = 1;
            goto FindJamoDifferenceExit;
        }
        if (*uw1 != *uw2)
        {
            //
            //  Found differences in Unicode weight.  We can stop the
            //  processing now.
            //
            goto FindJamoDifferenceExit;
        }

        //
        //  When we get here, we know that we have the same Unicode Weight.
        //  Check if we need to record the WhichJamo.
        //
        if ((*pState & STATE_JAMO_WEIGHT) &&
            ((oldHangulsFound1 > 0) || (oldHangulsFound2 > 0)))
        {
            if ((oldHangulsFound1 > 0) && (oldHangulsFound2 > 0))
            {
                *WhichJamo = (int)memcmp( JamoWeight1,
                                                    JamoWeight2,
                                                    sizeof(JamoWeight1) ) + 2;
            }
            else if (oldHangulsFound1 > 0)
            {
                *WhichJamo = CSTR_GREATER_THAN;
            }
            else
            {
                *WhichJamo = CSTR_LESS_THAN;
            }
            *pState &= ~STATE_JAMO_WEIGHT;
            oldHangulsFound1 = oldHangulsFound2 = 0;
        }
        (*ppString1)++; (*ctr1)--;
        (*ppString2)++; (*ctr2)--;

        if (AT_STRING_END(*ctr1, *ppString1, cchCount1) ||
            AT_STRING_END(*ctr2, *ppString2, cchCount2))
        {
            break;
        }
        *pWeight1 = *pWeight2 = CMP_INVALID_WEIGHT;
    }

    //
    //  If we drop out of the while loop because we reach the end of strings,
    //  decrement the pointers by one because loops in CompareString() will
    //  increase the pointers at the end of the loop.
    //
    //  If we drop out of the while loop because the goto's in it, we are
    //  already off by one.
    //
    if (AT_STRING_END(*ctr1, *ppString1, cchCount1))
    {
        (*ppString1)--; (*ctr1)++;
    }
    if (AT_STRING_END(*ctr2, *ppString2, cchCount2))
    {
        (*ppString2)--; (*ctr2)++;
    }

FindJamoDifferenceExit:
    *pLastJamo = *ppString1;
    return (bRestart);
}
