From 78280ad21f833411074e7ff0aa3d3345fb9e0704 Mon Sep 17 00:00:00 2001 From: Mark Jansen Date: Sun, 1 May 2016 19:12:06 +0000 Subject: [PATCH] =?UTF-8?q?[APPHELP][APPHELP=5FAPITEST]=20Add=20SdbGetFile?= =?UTF-8?q?Attributes=20+=20tests,=20based=20on=20the=20work=20of=20Mislav?= =?UTF-8?q?=20Bla=C5=BEevi=C4=87=20CORE-10367=20-=20Implement=20SdbGetFile?= =?UTF-8?q?Attributes=20(based=20on=20the=20work=20of=20Mislav=20Bla=C5=BE?= =?UTF-8?q?evi=C4=87)=20-=20Add=20tests=20for=20SdbGetFileAttributes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit svn path=/trunk/; revision=71226 --- reactos/dll/appcompat/apphelp/CMakeLists.txt | 8 +- reactos/dll/appcompat/apphelp/apphelp.h | 26 + reactos/dll/appcompat/apphelp/apphelp.spec | 4 +- reactos/dll/appcompat/apphelp/sdbapi.c | 93 ++- reactos/dll/appcompat/apphelp/sdbfileattr.c | 371 ++++++++++++ rostests/apitests/apphelp/CMakeLists.txt | 3 +- rostests/apitests/apphelp/apphelp.c | 441 ++++++++++++++ rostests/apitests/apphelp/data.c | 573 +++++++++++++++++++ 8 files changed, 1508 insertions(+), 11 deletions(-) create mode 100644 reactos/dll/appcompat/apphelp/sdbfileattr.c create mode 100644 rostests/apitests/apphelp/data.c diff --git a/reactos/dll/appcompat/apphelp/CMakeLists.txt b/reactos/dll/appcompat/apphelp/CMakeLists.txt index e1beb19f497..58642ffb3d0 100644 --- a/reactos/dll/appcompat/apphelp/CMakeLists.txt +++ b/reactos/dll/appcompat/apphelp/CMakeLists.txt @@ -3,8 +3,9 @@ spec2def(apphelp.dll apphelp.spec ADD_IMPORTLIB) list(APPEND SOURCE apphelp.c - sdbapi.c layer.c + sdbapi.c + sdbfileattr.c apphelp.spec apphelp.h ${CMAKE_CURRENT_BINARY_DIR}/apphelp_stubs.c) @@ -15,6 +16,7 @@ add_library(apphelp SHARED set_module_type(apphelp win32dll) target_link_libraries(apphelp wine) -#add_delay_importlibs(apphelp version imagehlp user32) -add_importlibs(apphelp msvcrt kernel32 ntdll) +# When binutils is fixed, we should move imagehlp to delay! CORE-6504 +add_delay_importlibs(apphelp version) +add_importlibs(apphelp msvcrt imagehlp kernel32 ntdll) add_cd_file(TARGET apphelp DESTINATION reactos/system32 FOR all) diff --git a/reactos/dll/appcompat/apphelp/apphelp.h b/reactos/dll/appcompat/apphelp/apphelp.h index 1665b7f6730..a0eda949346 100644 --- a/reactos/dll/appcompat/apphelp/apphelp.h +++ b/reactos/dll/appcompat/apphelp/apphelp.h @@ -32,6 +32,16 @@ typedef UINT64 QWORD; #define TAGREF_NULL (0) #define TAGREF_ROOT (0) +typedef struct tagATTRINFO { + TAG type; + DWORD flags; + union { + QWORD qwattr; + DWORD dwattr; + WCHAR *lpattr; + }; +} ATTRINFO, *PATTRINFO; + typedef enum _SHIM_LOG_LEVEL { SHIM_ERR = 1, SHIM_WARN = 2, @@ -72,6 +82,19 @@ void SdbpFree(LPVOID mem); #endif +typedef struct tagMEMMAPPED { + HANDLE file; + HANDLE section; + PBYTE view; + SIZE_T size; + SIZE_T mapped_size; +} MEMMAPPED, *PMEMMAPPED; + +BOOL WINAPI SdbpOpenMemMappedFile(LPCWSTR path, PMEMMAPPED mapping); +void WINAPI SdbpCloseMemMappedFile(PMEMMAPPED mapping); +DWORD SdbpStrlen(LPCWSTR string); +PWSTR SdbpStrDup(LPCWSTR string); + /* layer.c */ BOOL WINAPI AllowPermLayer(PCWSTR path); @@ -79,6 +102,9 @@ BOOL WINAPI SdbGetPermLayerKeys(PCWSTR wszPath, PWSTR pwszLayers, PDWORD pdwByte BOOL WINAPI SetPermLayerState(PCWSTR wszPath, PCWSTR wszLayer, DWORD dwFlags, BOOL bMachine, BOOL bEnable); +#define ATTRIBUTE_AVAILABLE 0x1 +#define ATTRIBUTE_FAILED 0x2 + #define TAGID_NULL 0x0 #define TAGID_ROOT 0x0 diff --git a/reactos/dll/appcompat/apphelp/apphelp.spec b/reactos/dll/appcompat/apphelp/apphelp.spec index 78f62c00b22..5323c9ba31b 100644 --- a/reactos/dll/appcompat/apphelp/apphelp.spec +++ b/reactos/dll/appcompat/apphelp/apphelp.spec @@ -48,7 +48,7 @@ @ stub SdbFindNextTag @ stub SdbFindNextTagRef @ stub SdbFreeDatabaseInformation -@ stub SdbFreeFileAttributes +@ stdcall SdbFreeFileAttributes(ptr) @ stub SdbFreeFileInfo @ stub SdbFreeFlagInfo @ stub SdbGetAppCompatDataSize @@ -61,7 +61,7 @@ @ stub SdbGetDatabaseVersion @ stub SdbGetDllPath @ stub SdbGetEntryFlags -@ stub SdbGetFileAttributes +@ stdcall SdbGetFileAttributes(wstr ptr ptr) @ stub SdbGetFileImageType @ stub SdbGetFileImageTypeEx @ stub SdbGetFileInfo diff --git a/reactos/dll/appcompat/apphelp/sdbapi.c b/reactos/dll/appcompat/apphelp/sdbapi.c index 5c99d37d3fc..fc6f2e32b0e 100644 --- a/reactos/dll/appcompat/apphelp/sdbapi.c +++ b/reactos/dll/appcompat/apphelp/sdbapi.c @@ -155,11 +155,6 @@ void SdbpHeapDeinit(void) HeapDestroy(g_Heap); } -DWORD SdbpStrlen(PCWSTR string) -{ - return (lstrlenW(string) + 1) * sizeof(WCHAR); -} - static HANDLE SdbpHeap(void) { return g_Heap; @@ -203,6 +198,94 @@ void SdbpFree(LPVOID mem HeapFree(SdbpHeap(), 0, mem); } +DWORD SdbpStrlen(PCWSTR string) +{ + return (lstrlenW(string) + 1) * sizeof(WCHAR); +} + +PWSTR SdbpStrDup(LPCWSTR string) +{ + PWSTR ret = SdbpAlloc(SdbpStrlen(string)); + lstrcpyW(ret, string); + return ret; +} + + +BOOL WINAPI SdbpOpenMemMappedFile(LPCWSTR path, PMEMMAPPED mapping) +{ + NTSTATUS Status; + OBJECT_ATTRIBUTES ObjectAttributes; + IO_STATUS_BLOCK IoStatusBlock; + FILE_STANDARD_INFORMATION FileStandard; + UNICODE_STRING FileName; + + RtlZeroMemory(mapping, sizeof(*mapping)); + + if(!RtlDosPathNameToNtPathName_U(path, &FileName, NULL, NULL)) + { + RtlFreeUnicodeString(&FileName); + return FALSE; + } + + InitializeObjectAttributes(&ObjectAttributes, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL); + Status = NtOpenFile(&mapping->file, GENERIC_READ | SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); + RtlFreeUnicodeString(&FileName); + + if (!NT_SUCCESS(Status)) + { + SHIM_ERR("Failed to open file %S: 0x%lx\n", path, Status); + return FALSE; + } + + Status = NtCreateSection(&mapping->section, STANDARD_RIGHTS_REQUIRED | SECTION_QUERY | SECTION_MAP_READ, 0, 0, PAGE_READONLY, SEC_COMMIT, mapping->file); + if (!NT_SUCCESS(Status)) + { + /* Special case */ + if (Status == STATUS_MAPPED_FILE_SIZE_ZERO) + { + NtClose(mapping->file); + mapping->file = mapping->section = NULL; + return TRUE; + } + SHIM_ERR("Failed to create mapping for file: 0x%lx\n", Status); + goto err_out; + } + + Status = NtQueryInformationFile(mapping->file, &IoStatusBlock, &FileStandard, sizeof(FileStandard), FileStandardInformation); + if (!NT_SUCCESS(Status)) + { + SHIM_ERR("Failed to read file info for file: 0x%lx\n", Status); + goto err_out; + } + + mapping->mapped_size = mapping->size = FileStandard.EndOfFile.LowPart; + Status = NtMapViewOfSection(mapping->section, NtCurrentProcess(), (PVOID*)&mapping->view, 0, 0, 0, &mapping->mapped_size, ViewUnmap, 0, PAGE_READONLY); + if (!NT_SUCCESS(Status)) + { + SHIM_ERR("Failed to map view of file: 0x%lx\n", Status); + goto err_out; + } + + return TRUE; + +err_out: + if (!mapping->view) + { + if (mapping->section) + NtClose(mapping->section); + NtClose(mapping->file); + } + return FALSE; +} + +void WINAPI SdbpCloseMemMappedFile(PMEMMAPPED mapping) +{ + NtUnmapViewOfSection(NtCurrentProcess(), mapping->view); + NtClose(mapping->section); + NtClose(mapping->file); + RtlZeroMemory(mapping, sizeof(*mapping)); +} + /** * Converts specified tag into a string. * diff --git a/reactos/dll/appcompat/apphelp/sdbfileattr.c b/reactos/dll/appcompat/apphelp/sdbfileattr.c new file mode 100644 index 00000000000..143fe16b1a6 --- /dev/null +++ b/reactos/dll/appcompat/apphelp/sdbfileattr.c @@ -0,0 +1,371 @@ +/* + * Copyright 2011 André Hentschel + * Copyright 2013 Mislav Blaževic + * Copyright 2015 Mark Jansen + * + * 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 "windef.h" +#include "winbase.h" +#include "apphelp.h" +#include "imagehlp.h" +#include "winver.h" + +#include "wine/unicode.h" + +#define NUM_ATTRIBUTES 28 + +static void WINAPI SdbpSetDWORDAttr(PATTRINFO attr, TAG tag, DWORD value) +{ + attr->type = tag; + attr->flags = ATTRIBUTE_AVAILABLE; + attr->dwattr = value; +} + +static void WINAPI SdbpSetQWORDAttr(PATTRINFO attr, TAG tag, QWORD value) +{ + attr->type = tag; + attr->flags = ATTRIBUTE_AVAILABLE; + attr->qwattr = value; +} + +static void WINAPI SdbpSetStringAttr(PATTRINFO attr, TAG tag, WCHAR *string) +{ + if (!string) + { + attr->flags = ATTRIBUTE_FAILED; + return; + } + + attr->type = tag; + attr->flags = ATTRIBUTE_AVAILABLE; + attr->lpattr = SdbpStrDup(string); +} + +static void WINAPI SdbpSetAttrFail(PATTRINFO attr) +{ + attr->flags = ATTRIBUTE_FAILED; +} + +static WCHAR* WINAPI SdbpGetStringAttr(LPWSTR translation, LPCWSTR attr, PVOID file_info) +{ + UINT size = 0; + PVOID buffer; + WCHAR value[128] = {0}; + + if (!file_info) + return NULL; + + snprintfW(value, 128, translation, attr); + if (VerQueryValueW(file_info, value, &buffer, &size) && size != 0) + return (WCHAR*)buffer; + + return NULL; +} + +static void WINAPI SdbpSetStringAttrFromAnsiString(PATTRINFO attr, TAG tag, PBYTE string, BYTE len) +{ + WCHAR* dest; + if (!string) + { + attr->flags = ATTRIBUTE_FAILED; + return; + } + + attr->type = tag; + attr->flags = ATTRIBUTE_AVAILABLE; + dest = attr->lpattr = SdbpAlloc((len+1) * sizeof(WCHAR)); + while (len--) + *(dest++) = *(string++); + *dest = 0; +} + +static void WINAPI SdbpSetStringAttrFromPascalString(PATTRINFO attr, TAG tag, PBYTE string) +{ + if (!string) + { + attr->flags = ATTRIBUTE_FAILED; + return; + } + + SdbpSetStringAttrFromAnsiString(attr, tag, string + 1, *string); +} + +static void SdbpReadFileVersion(PATTRINFO attr_info, PVOID file_info) +{ + static const WCHAR str_root[] = {'\\',0}; + + VS_FIXEDFILEINFO* fixed_info; + UINT size; + if (file_info && VerQueryValueW(file_info, str_root, (LPVOID*)&fixed_info, &size) && size) + { + if (fixed_info->dwSignature == VS_FFI_SIGNATURE) + { + LARGE_INTEGER version; + version.HighPart = fixed_info->dwFileVersionMS; + version.LowPart = fixed_info->dwFileVersionLS; + SdbpSetQWORDAttr(&attr_info[2], TAG_BIN_FILE_VERSION, version.QuadPart); + SdbpSetQWORDAttr(&attr_info[21], TAG_UPTO_BIN_FILE_VERSION, version.QuadPart); + version.HighPart = fixed_info->dwProductVersionMS; + version.LowPart = fixed_info->dwProductVersionLS; + SdbpSetQWORDAttr(&attr_info[3], TAG_BIN_PRODUCT_VERSION, version.QuadPart); + SdbpSetQWORDAttr(&attr_info[22], TAG_UPTO_BIN_PRODUCT_VERSION, version.QuadPart); + + SdbpSetDWORDAttr(&attr_info[12], TAG_VERDATEHI, fixed_info->dwFileDateMS); + SdbpSetDWORDAttr(&attr_info[13], TAG_VERDATELO, fixed_info->dwFileDateLS); + SdbpSetDWORDAttr(&attr_info[14], TAG_VERFILEOS, fixed_info->dwFileOS); /* 0x000, 0x4, 0x40004, 0x40000, 0x10004, 0x10001*/ + SdbpSetDWORDAttr(&attr_info[15], TAG_VERFILETYPE, fixed_info->dwFileType); /* VFT_APP, VFT_DLL, .... */ + return; + } + } + + SdbpSetAttrFail(&attr_info[2]); + SdbpSetAttrFail(&attr_info[3]); + SdbpSetAttrFail(&attr_info[12]); + SdbpSetAttrFail(&attr_info[13]); + SdbpSetAttrFail(&attr_info[14]); + SdbpSetAttrFail(&attr_info[15]); + SdbpSetAttrFail(&attr_info[21]); + SdbpSetAttrFail(&attr_info[22]); +} + +static DWORD WINAPI SdbpCalculateFileChecksum(PMEMMAPPED mapping) +{ + size_t n, size; + PDWORD data; + DWORD checks = 0, carry = 0; + + if (mapping->size < 4) + return 0; + + if (mapping->size >= 0x1000) + { + size = 0x1000; + if (mapping->size < 0x1200) + data = (PDWORD)(mapping->view + mapping->size - size); + else + data = (PDWORD)mapping->view + (0x200 / 4); + } + else + { + data = (PDWORD)mapping->view; + size = mapping->size; + } + + for (n = 0; n < size / 4; ++n) + { + checks += *data; + carry = (checks & 1) ? 0x80000000 : 0; + checks >>= 1; + checks |= carry; + ++data; + } + return checks; +} + +static DWORD WINAPI SdbpGetModuleType(PMEMMAPPED mapping) +{ + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)mapping->view; + PIMAGE_OS2_HEADER os2; + + if (mapping->size < 2 || dos->e_magic != IMAGE_DOS_SIGNATURE) + return 0; + + if (mapping->size < sizeof(IMAGE_DOS_HEADER) || mapping->size < (dos->e_lfanew+2)) + return 1; + + os2 = (PIMAGE_OS2_HEADER)((PBYTE)dos + dos->e_lfanew); + if (os2->ne_magic == IMAGE_OS2_SIGNATURE || os2->ne_magic == IMAGE_OS2_SIGNATURE_LE) + return 2; + + if (mapping->size >= (dos->e_lfanew + 4) && ((PIMAGE_NT_HEADERS)os2)->Signature == IMAGE_NT_SIGNATURE) + return 3; + + return 1; +} + +/** + * Frees attribute data allocated by SdbGetFileAttributes. + * + * @note Unlike Windows, this implementation will not crash if attr_info is NULL. + * + * @param [in] attr_info Pointer to array of ATTRINFO which will be freed. + * + * @return TRUE if it succeeds, FALSE if it fails. + */ +BOOL WINAPI SdbFreeFileAttributes(PATTRINFO attr_info) +{ + WORD i; + + if (!attr_info) + return FALSE; + + for (i = 0; i < NUM_ATTRIBUTES; i++) + if ((attr_info[i].type & TAG_TYPE_MASK) == TAG_TYPE_STRINGREF) + SdbFree(attr_info[i].lpattr); + SdbFree(attr_info); + return TRUE; +} + +/** + * Retrieves attribute data shim database requires to match a file with database entry + * + * @note You must free the attr_info allocated by this function by calling SdbFreeFileAttributes. + * + * @param [in] path Path to the file. + * @param [out] attr_info_ret Pointer to array of ATTRINFO. Contains attribute data. + * @param [out] attr_count Number of attributes in attr_info. + * + * @return TRUE if it succeeds, FALSE if it fails. + */ +BOOL WINAPI SdbGetFileAttributes(LPCWSTR path, PATTRINFO *attr_info_ret, LPDWORD attr_count) +{ + static const WCHAR str_tinfo[] = {'\\','V','a','r','F','i','l','e','I','n','f','o','\\','T','r','a','n','s','l','a','t','i','o','n',0}; + static const WCHAR str_trans[] = {'\\','S','t','r','i','n','g','F','i','l','e','I','n','f','o','\\','%','0','4','x','%','0','4','x','\\','%','%','s',0}; + static const WCHAR str_CompanyName[] = {'C','o','m','p','a','n','y','N','a','m','e',0}; + static const WCHAR str_FileDescription[] = {'F','i','l','e','D','e','s','c','r','i','p','t','i','o','n',0}; + static const WCHAR str_FileVersion[] = {'F','i','l','e','V','e','r','s','i','o','n',0}; + static const WCHAR str_InternalName[] = {'I','n','t','e','r','n','a','l','N','a','m','e',0}; + static const WCHAR str_LegalCopyright[] = {'L','e','g','a','l','C','o','p','y','r','i','g','h','t',0}; + static const WCHAR str_OriginalFilename[] = {'O','r','i','g','i','n','a','l','F','i','l','e','n','a','m','e',0}; + static const WCHAR str_ProductName[] = {'P','r','o','d','u','c','t','N','a','m','e',0}; + static const WCHAR str_ProductVersion[] = {'P','r','o','d','u','c','t','V','e','r','s','i','o','n',0}; + + PIMAGE_NT_HEADERS headers; + MEMMAPPED mapped; + PVOID file_info = 0; + DWORD headersum, checksum, module_type; + WCHAR translation[128] = {0}; + PATTRINFO attr_info; + + struct LANGANDCODEPAGE { + WORD language; + WORD code_page; + } *lang_page; + + if (!SdbpOpenMemMappedFile(path, &mapped)) + { + SHIM_ERR("Error retrieving FILEINFO structure\n"); + return FALSE; + } + + attr_info = (PATTRINFO)SdbAlloc(NUM_ATTRIBUTES * sizeof(ATTRINFO)); + + SdbpSetDWORDAttr(&attr_info[0], TAG_SIZE, mapped.size); + if (mapped.size) + SdbpSetDWORDAttr(&attr_info[1], TAG_CHECKSUM, SdbpCalculateFileChecksum(&mapped)); + else + SdbpSetAttrFail(&attr_info[1]); + module_type = SdbpGetModuleType(&mapped); + + if (module_type) + SdbpSetDWORDAttr(&attr_info[16], TAG_MODULE_TYPE, module_type); + else + SdbpSetAttrFail(&attr_info[16]); /* TAG_MODULE_TYPE */ + + headers = CheckSumMappedFile(mapped.view, mapped.size, &headersum, &checksum); + if (headers) + { + DWORD info_size; + SIZE_T export_dir_size; + PIMAGE_EXPORT_DIRECTORY export_dir; + + info_size = GetFileVersionInfoSizeW(path, NULL); + if (info_size != 0) + { + UINT page_size = 0; + file_info = SdbAlloc(info_size); + GetFileVersionInfoW(path, 0, info_size, file_info); + VerQueryValueW(file_info, str_tinfo, (LPVOID)&lang_page, &page_size); + snprintfW(translation, 128, str_trans, lang_page->language, lang_page->code_page); + } + + /* Handles 2, 3, 12, 13, 14, 15, 21, 22 */ + SdbpReadFileVersion(attr_info, file_info); + + SdbpSetStringAttr(&attr_info[4], TAG_PRODUCT_VERSION, SdbpGetStringAttr(translation, str_ProductVersion, file_info)); + SdbpSetStringAttr(&attr_info[5], TAG_FILE_DESCRIPTION, SdbpGetStringAttr(translation, str_FileDescription, file_info)); + SdbpSetStringAttr(&attr_info[6], TAG_COMPANY_NAME, SdbpGetStringAttr(translation, str_CompanyName, file_info)); + SdbpSetStringAttr(&attr_info[7], TAG_PRODUCT_NAME, SdbpGetStringAttr(translation, str_ProductName, file_info)); + SdbpSetStringAttr(&attr_info[8], TAG_FILE_VERSION, SdbpGetStringAttr(translation, str_FileVersion, file_info)); + SdbpSetStringAttr(&attr_info[9], TAG_ORIGINAL_FILENAME, SdbpGetStringAttr(translation, str_OriginalFilename, file_info)); + SdbpSetStringAttr(&attr_info[10], TAG_INTERNAL_NAME, SdbpGetStringAttr(translation, str_InternalName, file_info)); + SdbpSetStringAttr(&attr_info[11], TAG_LEGAL_COPYRIGHT, SdbpGetStringAttr(translation, str_LegalCopyright, file_info)); + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx */ + + SdbpSetDWORDAttr(&attr_info[17], TAG_PE_CHECKSUM, headers->OptionalHeader.CheckSum); + + SdbpSetDWORDAttr(&attr_info[18], TAG_LINKER_VERSION, /* mislabeled! */ + ((DWORD)headers->OptionalHeader.MajorImageVersion) << 16 | headers->OptionalHeader.MinorImageVersion); + SdbpSetAttrFail(&attr_info[19]); /* TAG_16BIT_DESCRIPTION */ + SdbpSetAttrFail(&attr_info[20]); /* TAG_16BIT_MODULE_NAME */ + + SdbpSetDWORDAttr(&attr_info[23], TAG_LINK_DATE, headers->FileHeader.TimeDateStamp); + SdbpSetDWORDAttr(&attr_info[24], TAG_UPTO_LINK_DATE, headers->FileHeader.TimeDateStamp); + + export_dir = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToData(mapped.view, FALSE, IMAGE_DIRECTORY_ENTRY_EXPORT, &export_dir_size); + if (export_dir) + { + PIMAGE_SECTION_HEADER section = NULL; + PBYTE export_name = ImageRvaToVa(headers, mapped.view, export_dir->Name, §ion); + if (export_name) + SdbpSetStringAttrFromAnsiString(&attr_info[25], TAG_EXPORT_NAME, export_name, strlen((char*)export_name)); + else + SdbpSetAttrFail(&attr_info[25]); /* TAG_EXPORT_NAME */ + } + else + { + SdbpSetAttrFail(&attr_info[25]); /* TAG_EXPORT_NAME */ + } + + if (info_size) + SdbpSetDWORDAttr(&attr_info[26], TAG_VER_LANGUAGE, lang_page->language); + + SdbpSetDWORDAttr(&attr_info[27], TAG_EXE_WRAPPER, 0); /* boolean */ + } + else + { + int n; + for (n = 2; n < NUM_ATTRIBUTES; ++n) + { + if (n != 16 && n != 26) + SdbpSetAttrFail(&attr_info[n]); + } + if (module_type == 2) + { + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)mapped.view; + PBYTE end = mapped.view + mapped.size, ptr; + PIMAGE_OS2_HEADER os2 = (PIMAGE_OS2_HEADER)((PBYTE)dos + dos->e_lfanew); + if ((PBYTE)(os2 + 1) <= end) + { + ptr = (PBYTE)dos + os2->ne_nrestab; + if (ptr <= end && (ptr + 1 + *ptr) <= end) + SdbpSetStringAttrFromPascalString(&attr_info[19], TAG_16BIT_DESCRIPTION, ptr); + ptr = (PBYTE)os2 + os2->ne_restab; + if (ptr <= end && (ptr + 1 + *ptr) <= end) + SdbpSetStringAttrFromPascalString(&attr_info[20], TAG_16BIT_MODULE_NAME, ptr); + } + } + } + + *attr_info_ret = attr_info; + *attr_count = NUM_ATTRIBUTES; /* As far as I know, this one is always 28 */ + + SdbFree(file_info); + SdbpCloseMemMappedFile(&mapped); + return TRUE; +} diff --git a/rostests/apitests/apphelp/CMakeLists.txt b/rostests/apitests/apphelp/CMakeLists.txt index be21b2bf8ff..cd1376dee25 100644 --- a/rostests/apitests/apphelp/CMakeLists.txt +++ b/rostests/apitests/apphelp/CMakeLists.txt @@ -2,8 +2,9 @@ add_definitions(-D__ROS_LONG64__) list(APPEND SOURCE - layerapi.c apphelp.c + data.c + layerapi.c testlist.c) add_executable(apphelp_apitest ${SOURCE}) diff --git a/rostests/apitests/apphelp/apphelp.c b/rostests/apitests/apphelp/apphelp.c index 8900c7a1bb3..f065ba82c21 100644 --- a/rostests/apitests/apphelp/apphelp.c +++ b/rostests/apitests/apphelp/apphelp.c @@ -35,6 +35,15 @@ #include "wine/test.h" +/* data.c */ +void test_create_exe_imp(const char* name, int skip_rsrc_exports); +void test_create_file_imp(const char* name, const char* contents, size_t len); +void test_create_ne_imp(const char* name, int skip_names); + +#define test_create_exe (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : test_create_exe_imp +#define test_create_file (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : test_create_file_imp +#define test_create_ne (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : test_create_ne_imp + static DWORD g_Version; @@ -48,6 +57,9 @@ static DWORD g_Version; typedef WORD TAG; +typedef DWORD TAGID; +typedef DWORD TAGREF; +typedef UINT64 QWORD; #define TAG_TYPE_MASK 0xF000 @@ -60,10 +72,55 @@ typedef WORD TAG; #define TAG_TYPE_LIST 0x7000 #define TAG_TYPE_STRING 0x8000 #define TAG_TYPE_BINARY 0x9000 +#define TAG_NULL 0x0 +#define TAG_SIZE (0x1 | TAG_TYPE_DWORD) +#define TAG_CHECKSUM (0x3 | TAG_TYPE_DWORD) +#define TAG_MODULE_TYPE (0x6 | TAG_TYPE_DWORD) +#define TAG_VERDATEHI (0x7 | TAG_TYPE_DWORD) +#define TAG_VERDATELO (0x8 | TAG_TYPE_DWORD) +#define TAG_VERFILEOS (0x9 | TAG_TYPE_DWORD) +#define TAG_VERFILETYPE (0xA | TAG_TYPE_DWORD) +#define TAG_PE_CHECKSUM (0xB | TAG_TYPE_DWORD) +#define TAG_VER_LANGUAGE (0x12 | TAG_TYPE_DWORD) +#define TAG_LINKER_VERSION (0x1C | TAG_TYPE_DWORD) +#define TAG_LINK_DATE (0x1D | TAG_TYPE_DWORD) +#define TAG_UPTO_LINK_DATE (0x1E | TAG_TYPE_DWORD) +#define TAG_EXE_WRAPPER (0x31 | TAG_TYPE_DWORD) +#define TAG_BIN_FILE_VERSION (0x2 | TAG_TYPE_QWORD) +#define TAG_BIN_PRODUCT_VERSION (0x3 | TAG_TYPE_QWORD) +#define TAG_UPTO_BIN_PRODUCT_VERSION (0x6 | TAG_TYPE_QWORD) +#define TAG_UPTO_BIN_FILE_VERSION (0xD | TAG_TYPE_QWORD) +#define TAG_NAME (0x1 | TAG_TYPE_STRINGREF) +#define TAG_COMPANY_NAME (0x9 | TAG_TYPE_STRINGREF) +#define TAG_PRODUCT_NAME (0x10 | TAG_TYPE_STRINGREF) +#define TAG_PRODUCT_VERSION (0x11 | TAG_TYPE_STRINGREF) +#define TAG_FILE_DESCRIPTION (0x12 | TAG_TYPE_STRINGREF) +#define TAG_FILE_VERSION (0x13 | TAG_TYPE_STRINGREF) +#define TAG_ORIGINAL_FILENAME (0x14 | TAG_TYPE_STRINGREF) +#define TAG_INTERNAL_NAME (0x15 | TAG_TYPE_STRINGREF) +#define TAG_LEGAL_COPYRIGHT (0x16 | TAG_TYPE_STRINGREF) +#define TAG_16BIT_DESCRIPTION (0x17 | TAG_TYPE_STRINGREF) +#define TAG_16BIT_MODULE_NAME (0x20 | TAG_TYPE_STRINGREF) +#define TAG_EXPORT_NAME (0x24 | TAG_TYPE_STRINGREF) +#define ATTRIBUTE_AVAILABLE 0x1 +#define ATTRIBUTE_FAILED 0x2 + +typedef struct tagATTRINFO { + TAG type; + DWORD flags; /* ATTRIBUTE_AVAILABLE, ATTRIBUTE_FAILED */ + union { + QWORD qwattr; + DWORD dwattr; + WCHAR *lpattr; + }; +} ATTRINFO, *PATTRINFO; + static HMODULE hdll; static LPCWSTR (WINAPI *pSdbTagToString)(TAG); +static BOOL (WINAPI *pSdbGetFileAttributes)(LPCWSTR, PATTRINFO *, LPDWORD); +static BOOL (WINAPI *pSdbFreeFileAttributes)(PATTRINFO); static void test_SdbTagToString(void) { @@ -368,6 +425,386 @@ static void test_SdbTagToStringAllTags(void) } } +static void expect_tag_skip_imp(PATTRINFO pattr, DWORD num) +{ + PATTRINFO p = &pattr[num]; + winetest_ok(p->type == TAG_NULL, "expected entry #%d to be TAG_NULL, was %x\n", num, p->type); + winetest_ok(p->flags == ATTRIBUTE_FAILED, "expected entry #%d to be failed, was %d\n", num, p->flags); + winetest_ok(p->qwattr == 0, "expected entry #%d to be 0, was 0x%I64x\n", num, p->qwattr); +} +static void expect_tag_empty_imp(PATTRINFO pattr, DWORD num) +{ + PATTRINFO p = &pattr[num]; + winetest_ok(p->type == TAG_NULL, "expected entry #%d to be TAG_NULL, was %x\n", num, p->type); + winetest_ok(p->flags == 0, "expected entry #%d to be 0, was %d\n", num, p->flags); + winetest_ok(p->qwattr == 0, "expected entry #%d to be 0, was 0x%I64x\n", num, p->qwattr); +} + +static void expect_tag_dword_imp(PATTRINFO pattr, DWORD num, TAG tag, DWORD value) +{ + PATTRINFO p = &pattr[num]; + winetest_ok(p->type == tag, "expected entry #%d to be %x, was %x\n", num, tag, p->type); + winetest_ok(p->flags == ATTRIBUTE_AVAILABLE, "expected entry #%d to be available, was %d\n", num, p->flags); + winetest_ok(p->dwattr == value, "expected entry #%d to be 0x%x, was 0x%x\n", num, value, p->dwattr); +} + +static void expect_tag_qword_imp(PATTRINFO pattr, DWORD num, TAG tag, QWORD value) +{ + PATTRINFO p = &pattr[num]; + winetest_ok(p->type == tag, "expected entry #%d to be %x, was %x\n", num, tag, p->type); + winetest_ok(p->flags == ATTRIBUTE_AVAILABLE, "expected entry #%d to be available, was %d\n", num, p->flags); + winetest_ok(p->qwattr == value, "expected entry #%d to be 0x%I64x, was 0x%I64x\n", num, value, p->qwattr); +} + +static void expect_tag_str_imp(PATTRINFO pattr, DWORD num, TAG tag, const WCHAR* value) +{ + PATTRINFO p = &pattr[num]; + winetest_ok(p->type == tag, "expected entry #%d to be %x, was %x\n", num, tag, p->type); + winetest_ok(p->flags == ATTRIBUTE_AVAILABLE, "expected entry #%d to be available, was %d\n", num, p->flags); + winetest_ok(p->lpattr && wcscmp(p->lpattr, value) == 0, "expected entry #%d to be %s, was %s\n", num, wine_dbgstr_w(value), wine_dbgstr_w(p->lpattr)); +} + +#define expect_tag_skip (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : expect_tag_skip_imp +#define expect_tag_empty (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : expect_tag_empty_imp +#define expect_tag_dword (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : expect_tag_dword_imp +#define expect_tag_qword (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : expect_tag_qword_imp +#define expect_tag_str (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : expect_tag_str_imp +#define expect_tag_skip_range(ptr, from, to) \ + do { \ + int n = (from), n_end = (to); \ + winetest_set_location(__FILE__, __LINE__); \ + for ( ; n < n_end; ++n) \ + expect_tag_skip_imp((ptr), n); \ + } while (0) +#define test_crc (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : test_crc_imp +#define test_crc2 (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : test_crc2_imp + +void test_onefile(WCHAR* filename) +{ + PATTRINFO pattrinfo; + DWORD num; + + if (!pSdbFreeFileAttributes) + { + hdll = LoadLibraryA("apphelp.dll"); + pSdbTagToString = (void *)GetProcAddress(hdll, "SdbTagToString"); + pSdbGetFileAttributes = (void *)GetProcAddress(hdll, "SdbGetFileAttributes"); + pSdbFreeFileAttributes = (void *)GetProcAddress(hdll, "SdbFreeFileAttributes"); + } + + if (pSdbGetFileAttributes(filename, &pattrinfo, &num)) + { + if (pattrinfo[16].flags == ATTRIBUTE_AVAILABLE) + { + if (pattrinfo[16].type != TAG_MODULE_TYPE)//SdbpSetAttrFail(&attr_info[16]); /* TAG_MODULE_TYPE (1: WIN16?) (3: WIN32?) (WIN64?), Win32VersionValue? */) + printf("FAIL TAG_MODULE_TYPE (%S)\n", filename); + if (pattrinfo[16].dwattr != 3 && pattrinfo[16].dwattr != 2) + printf("TAG_MODULE_TYPE(%S): %d\n", filename, pattrinfo[16].dwattr); // C:\Program Files (x86)\Windows Kits\8.1\Lib\win7\stub512.com + if (pattrinfo[16].dwattr == 2) + { + printf("TAG_MODULE_TYPE(%S): %d, %d\n", filename, pattrinfo[16].dwattr, pattrinfo[0].dwattr); + } + } + + if (pattrinfo[27].flags == ATTRIBUTE_AVAILABLE) + { + if (pattrinfo[27].type != TAG_EXE_WRAPPER) + printf("FAIL TAG_EXE_WRAPPER (%S)\n", filename); + if (pattrinfo[27].dwattr != 0) + printf("TAG_EXE_WRAPPER(%S): %d\n", filename, pattrinfo[27].dwattr); + } + + pSdbFreeFileAttributes(pattrinfo); + } +} + +static void test_crc_imp(size_t len, DWORD expected) +{ + static const WCHAR path[] = {'t','e','s','t','x','x','.','e','x','e',0}; + static char crc_test[] = {4, 4, 4, 4, 1, 1, 1, 1, 4, 4, 4, 4, 2, 2, 2, 2}; + + PATTRINFO pattrinfo = (PATTRINFO)0xdead; + DWORD num = 333; + BOOL ret; + + test_create_file_imp("testxx.exe", crc_test, len); + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + winetest_ok(ret != FALSE, "expected SdbGetFileAttributes to succeed.\n"); + winetest_ok(pattrinfo != (PATTRINFO)0xdead, "expected a valid pointer.\n"); + winetest_ok(num == 28, "expected 28 items, got %d.\n", num); + + if (num == 28 && ret) + { + expect_tag_dword_imp(pattrinfo, 1, TAG_CHECKSUM, expected); + } + if (ret) + pSdbFreeFileAttributes(pattrinfo); +} + +static void test_crc2_imp(size_t len, int fill, DWORD expected) +{ + static const WCHAR path[] = {'t','e','s','t','x','x','.','e','x','e',0}; + + PATTRINFO pattrinfo = (PATTRINFO)0xdead; + DWORD num = 333; + BOOL ret; + size_t n; + char* crc_test = malloc(len); + for (n = 0; n < len; ++n) + crc_test[n] = (char)(fill ? fill : n); + + test_create_file_imp("testxx.exe", crc_test, len); + free(crc_test); + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + winetest_ok(ret != FALSE, "expected SdbGetFileAttributes to succeed.\n"); + winetest_ok(pattrinfo != (PATTRINFO)0xdead, "expected a valid pointer.\n"); + winetest_ok(num == 28, "expected 28 items, got %d.\n", num); + + if (num == 28 && ret) + { + expect_tag_dword_imp(pattrinfo, 0, TAG_SIZE, len); + expect_tag_dword_imp(pattrinfo, 1, TAG_CHECKSUM, expected); + } + if (ret) + pSdbFreeFileAttributes(pattrinfo); +} + + + +static void test_ApplicationAttributes(void) +{ + static const WCHAR path[] = {'t','e','s','t','x','x','.','e','x','e',0}; + static const WCHAR PRODUCT_VERSION[] = {'1','.','0','.','0','.','1',0}; + static const WCHAR FILE_DESCRIPTION[] = {'F','i','l','e','D','e','s','c','r','i','p','t','i','o','n',0}; + static const WCHAR COMPANY_NAME[] = {'C','o','m','p','a','n','y','N','a','m','e',0}; + static const WCHAR PRODUCT_NAME[] = {'P','r','o','d','u','c','t','N','a','m','e',0}; + static const WCHAR FILE_VERSION[] = {'1','.','0','.','0','.','0',0}; + static const WCHAR ORIGINAL_FILENAME[] = {'O','r','i','g','i','n','a','l','F','i','l','e','n','a','m','e',0}; + static const WCHAR INTERNAL_NAME[] = {'I','n','t','e','r','n','a','l','N','a','m','e',0}; + static const WCHAR LEGAL_COPYRIGHT[] = {'L','e','g','a','l','C','o','p','y','r','i','g','h','t',0}; + static const WCHAR EXPORT_NAME[] = {'T','e','S','t','2','.','e','x','e',0}; + static const WCHAR OS2_DESCRIPTION[] = {'M','O','D',' ','D','E','S','C','R','I','P','T','I','O','N',' ','H','E','R','E',0}; + static const WCHAR OS2_EXPORT_NAME[] = {'T','E','S','T','M','O','D','.','h','X','x',0}; + static const WCHAR OS2_DESCRIPTION_broken[] = {'Z',0}; + static const WCHAR OS2_EXPORT_NAME_broken[] = {'E',0}; + + PATTRINFO pattrinfo = (PATTRINFO)0xdead; + DWORD num = 333; + BOOL ret; + + /* ensure the file is not there. */ + DeleteFileA("testxx.exe"); + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + ok(ret == FALSE, "expected SdbGetFileAttributes to fail.\n"); + ok(pattrinfo == (PATTRINFO)0xdead, "expected the pointer not to change.\n"); + ok(num == 333, "expected the number of items not to change.\n"); + if (ret) + pSdbFreeFileAttributes(pattrinfo); + + /* Test a file with as much features as possible */ + test_create_exe("testxx.exe", 0); + + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + ok(ret != FALSE, "expected SdbGetFileAttributes to succeed.\n"); + ok(pattrinfo != (PATTRINFO)0xdead, "expected a valid pointer.\n"); + ok(num == 28, "expected 28 items, got %d.\n", num); + + if (num == 28 && ret) + { + expect_tag_dword(pattrinfo, 0, TAG_SIZE, 0x800); + expect_tag_dword(pattrinfo, 1, TAG_CHECKSUM, 0x178bd629); + expect_tag_qword(pattrinfo, 2, TAG_BIN_FILE_VERSION, 0x1000000000000ull); + expect_tag_qword(pattrinfo, 3, TAG_BIN_PRODUCT_VERSION, 0x1000000000001ull); + expect_tag_str(pattrinfo, 4, TAG_PRODUCT_VERSION, PRODUCT_VERSION); + expect_tag_str(pattrinfo, 5, TAG_FILE_DESCRIPTION, FILE_DESCRIPTION); + expect_tag_str(pattrinfo, 6, TAG_COMPANY_NAME, COMPANY_NAME); + expect_tag_str(pattrinfo, 7, TAG_PRODUCT_NAME, PRODUCT_NAME); + expect_tag_str(pattrinfo, 8, TAG_FILE_VERSION, FILE_VERSION); + expect_tag_str(pattrinfo, 9, TAG_ORIGINAL_FILENAME, ORIGINAL_FILENAME); + expect_tag_str(pattrinfo, 10, TAG_INTERNAL_NAME, INTERNAL_NAME); + expect_tag_str(pattrinfo, 11, TAG_LEGAL_COPYRIGHT, LEGAL_COPYRIGHT); + expect_tag_dword(pattrinfo, 12, TAG_VERDATEHI, 0x1d1a019); + expect_tag_dword(pattrinfo, 13, TAG_VERDATELO, 0xac754c50); + expect_tag_dword(pattrinfo, 14, TAG_VERFILEOS, VOS__WINDOWS32); + expect_tag_dword(pattrinfo, 15, TAG_VERFILETYPE, VFT_APP); + expect_tag_dword(pattrinfo, 16, TAG_MODULE_TYPE, 0x3); /* Win32 */ + expect_tag_dword(pattrinfo, 17, TAG_PE_CHECKSUM, 0xBAAD); + expect_tag_dword(pattrinfo, 18, TAG_LINKER_VERSION, 0x40002); + expect_tag_skip(pattrinfo, 19); /* TAG_16BIT_DESCRIPTION */ + expect_tag_skip(pattrinfo, 20); /* TAG_16BIT_MODULE_NAME */ + expect_tag_qword(pattrinfo, 21, TAG_UPTO_BIN_FILE_VERSION, 0x1000000000000ull); + expect_tag_qword(pattrinfo, 22, TAG_UPTO_BIN_PRODUCT_VERSION, 0x1000000000001ull); + expect_tag_dword(pattrinfo, 23, TAG_LINK_DATE, 0x12345); + expect_tag_dword(pattrinfo, 24, TAG_UPTO_LINK_DATE, 0x12345); + expect_tag_str(pattrinfo, 25, TAG_EXPORT_NAME, EXPORT_NAME); + expect_tag_dword(pattrinfo, 26, TAG_VER_LANGUAGE, 0xffff); + expect_tag_dword(pattrinfo, 27, TAG_EXE_WRAPPER, 0x0); + } + if (ret) + pSdbFreeFileAttributes(pattrinfo); + + + /* Disable resource and exports */ + test_create_exe("testxx.exe", 1); + + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + ok(ret != FALSE, "expected SdbGetFileAttributes to succeed.\n"); + ok(pattrinfo != (PATTRINFO)0xdead, "expected a valid pointer.\n"); + ok(num == 28, "expected 28 items, got %d.\n", num); + + if (num == 28 && ret) + { + expect_tag_dword(pattrinfo, 0, TAG_SIZE, 0x800); + expect_tag_dword(pattrinfo, 1, TAG_CHECKSUM, 0xea7caffd); + expect_tag_skip_range(pattrinfo, 2, 16); + expect_tag_dword(pattrinfo, 16, TAG_MODULE_TYPE, 0x3); /* Win32 */ + expect_tag_dword(pattrinfo, 17, TAG_PE_CHECKSUM, 0xBAAD); + expect_tag_dword(pattrinfo, 18, TAG_LINKER_VERSION, 0x40002); + expect_tag_skip_range(pattrinfo, 19, 23); + expect_tag_dword(pattrinfo, 23, TAG_LINK_DATE, 0x12345); + expect_tag_dword(pattrinfo, 24, TAG_UPTO_LINK_DATE, 0x12345); + expect_tag_skip(pattrinfo, 25); /* TAG_EXPORT_NAME */ + expect_tag_empty(pattrinfo, 26); /* TAG_VER_LANGUAGE */ + expect_tag_dword(pattrinfo, 27, TAG_EXE_WRAPPER, 0x0); + } + if (ret) + pSdbFreeFileAttributes(pattrinfo); + + /* A file with just 'MZ' */ + test_create_file("testxx.exe", "MZ", 2); + + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + ok(ret != FALSE, "expected SdbGetFileAttributes to succeed.\n"); + ok(pattrinfo != (PATTRINFO)0xdead, "expected a valid pointer.\n"); + ok(num == 28, "expected 28 items, got %d.\n", num); + + if (num == 28 && ret) + { + expect_tag_dword(pattrinfo, 0, TAG_SIZE, 0x2); + expect_tag_dword(pattrinfo, 1, TAG_CHECKSUM, 0); + expect_tag_skip_range(pattrinfo, 2, 16); + expect_tag_dword(pattrinfo, 16, TAG_MODULE_TYPE, 0x1); + expect_tag_skip_range(pattrinfo, 17, 26); + expect_tag_empty(pattrinfo, 26); /* TAG_VER_LANGUAGE */ + expect_tag_skip(pattrinfo, 27); /* TAG_EXE_WRAPPER */ + } + if (ret) + pSdbFreeFileAttributes(pattrinfo); + + /* Empty file */ + test_create_file("testxx.exe", NULL, 0); + + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + ok(ret != FALSE, "expected SdbGetFileAttributes to succeed.\n"); + ok(pattrinfo != (PATTRINFO)0xdead, "expected a valid pointer.\n"); + ok(num == 28, "expected 28 items, got %d.\n", num); + + if (num == 28 && ret) + { + expect_tag_dword(pattrinfo, 0, TAG_SIZE, 0); + expect_tag_skip_range(pattrinfo, 1, 26); + expect_tag_empty(pattrinfo, 26); /* TAG_VER_LANGUAGE */ + expect_tag_skip(pattrinfo, 27); /* TAG_EXE_WRAPPER */ + } + if (ret) + pSdbFreeFileAttributes(pattrinfo); + + /* minimal NE executable */ + test_create_ne("testxx.exe", 0); + + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + ok(ret != FALSE, "expected SdbGetFileAttributes to succeed.\n"); + ok(pattrinfo != (PATTRINFO)0xdead, "expected a valid pointer.\n"); + ok(num == 28, "expected 28 items, got %d.\n", num); + + if (num == 28 && ret) + { + expect_tag_dword(pattrinfo, 0, TAG_SIZE, 0xa8); + expect_tag_dword(pattrinfo, 1, TAG_CHECKSUM, 0xf2abe4e9); + expect_tag_skip_range(pattrinfo, 2, 16); + expect_tag_dword(pattrinfo, 16, TAG_MODULE_TYPE, 0x2); + expect_tag_skip(pattrinfo, 17); /* TAG_PE_CHECKSUM */ + expect_tag_skip(pattrinfo, 18); /* TAG_LINKER_VERSION */ + expect_tag_str(pattrinfo, 19, TAG_16BIT_DESCRIPTION, OS2_DESCRIPTION); + expect_tag_str(pattrinfo, 20, TAG_16BIT_MODULE_NAME, OS2_EXPORT_NAME); + expect_tag_skip_range(pattrinfo, 21, 26); + expect_tag_empty(pattrinfo, 26); /* TAG_VER_LANGUAGE */ + expect_tag_skip(pattrinfo, 27); /* TAG_EXE_WRAPPER */ + } + if (ret) + pSdbFreeFileAttributes(pattrinfo); + + /* NE executable with description / module name pointers zero, to show they are always used */ + test_create_ne("testxx.exe", 1); + + ret = pSdbGetFileAttributes(path, &pattrinfo, &num); + ok(ret != FALSE, "expected SdbGetFileAttributes to succeed.\n"); + ok(pattrinfo != (PATTRINFO)0xdead, "expected a valid pointer.\n"); + ok(num == 28, "expected 28 items, got %d.\n", num); + + if (num == 28 && ret) + { + expect_tag_dword(pattrinfo, 0, TAG_SIZE, 0xa8); + expect_tag_dword(pattrinfo, 1, TAG_CHECKSUM, 0xddcbe4c9); + expect_tag_skip_range(pattrinfo, 2, 16); + expect_tag_dword(pattrinfo, 16, TAG_MODULE_TYPE, 0x2); + expect_tag_skip(pattrinfo, 17); /* TAG_PE_CHECKSUM */ + expect_tag_skip(pattrinfo, 18); /* TAG_LINKER_VERSION */ + expect_tag_str(pattrinfo, 19, TAG_16BIT_DESCRIPTION, OS2_DESCRIPTION_broken); /* the 'Z' from 'MZ' */ + expect_tag_str(pattrinfo, 20, TAG_16BIT_MODULE_NAME, OS2_EXPORT_NAME_broken); /* the 'E' from 'NE' */ + expect_tag_skip_range(pattrinfo, 21, 26); + expect_tag_empty(pattrinfo, 26); /* TAG_VER_LANGUAGE */ + expect_tag_skip(pattrinfo, 27); /* TAG_EXE_WRAPPER */ + } + if (ret) + pSdbFreeFileAttributes(pattrinfo); + + test_crc(1, 0); + test_crc(2, 0); + test_crc(3, 0); + test_crc(4, 0x2020202); + test_crc(5, 0x2020202); + test_crc(6, 0x2020202); + test_crc(7, 0x2020202); + test_crc(8, 0x81818181); + test_crc(9, 0x81818181); + test_crc(10, 0x81818181); + test_crc(11, 0x81818181); + test_crc(12, 0xc2c2c2c2); + test_crc(16, 0x62626262); + + /* This seems to be the cutoff point */ + test_crc2(0xffc, 4, 0xfbfbfcfc); + test_crc2(0xffc, 8, 0x7070717); + test_crc2(0xffc, 0xcc, 0xc8eba002); + test_crc2(0xffc, 0, 0x4622028d); + + test_crc2(0x1000, 4, 0x80); + test_crc2(0x1000, 8, 0x8787878f); + test_crc2(0x1000, 0xcc, 0x4adc3667); + test_crc2(0x1000, 0, 0xa3108044); + + /* Here is another cutoff point */ + test_crc2(0x11fc, 4, 0x80); + test_crc2(0x11fc, 8, 0x8787878f); + test_crc2(0x11fc, 0xcc, 0x4adc3667); + test_crc2(0x11fc, 0, 0xf03e0800); + + test_crc2(0x1200, 4, 0x80); + test_crc2(0x1200, 8, 0x8787878f); + test_crc2(0x1200, 0xcc, 0x4adc3667); + test_crc2(0x1200, 0, 0xa3108044); + + /* After that, it stays the same for all sizes */ + test_crc2(0xf000, 4, 0x80); + test_crc2(0xf000, 8, 0x8787878f); + test_crc2(0xf000, 0xcc, 0x4adc3667); + test_crc2(0xf000, 0, 0xa3108044); + + + DeleteFileA("testxx.exe"); +} + START_TEST(apphelp) { RTL_OSVERSIONINFOEXW rtlinfo; @@ -383,6 +820,10 @@ START_TEST(apphelp) //SetEnvironmentVariable("DEBUGCHANNEL", "+apphelp"); hdll = LoadLibraryA("apphelp.dll"); pSdbTagToString = (void *) GetProcAddress(hdll, "SdbTagToString"); + pSdbGetFileAttributes = (void *) GetProcAddress(hdll, "SdbGetFileAttributes"); + pSdbFreeFileAttributes = (void *) GetProcAddress(hdll, "SdbFreeFileAttributes"); + + test_ApplicationAttributes(); test_SdbTagToString(); #ifdef __REACTOS__ if (g_Version < VERSION_WIN7) diff --git a/rostests/apitests/apphelp/data.c b/rostests/apitests/apphelp/data.c new file mode 100644 index 00000000000..be732d654e1 --- /dev/null +++ b/rostests/apitests/apphelp/data.c @@ -0,0 +1,573 @@ +/* + * Copyright 2015 Mark Jansen + * + * 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 +#include +#include "wine/test.h" + + +static IMAGE_DOS_HEADER dos_header = +{ + IMAGE_DOS_SIGNATURE, /* e_magic */ + 144, /* e_cblp */ + 3, /* e_cp */ + 0, /* e_crlc */ + 4, /* e_cparhdr */ + 0, /* e_minalloc */ + 65535, /* e_maxalloc */ + 0, /* e_ss */ + 184, /* e_sp */ + 0, /* e_csum */ + 0, /* e_ip */ + 0, /* e_cs */ + 64, /* e_lfarlc */ + 0, /* e_ovno */ + { 0 }, /* e_res[4] */ + 0, /* e_oemid */ + 0, /* e_oeminfo */ + { 0 }, /* e_res2[10] */ + 0x80 /* e_lfanew */ +}; + +static IMAGE_NT_HEADERS32 nt_header = +{ + IMAGE_NT_SIGNATURE, /* Signature */ + { + IMAGE_FILE_MACHINE_I386, /* Machine */ + 2, /* NumberOfSections */ + 0x12345, /* TimeDateStamp */ + 0, /* PointerToSymbolTable */ + 0, /* NumberOfSymbols */ + sizeof(IMAGE_OPTIONAL_HEADER), /* SizeOfOptionalHeader */ + IMAGE_FILE_RELOCS_STRIPPED | IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LINE_NUMS_STRIPPED | IMAGE_FILE_32BIT_MACHINE /* Characteristics */ + }, + { + IMAGE_NT_OPTIONAL_HDR_MAGIC, /* Magic */ + 6, /* MajorLinkerVersion */ + 3, /* MinorLinkerVersion */ + 0, /* SizeOfCode */ + 0, /* SizeOfInitializedData */ + 0, /* SizeOfUninitializedData */ + 0x1000, /* AddressOfEntryPoint */ + 0x1000, /* BaseOfCode */ +#ifndef _WIN64 + 0, /* BaseOfData */ +#endif + 0x400000, /* ImageBase */ + 0x1000, /* SectionAlignment */ + 0x200, /* FileAlignment */ + 4, /* MajorOperatingSystemVersion */ + 1, /* MinorOperatingSystemVersion */ + 4, /* MajorImageVersion */ + 2, /* MinorImageVersion */ + 4, /* MajorSubsystemVersion */ + 3, /* MinorSubsystemVersion */ + 0, /* Win32VersionValue */ + 0x3000, /* SizeOfImage */ + 0x200, /* SizeOfHeaders */ + 0xBAAD, /* CheckSum: This checksum is not the correct checksum, intentionally! */ + IMAGE_SUBSYSTEM_WINDOWS_CUI, /* Subsystem */ + 0, /* DllCharacteristics */ + 0x100000, /* SizeOfStackReserve */ + 0x1000, /* SizeOfStackCommit */ + 0x100000, /* SizeOfHeapReserve */ + 0x1000, /* SizeOfHeapCommit */ + 0, /* LoaderFlags */ + 0x10, /* NumberOfRvaAndSizes */ + { + /* IMAGE_DIRECTORY_ENTRY_EXPORT */ + { + 0x2370, /* VirtualAddress */ + 76, /* Size */ + }, + { 0 }, + /* IMAGE_DIRECTORY_ENTRY_RESOURCE */ + { + 0x2000, /* VirtualAddress */ + 868, /* Size */ + }, + } /* DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] */ + } +}; + +static IMAGE_SECTION_HEADER section_headers[] = +{ + { + { '.','t','e','x','t',0 }, /* Name */ + { 24 }, /* VirtualSize */ + 0x1000, /* VirtualAddress */ + 0x200, /* SizeOfRawData */ + 0x200, /* PointerToRawData */ + 0, /* PointerToRelocations */ + 0, /* PointerToLinenumbers */ + 0, /* NumberOfRelocations */ + 0, /* NumberOfLinenumbers */ + IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ /* Characteristics */ + }, + { + { '.','r','s','r','c',0 }, /* Name */ + { 880 }, /* VirtualSize */ + 0x2000, /* VirtualAddress */ + 0x400, /* SizeOfRawData */ + 0x400, /* PointerToRawData */ + 0, /* PointerToRelocations */ + 0, /* PointerToLinenumbers */ + 0, /* NumberOfRelocations */ + 0, /* NumberOfLinenumbers */ + IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ /* Characteristics */ + } +}; + +static const unsigned char text_section[] = +{ + 0x33, 0xc0, /* xor eax, eax */ + 0xc3 /* ret */ +}; + + +/* taken from fusionpriv.h */ +typedef struct +{ + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[17]; + VS_FIXEDFILEINFO Value; +} VS_VERSIONINFO; + +typedef struct +{ + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[15]; +} STRINGFILEINFO; + +typedef struct +{ + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[9]; +} STRINGTABLE; + +typedef struct +{ + WORD wLength; + WORD wValueLength; + WORD wType; +} STRINGHDR; + +typedef struct +{ + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[13]; +} VARFILEINFO; + +typedef struct +{ + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[13]; + DWORD Value; +} VAR; + +typedef struct rsrc_section_t +{ + IMAGE_RESOURCE_DIRECTORY header; + IMAGE_RESOURCE_DIRECTORY_ENTRY file_info_id; + IMAGE_RESOURCE_DIRECTORY file_info_header; + IMAGE_RESOURCE_DIRECTORY_ENTRY version_info_id; + IMAGE_RESOURCE_DIRECTORY version_info_header; + IMAGE_RESOURCE_DIRECTORY_ENTRY version_lang_id; + IMAGE_RESOURCE_DATA_ENTRY version_data_entry; + + VS_VERSIONINFO version_info; + STRINGFILEINFO string_file_info; + STRINGTABLE string_table; + + STRINGHDR FileVersion_hdr; + WCHAR FileVersion_key[13]; + WCHAR FileVersion_val[8]; + + STRINGHDR ProductVersion_hdr; + WCHAR ProductVersion_key[15]; + WCHAR ProductVersion_val[8]; + + STRINGHDR CompanyName_hdr; + WCHAR CompanyName_key[13]; + WCHAR CompanyName_val[12]; + + STRINGHDR FileDescription_hdr; + WCHAR FileDescription_key[17]; + WCHAR FileDescription_val[16]; + + STRINGHDR InternalName_hdr; + WCHAR InternalName_key[13]; + WCHAR InternalName_val[14]; + + STRINGHDR LegalCopyright_hdr; + WCHAR LegalCopyright_key[15]; + WCHAR LegalCopyright_val[16]; + + STRINGHDR LegalTrademarks_hdr; + WCHAR LegalTrademarks_key[17]; + WCHAR LegalTrademarks_val[16]; + + STRINGHDR OriginalFilename_hdr; + WCHAR OriginalFilename_key[17]; + WCHAR OriginalFilename_val[18]; + + STRINGHDR Productname_hdr; + WCHAR Productname_key[13]; + WCHAR Productname_val[12]; + + VARFILEINFO file_info; + VAR translation; +} rsrc_section_t; + +static const rsrc_section_t rsrc_section = +{ + /* header */ + { + 0, /* Characteristics */ + 0x55FE8E21, /* TimeDateStamp, 20/09/2015 10:44:49 */ + 0, /* MajorVersion */ + 0, /* MinorVersion */ + 0, /* NumberOfNamedEntries */ + 1, /* NumberOfIdEntries */ + }, + /* file_info_id */ + { + {{ + (DWORD)VS_FILE_INFO, /* NameOffset:31 */ + 0 /* NameIsString:1 */ + }}, + { + 0x80000018 /* OffsetToData */ + } + }, + /* file_info_header */ + { + 0, /* Characteristics */ + 0x55FE8E21, /* TimeDateStamp, 20/09/2015 10:44:49 */ + 0, /* MajorVersion */ + 0, /* MinorVersion */ + 0, /* NumberOfNamedEntries */ + 1, /* NumberOfIdEntries */ + }, + /* version_info_id */ + { + {{ + VS_VERSION_INFO, /* NameOffset:31 */ + 0 /* NameIsString:1 */ + }}, + { + 0x80000030 /* OffsetToData */ + }, + }, + /* version_info_header */ + { + 0, /* Characteristics */ + 0x55FE8E21, /* TimeDateStamp, 20/09/2015 10:44:49 */ + 0, /* MajorVersion */ + 0, /* MinorVersion */ + 0, /* NumberOfNamedEntries */ + 1, /* NumberOfIdEntries */ + }, + /* version_lang_id */ + { + {{ + 1033, /* NameOffset:31 */ + 0 /* NameIsString:1 */ + }}, + { + 0x48 /* OffsetToDirectory */ + } + }, + /* version_data_entry */ + { + 0x2058, /* OffsetToData */ + 0x30C, /* Size */ + 0, /* CodePage */ + 0, /* Reserved */ + }, + + /* version_info */ + { + 0x30C, /* wLength */ + 0x34, /* wValueLength */ + 0, /* wType: Binary */ + { 'V','S','_','V','E','R','S','I','O','N','_','I','N','F','O','\0','\0' }, /* szKey[17] */ + /* Value */ + { + 0xFEEF04BD, /* dwSignature */ + 0x10000, /* dwStrucVersion */ + 0x10000, /* dwFileVersionMS */ + 0, /* dwFileVersionLS */ + 0x10000, /* dwProductVersionMS */ + 1, /* dwProductVersionLS */ + 0, /* dwFileFlagsMask */ + 0, /* dwFileFlags */ + VOS__WINDOWS32, /* dwFileOS */ + VFT_APP, /* dwFileType */ + 0, /* dwFileSubtype */ + 0x01d1a019, /* dwFileDateMS */ + 0xac754c50 /* dwFileDateLS */ + }, + }, + + /* string_file_info */ + { + 0x26C, /* wLength */ + 0, /* wValueLength */ + 1, /* wType: Text */ + { 'S','t','r','i','n','g','F','i','l','e','I','n','f','o','\0' } /* szKey[15] */ + }, + /* string_table */ + { + 0x248, /* wLength */ + 0, /* wValueLength */ + 1, /* wType: Text */ + { 'F','F','F','F','0','0','0','0','\0' } /* szKey[9] */ + }, + + /* FileVersion */ + { + 48, /* wLength */ + 8, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'F','i','l','e','V','e','r','s','i','o','n','\0' }, + { '1','.','0','.','0','.','0','\0' }, + + /* ProductVersion */ + { + 52, /* wLength */ + 8, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'P','r','o','d','u','c','t','V','e','r','s','i','o','n','\0' }, + { '1','.','0','.','0','.','1','\0' }, + + /* CompanyName */ + { + 56, /* wLength */ + 12, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'C','o','m','p','a','n','y','N','a','m','e','\0' }, + { 'C','o','m','p','a','n','y','N','a','m','e','\0' }, + + /* FileDescription */ + { + 72, /* wLength */ + 16, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'F','i','l','e','D','e','s','c','r','i','p','t','i','o','n','\0' }, + { 'F','i','l','e','D','e','s','c','r','i','p','t','i','o','n','\0' }, + + /* InternalName */ + { + 58, /* wLength */ + 13, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'I','n','t','e','r','n','a','l','N','a','m','e','\0' }, + { 'I','n','t','e','r','n','a','l','N','a','m','e','\0' }, + + /* LegalCopyright */ + { + 66, /* wLength */ + 15, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'L','e','g','a','l','C','o','p','y','r','i','g','h','t','\0' }, + { 'L','e','g','a','l','C','o','p','y','r','i','g','h','t','\0' }, + + /* LegalTrademarks */ + { + 72, /* wLength */ + 16, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'L','e','g','a','l','T','r','a','d','e','m','a','r','k','s','\0' }, + { 'L','e','g','a','l','T','r','a','d','e','m','a','r','k','s','\0' }, + + /* OriginalFilename */ + { + 74, /* wLength */ + 17, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'O','r','i','g','i','n','a','l','F','i','l','e','n','a','m','e','\0' }, + { 'O','r','i','g','i','n','a','l','F','i','l','e','n','a','m','e','\0' }, + + /* ProductName */ + { + 56, /* wLength */ + 12, /* wValueLength */ + 1, /* wType: Text */ + }, + { 'P','r','o','d','u','c','t','N','a','m','e','\0' }, + { 'P','r','o','d','u','c','t','N','a','m','e','\0' }, + + + /* file_info */ + { + 0x44, /* wLength */ + 0, /* wValueLength */ + 1, /* wType: Text */ + { 'V','a','r','F','i','l','e','I','n','f','o','\0' } /* szKey[13] */ + }, + + /* translation */ + { + 0x24, /* wLength */ + 4, /* wValueLength */ + 0, /* wType: Binary */ + { 'T','r','a','n','s','l','a','t','i','o','n','\0' }, /* szKey[13] */ + 0xffff /* Value */ + } +}; + +typedef struct export_section_t +{ + IMAGE_EXPORT_DIRECTORY desc; + char binary_name[10]; +} export_section_t; + +/* This export section is not complete, but the Name RVA is only taken into account */ +static export_section_t export_dir = +{ + { + 0, /* Characteristics */ + 0, /* TimeDateStamp */ + 0, /* MajorVersion */ + 0, /* MinorVersion */ + 0x2398, /* Name (RVA) */ + 1, /* Base */ + 0, /* NumberOfFunctions */ + 0, /* NumberOfNames */ + 0, /* AddressOfFunctions (RVA) */ + 0, /* AddressOfNames (RVA) */ + 0, /* AddressOfNameOrdinals (RVA) */ + }, + { 'T','e','S','t','2','.','e','x','e',0 }, /* binary_name */ +}; + + +void test_create_exe_imp(const char* name, int skip_rsrc_exports) +{ + HANDLE file; + char *buf, *cur; + DWORD size = 0x800; + buf = malloc(size); + + file = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + winetest_ok(file != INVALID_HANDLE_VALUE, "can't create file\n"); + if(file == INVALID_HANDLE_VALUE) + return; + + memset(buf, 0, size); + cur = buf; + cur = memcpy(buf, &dos_header, sizeof(dos_header)); + cur += dos_header.e_lfanew; + + memcpy(cur, &nt_header, sizeof(nt_header)); + if (skip_rsrc_exports) + { + ((IMAGE_NT_HEADERS32*)cur)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress = 0; + ((IMAGE_NT_HEADERS32*)cur)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = 0; + } + cur += sizeof(nt_header); + memcpy(cur, section_headers, sizeof(section_headers)); + + /* write code section: */ + cur = buf + section_headers[0].PointerToRawData; + memcpy(cur, text_section, sizeof(text_section)); + + if (!skip_rsrc_exports) + { + /* write resource section: */ + cur = buf + section_headers[1].PointerToRawData; + memcpy(cur, &rsrc_section, sizeof(rsrc_section)); + + /* write minimal export directory: */ + cur += 0x370; + memcpy(cur, &export_dir, sizeof(export_dir)); + } + + WriteFile(file, buf, size, &size, NULL); + free(buf); + CloseHandle(file); +} + + +/* Almost everything in this filetype is ignored, only e_lfanew, ne_restab and ne_nrestab are relevant */ +void test_create_ne_imp(const char* name, int skip_names) +{ + HANDLE file; + DWORD size; + IMAGE_DOS_HEADER MZ_hdr = { IMAGE_DOS_SIGNATURE, 0 }; + IMAGE_OS2_HEADER NE_hdr = { IMAGE_OS2_SIGNATURE, 0 }; + static const BYTE NE_names[] = + { + /* Show that the length is used, not the nullterm*/ + 11,'T','E','S','T','M','O','D','.','h','X','x','x',0,0,0, + 20,'M','O','D',' ','D','E','S','C','R','I','P','T','I','O','N',' ','H','E','R','E',0,0,0 + }; + + file = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + winetest_ok(file != INVALID_HANDLE_VALUE, "can't create file\n"); + if(file == INVALID_HANDLE_VALUE) + return; + + MZ_hdr.e_lfanew = sizeof(MZ_hdr); + if (!skip_names) + { + NE_hdr.ne_restab = sizeof(NE_hdr); /* First entry (pascal string + ordinal) = module name */ + NE_hdr.ne_nrestab = sizeof(MZ_hdr) + sizeof(NE_hdr) + 16; /* First entry (pascal string + ordinal) = module description */ + } + + WriteFile(file, &MZ_hdr, sizeof(MZ_hdr), &size, NULL); + WriteFile(file, &NE_hdr, sizeof(NE_hdr), &size, NULL); + WriteFile(file, NE_names, sizeof(NE_names), &size, NULL); + + CloseHandle(file); +} + +void test_create_file_imp(const char* name, const char* contents, size_t len) +{ + HANDLE file = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + winetest_ok(file != INVALID_HANDLE_VALUE, "can't create file\n"); + if (file != INVALID_HANDLE_VALUE) + { + if (contents && len) + { + DWORD size; + WriteFile(file, contents, len, &size, NULL); + } + CloseHandle(file); + } +} +