//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1996 - 2000.
//
//  File:       main.cxx
//
//  Contents:   Fixes a catalog after propagating from one machine to another.
//
//  History:    12 Jan 2000    dlee   Created from propdump
//
//--------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <proprec.hxx>
#include <propdesc.hxx>
#include "mmfile.hxx"

DECLARE_INFOLEVEL(ci)

unsigned const SixtyFourK = 1024 * 64;

//#define LOG

CCoTaskAllocator CoTaskAllocator; // exported data definition

void * CCoTaskAllocator::Allocate(ULONG cbSize)
{
    return CoTaskMemAlloc( cbSize );
}

void CCoTaskAllocator::Free(void *pv)
{
    CoTaskMemFree(pv);
}

void Usage()
{
    printf( "Usage: PropCi directory\n");
    printf( "       directory specifies the directory where the catalog exists.\n");
    printf( "\n" );
    printf( "This application converts file indexes in the catalog specified.\n" );
    exit( 1 );
}

WCHAR const * wcsistr( WCHAR const * wcsString, WCHAR const * wcsPattern )
{
    if ( (wcsPattern == 0) || (*wcsPattern == 0) )
        return wcsString;

    ULONG cwcPattern = wcslen(wcsPattern);

    while ( *wcsString != 0 )
    {
        while ( (*wcsString != 0) &&
                (towupper(*wcsString) != towupper(*wcsPattern)) )
            wcsString++;

        if ( 0 == *wcsString )
            return 0;

        if ( _wcsnicmp( wcsString, wcsPattern, cwcPattern) == 0 )
            return wcsString;

        wcsString++;
    }

    return 0;
} //wcsistr

void AppendBackslash( WCHAR *p )
{
    int x = wcslen( p );
    if ( 0 != x && L'\\' != p[x-1] )
    {
        p[x] = L'\\';
        p[x+1] = 0;
    }
} //AppendBackslash


void FindFieldRec(
    WCHAR const * pwcFile,
    PROPID        pid,
    CPropDesc &   prop,
    ULONG &       cTotal,
    ULONG &       cFixed,
    ULONG &       culFixed )
{
    cTotal = 0;
    cFixed = 0;
    culFixed = 0;

    HANDLE h = CreateFile( pwcFile,
                           GENERIC_READ,
                           FILE_SHARE_READ,
                           0,
                           OPEN_EXISTING,
                           0,
                           0 );

    if ( INVALID_HANDLE_VALUE == h )
    {
        printf( "Can't open file %ws. Error %u\n", pwcFile, GetLastError() );
        exit( 1 );
    }

    ULONG cbRead;
    static BYTE abTemp[SixtyFourK];
    if ( ReadFile( h,
                   abTemp,
                   sizeof abTemp ,
                   &cbRead,
                   0 ) )
    {
        // Loop through records

        BOOL fFound = FALSE;
        CPropDesc * pPropDesc = (CPropDesc *)abTemp;

        for ( unsigned i = 0;
              i < cbRead/(sizeof(CPropDesc) + sizeof(ULONG));
              i++, pPropDesc = (CPropDesc *)(((BYTE *)pPropDesc) + sizeof(CPropDesc) + sizeof(ULONG)) )
        {
            if ( 0 != pPropDesc->Pid() )
            {
                if ( pPropDesc->Pid() == pid )
                {
                    memcpy( &prop, pPropDesc, sizeof prop );
                    fFound = TRUE;
                }

                cTotal++;

                if ( pPropDesc->Offset() != -1 )
                {
                    cFixed++;
                    culFixed += (pPropDesc->Size() / sizeof(DWORD));
                }
            }
        }

        if ( !fFound )
        {
            printf( "can't find pid %#x\n", pid );
            exit( 1 );
        }
    }
    else
    {
        printf( "Can't read file %ws.  Error %u\n", pwcFile, GetLastError() );
        exit( 1 );
    }

#ifdef LOG
    printf( "pid %d, total %d, fixed %d\n", pid, cTotal, cFixed );

    printf( "  pid %d, vt %d, ostart %d, cbmax %d, ordinal %d, mask %d, rec %d, fmodify %d\n",
               prop.Pid(),
               prop.Type(),
               prop.Offset(),
               prop.Size(),
               prop.Ordinal(),
               prop.Mask(),
               prop.Record(),
               prop.Modifiable() );
#endif

    CloseHandle( h );
} //FindFieldRec

NTSTATUS CiNtOpenNoThrow(
    HANDLE & h,
    WCHAR const * pwcsPath,
    ACCESS_MASK DesiredAccess,
    ULONG ShareAccess,
    ULONG OpenOptions )
{
    h = INVALID_HANDLE_VALUE;
    UNICODE_STRING uScope;

    if ( !RtlDosPathNameToNtPathName_U( pwcsPath,
                                       &uScope,
                                        0,
                                        0 ) )
        return STATUS_INSUFFICIENT_RESOURCES;

    //
    // Open the file
    //

    OBJECT_ATTRIBUTES ObjectAttr;

    InitializeObjectAttributes( &ObjectAttr,          // Structure
                                &uScope,              // Name
                                OBJ_CASE_INSENSITIVE, // Attributes
                                0,                    // Root
                                0 );                  // Security

    IO_STATUS_BLOCK IoStatus;
    NTSTATUS Status = NtOpenFile( &h,                 // Handle
                                  DesiredAccess,      // Access
                                  &ObjectAttr,        // Object Attributes
                                  &IoStatus,          // I/O Status block
                                  ShareAccess,        // Shared access
                                  OpenOptions );      // Flags

    RtlFreeHeap( RtlProcessHeap(), 0, uScope.Buffer );

    return Status;
} //CiNtOpenNoThrow

FILEID GetFileId( WCHAR * pwcPath )
{
    if ( 2 == wcslen( pwcPath ) )
        wcscat( pwcPath, L"\\" );

    HANDLE h;
    NTSTATUS status = CiNtOpenNoThrow( h,
                                       pwcPath,
                                       FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                       0 );

    if ( FAILED( status ) )
    {
        printf( "Can't open file %ws to get fileid. Error %#x\n", pwcPath, status );
        return 0;
    }

    FILE_INTERNAL_INFORMATION fii;
    IO_STATUS_BLOCK IoStatus;
    status = NtQueryInformationFile( h,
                                     &IoStatus,
                                     &fii,
                                     sizeof fii,
                                     FileInternalInformation );

    NtClose( h );

    if ( NT_SUCCESS( status ) )
        status = IoStatus.Status;

    if ( NT_SUCCESS( status ) )
        return fii.IndexNumber.QuadPart;

    return 0;
} //GetFileId

void GetRecordInformation(
    WCHAR const * pwcFile,
    ULONG &       cRecPerBuf,
    ULONG &       cbRec )
{
    cRecPerBuf = 0;
    cbRec = 0;

    HANDLE h = CreateFile( pwcFile,
                           GENERIC_READ,
                           FILE_SHARE_READ,
                           0,
                           OPEN_EXISTING,
                           0,
                           0 );

    if ( INVALID_HANDLE_VALUE == h )
    {
        printf( "Can't open file %ws. Error %u\n", pwcFile, GetLastError() );
        exit( 1 );
    }

    ULONG cbRead;

    static BYTE abTemp[SixtyFourK];
    if ( ReadFile( h,
                   abTemp,
                   sizeof(abTemp),
                   &cbRead,
                   0 ) )
    {
        //
        // Determine record size
        //

        if ( abTemp[0] != 0 || abTemp[1] != 0 )
        {
            printf( "Record 0 not blank.  File corrupt!\n" );
            exit( 1 );
        }

        // First record should be all empty and only the first
        // record should be so. So counting all leading zeros gives us
        // the size of the record.
        for ( unsigned i = 0; i < cbRead && abTemp[i] == 0; i++ )
            continue;

        if ( i == cbRead )
        {
            printf( "First %uK segment all zero!.  File corrupt!\n", sizeof(abTemp)/1024 );
            exit( 1 );
        }

        switch ( *(USHORT *)&abTemp[i] )
        {
            case 0x5555:
            case 0xAAAA:
            case 0xBBBB:
            case 0xCCCC:
            case 0xDDDD:
                cbRec = i;
    
                if ( cbRec % 4 != 0 )
                {
                    printf( "Record size (%u bytes) not DWORD aligned!\n\n", cbRec );
                    exit( 1 );
                }
    
                cRecPerBuf = sizeof(abTemp) / cbRec;
                break;
    
            default:
                printf( "First non-zero byte is not a proper signature (%u)!\n", *(SHORT *)&abTemp[i] );
                exit( 1 );
        }
    }
    else
    {
        printf( "can't read from file %ws, error %d\n", pwcFile, GetLastError() );
        exit( 1 );
    }

#ifdef LOG
    printf( "cRecPerBuf %d, cbRec %d\n", cRecPerBuf, cbRec );
#endif

    CloseHandle( h );
} //GetRecordInformation

void PatchFileIDs(
    WCHAR const * pwcDir,
    WCHAR const * pwcPri,
    WCHAR const * pwcSec )
{
    //
    // First, read the property specifications for secondary wid, fileindex,
    // lastseen, and path.  The first 3 are in the primary and the last in
    // the secondary.
    //

    WCHAR awcPriProp[ MAX_PATH ];
    wcscpy( awcPriProp, pwcDir );
    wcscat( awcPriProp, L"CIP10000.001" );

    ULONG cPriTotal, cPriFixed, culPriFixed;
    CPropDesc SecWidPtrFieldRec;

    FindFieldRec( awcPriProp,
                  pidSecondaryStorage,
                  SecWidPtrFieldRec,
                  cPriTotal,
                  cPriFixed,
                  culPriFixed );

    CPropDesc FileIdFieldRec;
    FindFieldRec( awcPriProp,
                  pidFileIndex,
                  FileIdFieldRec,
                  cPriTotal,
                  cPriFixed,
                  culPriFixed );

    CPropDesc LastSeenFieldRec;
    FindFieldRec( awcPriProp,
                  pidLastSeenTime,
                  LastSeenFieldRec,
                  cPriTotal,
                  cPriFixed,
                  culPriFixed );

    WCHAR awcSecProp[ MAX_PATH ];
    wcscpy( awcSecProp, pwcDir );
    wcscat( awcSecProp, L"CIP20000.001" );

    ULONG cSecTotal, cSecFixed, culSecFixed;
    CPropDesc PathFieldRec;

    FindFieldRec( awcSecProp,
                  pidPath,
                  PathFieldRec,
                  cSecTotal,
                  cSecFixed,
                  culSecFixed );

    //
    // Get information about the number and size of records.
    //

    ULONG cPriRecPerBuf, cbPriRec;
    GetRecordInformation( pwcPri, cPriRecPerBuf, cbPriRec );

    ULONG cSecRecPerBuf, cbSecRec;
    GetRecordInformation( pwcSec, cSecRecPerBuf, cbSecRec );

    //
    // Walk the property store, get the secondary wid, read the path from
    // the secondary store, lookup the fileid, and write the fileid back
    // info the primary store.
    //

    CMMFile pri( pwcPri, TRUE );
    if ( !pri.Ok() )
    {
        printf( "can't map primary\n" );
        exit( 1 );
    }

    BYTE * pb = (BYTE *) pri.GetMapping();
    BYTE * pbBase = pb;

    CMMFile sec( pwcSec, TRUE );
    if ( !sec.Ok() )
    {
        printf( "can't map secondary\n" );
        exit( 1 );
    }

    BYTE * pbSecBase = (BYTE *) sec.GetMapping();

    FILETIME ftNow;
    GetSystemTimeAsFileTime( &ftNow );

#ifdef LOG
    printf( "pb %#x, size %d\n", pb, pri.FileSize() );
#endif

    ULONG iRec = 0, iRecTotal = 0;

    do
    {
#ifdef LOG
        printf( "record %d\n", iRecTotal );
#endif
        // If we're at the end of a 64k page, go on to the next one.

        if ( iRec == cPriRecPerBuf )
        {
            iRec = 0;
            pb += 65536;
        }

        COnDiskPropertyRecord * pRec = new( iRec, pb, cbPriRec/4 ) COnDiskPropertyRecord;

        if ( (BYTE *) pRec >= ( pbBase + pri.FileSize() ) )
            break;

        if ( pRec->IsTopLevel() )
        {
            PROPVARIANT var;
            static BYTE abExtra[MAX_PATH * 5];
            unsigned cbExtra = sizeof(abExtra);

            pRec->ReadFixed( SecWidPtrFieldRec.Ordinal(),
                             SecWidPtrFieldRec.Mask(),
                             SecWidPtrFieldRec.Offset(),
                             cPriTotal,
                             SecWidPtrFieldRec.Type(),
                             var,
                             abExtra,
                             &cbExtra,
                             *((PStorage *)0) );

            if ( VT_UI4 != var.vt )
            {
                printf( "failure: secondary wid wasn't a UI4\n" );
                exit( 1 );
            }

#ifdef LOG
            printf( "secondary wid %d\n", var.ulVal );
#endif

            ULONG iTargetSection = var.ulVal/cSecRecPerBuf;

            BYTE * pbSec = pbSecBase + ( iTargetSection * SixtyFourK );

            // Get the secondary store record

            COnDiskPropertyRecord * pRec2 = new( var.ulVal % cSecRecPerBuf, pbSec, cbSecRec/4 ) COnDiskPropertyRecord;

            // Read the path
            cbExtra = sizeof abExtra;

#ifdef LOG
            printf( "pRec2: %#x\n", pRec2 );
#endif

            var.vt = VT_EMPTY;

            if ( !pRec2->ReadVariable( PathFieldRec.Ordinal(),
                                       PathFieldRec.Mask(),
                                       culSecFixed,
                                       cSecTotal,
                                       cSecFixed,
                                       var,
                                       abExtra,
                                       &cbExtra ) )
            {
                // It's in an overflow record

                BOOL fOk;

                do
                {
                    //
                    // Check for existing overflow block.
                    //
    
                    WORKID widOverflow = pRec2->OverflowBlock();
    
                    if ( 0 == widOverflow )
                        break;

                    iTargetSection = widOverflow / cSecRecPerBuf;
                    pbSec = pbSecBase + ( iTargetSection * SixtyFourK );
                    pRec2 = new( widOverflow % cSecRecPerBuf, pbSec, cbSecRec/4 ) COnDiskPropertyRecord;

#ifdef LOG
                    printf( "overflow pRec2: %#x\n", pRec2 );
#endif
                    ULONG Ordinal = PathFieldRec.Ordinal() - cSecFixed;
                    DWORD Mask = (1 << ((Ordinal % 16) * 2) );
    
                    fOk = pRec2->ReadVariable( Ordinal,  // Ordinal (assuming 0 fixed)
                                               Mask,     // Mask (assuming 0 fixed)
                                               0,        // Fixed properties
                                               cSecTotal - cSecFixed,
                                               0,        // Count of fixed properties
                                               var,
                                               abExtra,
                                               &cbExtra );
                } while ( !fOk );
            }

            if ( VT_LPWSTR == var.vt )
            {
                // Get and set the fileindex for this volume

                FILEID fid = GetFileId( var.pwszVal );

                PROPVARIANT varId;
                varId.vt = VT_UI8;
                varId.hVal.QuadPart = fid;

                pRec->WriteFixed( FileIdFieldRec.Ordinal(),
                                  FileIdFieldRec.Mask(),
                                  FileIdFieldRec.Offset(),
                                  FileIdFieldRec.Type(),
                                  cPriTotal,
                                  varId );

                // Set the last seen time so we don't reindex the file

                PROPVARIANT varLS;
                varLS.vt = VT_FILETIME;
                varLS.filetime = ftNow;

                pRec->WriteFixed( LastSeenFieldRec.Ordinal(),
                                  LastSeenFieldRec.Mask(),
                                  LastSeenFieldRec.Offset(),
                                  LastSeenFieldRec.Type(),
                                  cPriTotal,
                                  varLS );

#ifdef LOG
                printf( "fileid %#I64x, %ws\n", fid, var.pwszVal );
#endif
            }
        }

        if ( pRec->IsValidType() )
        {
            iRec += pRec->CountRecords();
            iRecTotal += pRec->CountRecords();
        }
        else
        {
            iRec++;
            iRecTotal++;
        }
    } while ( TRUE );

    pri.Flush();
} //PatchFileIDs

void GetNtVolumeInformation(
    ULONG       ulVolumeId,
    ULONGLONG & ullVolumeCreationTime,
    ULONG &     ulVolumeSerialNumber,
    ULONGLONG & ullJournalId )
{
    ullVolumeCreationTime = 0;
    ulVolumeSerialNumber = 0;
    ullJournalId = 0;

    WCHAR wszVolumePath[] = L"\\\\.\\a:";
    wszVolumePath[4] = (WCHAR) ulVolumeId;

    FILE_FS_VOLUME_INFORMATION VolumeInfo;
    VolumeInfo.VolumeCreationTime.QuadPart = 0;
    VolumeInfo.VolumeSerialNumber = 0;

    HANDLE hVolume = CreateFile( wszVolumePath,
                                 GENERIC_READ | GENERIC_WRITE,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
                                 0,
                                 OPEN_EXISTING,
                                 0,
                                 0 );

    if ( INVALID_HANDLE_VALUE == hVolume )
    {
        printf( "failure: can't open volume %ws\n", wszVolumePath );
        exit( 1 );
    }

    IO_STATUS_BLOCK iosb;
    RtlZeroMemory( &VolumeInfo, sizeof VolumeInfo );
    NtQueryVolumeInformationFile( hVolume,
                                  &iosb,
                                  &VolumeInfo,
                                  sizeof(VolumeInfo),
                                  FileFsVolumeInformation );

    //
    // This call will only succeed on NTFS NT5 w/ USN Journal enabled.
    //

    USN_JOURNAL_DATA UsnJournalInfo;
    RtlZeroMemory( &UsnJournalInfo, sizeof UsnJournalInfo );
    NTSTATUS Status = NtFsControlFile( hVolume,
                                       0,
                                       0,
                                       0,
                                       &iosb,
                                       FSCTL_QUERY_USN_JOURNAL,
                                       0,
                                       0,
                                       &UsnJournalInfo,
                                       sizeof UsnJournalInfo );

    if ( NT_SUCCESS(Status) && NT_SUCCESS( iosb.Status ) )
    {
        // cool, it's a usn volume
    } 
    else if ( ( STATUS_JOURNAL_NOT_ACTIVE == Status ||
                STATUS_INVALID_DEVICE_STATE == Status ) )
    {
        //
        // Usn journal not created, create it
        //

        CREATE_USN_JOURNAL_DATA usnCreateData;
        usnCreateData.MaximumSize = 0x800000; // 8 meg
        usnCreateData.AllocationDelta = 0x100000; // 1 meg

        Status = NtFsControlFile( hVolume,
                                  0,
                                  0,
                                  0,
                                  &iosb,
                                  FSCTL_CREATE_USN_JOURNAL,
                                  &usnCreateData,
                                  sizeof usnCreateData,
                                  0,
                                  0 );
        if ( NT_SUCCESS( Status ) && NT_SUCCESS( iosb.Status ) )
        {
            Status = NtFsControlFile( hVolume,
                                      0,
                                      0,
                                      0,
                                      &iosb,
                                      FSCTL_QUERY_USN_JOURNAL,
                                      0,
                                      0,
                                      &UsnJournalInfo,
                                      sizeof UsnJournalInfo );

            if (! ( NT_SUCCESS(Status) && NT_SUCCESS( iosb.Status ) ) )
            {
                printf( "can't query usn vol info after creating it %#x\n", Status );
                exit( 1 );
            }
        }
        else
        {
            printf( "can't create usn journal %#x\n", Status );
            exit( 1 );
        }
    }
    else
    {
        printf( "can't get USN information, probably FAT: %#x\n", Status );
    }

    ullVolumeCreationTime = VolumeInfo.VolumeCreationTime.QuadPart;
    ulVolumeSerialNumber = VolumeInfo.VolumeSerialNumber;
    ullJournalId = UsnJournalInfo.UsnJournalID;

    printf( "      new volumecreationtime: %#I64x\n", ullVolumeCreationTime );
    printf( "      new volumeserialnumber: %#x\n", ulVolumeSerialNumber );
    printf( "      new journalid: %#I64x\n", ullJournalId );

    CloseHandle( hVolume );
} //GetNtVolumeInformation

void PatchScopeTable( WCHAR const * pwcDir )
{
    // Find out how many scopes are in the scope table

    ULONG cScopes = 0;

    {
        WCHAR awcControl[ MAX_PATH ];
        wcscpy( awcControl, pwcDir );
        wcscat( awcControl, L"cisp0000.000" );

        CMMFile Control( awcControl, FALSE );

        if ( 0 == Control.GetMapping() )
        {
            printf( "can't open file %ws\n", awcControl );
            exit( 1 );
        }
 
        cScopes = ((ULONG *) Control.GetMapping())[4];
    }

    WCHAR awcOne[ MAX_PATH ];
    wcscpy( awcOne, pwcDir );
    wcscat( awcOne, L"cisp0000.001" );
    
    // Loop through the scopes and patch 

    {
        printf( "  scope table: %ws has %d scopes\n", awcOne, cScopes );

        CMMFile One( awcOne, TRUE );
        if ( !One.Ok() )
        {
            printf( "can't map scope table\n" );
            exit( 1 );
        }
    
        BYTE * pb = (BYTE *) One.GetMapping();
    
        for ( ULONG i = 0; i < cScopes; i++ )
        {
            const LONGLONG eSigCiScopeTable = 0x5158515851585158i64;
            LONGLONG signature;
            memcpy( &signature, pb, sizeof signature );
            pb += sizeof signature;

            if ( 0 == signature )
                break;

            printf( "  scope record: \n" );

            if ( eSigCiScopeTable != signature )
            {
                printf( "invalid scope signature: %#I64x\n", signature );
                exit( 1 );
            }

            VOLUMEID volumeId;
            memcpy( &volumeId, pb, sizeof volumeId );
            printf( "    volumeId: %x\n", volumeId );
            pb += sizeof volumeId;

            ULONGLONG  ullNewVolumeCreationTime;
            ULONG      ulNewVolumeSerialNumber;
            ULONGLONG  ullNewJournalId;

            GetNtVolumeInformation( volumeId,
                                    ullNewVolumeCreationTime,
                                    ulNewVolumeSerialNumber,
                                    ullNewJournalId );

            ULONGLONG ullVolumeCreationTime;
            memcpy( &ullVolumeCreationTime, pb, sizeof ullVolumeCreationTime );
            printf( "    creation time: %#I64x\n", ullVolumeCreationTime );
            memcpy( pb,
                    &ullNewVolumeCreationTime,
                    sizeof ullNewVolumeCreationTime );
            pb += sizeof ullVolumeCreationTime;

            ULONG ulVolumeSerialNumber;
            memcpy( &ulVolumeSerialNumber, pb, sizeof ulVolumeSerialNumber );
            printf( "    serial number: %x\n", ulVolumeSerialNumber );
            memcpy( pb,
                    &ulNewVolumeSerialNumber,
                    sizeof ulNewVolumeSerialNumber );
            pb += sizeof ulVolumeSerialNumber;
    
            if ( CI_VOLID_USN_NOT_ENABLED == volumeId )
            {
                FILETIME ft;
                memcpy( &ft, pb, sizeof ft );
                printf( "    filetime: %#I64x\n", ft );
                pb += sizeof ft;
            }
            else
            {
                USN usn;
                memcpy( &usn, pb, sizeof usn );
                printf( "    usn: %#I64x\n", usn );
                USN usnNewMax = 0;
                memcpy( pb,
                        &usnNewMax,
                        sizeof usnNewMax );
                pb += sizeof usn;
    
                ULONGLONG JournalId;
                memcpy( &JournalId, pb, sizeof JournalId );
                printf( "    JournalId: %#I64x\n", JournalId );
                memcpy( pb,
                        &ullNewJournalId,
                        sizeof ullNewJournalId );
                pb += sizeof JournalId;
            }
    
            ULONG cwcPath;
            memcpy( &cwcPath, pb, sizeof cwcPath );
            pb += sizeof cwcPath;
    
            WCHAR awcPath[ MAX_PATH ];
            memcpy( awcPath, pb, cwcPath * sizeof WCHAR );
            printf( "    path: %ws\n", awcPath );
            pb += ( cwcPath * sizeof WCHAR );
        }

        One.Flush();
    }

    WCHAR awcTwo[ MAX_PATH ];
    wcscpy( awcTwo, pwcDir );
    wcscat( awcTwo, L"cisp0000.002" );

    BOOL f = CopyFile( awcOne, awcTwo, FALSE );

    if ( !f )
    {
        printf( "can't copy scope list, error %d\n", GetLastError() );
        exit( 1 );
    }
} //PatchScopeDir

extern "C" int __cdecl wmain( int argc, WCHAR * argv[] )
{
    if ( 2 != argc )
        Usage();

    //
    // The lone argument is the catalog directory, with or without catalog.wci
    //

    WCHAR awcDir[ MAX_PATH ];
    wcscpy( awcDir, argv[1] );

    AppendBackslash( awcDir );

    if ( 0 == wcsistr( awcDir, L"catalog.wci" ) )
        wcscat( awcDir, L"catalog.wci\\" );

    // Find and validate the primary and secondary stores exist

    WCHAR awcPri[MAX_PATH];
    wcscpy( awcPri, awcDir );
    wcscat( awcPri, L"*.ps1" );

    WIN32_FIND_DATA findData;
    HANDLE h = FindFirstFile( awcPri, &findData );
    if ( INVALID_HANDLE_VALUE == h )
    {
        printf( "can't find primary *.ps1 store\n" );
        exit( 1 );
    }
    FindClose( h );
    wcscpy( awcPri, awcDir );
    wcscat( awcPri, findData.cFileName );

    WCHAR awcSec[MAX_PATH];
    wcscpy( awcSec, awcDir );
    wcscat( awcSec, L"*.ps2" );

    h = FindFirstFile( awcSec, &findData );
    if ( INVALID_HANDLE_VALUE == h )
    {
        printf( "can't find secondary *.ps2 store\n" );
        exit( 1 );
    }
    FindClose( h );
    wcscpy( awcSec, awcDir );
    wcscat( awcSec, findData.cFileName );

    //
    // Do the core work here -- patch the file IDs in the primary store.
    // Also whack the last seen times so the files aren't refiltered in
    // case they were copied to the target machine after the catalog was
    // snapped.
    //

    printf( "patching file IDs\n" );
    PatchFileIDs( awcDir, awcPri, awcSec );

    //
    // Patch the scope table so cisvc doesn't think it's a different volume.
    //

    printf( "patching the scope table\n" );
    PatchScopeTable( awcDir );

    //
    // Delete the old fileid hash table since the fileids are wrong.
    // It'll get recreated automatically when the catalog is opened.
    //

    printf( "deleting the fileid hash map\n" );
    WCHAR awcHashMap[ MAX_PATH ];
    wcscpy( awcHashMap, awcDir );
    wcscat( awcHashMap, L"cicat.fid" );

    DeleteFile( awcHashMap );

    printf( "catalog successfully converted\n" );

    return 0;
} //wmain