/*++

Copyright (c) 1993 Microsoft Corporation

Module Name:

    decomp.c

Abstract:

    File decompression support routines.

Author:

    Ted Miller (tedm) 1-Feb-1995

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop
#include <pshpack1.h>
struct LZINFO;
typedef struct LZINFO *PLZINFO;
#include <lz_header.h>
#include <poppack.h>


typedef struct _SFD_INFO {
    unsigned FileCount;
    PCTSTR TargetFile;
    BOOL GotTimestamp;
    FILETIME FileTime;
} SFD_INFO, *PSFD_INFO;


UINT
pGetCompressInfoCB(
    IN PVOID Context,
    IN UINT  Notification,
    IN UINT_PTR  Param1,
    IN UINT_PTR  Param2
    );

UINT
pSingleFileDecompCB(
    IN PVOID Context,
    IN UINT  Notification,
    IN UINT_PTR  Param1,
    IN UINT_PTR  Param2
    );

//
// OldMyMalloc/OldMyFree used by SetupGetFileCompressionInfo
// for app-compat
//
PVOID
OldMyMalloc(
    IN DWORD Size
    );

VOID
OldMyFree(
    IN PVOID Block
    );

PTSTR
SetupGenerateCompressedName(
    IN PCTSTR Filename
    )

/*++

Routine Description:

    Given a filename, generate the compressed form of the name.
    The compressed form is generated as follows:

    Look backwards for a dot.  If there is no dot, append "._" to the name.
    If there is a dot followed by 0, 1, or 2 charcaters, append "_".
    Otherwise there is a 3-character or greater extension and we replace
    the last character with "_".

Arguments:

    Filename - supplies filename whose compressed form is desired.

Return Value:

    Pointer to buffer containing nul-terminated compressed-form filename.
    The caller must free this buffer via MyFree().

--*/

{
    PTSTR CompressedName,p,q;
    UINT u;

    //
    // The maximum length of the compressed filename is the length of the
    // original name plus 2 (for ._).
    //
    if(CompressedName = MyMalloc((lstrlen(Filename)+3)*sizeof(TCHAR))) {

        lstrcpy(CompressedName,Filename);

        p = _tcsrchr(CompressedName,TEXT('.'));
        q = _tcsrchr(CompressedName,TEXT('\\'));
        if(q < p) {

            //
            // If there are 0, 1, or 2 characters after the dot, just append
            // the underscore.  p points to the dot so include that in the length.
            //
            u = lstrlen(p);
            if(u < 4) {
                lstrcat(CompressedName,TEXT("_"));
            } else {
                //
                // There are at least 3 characters in the extension.
                // Replace the final one with an underscore.
                //
                p[u-1] = TEXT('_');
            }
        } else {
            //
            // No dot, just add ._.
            //
            lstrcat(CompressedName,TEXT("._"));
        }
    }

    return(CompressedName);
}


DWORD
pSetupAttemptLocate(
    IN  PCTSTR           FileName,
    OUT PBOOL            Found,
    OUT PWIN32_FIND_DATA FindData
    )

/*++

Routine Description:

    Attempt to locate a source file via FindFirstFile().

    Errors of the 'file not found' type are not considered errors
    and result in NO_ERROR. Any non-NO_ERROR return indicates that
    we could not determine whether the file is present or not
    because of some hardware or system problem, etc.

Arguments:

    FileName - supplies filename of the file to be located.

    Found - receives a value indicating whether the file was found.
        This value is only valid when the function returns NO_ERROR.

    FindData - if found, returns win32 find data for the file.

Return Value:

    Win32 error code indicating the outcome. If NO_ERROR, check
    the Found return value to see whether the file was found.

--*/

{
    DWORD d;

    if(*Found = FileExists(FileName,FindData)) {
        d = NO_ERROR;
    } else {
        //
        // We didn't find the file. See whether that was because
        // the file wasn't there or because some other error occured.
        //
        d = GetLastError();

        if((d == ERROR_NO_MORE_FILES)
        || (d == ERROR_FILE_NOT_FOUND)
        || (d == ERROR_PATH_NOT_FOUND)
        || (d == ERROR_BAD_NETPATH))
        {
            d = NO_ERROR;
        }
    }

    return(d);
}


DWORD
SetupDetermineSourceFileName(
    IN  PCTSTR            FileName,
    OUT PBOOL             UsedCompressedName,
    OUT PTSTR            *FileNameLocated,
    OUT PWIN32_FIND_DATA  FindData
    )

/*++

Routine Description:

    Attempt to locate a source file whose name can be compressed
    or uncompressed.

    The order of attempt is

    - the name as given (should be the uncompressed name)
    - the compressed form, using _ as the compression char
    - the compressed form, using $ as the compression char

Arguments:

    FileName - supplies filename of the file to be located.

    UsedCompressedName - receives a boolean indicating whether
        the filename we located seems to indicate that the file
        is compressed.

    FileNameLocated - receives a pointer to the filename actually
        located. The caller must free with MyFree().

    FindData - if found, returns win32 find data for the file.

Return Value:

    Win32 error code indicating the outcome.

    ERROR_FILE_NOT_FOUND - normal code indicating everything is ok
        but we can't find the file

    NO_ERROR - file was located; check UsedCompressedName and FileNameOpened.

    Others - something is wrong with the hardware or system.

--*/

{
    DWORD d;
    PTSTR TryName;
    BOOL Found;


    TryName = DuplicateString(FileName);
    if(!TryName) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    *UsedCompressedName = FALSE;
    *FileNameLocated = TryName;

    d = pSetupAttemptLocate(TryName,&Found,FindData);
    if(d != NO_ERROR) {
        MyFree(TryName);
        *FileNameLocated = NULL;
        return(d);
    }

    if(Found) {
        return(NO_ERROR);
    }

    MyFree(TryName);
    *UsedCompressedName = TRUE;
    *FileNameLocated = NULL;

    TryName = SetupGenerateCompressedName(FileName);
    if(!TryName) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    *FileNameLocated = TryName;

    d = pSetupAttemptLocate(TryName,&Found,FindData);
    if(d != NO_ERROR) {
        MyFree(TryName);
        *FileNameLocated = NULL;
        return(d);
    }

    if(Found) {
        return(NO_ERROR);
    }

    MYASSERT(TryName[lstrlen(TryName)-1] == TEXT('_'));
    TryName[lstrlen(TryName)-1] = TEXT('$');

    d = pSetupAttemptLocate(TryName,&Found,FindData);

    if((d != NO_ERROR) || !Found) {
        *FileNameLocated = NULL;
        MyFree(TryName);
    }

    return(Found ? NO_ERROR : ERROR_FILE_NOT_FOUND);
}

BOOL
pSetupDoesFileMatch(
    IN  PCTSTR            InputName,
    IN  PCTSTR            CompareName,
    OUT PBOOL             UsedCompressedName,
    OUT PTSTR            *FileNameLocated
    )

/*++

Routine Description:

    determine if the specified input file matches the
    name to compare it with.  We try the undecorated name
    as well as the compressed versions of the file name.

    The order of attempt is

    - the name as given (should be the uncompressed name)
    - the compressed form, using _ as the compression char
    - the compressed form, using $ as the compression char

Arguments:

    FileName - supplies filename we're looking at.

    CompareName -supplies the filename we're comparing against

    UsedCompressedName - receives a boolean indicating whether
        the filename we located seems to indicate that the file
        is compressed.

    FileNameLocated - receives a pointer to the filename actually
        located. The caller must free with MyFree().


Return Value:

    Win32 error code indicating the outcome.

    ERROR_FILE_NOT_FOUND - normal code indicating everything is ok
        but we can't find the file

    NO_ERROR - file was located; check UsedCompressedName and FileNameOpened.

    Others - something is wrong with the hardware or system.

--*/

{
    DWORD d;
    PTSTR TryName,TargetName,src,dst;
    BOOL Found;


    TryName = DuplicateString(InputName);
    if(!TryName) {
        return(FALSE);
    }

    TargetName = DuplicateString(CompareName);
    if(!TargetName) {
        MyFree(TryName);
        return(FALSE);
    }

    dst = _tcsrchr(TryName,TEXT('.'));
    if (dst) {
        *dst = 0;
    }
    src = _tcsrchr(TargetName,TEXT('.'));
    if (src) {
        *src = 0;
    }

    if (lstrcmpi(TargetName,TryName)) {
        // the "surnames" do not match, so none of the other comparisons will work.
        MyFree(TryName);
        MyFree(TargetName);
        return(FALSE);
    }

    if (dst) {
        *dst = TEXT('.');
    }

    if (src) {
        *src = TEXT('.');
    }

    *UsedCompressedName = FALSE;
    *FileNameLocated = TryName;

    if (!lstrcmpi(TryName,TargetName)) {
        // we matched
        MyFree(TargetName);
        return(TRUE);
    }

    MyFree(TryName);
    *UsedCompressedName = TRUE;

    TryName = SetupGenerateCompressedName(TargetName);
    if(!TryName) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    *FileNameLocated = TryName;

    if (!lstrcmpi(TryName,InputName)) {
        // we matched
        MyFree(TargetName);
        return(TRUE);
    }

    MYASSERT(TryName[lstrlen(TryName)-1] == TEXT('_'));
    TryName[lstrlen(TryName)-1] = TEXT('$');

    if (!lstrcmpi(TryName,InputName)) {
        // we matched
        MyFree(TargetName);
        return(TRUE);
    }

    //
    // no match
    //
    MyFree(TargetName);
    MyFree(TryName);

    return(FALSE);
}



DWORD
pSetupDecompressWinLzFile(
    IN PTSTR SourceFileName,
    IN PTSTR TargetFileName
    )

/*++

Routine Description:

    Determine whether a file is compressed, and retreive additional
    information about it.

Arguments:

    SourceFileName - supplies filename of the file to be checked.
        This filename is used as a base; if not found then we look
        for the 2 compressed forms (ie, foo.ex_, foo.ex$) as well.

    ActualSourceFileName - receives a pointer to the filename
        that was actually located. Caller can free with MyFree().
        Valid only if the return code from this routine is NO_ERROR.

    SourceFileSize - receives the size of the located file in its
        current (ie, compressed) form. Valid only if this routine
        returns NO_ERROR.

    TargetFileSize - receives the uncompressed size of the file.
        If the file is not compressed this will be the same as
        SourceFileSize. Valid only if this routine returns NO_ERROR.

    CompressionType - receives a value indicating the compression type.
        Valid only if this routine returns NO_ERROR.

Return Value:

    Win32 error code indicating the outcome.

    ERROR_FILE_NOT_FOUND - normal code indicating everything is ok
        but we can't find the file

    NO_ERROR - file was located and output params are filled in.

    Others - something is wrong with the hardware or system.

--*/

{
    INT hSrc,hDst;
    OFSTRUCT ofSrc,ofDst;
    LONG l;
    DWORD d;
    FILETIME CreateTime,AccessTime,WriteTime;

    //
    // Get the timestamp of the source.
    //
    d = GetSetFileTimestamp(
            SourceFileName,
            &CreateTime,
            &AccessTime,
            &WriteTime,
            FALSE
            );

    if(d != NO_ERROR) {
        return(d);
    }

    hSrc = LZOpenFile(SourceFileName,&ofSrc,OF_READ|OF_SHARE_DENY_WRITE);
    if(hSrc >= 0) {

        hDst = LZOpenFile(TargetFileName,&ofSrc,OF_CREATE|OF_WRITE|OF_SHARE_EXCLUSIVE);
        if(hDst >= 0) {

            l = LZCopy(hSrc,hDst);
            if(l >= 0) {
                l = 0;

                //
                // Set the timestamp of the target. The file is already there
                // so just ignore errors.
                //
                GetSetFileTimestamp(
                    TargetFileName,
                    &CreateTime,
                    &AccessTime,
                    &WriteTime,
                    TRUE
                    );
            }

            LZClose(hDst);

        } else {
            l = hDst;
        }

        LZClose(hSrc);

    } else {
        l = hSrc;
    }

    //
    // lz error to win32 error
    //
    switch(l) {

    case 0:
        return(NO_ERROR);

    case LZERROR_BADINHANDLE:
    case LZERROR_READ:
        return(ERROR_READ_FAULT);

    case LZERROR_BADOUTHANDLE:
    case LZERROR_WRITE:
        return(ERROR_WRITE_FAULT);

    case LZERROR_GLOBALLOC:
    case LZERROR_GLOBLOCK:
        return(ERROR_NOT_ENOUGH_MEMORY);

    case LZERROR_BADVALUE:
    case LZERROR_UNKNOWNALG:
        return(ERROR_INVALID_DATA);

    default:
        return(ERROR_INVALID_FUNCTION);
    }
}


DWORD
SetupInternalGetFileCompressionInfo(
    IN  PCTSTR            SourceFileName,
    OUT PTSTR            *ActualSourceFileName,
    OUT PWIN32_FIND_DATA  SourceFindData,
    OUT PDWORD            TargetFileSize,
    OUT PUINT             CompressionType
    )

/*++

Routine Description:

    Determine whether a file is compressed, and retreive additional
    information about it.

Arguments:

    SourceFileName - supplies filename of the file to be checked.
        This filename is used as a base; if not found then we look
        for the 2 compressed forms (ie, foo.ex_, foo.ex$) as well.

    ActualSourceFileName - receives a pointer to the filename
        that was actually located. Caller can free with MyFree().
        Valid only if the return code from this routine is NO_ERROR.

    SourceFindData - receives win32 find data for the located file in its
        current (ie, compressed) form. Valid only if this routine
        returns NO_ERROR.

    TargetFileSize - receives the uncompressed size of the file.
        If the file is not compressed this will be the same as
        SourceFileSize. Valid only if this routine returns NO_ERROR.

    CompressionType - receives a value indicating the compression type.
        Valid only if this routine returns NO_ERROR.

Return Value:

    Win32 error code indicating the outcome.

    ERROR_FILE_NOT_FOUND - normal code indicating everything is ok
        but we can't find the file

    NO_ERROR - file was located and output params are filled in.

    Others - something is wrong with the hardware or system.

--*/

{
    DWORD d;
    DWORD caberr = NO_ERROR;
    BOOL b;
    HANDLE hFile,hMapping;
    DWORD size;
    FH UNALIGNED *LZHeader;

    d = SetupDetermineSourceFileName(
            SourceFileName,
            &b,
            ActualSourceFileName,
            SourceFindData
            );

    if(d != NO_ERROR) {
        return(d);
    }

    //
    // If the file is 0-length it isn't compressed;
    // trying to map it in below will fail in this case.
    //
    if(SourceFindData->nFileSizeLow) {

        //
        // See if it's a diamond file.
        //
        d = DiamondProcessCabinet(
                *ActualSourceFileName,
                0,
                pGetCompressInfoCB,
                &size,
                TRUE
                );

        if(d == NO_ERROR) {

            *TargetFileSize = size;
            *CompressionType = FILE_COMPRESSION_MSZIP;
            return(NO_ERROR);
        } else if (d != ERROR_INVALID_DATA) {
            //
            // general problems not specific to the file format itself
            // however if this might be a plain file, ignore it
            //
            size_t len1 = lstrlen(SourceFileName);
            size_t len2 = lstrlen(*ActualSourceFileName);
            TCHAR c1 = *CharPrev(SourceFileName,SourceFileName+len1);
            TCHAR c2 = *CharPrev(*ActualSourceFileName,*ActualSourceFileName+len2);
            if(((c2 == TEXT('_')) || (c2 == TEXT('$'))) && ((len1 != len2) || (c1 != c2))) {
                //
                // ActualSourceFileName ends in '_' or '$' and is a modification of SourceFileName
                // don't let us try and parse this as a plain file
                //
                caberr = d;
            }
        }

        //
        // See if it's a WINLZ file.
        //
        d = pSetupOpenAndMapFileForRead(
                *ActualSourceFileName,
                &SourceFindData->nFileSizeLow,
                &hFile,
                &hMapping,
                (PVOID *)&LZHeader
                );

        if(d != NO_ERROR) {
            MyFree(*ActualSourceFileName);
            return(d);
        }

        b = FALSE;
        try {
            if((SourceFindData->nFileSizeLow >= HEADER_LEN)
            && !memcmp(LZHeader->rgbyteMagic,COMP_SIG,COMP_SIG_LEN)
            && RecognizeCompAlg(LZHeader->byteAlgorithm))
            {
                *TargetFileSize = LZHeader->cbulUncompSize;
                b = TRUE;
            }
        } except(EXCEPTION_EXECUTE_HANDLER) {
            ;
        }

        pSetupUnmapAndCloseFile(hFile,hMapping,LZHeader);

        if(b) {
            *CompressionType = FILE_COMPRESSION_WINLZA;
            return(NO_ERROR);
        }

        if(caberr) {
            //
            // looks like a compressed file and DiamondProcessCabinet
            // returned a suspicious error
            //
            return(caberr);
        }
    }

    //
    // File is not compressed.
    //
    *CompressionType = FILE_COMPRESSION_NONE;
    *TargetFileSize = SourceFindData->nFileSizeLow;
    return(NO_ERROR);
}


UINT
pGetCompressInfoCB(
    IN PVOID Context,
    IN UINT  Notification,
    IN UINT_PTR  Param1,
    IN UINT_PTR  Param2
    )
{
    PFILE_IN_CABINET_INFO FileInfo;
    DWORD rc;

    switch(Notification) {

    case SPFILENOTIFY_CABINETINFO:
        //
        // We don't do anything with this.
        //
        rc = NO_ERROR;
        break;

    case SPFILENOTIFY_FILEINCABINET:
        //
        // New file within a cabinet.
        //
        // We don't ever want to copy the file. Save size info
        // and abort.
        //
        FileInfo = (PFILE_IN_CABINET_INFO)Param1;

        *((PDWORD)Context) = FileInfo->FileSize;

        FileInfo->Win32Error = NO_ERROR;
        rc = FILEOP_ABORT;
        SetLastError(NO_ERROR);
        break;

    //case SPFILENOTIFY_FILEEXTRACTED:
    //case SPFILENOTIFY_NEEDNEWCABINET:
    default:
        //
        // We should never get these.
        //
        MYASSERT(0);
        rc = ERROR_INVALID_FUNCTION;
        break;
    }

    return(rc);
}


#ifdef UNICODE
//
// ANSI version
//
DWORD
SetupGetFileCompressionInfoA(
    IN  PCSTR   SourceFileName,
    OUT PSTR   *ActualSourceFileName,
    OUT PDWORD  SourceFileSize,
    OUT PDWORD  TargetFileSize,
    OUT PUINT   CompressionType
    )
{
    WIN32_FIND_DATA FindData;
    DWORD d;
    PCWSTR source;
    PWSTR actualsource = NULL;
    PSTR actualsourceansi = NULL;
    PSTR la_actualsourceansi = NULL;
    DWORD targetsize;
    UINT type;

    d = pSetupCaptureAndConvertAnsiArg(SourceFileName,&source);
    if(d != NO_ERROR) {
        return(d);
    }

    d = SetupInternalGetFileCompressionInfo(source,&actualsource,&FindData,&targetsize,&type);

    if(d == NO_ERROR) {

        MYASSERT(actualsource);

        if((actualsourceansi = pSetupUnicodeToAnsi(actualsource))==NULL) {
            d = ERROR_NOT_ENOUGH_MEMORY;
            goto clean0;
        }
        if((la_actualsourceansi = (PSTR)OldMyMalloc(1+strlen(actualsourceansi)))==NULL) {
            d = ERROR_NOT_ENOUGH_MEMORY;
            goto clean0;
        }
        strcpy(la_actualsourceansi,actualsourceansi);
        try {
            *SourceFileSize = FindData.nFileSizeLow;
            *ActualSourceFileName = la_actualsourceansi; // free using LocalFree
            *TargetFileSize = targetsize;
            *CompressionType = type;
            la_actualsourceansi = NULL;
        } except(EXCEPTION_EXECUTE_HANDLER) {
            d = ERROR_INVALID_PARAMETER;
        }
    }

clean0:
    if(actualsource) {
        MyFree(actualsource);
    }
    if(actualsourceansi) {
        MyFree(actualsourceansi);
    }
    if(la_actualsourceansi) {
        OldMyFree(la_actualsourceansi);
    }

    MyFree(source);

    return(d);
}
#else
//
// Unicode stub
//
DWORD
SetupGetFileCompressionInfoW(
    IN  PCWSTR  SourceFileName,
    OUT PWSTR  *ActualSourceFileName,
    OUT PDWORD  SourceFileSize,
    OUT PDWORD  TargetFileSize,
    OUT PUINT   CompressionType
    )
{
    UNREFERENCED_PARAMETER(SourceFileName);
    UNREFERENCED_PARAMETER(ActualSourceFileName);
    UNREFERENCED_PARAMETER(SourceFileSize);
    UNREFERENCED_PARAMETER(TargetFileSize);
    UNREFERENCED_PARAMETER(CompressionType);
    return(ERROR_CALL_NOT_IMPLEMENTED);
}
#endif

DWORD
SetupGetFileCompressionInfo(
    IN  PCTSTR  SourceFileName,
    OUT PTSTR  *ActualSourceFileName,
    OUT PDWORD  SourceFileSize,
    OUT PDWORD  TargetFileSize,
    OUT PUINT   CompressionType
    )

/*++

Routine Description:

    Here for App-Compat only
    Replaced by SetupGetFileCompressionInfoEx

    Return pointer is allocated by OldMyMalloc,
    it can be freed by (*cough*) OldMyFree (exported as MyFree)
    This is because there are apps out there that use this,
    and run-time link to setupapi!MyFree to release memory

    !!!! DO NOT USE THIS API !!!!

Arguments:

    SourceFileName - supplies filename of the file to be checked.
        This filename is used as a base; if not found then we look
        for the 2 compressed forms (ie, foo.ex_, foo.ex$) as well.

    ActualSourceFileName - receives a pointer to the filename
        that was actually located. Caller can free with exported MyFree().
        Valid only if the return code from this routine is NO_ERROR.

    SourceFileSize - receives the size of the located file in its
        current (ie, compressed) form. Valid only if this routine
        returns NO_ERROR.

    TargetFileSize - receives the uncompressed size of the file.
        If the file is not compressed this will be the same as
        SourceFileSize. Valid only if this routine returns NO_ERROR.

    CompressionType - receives a value indicating the compression type.
        Valid only if this routine returns NO_ERROR.

Return Value:

    Win32 error code indicating the outcome.

    ERROR_FILE_NOT_FOUND - normal code indicating everything is ok
        but we can't find the file

    NO_ERROR - file was located and output params are filled in.

    Others - something is wrong with the hardware or system.

--*/

{
    WIN32_FIND_DATA FindData;
    DWORD d;
    PCTSTR source;
    PTSTR actualsource = NULL;
    PTSTR la_actualsource = NULL;
    DWORD targetsize;
    UINT type;

    d = CaptureStringArg(SourceFileName,&source);
    if(d != NO_ERROR) {
        return(d);
    }

    d = SetupInternalGetFileCompressionInfo(source,&actualsource,&FindData,&targetsize,&type);

    if(d == NO_ERROR) {
        MYASSERT(actualsource);
        la_actualsource = (PTSTR)OldMyMalloc(sizeof(TCHAR)*(1+lstrlen(actualsource)));
        if (la_actualsource == NULL) {
            MyFree(actualsource);
            MyFree(source);
            return ERROR_NOT_ENOUGH_MEMORY;
        }
        lstrcpy(la_actualsource,actualsource);
        try {
            *SourceFileSize = FindData.nFileSizeLow;
            *ActualSourceFileName = la_actualsource; // free using LocalFree
            *TargetFileSize = targetsize;
            *CompressionType = type;
        } except(EXCEPTION_EXECUTE_HANDLER) {
            d = ERROR_INVALID_PARAMETER;
        }
        if(d != NO_ERROR) {
            OldMyFree(la_actualsource);
        }
        MyFree(actualsource);
    }

    MyFree(source);

    return(d);
}


#ifdef UNICODE
//
// ANSI version
//
BOOL
WINAPI
SetupGetFileCompressionInfoExA(
    IN  PCSTR   SourceFileName,
    IN  PSTR    ActualSourceFileNameBuffer,
    IN  DWORD   ActualSourceFileNameBufferLen,
    OUT PDWORD  RequiredBufferLen,              OPTIONAL
    OUT PDWORD  SourceFileSize,
    OUT PDWORD  TargetFileSize,
    OUT PUINT   CompressionType
    )
{
    WIN32_FIND_DATA FindData;
    DWORD d;
    PCWSTR source;
    PWSTR actualsource = NULL;
    PSTR actualsourceansi = NULL;
    DWORD targetsize;
    DWORD reqbufsize;
    UINT type;

    d = pSetupCaptureAndConvertAnsiArg(SourceFileName,&source);
    if(d != NO_ERROR) {
        SetLastError(d);
        return (d==NO_ERROR);
    }

    d = SetupInternalGetFileCompressionInfo(source,&actualsource,&FindData,&targetsize,&type);

    if(d == NO_ERROR) {
        MYASSERT(actualsource);
        actualsourceansi = pSetupUnicodeToAnsi(actualsource);
        if(actualsourceansi != NULL) {
            try {
                reqbufsize = strlen(actualsourceansi)+1;
                if (RequiredBufferLen) {
                    *RequiredBufferLen = reqbufsize;
                }
                if(ActualSourceFileNameBuffer) {
                    if((ActualSourceFileNameBufferLen < reqbufsize)) {
                        d = ERROR_INSUFFICIENT_BUFFER;
                    } else {
                        strcpy(ActualSourceFileNameBuffer,actualsourceansi);
                    }
                } else if(ActualSourceFileNameBufferLen) {
                    d = ERROR_INVALID_USER_BUFFER;
                }
                *SourceFileSize = FindData.nFileSizeLow;
                *TargetFileSize = targetsize;
                *CompressionType = type;
            } except(EXCEPTION_EXECUTE_HANDLER) {
                d = ERROR_INVALID_PARAMETER;
            }
        } else {
            d = ERROR_NOT_ENOUGH_MEMORY;
        }
    }

    if(actualsource) {
        MyFree(actualsource);
    }
    if(actualsourceansi) {
        MyFree(actualsourceansi);
    }
    MyFree(source);

    SetLastError(d);
    return (d==NO_ERROR);
}
#else
//
// Unicode stub
//
BOOL
WINAPI
SetupGetFileCompressionInfoExW(
    IN  PCWSTR  SourceFileName,
    IN  PWSTR   ActualSourceFileNameBuffer,
    IN  DWORD   ActualSourceFileNameBufferLen,
    OUT PDWORD  RequiredBufferLen,              OPTIONAL
    OUT PDWORD  SourceFileSize,
    OUT PDWORD  TargetFileSize,
    OUT PUINT   CompressionType
    )
{
    UNREFERENCED_PARAMETER(SourceFileName);
    UNREFERENCED_PARAMETER(ActualSourceFileNameBuffer);
    UNREFERENCED_PARAMETER(ActualSourceFileNameBufferLen);
    UNREFERENCED_PARAMETER(RequiredBufferLen);
    UNREFERENCED_PARAMETER(SourceFileSize);
    UNREFERENCED_PARAMETER(TargetFileSize);
    UNREFERENCED_PARAMETER(CompressionType);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return FALSE;
}
#endif

BOOL
WINAPI
SetupGetFileCompressionInfoEx(
    IN  PCTSTR  SourceFileName,
    IN  PTSTR   ActualSourceFileNameBuffer,
    IN  DWORD   ActualSourceFileNameBufferLen,
    OUT PDWORD  RequiredBufferLen,              OPTIONAL
    OUT PDWORD  SourceFileSize,
    OUT PDWORD  TargetFileSize,
    OUT PUINT   CompressionType
    )

/*++

Routine Description:

    Determine whether a file is compressed, and retreive additional
    information about it.
    This is the replacement for the very broken SetupGetFileCompressionInfo
    Caller must pass in a buffer
    If the buffer is NULL, return size
    and all other parameters filled out (unless some other error occurred)
    note however, you would typically call this with a buffer size of MAX_PATH.

Arguments:

    SourceFileName - supplies filename of the file to be checked.
        This filename is used as a base; if not found then we look
        for the 2 compressed forms (ie, foo.ex_, foo.ex$) as well.

    ActualSourceFileNameBuffer - if not NULL, receives actual filename
        Valid only if the return code from this routine is NO_ERROR.

    ActualSourceFileNameBufferLen - pass in length (characters) of
        ActualSourceFileNameBuffer. must be 0 if ActualSourceFileNameBuffer
        is NULL.

    RequiredBufferLen - if not NULL, filled with length of actual filename
        including terminating NULL.
        Valid only if the return code from this routine is NO_ERROR or ERROR_INSUFFICIENT_BUFFER.

    SourceFileSize - receives the size of the located file in its
        current (ie, compressed) form. Valid only if this routine
        returns NO_ERROR or  ERROR_INSUFFICIENT_BUFFER.

    TargetFileSize - receives the uncompressed size of the file.
        If the file is not compressed this will be the same as
        SourceFileSize. Valid only if this routine returns NO_ERROR or  ERROR_INSUFFICIENT_BUFFER.

    CompressionType - receives a value indicating the compression type.
        Valid only if this routine returns NO_ERROR or  ERROR_INSUFFICIENT_BUFFER.

Return Value:

    TRUE indicating success (NO_ERROR)
    FALSE indicating failure
    GetLastError() provides Win32 error code indicating the outcome.

    ERROR_FILE_NOT_FOUND - normal code indicating everything is ok
        but we can't find the file

    NO_ERROR - file was located and all output params are filled in including ActualSourceFileNameBuffer.
        also returned if ActualSourceFileNameBuffer is NULL

    ERROR_INSUFFICIENT_BUFFER - file was located and output params are filled in, excluding
        ActualSourceFileNameBuffer.

    Others - something is wrong with the hardware or system.

--*/

{
    WIN32_FIND_DATA FindData;
    DWORD d;
    PCTSTR source;
    PTSTR actualsource = NULL;
    DWORD targetsize;
    UINT type;
    DWORD reqbufsize;

    d = CaptureStringArg(SourceFileName,&source);
    if(d != NO_ERROR) {
        SetLastError(d);
        return (d==NO_ERROR);
    }

    d = SetupInternalGetFileCompressionInfo(source,&actualsource,&FindData,&targetsize,&type);

    if(d == NO_ERROR) {
        MYASSERT(actualsource);
        try {
            reqbufsize = lstrlen(actualsource)+1;
            if (RequiredBufferLen) {
                *RequiredBufferLen = reqbufsize;
            }
            if(ActualSourceFileNameBuffer) {
                if(ActualSourceFileNameBufferLen < reqbufsize) {
                    d = ERROR_INSUFFICIENT_BUFFER;
                } else {
                    lstrcpy(ActualSourceFileNameBuffer,actualsource);
                }
            } else if(ActualSourceFileNameBufferLen) {
                d = ERROR_INVALID_USER_BUFFER;
            }
            *SourceFileSize = FindData.nFileSizeLow;
            *TargetFileSize = targetsize;
            *CompressionType = type;
        } except(EXCEPTION_EXECUTE_HANDLER) {
            d = ERROR_INVALID_PARAMETER;
        }
        MyFree(actualsource);
    }

    MyFree(source);

    SetLastError(d);
    return (d==NO_ERROR);
}


#ifdef UNICODE
//
// ANSI version
//
DWORD
SetupDecompressOrCopyFileA(
    IN  PCSTR   SourceFileName,
    OUT PCSTR   TargetFileName,
    OUT PUINT   CompressionType OPTIONAL
    )
{
    DWORD rc;
    PCWSTR s,t;

    rc = pSetupCaptureAndConvertAnsiArg(SourceFileName,&s);
    if(rc == NO_ERROR) {

        rc = pSetupCaptureAndConvertAnsiArg(TargetFileName,&t);
        if(rc == NO_ERROR) {

            rc = pSetupDecompressOrCopyFile(s,t,CompressionType,FALSE,NULL);
            MyFree(t);
        }

        MyFree(s);
    }

    return(rc);
}
#else
//
// Unicode stub
//
DWORD
SetupDecompressOrCopyFileW(
    IN  PCWSTR  SourceFileName,
    OUT PCWSTR  TargetFileName,
    OUT PUINT   CompressionType OPTIONAL
    )
{
    UNREFERENCED_PARAMETER(SourceFileName);
    UNREFERENCED_PARAMETER(TargetFileName);
    UNREFERENCED_PARAMETER(CompressionType);
    return(ERROR_CALL_NOT_IMPLEMENTED);
}
#endif

DWORD
SetupDecompressOrCopyFile(
    IN PCTSTR SourceFileName,
    IN PCTSTR TargetFileName,
    IN PUINT  CompressionType OPTIONAL
    )

/*++

Routine Description:

    Decompress or copy a file.

Arguments:

    SourceFileName - supplies filename of the file to be decompressed.
        If CompressionType is specified, no additional processing is
        performed on this name -- the caller is responsible for determining
        the actual file name (ie, foo.ex_ instead of foo.exe) before calling
        this routine. If CompressionType is not specified, then this routine
        attempts to locate the compressed form of the filename if the file
        is not found with the name given.

    TargetFileName - supplies filename of target file.

    CompressionType - if specified, supplies type of compression in use
        on the source. This can be determined by calling
        SetupGetFileCompressionInfo(). Specifying FILE_COMPRESSION_NONE
        results in the file being copied and not decompressed,
        regardless of the type of compression that may be in use on the source.
        If this value is not specified then this routine attempts to determine
        the compression type and decompresses/copies accordingly.

Return Value:

    Win32 error code indicating the outcome.

--*/

{
    DWORD rc;
    PCTSTR s,t;

    rc = CaptureStringArg(SourceFileName,&s);
    if(rc == NO_ERROR) {

        rc = CaptureStringArg(TargetFileName,&t);
        if(rc == NO_ERROR) {

            rc = pSetupDecompressOrCopyFile(s,t,CompressionType,FALSE,NULL);
            MyFree(t);
        }

        MyFree(s);
    }

    return(rc);
}


DWORD
pSetupDecompressOrCopyFile(
    IN  PCTSTR SourceFileName,
    IN  PCTSTR TargetFileName,
    IN  PUINT  CompressionType, OPTIONAL
    IN  BOOL   AllowMove,
    OUT PBOOL  Moved            OPTIONAL
    )

/*++

Routine Description:

    Decompress or copy a file.

Arguments:

    SourceFileName - supplies filename of the file to be decompressed.
        If CompressionType is specified, no additional processing is
        performed on this name -- the caller is responsible for determining
        the actual file name (ie, foo.ex_ instead of foo.exe) before calling
        this routine. If CompressionType is not specified, then this routine
        attempts to locate the compressed form of the filename if the file
        is not found with the name given.

    TargetFileName - supplies filename of target file.

    CompressionType - if specified, supplies type of compression in use
        on the source. This can be determined by calling
        SetupGetFileCompressionInfo(). Specifying FILE_COMPRESSION_NONE
        results in the file being copied and not decompressed,
        regardless of the type of compression that may be in use on the source.
        If this value is not specified then this routine attempts to determine
        the compression type and decompresses/copies accordingly.

    AllowMove - if specified, then files that do not require decompression
        will be moved instead of copied.

    Moved - if specified receives a boolean indicating whether the file was
        moved (as opposed to copied or decompressed).

Return Value:

    Win32 error code indicating the outcome.

--*/

{
    DWORD d;
    UINT ComprType;
    PTSTR ActualName;
    DWORD TargetSize;
    FILETIME CreateTime,AccessTime,WriteTime;
    SFD_INFO CBData;
    BOOL moved;
    WIN32_FIND_DATA FindData;

    if(Moved) {
        *Moved = FALSE;
    }

    if(CompressionType) {
        ComprType = *CompressionType;
        ActualName = (PTSTR)SourceFileName;
    } else {
        //
        // Need to determine compresison type.
        //
        d = SetupInternalGetFileCompressionInfo(
                SourceFileName,
                &ActualName,
                &FindData,
                &TargetSize,
                &ComprType
                );

        if(d != NO_ERROR) {
            return(d);
        }
    }

    //
    // Blast the target file. Ignore if failure -- it'll be caught later.
    //
    SetFileAttributes(TargetFileName,FILE_ATTRIBUTE_NORMAL);
    DeleteFile(TargetFileName);

    switch(ComprType) {

    case FILE_COMPRESSION_NONE:
        moved = (AllowMove ? MoveFile(ActualName,TargetFileName) : FALSE);
        if(moved) {
            d = NO_ERROR;
            if(Moved) {
                *Moved = TRUE;
            }
        } else {
            d = GetSetFileTimestamp(ActualName,&CreateTime,&AccessTime,&WriteTime,FALSE);
            if(d == NO_ERROR) {
                d = CopyFile(ActualName,TargetFileName,FALSE) ? NO_ERROR : GetLastError();
                if(d == NO_ERROR) {
                    GetSetFileTimestamp(TargetFileName,&CreateTime,&AccessTime,&WriteTime,TRUE);
                }
            }
        }
        break;

    case FILE_COMPRESSION_WINLZA:
        d = pSetupDecompressWinLzFile(ActualName,(PTSTR)TargetFileName);
        break;

    case FILE_COMPRESSION_MSZIP:

        CBData.FileCount = 0;
        CBData.TargetFile = TargetFileName;
        CBData.GotTimestamp = FALSE;

        d = DiamondProcessCabinet(
                ActualName,
                0,
                pSingleFileDecompCB,
                &CBData,
                TRUE
                );
        break;

    default:
        d = ERROR_INVALID_PARAMETER;
        break;
    }

    if(!CompressionType) {
        MyFree(ActualName);
    }

    return(d);
}


UINT
pSingleFileDecompCB(
    IN PVOID Context,
    IN UINT  Notification,
    IN UINT_PTR  Param1,
    IN UINT_PTR  Param2
    )
{
    PSFD_INFO Data;
    PFILE_IN_CABINET_INFO FileInfo;
    PFILEPATHS FilePaths;
    DWORD rc;
    HANDLE h;

    Data = Context;

    switch(Notification) {

    case SPFILENOTIFY_CABINETINFO:
        //
        // We don't do anything with this.
        //
        rc = NO_ERROR;
        break;

    case SPFILENOTIFY_FILEINCABINET:
        //
        // New file within a cabinet.
        //
        FileInfo = (PFILE_IN_CABINET_INFO)Param1;
        FileInfo->Win32Error = NO_ERROR;

        //
        // We only want the first file. If this is a subsequent file,
        // bail out.
        //
        if(Data->FileCount++) {

            rc = FILEOP_ABORT;
            SetLastError(NO_ERROR);

        } else {
            //
            // We want the file. Ignore the names in the cabinet and
            // use the name given to us. Also, we want to preserve
            // the timestamp of the cabinet, not of the file within it.
            //
            lstrcpyn(FileInfo->FullTargetName,Data->TargetFile,MAX_PATH);

            h = CreateFile(
                    (PCTSTR)Param2,         // cabinet filename
                    GENERIC_READ,
                    FILE_SHARE_READ,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL
                    );

            if(h != INVALID_HANDLE_VALUE) {
                if(GetFileTime(h,NULL,NULL,&Data->FileTime)) {
                    Data->GotTimestamp = TRUE;
                }
                CloseHandle(h);
            }

            rc = FILEOP_DOIT;
        }

        break;

    case SPFILENOTIFY_FILEEXTRACTED:
        //
        // File was successfully extracted.
        // Preserve timestamp.
        //
        FilePaths = (PFILEPATHS)Param1;

        if(Data->GotTimestamp) {

            h = CreateFile(
                    FilePaths->Target,
                    GENERIC_WRITE,
                    FILE_SHARE_READ,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL
                    );

            if(h != INVALID_HANDLE_VALUE) {
                SetFileTime(h,NULL,NULL,&Data->FileTime);
                CloseHandle(h);
            }
        }

        rc = NO_ERROR;
        break;

    //case SPFILENOTIFY_NEEDNEWCABINET:
    default:
        //
        // We should never get this.
        //
        MYASSERT(0);
        rc = ERROR_INVALID_FUNCTION;
        break;
    }

    return(rc);
}