/*
 * Miscellaneous tests
 *
 * Copyright 2007 James Hawkins
 * Copyright 2007 Hans Leidekker
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "winuser.h"
#include "winreg.h"
#include "setupapi.h"
#include "cfgmgr32.h"

#include "wine/test.h"

static CHAR CURR_DIR[MAX_PATH];

/* test:
 *  - fails if not administrator
 *  - what if it's not a .inf file?
 *  - copied to %windir%/Inf
 *  - SourceInfFileName should be a full path
 *  - SourceInfFileName should be <= MAX_PATH
 *  - copy styles
 */

static BOOL (WINAPI *pSetupGetFileCompressionInfoExA)(PCSTR, PSTR, DWORD, PDWORD, PDWORD, PDWORD, PUINT);
static BOOL (WINAPI *pSetupCopyOEMInfA)(PCSTR, PCSTR, DWORD, DWORD, PSTR, DWORD, PDWORD, PSTR *);
static BOOL (WINAPI *pSetupQueryInfOriginalFileInformationA)(PSP_INF_INFORMATION, UINT, PSP_ALTPLATFORM_INFO, PSP_ORIGINAL_FILE_INFO_A);
static BOOL (WINAPI *pSetupUninstallOEMInfA)(PCSTR, DWORD, PVOID);

static void create_inf_file(LPCSTR filename)
{
    DWORD dwNumberOfBytesWritten;
    HANDLE hf = CreateFileA(filename, GENERIC_WRITE, 0, NULL,
                            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    static const char data[] =
        "[Version]\n"
        "Signature=\"$Chicago$\"\n"
        "AdvancedINF=2.5\n"
        "[DefaultInstall]\n"
        "RegisterOCXs=RegisterOCXsSection\n"
        "[RegisterOCXsSection]\n"
        "%%11%%\\ole32.dll\n";

    WriteFile(hf, data, sizeof(data) - 1, &dwNumberOfBytesWritten, NULL);
    CloseHandle(hf);
}

static void get_temp_filename(LPSTR path)
{
    CHAR temp[MAX_PATH];
    LPSTR ptr;

    GetTempFileNameA(CURR_DIR, "set", 0, temp);
    ptr = strrchr(temp, '\\');

    strcpy(path, ptr + 1);
}

static BOOL file_exists(LPSTR path)
{
    return GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES;
}

static BOOL check_format(LPSTR path, LPSTR inf)
{
    CHAR check[MAX_PATH];
    BOOL res;

    static const CHAR format[] = "\\INF\\oem";

    GetWindowsDirectoryA(check, MAX_PATH);
    strcat(check, format);
    res = CompareStringA(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, check, -1, path, strlen(check)) == CSTR_EQUAL &&
          path[strlen(check)] != '\\';

    return (!inf) ? res : res && (inf == path + strlen(check) - 3);
}

static void test_original_file_name(LPCSTR original, LPCSTR dest)
{
    HINF hinf;
    PSP_INF_INFORMATION pspii;
    SP_ORIGINAL_FILE_INFO_A spofi;
    BOOL res;
    DWORD size;

    if (!pSetupQueryInfOriginalFileInformationA)
    {
        win_skip("SetupQueryInfOriginalFileInformationA is not available\n");
        return;
    }

    hinf = SetupOpenInfFileA(dest, NULL, INF_STYLE_WIN4, NULL);
    ok(hinf != NULL, "SetupOpenInfFileA failed with error %d\n", GetLastError());

    res = SetupGetInfInformationA(hinf, INFINFO_INF_SPEC_IS_HINF, NULL, 0, &size);
    ok(res, "SetupGetInfInformation failed with error %d\n", GetLastError());

    pspii = HeapAlloc(GetProcessHeap(), 0, size);

    res = SetupGetInfInformationA(hinf, INFINFO_INF_SPEC_IS_HINF, pspii, size, NULL);
    ok(res, "SetupGetInfInformation failed with error %d\n", GetLastError());

    spofi.cbSize = 0;
    res = pSetupQueryInfOriginalFileInformationA(pspii, 0, NULL, &spofi);
    ok(!res && GetLastError() == ERROR_INVALID_USER_BUFFER,
        "SetupQueryInfOriginalFileInformationA should have failed with ERROR_INVALID_USER_BUFFER instead of %d\n", GetLastError());

    spofi.cbSize = sizeof(spofi);
    res = pSetupQueryInfOriginalFileInformationA(pspii, 0, NULL, &spofi);
    ok(res, "SetupQueryInfOriginalFileInformationA failed with error %d\n", GetLastError());
    ok(!spofi.OriginalCatalogName[0], "spofi.OriginalCatalogName should have been \"\" instead of \"%s\"\n", spofi.OriginalCatalogName);
    todo_wine
    ok(!strcmp(original, spofi.OriginalInfName), "spofi.OriginalInfName of %s didn't match real original name %s\n", spofi.OriginalInfName, original);

    HeapFree(GetProcessHeap(), 0, pspii);

    SetupCloseInfFile(hinf);
}

static void test_SetupCopyOEMInf(void)
{
    CHAR toolong[MAX_PATH * 2];
    CHAR path[MAX_PATH], dest[MAX_PATH];
    CHAR tmpfile[MAX_PATH], dest_save[MAX_PATH];
    LPSTR inf = NULL;
    DWORD size;
    BOOL res;

    /* try NULL SourceInfFileName */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(NULL, NULL, 0, SP_COPY_NOOVERWRITE, NULL, 0, NULL, NULL);
    ok(res == FALSE, "Expected FALSE, got %d\n", res);
    ok(GetLastError() == ERROR_INVALID_PARAMETER,
       "Expected ERROR_INVALID_PARAMETER, got %d\n", GetLastError());

    /* try empty SourceInfFileName */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA("", NULL, 0, SP_COPY_NOOVERWRITE, NULL, 0, NULL, NULL);
    ok(res == FALSE, "Expected FALSE, got %d\n", res);
    ok(GetLastError() == ERROR_FILE_NOT_FOUND ||
       GetLastError() == ERROR_BAD_PATHNAME || /* Win98 */
       GetLastError() == ERROR_INVALID_PARAMETER, /* Vista, W2K8 */
       "Unexpected error : %d\n", GetLastError());

    /* try a relative nonexistent SourceInfFileName */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA("nonexistent", NULL, 0, SP_COPY_NOOVERWRITE, NULL, 0, NULL, NULL);
    ok(res == FALSE, "Expected FALSE, got %d\n", res);
    ok(GetLastError() == ERROR_FILE_NOT_FOUND,
       "Expected ERROR_FILE_NOT_FOUND, got %d\n", GetLastError());

    /* try an absolute nonexistent SourceInfFileName */
    strcpy(path, CURR_DIR);
    strcat(path, "\\nonexistent");
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, 0, SP_COPY_NOOVERWRITE, NULL, 0, NULL, NULL);
    ok(res == FALSE, "Expected FALSE, got %d\n", res);
    ok(GetLastError() == ERROR_FILE_NOT_FOUND,
       "Expected ERROR_FILE_NOT_FOUND, got %d\n", GetLastError());

    /* try a long SourceInfFileName */
    memset(toolong, 'a', MAX_PATH * 2);
    toolong[MAX_PATH * 2 - 1] = '\0';
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(toolong, NULL, 0, SP_COPY_NOOVERWRITE, NULL, 0, NULL, NULL);
    ok(res == FALSE, "Expected FALSE, got %d\n", res);
    ok(GetLastError() == ERROR_FILE_NOT_FOUND ||
       GetLastError() == ERROR_FILENAME_EXCED_RANGE, /* Win98 */
       "Expected ERROR_FILE_NOT_FOUND or ERROR_FILENAME_EXCED_RANGE, got %d\n", GetLastError());

    get_temp_filename(tmpfile);
    create_inf_file(tmpfile);

    /* try a relative SourceInfFileName */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(tmpfile, NULL, 0, SP_COPY_NOOVERWRITE, NULL, 0, NULL, NULL);
    ok(res == FALSE ||
       broken(res == TRUE), /* Win98 */
       "Expected FALSE, got %d\n", res);
    if (GetLastError() == ERROR_WRONG_INF_TYPE || GetLastError() == ERROR_UNSUPPORTED_TYPE /* Win7 */)
    {
       /* FIXME:
        * Vista needs a [Manufacturer] entry in the inf file. Doing this will give some
        * popups during the installation though as it also needs a catalog file (signed?).
        */
       win_skip("Needs a different inf file on Vista+\n");
       DeleteFileA(tmpfile);
       return;
    }

    ok(GetLastError() == ERROR_FILE_NOT_FOUND ||
       broken(GetLastError() == ERROR_SUCCESS), /* Win98 */
       "Expected ERROR_FILE_NOT_FOUND, got %d\n", GetLastError());
    ok(file_exists(tmpfile), "Expected tmpfile to exist\n");

    /* try SP_COPY_REPLACEONLY, dest does not exist */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, SP_COPY_REPLACEONLY, NULL, 0, NULL, NULL);
    ok(res == FALSE, "Expected FALSE, got %d\n", res);
    ok(GetLastError() == ERROR_FILE_NOT_FOUND,
       "Expected ERROR_FILE_NOT_FOUND, got %d\n", GetLastError());
    ok(file_exists(tmpfile), "Expected source inf to exist\n");

    /* try an absolute SourceInfFileName, without DestinationInfFileName */
    strcpy(path, CURR_DIR);
    strcat(path, "\\");
    strcat(path, tmpfile);
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, 0, NULL, 0, NULL, NULL);
    if (!res && GetLastError() == ERROR_ACCESS_DENIED)
    {
        skip("SetupCopyOEMInfA() failed on insufficient permissions\n");
        return;
    }
    ok(res == TRUE, "Expected TRUE, got %d\n", res);
    ok(GetLastError() == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", GetLastError());
    ok(file_exists(path), "Expected source inf to exist\n");

    /* try SP_COPY_REPLACEONLY, dest exists */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, SP_COPY_REPLACEONLY, NULL, 0, NULL, NULL);
    ok(res == TRUE, "Expected TRUE, got %d\n", res);
    ok(GetLastError() == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", GetLastError());
    ok(file_exists(path), "Expected source inf to exist\n");

    /* try SP_COPY_NOOVERWRITE */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, SP_COPY_NOOVERWRITE, NULL, 0, NULL, NULL);
    ok(res == FALSE, "Expected FALSE, got %d\n", res);
    ok(GetLastError() == ERROR_FILE_EXISTS,
       "Expected ERROR_FILE_EXISTS, got %d\n", GetLastError());

    /* get the DestinationInfFileName */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, 0, dest, MAX_PATH, NULL, NULL);
    ok(res == TRUE, "Expected TRUE, got %d\n", res);
    ok(GetLastError() == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", GetLastError());
    ok(strlen(dest) != 0, "Expected a non-zero length string\n");
    ok(file_exists(dest), "Expected destination inf to exist\n");
    ok(check_format(dest, NULL), "Expected %%windir%%\\inf\\OEMx.inf, got %s\n", dest);
    ok(file_exists(path), "Expected source inf to exist\n");

    strcpy(dest_save, dest);
    DeleteFileA(dest_save);

    /* get the DestinationInfFileName, DestinationInfFileNameSize is too small
     *   - inf is still copied
     */
    strcpy(dest, "aaa");
    size = 0;
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, 0, dest, 5, &size, NULL);
    ok(res == FALSE, "Expected FALSE, got %d\n", res);
    ok(GetLastError() == ERROR_INSUFFICIENT_BUFFER,
       "Expected ERROR_INSUFFICIENT_BUFFER, got %d\n", GetLastError());
    ok(file_exists(path), "Expected source inf to exist\n");
    ok(file_exists(dest_save), "Expected dest inf to exist\n");
    ok(!strcmp(dest, "aaa"), "Expected dest to be unchanged\n");
    ok(size == strlen(dest_save) + 1, "Expected size to be lstrlen(dest_save) + 1\n");

    /* get the DestinationInfFileName and DestinationInfFileNameSize */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, 0, dest, MAX_PATH, &size, NULL);
    ok(res == TRUE, "Expected TRUE, got %d\n", res);
    ok(GetLastError() == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", GetLastError());
    ok(lstrlenA(dest) + 1 == size, "Expected sizes to match, got (%d, %d)\n", lstrlenA(dest), size);
    ok(file_exists(dest), "Expected destination inf to exist\n");
    ok(check_format(dest, NULL), "Expected %%windir%%\\inf\\OEMx.inf, got %s\n", dest);
    ok(file_exists(path), "Expected source inf to exist\n");
    ok(size == lstrlenA(dest_save) + 1, "Expected size to be lstrlen(dest_save) + 1\n");

    test_original_file_name(strrchr(path, '\\') + 1, dest);

    /* get the DestinationInfFileName, DestinationInfFileNameSize, and DestinationInfFileNameComponent */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, 0, dest, MAX_PATH, &size, &inf);
    ok(res == TRUE, "Expected TRUE, got %d\n", res);
    ok(GetLastError() == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", GetLastError());
    ok(lstrlenA(dest) + 1 == size, "Expected sizes to match, got (%d, %d)\n", lstrlenA(dest), size);
    ok(file_exists(dest), "Expected destination inf to exist\n");
    ok((inf && inf[0] != 0) ||
       broken(!inf), /* Win98 */
       "Expected inf to point to the filename\n");
    ok(check_format(dest, inf), "Expected %%windir%%\\inf\\OEMx.inf, got %s\n", dest);
    ok(file_exists(path), "Expected source inf to exist\n");
    ok(size == lstrlenA(dest_save) + 1, "Expected size to be lstrlen(dest_save) + 1\n");

    /* try SP_COPY_DELETESOURCE */
    SetLastError(0xdeadbeef);
    res = pSetupCopyOEMInfA(path, NULL, SPOST_NONE, SP_COPY_DELETESOURCE, NULL, 0, NULL, NULL);
    ok(res == TRUE, "Expected TRUE, got %d\n", res);
    ok(GetLastError() == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", GetLastError());
    ok(!file_exists(path), "Expected source inf to not exist\n");

    if (pSetupUninstallOEMInfA)
    {
        char pnf[MAX_PATH];
        char *pnffile;
        char *destfile = strrchr(dest, '\\') + 1;

        strcpy(pnf, dest);
        *(strrchr(pnf, '.') + 1) = 'p';
        pnffile = strrchr(pnf, '\\') + 1;

        SetLastError(0xdeadbeef);
        res = pSetupUninstallOEMInfA(destfile, 0, NULL);
        if(!res)
            res = pSetupUninstallOEMInfA(pnffile, 0, NULL);
        ok(res, "Failed to uninstall '%s'/'%s' : %d\n", destfile,
           pnffile, GetLastError());
        todo_wine ok(!file_exists(dest), "Expected inf '%s' to not exist\n", dest);
        if(file_exists(dest))
        {
            SetLastError(0xdeadbeef);
            res = DeleteFileA(dest);
            ok(res, "Failed to delete file '%s' : %d\n", dest, GetLastError());
        }
        ok(!file_exists(pnf), "Expected pnf '%s' to not exist\n", pnf);
        if(file_exists(pnf))
        {
            SetLastError(0xdeadbeef);
            res = DeleteFileA(pnf);
            ok(res, "Failed to delete file '%s' : %d\n", pnf, GetLastError());
        }
    }
    else
    {
        /* Win9x/WinMe */
        SetLastError(0xdeadbeef);
        res = DeleteFileA(dest);
        ok(res, "Failed to delete file '%s' : %d\n", dest, GetLastError());

        /* On WinMe we also need to remove the .pnf file */
        *(strrchr(dest, '.') + 1) = 'p';
        DeleteFileA(dest);
    }
}

static void create_source_file(LPSTR filename, const BYTE *data, DWORD size)
{
    HANDLE handle;
    DWORD written;

    handle = CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    WriteFile(handle, data, size, &written, NULL);
    CloseHandle(handle);
}

static BOOL compare_file_data(LPSTR file, const BYTE *data, DWORD size)
{
    DWORD read;
    HANDLE handle;
    BOOL ret = FALSE;
    LPBYTE buffer;

    handle = CreateFileA(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    buffer = HeapAlloc(GetProcessHeap(), 0, size);
    if (buffer)
    {
        ReadFile(handle, buffer, size, &read, NULL);
        if (read == size && !memcmp(data, buffer, size)) ret = TRUE;
        HeapFree(GetProcessHeap(), 0, buffer);
    }
    CloseHandle(handle);
    return ret;
}

static const BYTE uncompressed[] = {
    'u','n','c','o','m','p','r','e','s','s','e','d','\r','\n'
};
static const BYTE laurence[] = {
    'l','a','u','r','e','n','c','e','\r','\n'
};
static const BYTE comp_lzx[] = {
    0x53, 0x5a, 0x44, 0x44, 0x88, 0xf0, 0x27, 0x33, 0x41, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xff, 0x00,
    0x00, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x3f, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64
};
static const BYTE comp_zip[] = {
    0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0xae, 0x81, 0x36, 0x75, 0x11,
    0x2c, 0x1b, 0x0e, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x15, 0x00, 0x77, 0x69,
    0x6e, 0x65, 0x55, 0x54, 0x09, 0x00, 0x03, 0xd6, 0x0d, 0x10, 0x46, 0xfd, 0x0d, 0x10, 0x46, 0x55,
    0x78, 0x04, 0x00, 0xe8, 0x03, 0xe8, 0x03, 0x00, 0x00, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72,
    0x65, 0x73, 0x73, 0x65, 0x64, 0x50, 0x4b, 0x01, 0x02, 0x17, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00,
    0x00, 0xbd, 0xae, 0x81, 0x36, 0x75, 0x11, 0x2c, 0x1b, 0x0e, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00,
    0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00,
    0x00, 0x00, 0x00, 0x77, 0x69, 0x6e, 0x65, 0x55, 0x54, 0x05, 0x00, 0x03, 0xd6, 0x0d, 0x10, 0x46,
    0x55, 0x78, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
    0x3f, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const BYTE comp_cab_lzx[] = {
    0x4d, 0x53, 0x43, 0x46, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x0f, 0x0e, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x36, 0x86, 0x72, 0x20, 0x00, 0x77, 0x69, 0x6e, 0x65,
    0x00, 0x19, 0xd0, 0x1a, 0xe3, 0x22, 0x00, 0x0e, 0x00, 0x5b, 0x80, 0x80, 0x8d, 0x00, 0x30, 0xe0,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x75, 0x6e, 0x63,
    0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x0d, 0x0a
};
static const BYTE comp_cab_zip[] =  {
    0x4d, 0x53, 0x43, 0x46, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x36, 0x2f, 0xa5, 0x20, 0x00, 0x77, 0x69, 0x6e, 0x65,
    0x00, 0x7c, 0x80, 0x26, 0x2b, 0x12, 0x00, 0x0e, 0x00, 0x43, 0x4b, 0x2b, 0xcd, 0x4b, 0xce, 0xcf,
    0x2d, 0x28, 0x4a, 0x2d, 0x2e, 0x4e, 0x4d, 0xe1, 0xe5, 0x02, 0x00
};
static const BYTE comp_cab_zip_multi[] = {
    0x4d, 0x53, 0x43, 0x46, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x38, 0xf0, 0x48, 0x20, 0x00, 0x74, 0x72, 0x69, 0x73,
    0x74, 0x72, 0x61, 0x6d, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1,
    0x38, 0xf0, 0x48, 0x20, 0x00, 0x77, 0x69, 0x6e, 0x65, 0x00, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00,
    0x00, 0x00, 0x00, 0x00, 0xd1, 0x38, 0xf0, 0x48, 0x20, 0x00, 0x73, 0x68, 0x61, 0x6e, 0x64, 0x79,
    0x00, 0x67, 0x2c, 0x03, 0x85, 0x23, 0x00, 0x20, 0x00, 0x43, 0x4b, 0xcb, 0x49, 0x2c, 0x2d, 0x4a,
    0xcd, 0x4b, 0x4e, 0xe5, 0xe5, 0x2a, 0xcd, 0x4b, 0xce, 0xcf, 0x2d, 0x28, 0x4a, 0x2d, 0x2e, 0x4e,
    0x4d, 0xe1, 0xe5, 0x2a, 0x2e, 0x49, 0x2d, 0xca, 0x03, 0x8a, 0x02, 0x00
};

static void test_SetupGetFileCompressionInfo(void)
{
    DWORD ret, source_size, target_size;
    char source[MAX_PATH], temp[MAX_PATH], *name;
    UINT type;

    GetTempPathA(sizeof(temp), temp);
    GetTempFileNameA(temp, "fci", 0, source);

    create_source_file(source, uncompressed, sizeof(uncompressed));

    ret = SetupGetFileCompressionInfoA(NULL, NULL, NULL, NULL, NULL);
    ok(ret == ERROR_INVALID_PARAMETER, "SetupGetFileCompressionInfo failed unexpectedly\n");

    ret = SetupGetFileCompressionInfoA(source, NULL, NULL, NULL, NULL);
    ok(ret == ERROR_INVALID_PARAMETER, "SetupGetFileCompressionInfo failed unexpectedly\n");

    ret = SetupGetFileCompressionInfoA(source, &name, NULL, NULL, NULL);
    ok(ret == ERROR_INVALID_PARAMETER, "SetupGetFileCompressionInfo failed unexpectedly\n");

    ret = SetupGetFileCompressionInfoA(source, &name, &source_size, NULL, NULL);
    ok(ret == ERROR_INVALID_PARAMETER, "SetupGetFileCompressionInfo failed unexpectedly\n");

    ret = SetupGetFileCompressionInfoA(source, &name, &source_size, &target_size, NULL);
    ok(ret == ERROR_INVALID_PARAMETER, "SetupGetFileCompressionInfo failed unexpectedly\n");

    name = NULL;
    source_size = target_size = 0;
    type = 5;

    ret = SetupGetFileCompressionInfoA(source, &name, &source_size, &target_size, &type);
    ok(!ret, "SetupGetFileCompressionInfo failed unexpectedly\n");
    ok(name && !lstrcmpA(name, source), "got %s, expected %s\n", name, source);
    ok(source_size == sizeof(uncompressed), "got %d\n", source_size);
    ok(target_size == sizeof(uncompressed), "got %d\n", target_size);
    ok(type == FILE_COMPRESSION_NONE, "got %d, expected FILE_COMPRESSION_NONE\n", type);

    MyFree(name);
    DeleteFileA(source);
}

static void test_SetupGetFileCompressionInfoEx(void)
{
    BOOL ret;
    DWORD required_len, source_size, target_size;
    char source[MAX_PATH], temp[MAX_PATH], name[MAX_PATH];
    UINT type;

    GetTempPathA(sizeof(temp), temp);
    GetTempFileNameA(temp, "doc", 0, source);

    ret = pSetupGetFileCompressionInfoExA(NULL, NULL, 0, NULL, NULL, NULL, NULL);
    ok(!ret, "SetupGetFileCompressionInfoEx succeeded unexpectedly\n");

    ret = pSetupGetFileCompressionInfoExA(source, NULL, 0, NULL, NULL, NULL, NULL);
    ok(!ret, "SetupGetFileCompressionInfoEx succeeded unexpectedly\n");

    ret = pSetupGetFileCompressionInfoExA(source, NULL, 0, &required_len, NULL, NULL, NULL);
    ok(!ret, "SetupGetFileCompressionInfoEx succeeded unexpectedly\n");
    ok(required_len == lstrlenA(source) + 1, "got %d, expected %d\n", required_len, lstrlenA(source) + 1);

    create_source_file(source, comp_lzx, sizeof(comp_lzx));

    ret = pSetupGetFileCompressionInfoExA(source, name, sizeof(name), &required_len, &source_size, &target_size, &type);
    ok(ret, "SetupGetFileCompressionInfoEx failed unexpectedly: %d\n", ret);
    ok(!lstrcmpA(name, source), "got %s, expected %s\n", name, source);
    ok(required_len == lstrlenA(source) + 1, "got %d, expected %d\n", required_len, lstrlenA(source) + 1);
    ok(source_size == sizeof(comp_lzx), "got %d\n", source_size);
    ok(target_size == sizeof(uncompressed), "got %d\n", target_size);
    ok(type == FILE_COMPRESSION_WINLZA, "got %d, expected FILE_COMPRESSION_WINLZA\n", type);
    DeleteFileA(source);

    create_source_file(source, comp_zip, sizeof(comp_zip));

    ret = pSetupGetFileCompressionInfoExA(source, name, sizeof(name), &required_len, &source_size, &target_size, &type);
    ok(ret, "SetupGetFileCompressionInfoEx failed unexpectedly: %d\n", ret);
    ok(!lstrcmpA(name, source), "got %s, expected %s\n", name, source);
    ok(required_len == lstrlenA(source) + 1, "got %d, expected %d\n", required_len, lstrlenA(source) + 1);
    ok(source_size == sizeof(comp_zip), "got %d\n", source_size);
    ok(target_size == sizeof(comp_zip), "got %d\n", target_size);
    ok(type == FILE_COMPRESSION_NONE, "got %d, expected FILE_COMPRESSION_NONE\n", type);
    DeleteFileA(source);

    create_source_file(source, comp_cab_lzx, sizeof(comp_cab_lzx));

    ret = pSetupGetFileCompressionInfoExA(source, name, sizeof(name), &required_len, &source_size, &target_size, &type);
    ok(ret, "SetupGetFileCompressionInfoEx failed unexpectedly: %d\n", ret);
    ok(!lstrcmpA(name, source), "got %s, expected %s\n", name, source);
    ok(required_len == lstrlenA(source) + 1, "got %d, expected %d\n", required_len, lstrlenA(source) + 1);
    ok(source_size == sizeof(comp_cab_lzx), "got %d\n", source_size);
    ok(target_size == sizeof(uncompressed), "got %d\n", target_size);
    ok(type == FILE_COMPRESSION_MSZIP, "got %d, expected FILE_COMPRESSION_MSZIP\n", type);
    DeleteFileA(source);

    create_source_file(source, comp_cab_zip, sizeof(comp_cab_zip));

    ret = pSetupGetFileCompressionInfoExA(source, name, sizeof(name), &required_len, &source_size, &target_size, &type);
    ok(ret, "SetupGetFileCompressionInfoEx failed unexpectedly: %d\n", ret);
    ok(!lstrcmpA(name, source), "got %s, expected %s\n", name, source);
    ok(required_len == lstrlenA(source) + 1, "got %d, expected %d\n", required_len, lstrlenA(source) + 1);
    ok(source_size == sizeof(comp_cab_zip), "got %d\n", source_size);
    ok(target_size == sizeof(uncompressed), "got %d\n", target_size);
    ok(type == FILE_COMPRESSION_MSZIP, "got %d, expected FILE_COMPRESSION_MSZIP\n", type);
    DeleteFileA(source);
}

static void test_SetupDecompressOrCopyFile(void)
{
    DWORD ret;
    char source[MAX_PATH], target[MAX_PATH], temp[MAX_PATH], *p;
    UINT type;
    int i;

    const struct
    {
        PCSTR source;
        PCSTR target;
        PUINT type;
    } invalid_parameters[] =
    {
        {NULL,   NULL,   NULL},
        {NULL,   NULL,   &type},
        {NULL,   target, NULL},
        {NULL,   target, &type},
        {source, NULL,   NULL},
        {source, NULL,   &type},
    };

    const struct
    {
        const char *filename;
        const BYTE *expected_buffer;
        const size_t buffer_size;
    } zip_multi_tests[] =
    {
        {"tristram",     laurence, sizeof(laurence)},
        {"tristram.txt", laurence, sizeof(laurence)},
        {"wine",         laurence, sizeof(laurence)},
        {"wine.txt",     laurence, sizeof(laurence)},
        {"shandy",       laurence, sizeof(laurence)},
        {"shandy.txt",   laurence, sizeof(laurence)},
        {"deadbeef",     laurence, sizeof(laurence)},
        {"deadbeef.txt", laurence, sizeof(laurence)},
    };

    GetTempPathA(sizeof(temp), temp);
    GetTempFileNameA(temp, "doc", 0, source);
    GetTempFileNameA(temp, "doc", 0, target);

    /* parameter tests */

    create_source_file(source, uncompressed, sizeof(uncompressed));

    for (i = 0; i < sizeof(invalid_parameters)/sizeof(invalid_parameters[0]); i++)
    {
        type = FILE_COMPRESSION_NONE;
        ret = SetupDecompressOrCopyFileA(invalid_parameters[i].source,
                                         invalid_parameters[i].target,
                                         invalid_parameters[i].type);
        ok(ret == ERROR_INVALID_PARAMETER,
           "[%d] Expected SetupDecompressOrCopyFileA to return ERROR_INVALID_PARAMETER, got %u\n",
           i, ret);

        /* try an invalid compression type */
        type = 5;
        ret = SetupDecompressOrCopyFileA(invalid_parameters[i].source,
                                         invalid_parameters[i].target,
                                         invalid_parameters[i].type);
        ok(ret == ERROR_INVALID_PARAMETER,
           "[%d] Expected SetupDecompressOrCopyFileA to return ERROR_INVALID_PARAMETER, got %u\n",
           i, ret);
    }

    type = 5; /* try an invalid compression type */
    ret = SetupDecompressOrCopyFileA(source, target, &type);
    ok(ret == ERROR_INVALID_PARAMETER, "SetupDecompressOrCopyFile failed unexpectedly\n");

    DeleteFileA(target);

    /* no compression tests */

    ret = SetupDecompressOrCopyFileA(source, target, NULL);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    ok(compare_file_data(target, uncompressed, sizeof(uncompressed)), "incorrect target file\n");

    /* try overwriting existing file */
    ret = SetupDecompressOrCopyFileA(source, target, NULL);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    DeleteFileA(target);

    type = FILE_COMPRESSION_NONE;
    ret = SetupDecompressOrCopyFileA(source, target, &type);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    ok(compare_file_data(target, uncompressed, sizeof(uncompressed)), "incorrect target file\n");
    DeleteFileA(target);

    type = FILE_COMPRESSION_WINLZA;
    ret = SetupDecompressOrCopyFileA(source, target, &type);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    ok(compare_file_data(target, uncompressed, sizeof(uncompressed)), "incorrect target file\n");
    DeleteFileA(target);

    /* lz compression tests */

    create_source_file(source, comp_lzx, sizeof(comp_lzx));

    ret = SetupDecompressOrCopyFileA(source, target, NULL);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    DeleteFileA(target);

    /* zip compression tests */

    create_source_file(source, comp_zip, sizeof(comp_zip));

    ret = SetupDecompressOrCopyFileA(source, target, NULL);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    ok(compare_file_data(target, comp_zip, sizeof(comp_zip)), "incorrect target file\n");
    DeleteFileA(target);

    /* cabinet compression tests */

    create_source_file(source, comp_cab_zip, sizeof(comp_cab_zip));

    p = strrchr(target, '\\');
    lstrcpyA(p + 1, "wine");

    ret = SetupDecompressOrCopyFileA(source, target, NULL);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    ok(compare_file_data(target, uncompressed, sizeof(uncompressed)), "incorrect target file\n");

    /* try overwriting existing file */
    ret = SetupDecompressOrCopyFileA(source, target, NULL);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);

    /* try zip compression */
    type = FILE_COMPRESSION_MSZIP;
    ret = SetupDecompressOrCopyFileA(source, target, &type);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    ok(compare_file_data(target, uncompressed, sizeof(uncompressed)), "incorrect target file\n");

    /* try no compression */
    type = FILE_COMPRESSION_NONE;
    ret = SetupDecompressOrCopyFileA(source, target, &type);
    ok(!ret, "SetupDecompressOrCopyFile failed unexpectedly: %d\n", ret);
    ok(compare_file_data(target, comp_cab_zip, sizeof(comp_cab_zip)), "incorrect target file\n");

    /* Show that SetupDecompressOrCopyFileA simply extracts the first file it
     * finds within the compressed cabinet. Contents are:
     * tristram -> "laurence\r\n"
     * wine     -> "uncompressed\r\n"
     * shandy   -> "sterne\r\n" */

    create_source_file(source, comp_cab_zip_multi, sizeof(comp_cab_zip_multi));

    p = strrchr(target, '\\');

    for (i = 0; i < sizeof(zip_multi_tests)/sizeof(zip_multi_tests[0]); i++)
    {
        lstrcpyA(p + 1, zip_multi_tests[i].filename);

        ret = SetupDecompressOrCopyFileA(source, target, NULL);
        ok(!ret, "[%d] SetupDecompressOrCopyFile failed unexpectedly: %d\n", i, ret);
        ok(compare_file_data(target, zip_multi_tests[i].expected_buffer, zip_multi_tests[i].buffer_size),
           "[%d] incorrect target file\n", i);
        DeleteFileA(target);
    }

    DeleteFileA(source);
}

static void test_SetupUninstallOEMInf(void)
{
    BOOL ret;

    SetLastError(0xdeadbeef);
    ret = pSetupUninstallOEMInfA(NULL, 0, NULL);
    ok(!ret, "Expected failure\n");
    ok(GetLastError() == ERROR_INVALID_PARAMETER, "Expected ERROR_INVALID_PARAMETER, got %08x\n", GetLastError());

    SetLastError(0xdeadbeef);
    ret = pSetupUninstallOEMInfA("", 0, NULL);
    todo_wine
    {
    ok(!ret, "Expected failure\n");
    ok(GetLastError() == ERROR_FILE_NOT_FOUND, "Expected ERROR_FILE_NOT_FOUND, got %08x\n", GetLastError());
    }

    SetLastError(0xdeadbeef);
    ret = pSetupUninstallOEMInfA("nonexistent.inf", 0, NULL);
    todo_wine
    {
    ok(!ret, "Expected failure\n");
    ok(GetLastError() == ERROR_FILE_NOT_FOUND, "Expected ERROR_FILE_NOT_FOUND, got %08x\n", GetLastError());
    }
}

struct default_callback_context
{
    DWORD     magic;
    HWND      owner;
    DWORD     unk1[4];
    DWORD_PTR unk2[7];
    HWND      progress;
    UINT      message;
    DWORD_PTR unk3[5];
};

static void test_defaultcallback(void)
{
    struct default_callback_context *ctxt;
    static const DWORD magic = 0x43515053; /* "SPQC" */
    HWND owner, progress;

    owner = (HWND)0x123;
    progress = (HWND)0x456;
    ctxt = SetupInitDefaultQueueCallbackEx(owner, progress, WM_USER, 0, NULL);
    ok(ctxt != NULL, "got %p\n", ctxt);

    ok(ctxt->magic == magic || broken(ctxt->magic != magic) /* win2000 */, "got magic 0x%08x\n", ctxt->magic);
    if (ctxt->magic == magic)
    {
        ok(ctxt->owner == owner, "got %p, expected %p\n", ctxt->owner, owner);
        ok(ctxt->progress == progress, "got %p, expected %p\n", ctxt->progress, progress);
        ok(ctxt->message == WM_USER, "got %d, expected %d\n", ctxt->message, WM_USER);
        SetupTermDefaultQueueCallback(ctxt);
    }
    else
    {
        win_skip("Skipping tests on old systems.\n");
        SetupTermDefaultQueueCallback(ctxt);
        return;
    }

    ctxt = SetupInitDefaultQueueCallback(owner);
    ok(ctxt->magic == magic, "got magic 0x%08x\n", ctxt->magic);
    ok(ctxt->owner == owner, "got %p, expected %p\n", ctxt->owner, owner);
    ok(ctxt->progress == NULL, "got %p, expected %p\n", ctxt->progress, progress);
    ok(ctxt->message == 0, "got %d\n", ctxt->message);
    SetupTermDefaultQueueCallback(ctxt);
}

static void test_SetupLogError(void)
{
    BOOL ret;
    DWORD error;

    SetLastError(0xdeadbeef);
    ret = SetupLogErrorA("Test without opening\r\n", LogSevInformation);
    error = GetLastError();
    ok(!ret, "SetupLogError succeeded\n");
    ok(error == ERROR_FILE_INVALID, "got wrong error: %d\n", error);

    SetLastError(0xdeadbeef);
    ret = SetupOpenLog(FALSE);
    if (!ret && GetLastError() == ERROR_ACCESS_DENIED)
    {
        skip("SetupOpenLog() failed on insufficient permissions\n");
        return;
    }
    ok(ret, "SetupOpenLog failed, error %d\n", GetLastError());

    SetLastError(0xdeadbeef);
    ret = SetupLogErrorA("Test with wrong log severity\r\n", LogSevMaximum);
    error = GetLastError();
    ok(!ret, "SetupLogError succeeded\n");
    ok(error == 0xdeadbeef, "got wrong error: %d\n", error);
    ret = SetupLogErrorA("Test without EOL", LogSevInformation);
    ok(ret, "SetupLogError failed\n");

    SetLastError(0xdeadbeef);
    ret = SetupLogErrorA(NULL, LogSevInformation);
    ok(ret || broken(!ret && GetLastError() == ERROR_INVALID_PARAMETER /* Win Vista+ */),
        "SetupLogError failed: %08x\n", GetLastError());

    SetLastError(0xdeadbeef);
    ret = SetupOpenLog(FALSE);
    ok(ret, "SetupOpenLog failed, error %d\n", GetLastError());

    SetupCloseLog();
}

static void test_CM_Get_Version(void)
{
    WORD ret;

    ret = CM_Get_Version();
    ok(ret == 0x0400, "got version %#x\n", ret);
}

START_TEST(misc)
{
    HMODULE hsetupapi = GetModuleHandleA("setupapi.dll");

    pSetupGetFileCompressionInfoExA = (void*)GetProcAddress(hsetupapi, "SetupGetFileCompressionInfoExA");
    pSetupCopyOEMInfA = (void*)GetProcAddress(hsetupapi, "SetupCopyOEMInfA");
    pSetupQueryInfOriginalFileInformationA = (void*)GetProcAddress(hsetupapi, "SetupQueryInfOriginalFileInformationA");
    pSetupUninstallOEMInfA = (void*)GetProcAddress(hsetupapi, "SetupUninstallOEMInfA");

    GetCurrentDirectoryA(MAX_PATH, CURR_DIR);

    if (pSetupCopyOEMInfA)
        test_SetupCopyOEMInf();
    else
        win_skip("SetupCopyOEMInfA is not available\n");

    test_SetupGetFileCompressionInfo();

    if (pSetupGetFileCompressionInfoExA)
        test_SetupGetFileCompressionInfoEx();
    else
        win_skip("SetupGetFileCompressionInfoExA is not available\n");

    test_SetupDecompressOrCopyFile();

    if (pSetupUninstallOEMInfA)
        test_SetupUninstallOEMInf();
    else
        win_skip("SetupUninstallOEMInfA is not available\n");

    test_defaultcallback();

    test_SetupLogError();
    test_CM_Get_Version();
}
