/*++

Copyright (c) 1996 Microsoft Corporation

Module Name:

    expandit.c

Abstract:

    Expandit provides routines to expand a given file as much as possible.
    It is useful for expanding cabinet files and executable files that may
    themselves contain cabinet files.

Author:

    Marc R. Whitten (marcw) 30-Jul-1998

Revision History:




--*/

#include "pch.h"
#include "migutilp.h"

UINT g_DirSequencer = 0;

BOOL
ExpandAllFilesW (
    IN PCWSTR FileDir,
    IN PCWSTR TempDir
    );

BOOL
ExpandFileW (
    IN PCWSTR FullPath,
    IN PCWSTR TempDir
    );

PWSTR
pGetExpandName (
    VOID
    )
{
    static WCHAR rName[MAX_WCHAR_PATH];

    g_DirSequencer++;
    swprintf (rName, L"EXP%04x", g_DirSequencer);

    return rName;
}

BOOL
pIsFileType (
    IN      PCWSTR FullPath,
    IN      PCWSTR TypePattern
    )
{
    PCWSTR p;

    p = GetFileExtensionFromPathW (FullPath);

    return p && IsPatternMatchW (TypePattern, p);
}


BOOL CALLBACK
pResNameCallback (
    IN      HANDLE hModule,   // module handle
    IN      LPCWSTR lpszType,  // pointer to resource type
    IN      LPWSTR lpszName,  // pointer to resource name
    IN      LONG lParam       // application-defined parameter
    )
{
    HRSRC hResource;
    DWORD size;
    HGLOBAL hGlobal;
    HANDLE hFile;
    PWSTR fileName;
    PWSTR dirName;
    PVOID srcBytes;
    UINT dontCare;
    BOOL rSuccess=TRUE;

    hResource = FindResourceW (hModule, lpszName, lpszType);

    if (hResource) {

        size = SizeofResource (hModule, hResource);

        if (size) {

            hGlobal = LoadResource (hModule, hResource);

            if (hGlobal) {

                srcBytes = LockResource (hGlobal);
                if (srcBytes) {

                    //
                    // Ok, lets see if this is a cabinet file..
                    //
                    if (size < 4 || *((PDWORD)srcBytes) != 0x4643534D) {
                        //
                        // Not a cabinet file.
                        //
                        return TRUE;
                    }

                    dirName = JoinPathsW ((PWSTR) lParam, pGetExpandName ());
                    fileName = JoinPathsW (dirName, L"temp.cab");

                    hFile = CreateFileW (
                        fileName,
                        GENERIC_READ | GENERIC_WRITE,
                        0, NULL,
                        CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL
                        );

                    if (hFile == INVALID_HANDLE_VALUE) {
                        DEBUGMSGW ((DBG_ERROR, "Unable to create file %s.", fileName));
                        return FALSE;
                    }

                    if (!WriteFile (hFile, srcBytes, size, &dontCare, NULL)) {
                        DEBUGMSGW ((DBG_ERROR, "Cannot write to file %s", fileName));
                        return FALSE;
                    }

                    CloseHandle (hFile);

                    //
                    // Expand this file.
                    //
                    rSuccess = ExpandFileW (fileName, dirName);
                    FreePathStringW (dirName);
                    FreePathStringW (fileName);

                }
            }
        }
    }

    return rSuccess;
}


BOOL CALLBACK
pResTypeCallback (
    IN      HANDLE hModule,  // resource-module handle
    IN      PWSTR lpszType,  // pointer to resource type
    IN      LONG lParam      // application-defined parameter
    )
{

    //
    // Oh what a pain. All of the RT types are #defined without A/Ws.
    // We need to converte the string to ansi, just to do this comparison.
    //

    PCSTR aString = ConvertWtoA (lpszType);


    if ((aString != RT_ACCELERATOR  ) &&
        (aString != RT_ANICURSOR    ) &&
        (aString != RT_ANIICON      ) &&
        (aString != RT_BITMAP       ) &&
        (aString != RT_CURSOR       ) &&
        (aString != RT_DIALOG       ) &&
        (aString != RT_FONT         ) &&
        (aString != RT_FONTDIR      ) &&
        (aString != RT_GROUP_CURSOR ) &&
        (aString != RT_GROUP_ICON   ) &&
        (aString != RT_HTML         ) &&
        (aString != RT_ICON         ) &&
        (aString != RT_MENU         ) &&
        (aString != RT_MESSAGETABLE ) &&
        (aString != RT_PLUGPLAY     ) &&
        (aString != RT_STRING       ) &&
        (aString != RT_VERSION      ) &&
        (aString != RT_VXD          ) &&
        (aString != RT_HTML         )
        ) {

        //
        // Unknown type. We assume it is a cabinet file and try to extract it.
        // Since it may not be a cabinet file, we eat the error.
        //
        if (!EnumResourceNamesW (hModule, lpszType, pResNameCallback, lParam)) {
            DEBUGMSGW ((DBG_ERROR, "Error enumerating resource names."));
        }
    }

    FreeConvertedStr (aString);

    return TRUE;
}




UINT CALLBACK
pCabFileCallback (
    IN      PVOID Context,          //context used by the callback routine
    IN      UINT Notification,      //notification sent to callback routine
    IN      UINT Param1,            //additional notification information
    IN      UINT Param2             //additional notification information
    )
{
    PCWSTR tempDir  = Context;
    PCWSTR fileName = (PCWSTR)Param2 ;
    PFILE_IN_CABINET_INFO_W fileInfo = (PFILE_IN_CABINET_INFO_W)Param1;
    PCWSTR fromPtr, toPtr;
    WCHAR tempStr [MEMDB_MAX];

    if (Notification == SPFILENOTIFY_FILEINCABINET) {

        if (toPtr = wcschr (fileInfo->NameInCabinet, L'\\')) {

            StringCopyW (fileInfo->FullTargetName, tempDir);
            fromPtr = fileInfo->NameInCabinet;

            while (toPtr) {

                StringCopyABW (tempStr, fromPtr, toPtr);
                StringCatW (fileInfo->FullTargetName, L"\\");
                StringCatW (fileInfo->FullTargetName, tempStr);
                CreateDirectoryW (fileInfo->FullTargetName, NULL);
                toPtr++;
                fromPtr = toPtr;
                toPtr   = wcschr (toPtr, L'\\');
            }
        }

        swprintf (fileInfo->FullTargetName, L"%ws\\%ws", tempDir, fileInfo->NameInCabinet);
        return FILEOP_DOIT;
    }

    return NO_ERROR;
}

BOOL
ExpandFileW (
    IN PCWSTR FullPath,
    IN PCWSTR TempDir
    )
{
    BOOL rSuccess = TRUE;
    PWSTR dirName = NULL;
    PWSTR fileName = NULL;
    HANDLE exeModule = NULL;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    CHAR aName[MAX_MBCHAR_PATH];
    PCWSTR uName = NULL;
    UINT bytesRead = 0;


    if (pIsFileType (FullPath, L"CAB")) {
        //
        // Expand this cabinet file and recursively handle all of its files.
        //
        dirName = JoinPathsW (TempDir, pGetExpandName());
        __try {

            if (!CreateDirectoryW (dirName, NULL)) {
                DEBUGMSGW ((DBG_ERROR, "ExpandFile: Cannot create directory %s", dirName));
                rSuccess = FALSE;
                __leave;
            }

            //
            // Expand the cabinet file into the temporary directory.
            //
            SetLastError (ERROR_SUCCESS);
            if (!SetupIterateCabinetW (FullPath, 0, pCabFileCallback, dirName)) {
                DEBUGMSGW ((DBG_ERROR, "ExpandFile: SetupIterateCabinet failed for file %s.", FullPath));
                rSuccess = FALSE;
                __leave;
            }

            //
            // Now, make sure all of the files in this SubDirectory are expanded.
            //
            rSuccess = ExpandAllFilesW (dirName, dirName);
        }
        __finally {

            FreePathStringW (dirName);
        }

    } else if (pIsFileType (FullPath, L"EXE")) {

        //
        // This is an executable file. Check to make sure that they aren't any cabinet files hanging out
        // inside of it.
        //
        exeModule = LoadLibraryExW (FullPath, NULL, LOAD_LIBRARY_AS_DATAFILE);

        if (!exeModule) {
            DEBUGMSGW ((DBG_ERROR, "ExpandFile: LoadLibraryEx failed for %s.", FullPath));
            return FALSE;
        }

        if (!EnumResourceTypesW (exeModule, pResTypeCallback, (LONG) TempDir)) {
            DEBUGMSGW ((DBG_ERROR, "ExpandFile: EnumResourceTypes failed for %s.", FullPath));
            FreeLibrary (exeModule);
            return FALSE;
        }



    } else if (pIsFileType (FullPath, L"*_")) {

        //
        // Compressed file. Decompress it.
        //
        hFile = CreateFileW (
            FullPath,
            GENERIC_READ,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL
            );


        if (hFile == INVALID_HANDLE_VALUE) {
            DEBUGMSGW ((DBG_ERROR, "ExpandFile: Unable to open file %s.", FullPath));
            return FALSE;
        }

        __try {

            //
            // The real file name is stored at 0x3c.
            //
            if (0xffffffff == SetFilePointer (hFile, 0x3c, NULL, FILE_BEGIN)) {
                DEBUGMSGW ((DBG_ERROR, "ExpandFile: Cannot get real file name for compressed file %s.", FullPath));
               rSuccess = FALSE;
               __leave;
            }

            if (!ReadFile (hFile, aName, sizeof(aName), &bytesRead, NULL)) {
                DEBUGMSG ((DBG_ERROR, "ExpandFile: Cannot read real file name for compressed file %s.", FullPath));
                rSuccess = FALSE;
                __leave;
            }

            if (bytesRead >= ByteCountW (FullPath)) {

                uName = ConvertAtoW (aName);

                if (StringIMatchTcharCountW (FullPath, uName, TcharCountW (FullPath) - 1)) {

                    dirName = JoinPathsW (TempDir, pGetExpandName());
                    fileName = JoinPathsW (dirName, uName);

                    //
                    // Create directory for this file and decompress it.
                    //
                    if (!CreateDirectoryW (dirName, NULL)) {
                        DEBUGMSGW ((DBG_ERROR, "ExpandFile: Cannot create directory %s", dirName));
                        rSuccess = FALSE;
                        __leave;
                    }

                    if (SetupDecompressOrCopyFileW (FullPath, fileName, NULL) != ERROR_SUCCESS) {
                        DEBUGMSGW ((DBG_ERROR, "ExpandFile: Cannot decompreses %s => %s.", FullPath, fileName));
                        rSuccess = FALSE;
                        __leave;
                    }

                    //
                    // Now, run expand recursively on the decompressed file. Could be a CAB or an EXE.
                    //
                    rSuccess = ExpandFileW (fileName, dirName);
                }

            }

        }
        __finally {

            CloseHandle (hFile);
            FreePathStringW (dirName);
            FreePathStringW (fileName);

        }

    }

    return rSuccess;
}

BOOL
ExpandAllFilesW (
    IN PCWSTR FileDir,
    IN PCWSTR TempDir
    )
{
    BOOL rSuccess = TRUE;
    TREE_ENUMW e;

    if (EnumFirstFileInTreeW (&e, FileDir, L"*", FALSE)) {
        do {

            if (!e.Directory) {
                rSuccess &= ExpandFileW (e.FullPath, TempDir);
            }

        } while (EnumNextFileInTreeW (&e));
    }

    DEBUGMSGW_IF ((!rSuccess,DBG_ERROR, "ExpandAllFilesW: One or more errors occurred while expanding all files in %s.", FileDir));

    return rSuccess;
}


BOOL
ExpandFileA (
    IN PCSTR FullPath,
    IN PCSTR TempDir
    )
{

    PCWSTR wFullPath = NULL;
    PCWSTR wTempDir = NULL;
    BOOL rSuccess = TRUE;

    MYASSERT(FullPath && TempDir);

    //
    // Convert args and call W version.
    //

    wFullPath = ConvertAtoW (FullPath);
    wTempDir = ConvertAtoW (TempDir);

    MYASSERT (wFullPath && wTempDir);

    rSuccess = ExpandFileW (wFullPath, wTempDir);

    FreeConvertedStr (wFullPath);
    FreeConvertedStr (wTempDir);

    return rSuccess;
}


BOOL
ExpandAllFilesA (
    IN PCSTR FileDir,
    IN PCSTR TempDir
    )
{

    PCWSTR wFileDir = NULL;
    PCWSTR wTempDir = NULL;
    BOOL rSuccess = TRUE;

    MYASSERT(FileDir && TempDir);

    //
    // Convert args and call W version.
    //

    wFileDir = ConvertAtoW (FileDir);
    wTempDir = ConvertAtoW (TempDir);

    MYASSERT (wFileDir && wTempDir);

    rSuccess = ExpandAllFilesW (wFileDir, wTempDir);

    FreeConvertedStr (wFileDir);
    FreeConvertedStr (wTempDir);

    return rSuccess;
}