Copy all win32 resources (#24308)
authorDavid Wrighton <davidwr@microsoft.com>
Wed, 1 May 2019 22:29:34 +0000 (15:29 -0700)
committerGitHub <noreply@github.com>
Wed, 1 May 2019 22:29:34 +0000 (15:29 -0700)
- Add crossgen test to verify file version is preserved
- Add support for general win32 resource copying to ReadyToRun
  - Copy all resources

src/inc/pedecoder.h
src/pal/inc/rt/palrt.h
src/utilcode/pedecoder.cpp
src/zap/zapheaders.cpp
src/zap/zapheaders.h
src/zap/zapimage.cpp
src/zap/zapimage.h
src/zap/zapnodetype.h
tests/src/readytorun/tests/fileversionpreservation/Properties/AssemblyInfo.cs [new file with mode: 0644]
tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.cs [new file with mode: 0644]
tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.csproj [new file with mode: 0644]

index 37051d9..5dff335 100644 (file)
@@ -106,6 +106,10 @@ inline CHECK CheckOverflow(RVA value1, COUNT_T value2)
 
 typedef DPTR(class PEDecoder) PTR_PEDecoder;
 
+typedef bool (*PEDecoder_ResourceTypesCallbackFunction)(LPCWSTR lpType, void* context);
+typedef bool (*PEDecoder_ResourceNamesCallbackFunction)(LPCWSTR lpName, LPCWSTR lpType, void* context);
+typedef bool (*PEDecoder_ResourceCallbackFunction)(LPCWSTR lpName, LPCWSTR lpType, DWORD langid, BYTE* data, COUNT_T cbData, void* context);
+
 class PEDecoder
 {
   public:
@@ -253,9 +257,9 @@ class PEDecoder
 
     // Win32 resources
     void *GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSize = NULL) const;
-  private:
-    DWORD ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pIsDictionary) const;
-    DWORD ReadResourceDataEntry(DWORD rva, COUNT_T *pSize) const;
+    bool EnumerateWin32ResourceTypes(PEDecoder_ResourceTypesCallbackFunction callback, void* context) const;
+    bool EnumerateWin32ResourceNames(LPCWSTR lpType, PEDecoder_ResourceNamesCallbackFunction callback, void* context) const;
+    bool EnumerateWin32Resources(LPCWSTR lpName, LPCWSTR lpType, PEDecoder_ResourceCallbackFunction callback, void* context) const;
   public:
 
     // COR header fields
index 89f28e7..6502cf5 100644 (file)
@@ -1133,8 +1133,9 @@ typedef JIT_DEBUG_INFO JIT_DEBUG_INFO32, *LPJIT_DEBUG_INFO32;
 typedef JIT_DEBUG_INFO JIT_DEBUG_INFO64, *LPJIT_DEBUG_INFO64;
 
 /******************* resources ***************************************/
-
+#define IS_INTRESOURCE(_r) ((((ULONG_PTR)(_r)) >> 16) == 0)
 #define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
+#define MAKEINTRESOURCE(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
 #define RT_RCDATA           MAKEINTRESOURCE(10)
 #define RT_VERSION          MAKEINTRESOURCE(16)
 
index 24cde95..411b462 100644 (file)
@@ -1790,26 +1790,73 @@ void PEDecoder::LayoutILOnly(void *base, BOOL allowFullPE) const
 
 #endif // #ifndef DACCESS_COMPILE
 
-DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pIsDictionary) const
+bool ReadResourceDirectoryHeader(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rva, IMAGE_RESOURCE_DIRECTORY_ENTRY** ppDirectoryEntries, IMAGE_RESOURCE_DIRECTORY **ppResourceDirectory)
 {
-    *pIsDictionary = FALSE;
+    if (!pDecoder->CheckRva(rva, sizeof(IMAGE_RESOURCE_DIRECTORY)))
+    {
+        return false;
+    }
 
-    if (!CheckRva(rva, sizeof(IMAGE_RESOURCE_DIRECTORY)))
+    *ppResourceDirectory = (IMAGE_RESOURCE_DIRECTORY *)pDecoder->GetRvaData(rva);
+
+    // Check to see if entire resource directory is accessible
+    if (!pDecoder->CheckRva(rva + sizeof(IMAGE_RESOURCE_DIRECTORY), 
+                       (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * (*ppResourceDirectory)->NumberOfNamedEntries) +
+                       (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * (*ppResourceDirectory)->NumberOfIdEntries)))
     {
-        return 0;
+        return false;
     }
 
-    IMAGE_RESOURCE_DIRECTORY *pResourceDirectory = (IMAGE_RESOURCE_DIRECTORY *)GetRvaData(rva);
+    *ppDirectoryEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)pDecoder->GetRvaData(rva + sizeof(IMAGE_RESOURCE_DIRECTORY));
+    return true;
+}
+
+bool ReadNameFromResourceDirectoryEntry(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries, DWORD iEntry, DWORD *pNameUInt, WCHAR **pNameStr)
+{
+    *pNameStr = NULL;
+    *pNameUInt = 0;
 
-    // Check to see if entire resource dictionary is accessible
-    if (!CheckRva(rva + sizeof(IMAGE_RESOURCE_DIRECTORY), 
-                       (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * pResourceDirectory->NumberOfNamedEntries) +
-                       (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * pResourceDirectory->NumberOfIdEntries)))
+    if (!IS_INTRESOURCE(pDirectoryEntries[iEntry].Name))
     {
-        return 0;
+        DWORD entryName = pDirectoryEntries[iEntry].Name;
+        if (!(entryName & IMAGE_RESOURCE_NAME_IS_STRING))
+            return false;
+        DWORD entryNameRva = (entryName & ~IMAGE_RESOURCE_NAME_IS_STRING) + rvaOfResourceSection;
+
+        if (!pDecoder->CheckRva(entryNameRva, sizeof(WORD)))
+            return false;
+
+        size_t entryNameLen = *(WORD*)pDecoder->GetRvaData(entryNameRva);
+        if (!pDecoder->CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen))))
+            return false;
+        *pNameStr = new(nothrow) WCHAR[entryNameLen + 1];
+        if ((*pNameStr) == NULL)
+            return false;
+        memcpy((*pNameStr), (WCHAR*)pDecoder->GetRvaData(entryNameRva + sizeof(WORD)), entryNameLen * sizeof(WCHAR));
+        (*pNameStr)[entryNameLen] = 0;
+    }
+    else
+    {
+        DWORD name = pDirectoryEntries[iEntry].Name;
+        if (!IS_INTRESOURCE(name))
+            return false;
+        
+        *pNameUInt = name;
     }
 
-    IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)GetRvaData(rva + sizeof(IMAGE_RESOURCE_DIRECTORY));
+    return true;
+}
+
+DWORD ReadResourceDirectory(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pisDirectory)
+{
+    *pisDirectory = FALSE;
+
+    IMAGE_RESOURCE_DIRECTORY* pResourceDirectory;
+    IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries;
+    if (!ReadResourceDirectoryHeader(pDecoder, rvaOfResourceSection, rva, &pDirectoryEntries, &pResourceDirectory))
+    {
+        return 0;
+    }
 
     // A fast implementation of resource lookup uses a binary search, but our needs are simple, and a linear search
     // is easier to prove correct, so do that instead.
@@ -1819,7 +1866,7 @@ DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, L
     {
         BOOL foundEntry = FALSE;
 
-        if (((UINT_PTR)name) <= 0xFFFF)
+        if (IS_INTRESOURCE(name))
         {
             // name is id
             if (pDirectoryEntries[iEntry].Name == (DWORD)(SIZE_T)name)
@@ -1834,24 +1881,24 @@ DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, L
             
             DWORD entryNameRva = (entryName & ~IMAGE_RESOURCE_NAME_IS_STRING) + rvaOfResourceSection;
 
-            if (!CheckRva(entryNameRva, sizeof(WORD)))
+            if (!pDecoder->CheckRva(entryNameRva, sizeof(WORD)))
                 return 0;
 
-            size_t entryNameLen = *(WORD*)GetRvaData(entryNameRva);
+            size_t entryNameLen = *(WORD*)pDecoder->GetRvaData(entryNameRva);
             if (wcslen(name) != entryNameLen)
                 continue;
 
-            if (!CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen))))
+            if (!pDecoder->CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen))))
                 return 0;
             
-            if (memcmp((WCHAR*)GetRvaData(entryNameRva + sizeof(WORD)), name, entryNameLen * sizeof(WCHAR)) == 0)
+            if (memcmp((WCHAR*)pDecoder->GetRvaData(entryNameRva + sizeof(WORD)), name, entryNameLen * sizeof(WCHAR)) == 0)
                 foundEntry = TRUE;
         }
 
         if (!foundEntry)
             continue;
 
-        *pIsDictionary = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+        *pisDirectory = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
         DWORD offsetToData = pDirectoryEntries[iEntry].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY;
         DWORD dataRva = rvaOfResourceSection + offsetToData;
         return dataRva;
@@ -1860,16 +1907,16 @@ DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, L
     return 0;
 }
 
-DWORD PEDecoder::ReadResourceDataEntry(DWORD rva, COUNT_T *pSize) const
+DWORD ReadResourceDataEntry(const PEDecoder *pDecoder, DWORD rva, COUNT_T *pSize)
 {
     *pSize = 0;
 
-    if (!CheckRva(rva, sizeof(IMAGE_RESOURCE_DATA_ENTRY)))
+    if (!pDecoder->CheckRva(rva, sizeof(IMAGE_RESOURCE_DATA_ENTRY)))
     {
         return 0;
     }
 
-    IMAGE_RESOURCE_DATA_ENTRY *pDataEntry = (IMAGE_RESOURCE_DATA_ENTRY *)GetRvaData(rva);
+    IMAGE_RESOURCE_DATA_ENTRY *pDataEntry = (IMAGE_RESOURCE_DATA_ENTRY *)pDecoder->GetRvaData(rva);
     *pSize = pDataEntry->Size;
     return pDataEntry->OffsetToData;
 }
@@ -1898,17 +1945,17 @@ void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSiz
     if (pDir->VirtualAddress == 0)
         return NULL;
 
-    BOOL isDictionary = FALSE;
-    DWORD nameTableRva = ReadResourceDictionary(pDir->VirtualAddress, pDir->VirtualAddress, lpType, &isDictionary);
+    BOOL isDirectory = FALSE;
+    DWORD nameTableRva = ReadResourceDirectory(this, pDir->VirtualAddress, pDir->VirtualAddress, lpType, &isDirectory);
 
-    if (!isDictionary)
+    if (!isDirectory)
         return NULL;
     
     if (nameTableRva == 0)
         return NULL;
 
-    DWORD languageTableRva = ReadResourceDictionary(pDir->VirtualAddress, nameTableRva, lpName, &isDictionary);
-    if (!isDictionary)
+    DWORD languageTableRva = ReadResourceDirectory(this, pDir->VirtualAddress, nameTableRva, lpName, &isDirectory);
+    if (!isDirectory)
         return NULL;
 
     if (languageTableRva == 0)
@@ -1918,14 +1965,14 @@ void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSiz
     // This translates to LANGID 0 as the initial lookup point, which is sufficient for the needs of this api at this time
     // (FindResource in the Windows api implements a large number of fallback paths which this api does not implement)
 
-    DWORD resourceDataEntryRva = ReadResourceDictionary(pDir->VirtualAddress, languageTableRva, 0, &isDictionary);
-    if (isDictionary) // This must not be a resource dictionary itself
+    DWORD resourceDataEntryRva = ReadResourceDirectory(this, pDir->VirtualAddress, languageTableRva, 0, &isDirectory);
+    if (isDirectory) // This must not be a resource directory itself
         return NULL;
 
     if (resourceDataEntryRva == 0)
         return NULL;
 
-    DWORD resourceDataRva = ReadResourceDataEntry(resourceDataEntryRva, pSize);
+    DWORD resourceDataRva = ReadResourceDataEntry(this, resourceDataEntryRva, pSize);
     if (!CheckRva(resourceDataRva, *pSize))
     {
         *pSize = 0;
@@ -1935,6 +1982,229 @@ void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSiz
     return (void*)GetRvaData(resourceDataRva);
 }
 
+typedef bool (*PEDecoder_EnumerateResourceTableFunction)(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context);
+
+struct ResourceEnumerateNamesState
+{
+    PEDecoder_ResourceNamesCallbackFunction namesCallback;
+    PEDecoder_ResourceCallbackFunction langIDcallback;
+    void *context;
+    LPCWSTR nameType;
+    LPCWSTR nameName;
+    PEDecoder_EnumerateResourceTableFunction callbackPerName;
+    PEDecoder_EnumerateResourceTableFunction callbackPerLangID;
+};
+
+struct ResourceEnumerateTypesState
+{
+    PEDecoder_ResourceTypesCallbackFunction callback;
+    void *context;
+    LPCWSTR nameType;
+};
+
+bool EnumerateWin32ResourceTable(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rvaOfResourceTable, PEDecoder_EnumerateResourceTableFunction resourceTableEnumerator, void *context)
+{
+    IMAGE_RESOURCE_DIRECTORY* pResourceDirectory;
+    IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries;
+    if (!ReadResourceDirectoryHeader(pDecoder, rvaOfResourceSection, rvaOfResourceTable, &pDirectoryEntries, &pResourceDirectory))
+    {
+        return false;
+    }
+
+    DWORD iEntryCount = (DWORD)pResourceDirectory->NumberOfNamedEntries + (DWORD)pResourceDirectory->NumberOfIdEntries;
+
+    for (DWORD iEntry = 0; iEntry < iEntryCount; iEntry++)
+    {
+        DWORD nameUInt;
+        NewArrayHolder<WCHAR> nameString;
+        if (!ReadNameFromResourceDirectoryEntry(pDecoder, rvaOfResourceSection, pDirectoryEntries, iEntry, &nameUInt, &nameString))
+            return false;
+
+        LPCWSTR name = MAKEINTRESOURCEW(nameUInt);
+        if (nameString != NULL)
+            name = &nameString[0];
+
+        bool isDirectory = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+        DWORD offsetToData = pDirectoryEntries[iEntry].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY;
+        DWORD dataRva = rvaOfResourceSection + offsetToData;
+
+        if (!resourceTableEnumerator(pDecoder, rvaOfResourceSection, isDirectory, name, dataRva, context))
+            return false;
+    }
+
+    return true;
+}
+
+bool DoesResourceNameMatch(LPCWSTR nameA, LPCWSTR nameB)
+{
+    bool foundEntry = false;
+
+    if (IS_INTRESOURCE(nameA))
+    {
+        // name is id
+        if (nameA == nameB)
+            foundEntry = true;
+    }
+    else
+    {
+        // name is a string. 
+
+        // Check for name enumerated is an id. If so, it doesn't match, skip to next.
+        if (IS_INTRESOURCE(nameB))
+            return false;
+        else
+            foundEntry = !wcscmp(nameB, nameA);
+    }
+
+    return foundEntry;
+}
+
+bool EnumerateLangIDs(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context)
+{
+    ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context;
+    if (isDirectory)
+        return false;
+
+    // Only LangIDs are permitted here
+    if (!IS_INTRESOURCE(name))
+        return false;
+
+    if (dataRVA == 0)
+        return false;
+
+    COUNT_T cbData;
+    DWORD resourceDataRva = ReadResourceDataEntry(pDecoder, dataRVA, &cbData);
+    if (!pDecoder->CheckRva(resourceDataRva, cbData))
+    {
+        return false;
+    }
+
+    BYTE *pData = (BYTE*)pDecoder->GetRvaData(resourceDataRva);
+
+    return state->langIDcallback(state->nameName, state->nameType, (DWORD)name, pData, cbData, state->context);
+}
+
+
+bool EnumerateNames(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context)
+{
+    ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context;
+    if (!isDirectory)
+        return false;
+
+    state->nameName = name;
+    return state->namesCallback(state->nameName, state->nameType, state->context);
+}
+
+bool EnumerateNamesForLangID(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context)
+{
+    ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context;
+    if (!isDirectory)
+        return false;
+
+    bool foundEntry = DoesResourceNameMatch(state->nameName, name);
+
+    if (foundEntry)
+        return EnumerateWin32ResourceTable(pDecoder, rvaOfResourceSection, dataRVA, state->callbackPerLangID, context);
+    else
+        return true; // Keep scanning
+}
+
+
+bool EnumerateTypes(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context)
+{
+    ResourceEnumerateTypesState *state = (ResourceEnumerateTypesState*)context;
+    if (!isDirectory)
+        return false;
+
+    state->nameType = name;
+    return state->callback(name, state->context);
+}
+
+bool EnumerateTypesForNames(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context)
+{
+    ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context;
+    if (!isDirectory)
+        return false;
+
+    bool foundEntry = DoesResourceNameMatch(state->nameType, name);
+
+    if (foundEntry)
+        return EnumerateWin32ResourceTable(pDecoder, rvaOfResourceSection, dataRVA, state->callbackPerName, context);
+    else
+        return true; // Keep scanning
+}
+
+
+bool PEDecoder::EnumerateWin32ResourceTypes(PEDecoder_ResourceTypesCallbackFunction callback, void* context) const
+{
+    if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE))
+        return true;
+
+    COUNT_T resourceDataSize = 0;
+    IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE);
+
+    if (pDir->VirtualAddress == 0)
+        return true;
+
+    DWORD rvaOfResourceSection = pDir->VirtualAddress;
+
+    ResourceEnumerateTypesState state;
+    state.context = context;
+    state.callback = callback;
+
+    return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypes, &state);
+}
+
+bool PEDecoder::EnumerateWin32ResourceNames(LPCWSTR lpType, PEDecoder_ResourceNamesCallbackFunction callback, void* context) const
+{
+    if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE))
+        return true;
+
+    COUNT_T resourceDataSize = 0;
+    IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE);
+
+    if (pDir->VirtualAddress == 0)
+        return true;
+
+    DWORD rvaOfResourceSection = pDir->VirtualAddress;
+
+    ResourceEnumerateNamesState state;
+    state.context = context;
+    state.namesCallback = callback;
+    state.langIDcallback = NULL;
+    state.nameType = lpType;
+    state.nameName = NULL;
+    state.callbackPerName = EnumerateNames;
+    state.callbackPerLangID = NULL;
+
+    return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypesForNames, &state);
+}
+
+bool PEDecoder::EnumerateWin32Resources(LPCWSTR lpName, LPCWSTR lpType, PEDecoder_ResourceCallbackFunction callback, void* context) const
+{
+    if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE))
+        return true;
+
+    COUNT_T resourceDataSize = 0;
+    IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE);
+
+    if (pDir->VirtualAddress == 0)
+        return true;
+
+    DWORD rvaOfResourceSection = pDir->VirtualAddress;
+
+    ResourceEnumerateNamesState state;
+    state.context = context;
+    state.namesCallback = NULL;
+    state.langIDcallback = callback;
+    state.nameType = lpType;
+    state.nameName = lpName;
+    state.callbackPerName = EnumerateNamesForLangID;
+    state.callbackPerLangID = EnumerateLangIDs;
+
+    return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypesForNames, &state);
+}
+
 BOOL PEDecoder::HasNativeHeader() const
 {
     CONTRACT(BOOL)
index c931d3b..cdf511b 100644 (file)
@@ -19,6 +19,9 @@
 #include "zapmetadata.h"
 #include "zapimport.h"
 
+#include <clr_std/vector>
+#include <clr_std/algorithm>
+
 //
 // IMAGE_COR20_HEADER
 //
@@ -190,54 +193,252 @@ void ZapImage::SaveCodeManagerEntry()
 // Needed for RT_VERSION.
 #define MAKEINTRESOURCE(v) MAKEINTRESOURCEW(v)
 
-void ZapVersionResource::Save(ZapWriter * pZapWriter)
+void ZapWin32ResourceDirectory::Save(ZapWriter * pZapWriter)
 {
-    // Resources are binary-sorted tree structure. Since we are saving just one resource
-    // the binary structure is degenerated link list. By convention, Windows uses three levels
-    // for resources: Type, Name, Language.
+    //
+    // The IMAGE_RESOURCE_DIRECTORY resource data structure is followed by a number of IMAGE_RESOURCE_DIRECTORY_ENTRY entries, which can either 
+    // point to other resource directories (RVAs to other ZapWin32ResourceDirectory nodes), or point to actual resource data (RVAs to a number 
+    // of IMAGE_RESOURCE_DATA_ENTRY entries that immediately follow the IMAGE_RESOURCE_DIRECTORY_ENTRY entries).
+    //
 
-    // See vctools\link\doc\pecoff.doc for detailed documentation of PE format.
+    //
+    // Sorting for resources is done in the following way accoring to the PE format specifications:
+    //   1) First, all the IMAGE_RESOURCE_DIRECTORY_ENTRY entries where the ID is a name string, sorted by names
+    //   2) Second, all the IMAGE_RESOURCE_DIRECTORY_ENTRY entries with non-string IDs, sorted by IDs.
+    //
+    struct ResourceSorter
+    {
+        bool operator() (DataOrSubDirectoryEntry& a, DataOrSubDirectoryEntry& b)
+        {
+            if (a.m_nameOrIdIsString && !b.m_nameOrIdIsString)
+                return true;
+            if (!a.m_nameOrIdIsString && b.m_nameOrIdIsString)
+                return false;
+            if (a.m_nameOrIdIsString)
+                return wcscmp(((ZapWin32ResourceString*)(a.m_pNameOrId))->GetString(), ((ZapWin32ResourceString*)(b.m_pNameOrId))->GetString()) < 0;
+            else
+                return a.m_pNameOrId < b.m_pNameOrId;
+        }
+    } resourceSorter;
+    std::sort(m_entries.begin(), m_entries.end(), resourceSorter);
 
-    VersionResourceHeader header;
 
-    ZeroMemory(&header, sizeof(header));
+    IMAGE_RESOURCE_DIRECTORY directory;
+    ZeroMemory(&directory, sizeof(IMAGE_RESOURCE_DIRECTORY));
+
+    for (auto& entry : m_entries)
+    {
+        if (entry.m_nameOrIdIsString)
+            directory.NumberOfNamedEntries++;
+        else
+            directory.NumberOfIdEntries++;
+    }
+    pZapWriter->Write(&directory, sizeof(IMAGE_RESOURCE_DIRECTORY));
 
-    header.TypeDir.NumberOfIdEntries = 1;
-    header.TypeEntry.Id = (USHORT)((ULONG_PTR)RT_VERSION);
-    header.TypeEntry.OffsetToDirectory = offsetof(VersionResourceHeader, NameDir);
-    header.TypeEntry.DataIsDirectory = 1;
+    // Offsets are based from the begining of the resources blob (see PE format documentation)
+    DWORD dataEntryRVA = this->GetRVA() - m_pWin32ResourceSection->GetRVA()
+        + sizeof(IMAGE_RESOURCE_DIRECTORY) + 
+        (DWORD)m_entries.size() * sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY);
 
-    header.NameDir.NumberOfIdEntries = 1;
-    header.NameEntry.Id = 1;
-    header.NameEntry.OffsetToDirectory = offsetof(VersionResourceHeader, LangDir);
-    header.NameEntry.DataIsDirectory = 1;
+    for (auto& entry : m_entries)
+    {
+        IMAGE_RESOURCE_DIRECTORY_ENTRY dirEntry;
+        ZeroMemory(&dirEntry, sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY));
 
-    header.LangDir.NumberOfIdEntries = 1;
-    header.LangEntry.OffsetToDirectory = offsetof(VersionResourceHeader, DataEntry);
+        if (entry.m_nameOrIdIsString)
+        {
+            // Offsets are based from the begining of the resources blob (see PE format documentation)
+            dirEntry.NameOffset = ((ZapWin32ResourceString*)(entry.m_pNameOrId))->GetRVA() - m_pWin32ResourceSection->GetRVA();
+            dirEntry.NameIsString = true;
+        }
+        else
+        {
+            _ASSERT(IS_INTRESOURCE(entry.m_pNameOrId));
+            dirEntry.Id = (WORD)((ULONG_PTR)entry.m_pNameOrId & 0xffff);
+        }
 
-    header.DataEntry.OffsetToData = m_pVersionData->GetRVA();
-    header.DataEntry.Size = m_pVersionData->GetSize();
+        if (entry.m_dataIsSubDirectory)
+        {
+            // Offsets are based from the begining of the resources blob (see PE format documentation)
+            dirEntry.OffsetToDirectory = entry.m_pDataOrSubDirectory->GetRVA() - m_pWin32ResourceSection->GetRVA();
+            dirEntry.DataIsDirectory = true;
+        }
+        else
+        {
+            dirEntry.OffsetToData = dataEntryRVA;
+            dataEntryRVA += sizeof(IMAGE_RESOURCE_DATA_ENTRY);
+        }
 
-    pZapWriter->Write(&header, sizeof(header));
+        pZapWriter->Write(&dirEntry, sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY));
+    }
+
+    for (auto& entry : m_entries)
+    {
+        if (entry.m_dataIsSubDirectory)
+            continue;
+
+        IMAGE_RESOURCE_DATA_ENTRY dataEntry;
+        ZeroMemory(&dataEntry, sizeof(IMAGE_RESOURCE_DATA_ENTRY));
+
+        dataEntry.OffsetToData = entry.m_pDataOrSubDirectory->GetRVA();
+        dataEntry.Size = entry.m_pDataOrSubDirectory->GetSize();
+
+        pZapWriter->Write(&dataEntry, sizeof(IMAGE_RESOURCE_DATA_ENTRY));
+    }
 }
 
-void ZapImage::CopyWin32VersionResource()
+void ZapImage::CopyWin32Resources()
 {
-    // Copy the version resource over so it is easy to see in the dumps where the ngened module came from
-    COUNT_T cbResourceData;
-    PVOID pResourceData = m_ModuleDecoder.GetWin32Resource(MAKEINTRESOURCE(1), RT_VERSION, &cbResourceData);
+#ifdef FEATURE_PREJIT
+    if (!IsReadyToRunCompilation())
+    {
+        // When compiling a fragile NGEN image, in order to avoid the risk of regression, only copy the RT_VERSION resource over so it 
+        // is easy to see in the dumps where the ngened module came from. For R2R, we copy all resources (new behavior).
+        COUNT_T cbResourceData;
+        PVOID pResourceData = m_ModuleDecoder.GetWin32Resource(MAKEINTRESOURCE(1), RT_VERSION, &cbResourceData);
+
+        if (!pResourceData || !cbResourceData)
+            return;
+
+        ZapBlob * pVersionData = new (GetHeap()) ZapBlobPtr(pResourceData, cbResourceData);
+
+        ZapWin32ResourceDirectory* pTypeDirectory = new (GetHeap()) ZapWin32ResourceDirectory(m_pWin32ResourceSection);
+        ZapWin32ResourceDirectory* pNameDirectory = new (GetHeap()) ZapWin32ResourceDirectory(m_pWin32ResourceSection);
+        ZapWin32ResourceDirectory* pLanguageDirectory = new (GetHeap()) ZapWin32ResourceDirectory(m_pWin32ResourceSection);
+
+        pTypeDirectory->AddEntry(RT_VERSION, false, pNameDirectory, true);
+        pNameDirectory->AddEntry(MAKEINTRESOURCE(1), false, pLanguageDirectory, true);
+        pLanguageDirectory->AddEntry(MAKEINTRESOURCE(0), false, pVersionData, false);
+
+        pTypeDirectory->PlaceNodeAndDependencies(m_pWin32ResourceSection);
+
+        m_pWin32ResourceSection->Place(pVersionData);
+
+        SetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE, m_pWin32ResourceSection);
 
-    if (!pResourceData || !cbResourceData)
         return;
+    }
+#endif
+
+    class ResourceEnumerationCallback
+    {
+        ZapImage* m_pZapImage;
+        PEDecoder* m_pModuleDecoder;
+
+        std::vector<ZapNode*> m_dataEntries;
+        std::vector<ZapBlob*> m_stringEntries;
+
+        ZapWin32ResourceDirectory* m_pRootDirectory;
+        ZapWin32ResourceDirectory* m_pCurrentTypesDirectory;
+        ZapWin32ResourceDirectory* m_pCurrentNamesDirectory;
+
+        bool AddResource(LPCWSTR lpszResourceName, LPCWSTR lpszResourceType, DWORD langID, BYTE* pResourceData, COUNT_T cbResourceData)
+        {
+            ZapBlob* pDataBlob = new (m_pZapImage->GetHeap()) ZapBlobPtr(pResourceData, cbResourceData);
+            m_dataEntries.push_back(pDataBlob);
+
+            m_pCurrentNamesDirectory->AddEntry((PVOID)(ULONG_PTR)langID, false, pDataBlob, false);
+
+            return true;
+        }
+
+        ZapWin32ResourceDirectory* CreateResourceSubDirectory(ZapWin32ResourceDirectory* pRootDir, LPCWSTR pNameOrId)
+        {
+            bool nameIsString = !IS_INTRESOURCE(pNameOrId);
+
+            PVOID pIdOrNameZapNode = (PVOID)pNameOrId;
+            if (nameIsString)
+            {
+                pIdOrNameZapNode = new (m_pZapImage->GetHeap()) ZapWin32ResourceString(pNameOrId);
+                m_stringEntries.push_back((ZapBlob*)pIdOrNameZapNode);
+            }
+
+            ZapWin32ResourceDirectory* pResult = new (m_pZapImage->GetHeap()) ZapWin32ResourceDirectory(m_pZapImage->m_pWin32ResourceSection);
+            pRootDir->AddEntry(pIdOrNameZapNode, nameIsString, pResult, true);
+
+            return pResult;
+        }
+
+    public:
+        ResourceEnumerationCallback(PEDecoder* pModuleDecoder, ZapImage* pZapImage)
+            : m_pZapImage(pZapImage), m_pModuleDecoder(pModuleDecoder)
+        {
+            m_pRootDirectory = new (pZapImage->GetHeap()) ZapWin32ResourceDirectory(pZapImage->m_pWin32ResourceSection);
+            m_pCurrentTypesDirectory = m_pCurrentNamesDirectory = NULL;
+        }
 
-    ZapBlob * pVersionData = new (GetHeap()) ZapBlobPtr(pResourceData, cbResourceData);
+        static bool EnumResourcesCallback(LPCWSTR lpszResourceName, LPCWSTR lpszResourceType, DWORD langID, BYTE* data, COUNT_T cbData, void *context)
+        {
+            ResourceEnumerationCallback* pCallback = (ResourceEnumerationCallback*)context;
+            // Third level in the enumeration: resources by langid for each name/type. 
 
-    ZapVersionResource * pVersionResource = new (GetHeap()) ZapVersionResource(pVersionData);
+            // Note that this callback is not equivalent to the Windows enumeration apis as this api provides the resource data
+            // itself, and the resources are guaranteed to be present directly in the associated binary. This does not exactly
+            // match the Windows api, but it is exactly what we want when copying all resource data.
 
-    m_pWin32ResourceSection->Place(pVersionResource);
-    m_pWin32ResourceSection->Place(pVersionData);
+            return pCallback->AddResource(lpszResourceName, lpszResourceType, langID, data, cbData);
+        }
+
+        static bool EnumResourceNamesCallback(LPCWSTR lpszResourceName, LPCWSTR lpszResourceType, void *context)
+        {
+            // Second level in the enumeration: resources by names for each resource type
+
+            ResourceEnumerationCallback* pCallback = (ResourceEnumerationCallback*)context;
+            pCallback->m_pCurrentNamesDirectory = pCallback->CreateResourceSubDirectory(pCallback->m_pCurrentTypesDirectory, lpszResourceName);
+            
+            return pCallback->m_pModuleDecoder->EnumerateWin32Resources(lpszResourceName, lpszResourceType, ResourceEnumerationCallback::EnumResourcesCallback, context);
+        }
 
-    SetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE, m_pWin32ResourceSection);
+        static bool EnumResourceTypesCallback(LPCWSTR lpszType, void *context)
+        {
+            // First level in the enumeration: resources by types
+
+            // Skip IBC resources
+            if (!IS_INTRESOURCE(lpszType) && (wcscmp(lpszType, W("IBC")) == 0))
+                return true;
+
+            ResourceEnumerationCallback* pCallback = (ResourceEnumerationCallback*)context;
+            pCallback->m_pCurrentTypesDirectory = pCallback->CreateResourceSubDirectory(pCallback->m_pRootDirectory, lpszType);
+
+            return pCallback->m_pModuleDecoder->EnumerateWin32ResourceNames(lpszType, ResourceEnumerationCallback::EnumResourceNamesCallback, context);
+        }
+
+        void PlaceResourceNodes(ZapVirtualSection* pWin32ResourceSection)
+        {
+            m_pRootDirectory->PlaceNodeAndDependencies(pWin32ResourceSection);
+
+            //
+            // These strings are stored together after the last Resource Directory entry and before the first Resource Data entry. This 
+            // minimizes the impact of these variable-length strings on the alignment of the fixed-size directory entries
+            //
+            for (auto& entry : m_stringEntries)
+                pWin32ResourceSection->Place(entry);
+
+            for (auto& entry : m_dataEntries)
+                pWin32ResourceSection->Place(entry);
+        }
+    };
+
+    ResourceEnumerationCallback callbacks(&m_ModuleDecoder, this);
+
+    HMODULE hModule = (HMODULE)dac_cast<TADDR>(m_ModuleDecoder.GetBase());
+
+    //
+    // Resources are binary-sorted tree structure. By convention, Windows uses three levels
+    // for resources: Type, Name, Language. To reduces the overall complexity, we'll copy and store resources in the
+    // "neutral" language only.
+    //
+
+    if (!m_ModuleDecoder.EnumerateWin32ResourceTypes(ResourceEnumerationCallback::EnumResourceTypesCallback, &callbacks))
+    {
+        ThrowHR(E_FAIL);
+    }
+    else
+    {
+        callbacks.PlaceResourceNodes(m_pWin32ResourceSection);
+
+        SetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE, m_pWin32ResourceSection);
+    }
 }
 #undef MAKEINTRESOURCE
 
index f966ced..415ea4e 100644 (file)
@@ -13,6 +13,8 @@
 #ifndef __ZAPHEADERS_H__
 #define __ZAPHEADERS_H__
 
+#include <clr_std/vector>
+
 //
 // IMAGE_COR20_HEADER
 //
@@ -184,33 +186,99 @@ public:
 };
 
 //
-// Version Resource
+// Win32 Resources
 //
 
-class ZapVersionResource : public ZapNode
+class ZapWin32ResourceString : public ZapNode
 {
-    ZapNode * m_pVersionData;
+    //
+    // This ZapNode maps the IMAGE_RESOURCE_DIR_STRING_U resource data structure for storing strings.
+    //
+
+    LPWSTR m_pString;
 
 public:
-    ZapVersionResource(ZapNode * pVersionData)
-        : m_pVersionData(pVersionData)
+    ZapWin32ResourceString(LPCWSTR pString)
+    { 
+        size_t strLen = wcslen(pString);
+        _ASSERT(pString != NULL && strLen < 0xffff);
+
+        m_pString = new WCHAR[strLen + 1];
+        wcscpy(m_pString, pString);
+        m_pString[strLen] = L'\0';
+    }
+
+    LPCWSTR GetString() { return m_pString; }
+
+    virtual DWORD GetSize()
+    {
+        return sizeof(WORD) + sizeof(WCHAR) * (DWORD)wcslen(m_pString);
+    }
+
+    virtual UINT GetAlignment()
+    {
+        return sizeof(WORD);
+    }
+
+    virtual ZapNodeType GetType()
     {
+        return ZapNodeType_Blob;
     }
 
-    struct VersionResourceHeader {
-        IMAGE_RESOURCE_DIRECTORY TypeDir;
-        IMAGE_RESOURCE_DIRECTORY_ENTRY TypeEntry;
-        IMAGE_RESOURCE_DIRECTORY NameDir;
-        IMAGE_RESOURCE_DIRECTORY_ENTRY NameEntry;
-        IMAGE_RESOURCE_DIRECTORY LangDir;
-        IMAGE_RESOURCE_DIRECTORY_ENTRY LangEntry;
-        IMAGE_RESOURCE_DATA_ENTRY DataEntry;
-        CHAR Data[0];
+    virtual void Save(ZapWriter * pZapWriter)
+    {
+        WORD size = (WORD)wcslen(m_pString);
+        pZapWriter->Write(&size, sizeof(WORD));
+        pZapWriter->Write((PVOID)m_pString, sizeof(WCHAR) * size);
+    }
+};
+
+class ZapWin32ResourceDirectory : public ZapNode
+{
+    //
+    // This ZapNode maps the IMAGE_RESOURCE_DIRECTORY resource data structure for storing a resource directory. Each directory
+    // is then followed by a number of IMAGE_RESOURCE_DIRECTORY_ENTRY entries, which can either point to other resource directories (RVAs
+    // to other ZapWin32ResourceDirectory nodes), or point to actual resource data (RVAs to a number of IMAGE_RESOURCE_DATA_ENTRY entries
+    // that immediately follow the IMAGE_RESOURCE_DIRECTORY_ENTRY entries).
+    //
+    // Refer to the PE resources format for more information (https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#the-rsrc-section)
+    //
+
+    struct DataOrSubDirectoryEntry
+    {
+        PVOID m_pNameOrId;
+        bool m_nameOrIdIsString;
+        ZapNode* m_pDataOrSubDirectory;
+        bool m_dataIsSubDirectory;
     };
+    std::vector<DataOrSubDirectoryEntry> m_entries;
+    ZapVirtualSection* m_pWin32ResourceSection;
+
+public:
+    ZapWin32ResourceDirectory(ZapVirtualSection* pWin32ResourceSection)
+        : m_pWin32ResourceSection(pWin32ResourceSection)
+    { }
+
+    void AddEntry(PVOID pNameOrId, bool nameOrIdIsString, ZapNode* pDataOrSubDirectory, bool dataIsSubDirectory)
+    {
+        DataOrSubDirectoryEntry entry;
+        entry.m_pDataOrSubDirectory = pDataOrSubDirectory;
+        entry.m_dataIsSubDirectory = dataIsSubDirectory;
+        entry.m_pNameOrId = pNameOrId;
+        entry.m_nameOrIdIsString = nameOrIdIsString;
+
+        m_entries.push_back(entry);
+    }
 
     virtual DWORD GetSize()
     {
-        return sizeof(VersionResourceHeader);
+        DWORD size = sizeof(IMAGE_RESOURCE_DIRECTORY) + (DWORD)m_entries.size() * sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY);
+        for (auto& entry : m_entries)
+        {
+            if (!entry.m_dataIsSubDirectory)
+                size += sizeof(IMAGE_RESOURCE_DATA_ENTRY);
+        }
+        return size;
     }
 
     virtual UINT GetAlignment()
@@ -220,7 +288,21 @@ public:
 
     virtual ZapNodeType GetType()
     {
-        return ZapNodeType_VersionResource;
+        return ZapNodeType_Win32Resources;
+    }
+
+    void PlaceNodeAndDependencies(ZapVirtualSection* pWin32ResourceSection)
+    {
+        pWin32ResourceSection->Place(this);
+
+        for (auto& entry : m_entries)
+        {
+            if (entry.m_dataIsSubDirectory)
+            {
+                ZapWin32ResourceDirectory* pSubDirNode = (ZapWin32ResourceDirectory*)entry.m_pDataOrSubDirectory;
+                pSubDirNode->PlaceNodeAndDependencies(pWin32ResourceSection);
+            }
+        }
     }
 
     virtual void Save(ZapWriter * pZapWriter);
index aa661f5..50df05f 100644 (file)
@@ -1496,7 +1496,7 @@ void ZapImage::OutputTables()
     }
 
     CopyDebugDirEntry();
-    CopyWin32VersionResource();
+    CopyWin32Resources();
 
     if (m_pILMetaData != NULL)
     {
index 832f2b1..d5317a3 100644 (file)
@@ -599,7 +599,7 @@ private:
     void OutputManifestMetadataForReadyToRun();
 
     void CopyDebugDirEntry();
-    void CopyWin32VersionResource();
+    void CopyWin32Resources();
 
     void OutputManifestMetadata();
     void OutputTables();
index ae7d58a..82bad5b 100644 (file)
@@ -35,7 +35,7 @@ enum ZapNodeType {
     ZapNodeType_CodeManagerEntry,
     ZapNodeType_MetaData,
     ZapNodeType_DebugDirectory,
-    ZapNodeType_VersionResource,
+    ZapNodeType_Win32Resources,
 
 // PlaceHolders
 
diff --git a/tests/src/readytorun/tests/fileversionpreservation/Properties/AssemblyInfo.cs b/tests/src/readytorun/tests/fileversionpreservation/Properties/AssemblyInfo.cs
new file mode 100644 (file)
index 0000000..2cef16e
--- /dev/null
@@ -0,0 +1,5 @@
+using System.Reflection;
+
+[assembly: AssemblyFileVersion("2.0.1.9")]
+[assembly: AssemblyVersion("2.0.1.10")]
+
diff --git a/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.cs b/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.cs
new file mode 100644 (file)
index 0000000..5839087
--- /dev/null
@@ -0,0 +1,39 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Diagnostics;
+
+public class Program 
+{
+    public static int Main() 
+    {
+        return RunTest();
+    }
+
+    public static bool CheckVersionResourceUsingApi(string filename)
+    {
+        FileVersionInfo fileVer = FileVersionInfo.GetVersionInfo(filename);
+
+        bool success = fileVer.FileVersion == "2.0.1.9";
+        if (!success)
+            Console.WriteLine($"{filename} has version \"{fileVer.FileVersion}\"");
+
+        return success;
+    }
+
+    public static int RunTest()
+    {
+        string ilfilename = typeof(Program).Assembly.Location;
+        string nifilename = ilfilename.Replace(".exe", ".ni.exe");
+
+        bool success = true;
+
+        if (!CheckVersionResourceUsingApi(ilfilename))
+            success = false;
+
+        if (!CheckVersionResourceUsingApi(nifilename))
+            success = false;
+
+        return success ? 100 : -1;
+    }
+}
diff --git a/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.csproj b/tests/src/readytorun/tests/fileversionpreservation/fileversionpreservation.csproj
new file mode 100644 (file)
index 0000000..48a10ed
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{7DECC55A-B584-4456-83BA-6C42A5B3B3CB}</ProjectGuid>
+    <OutputType>exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <DefineConstants>$(DefineConstants);STATIC;CORECLR</DefineConstants>
+    <CLRTestPriority>0</CLRTestPriority>
+    <DebugType>PdbOnly</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="fileversionpreservation.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <PropertyGroup>
+    <CLRTestBatchPreCommands><![CDATA[
+$(CLRTestBatchPreCommands)
+%Core_Root%\crossgen /readytorun /platform_assemblies_paths %Core_Root%%3B%25CD% /out fileversionpreservation.ni.exe fileversionpreservation.exe
+]]></CLRTestBatchPreCommands>
+    <BashCLRTestPreCommands><![CDATA[
+$(BashCLRTestPreCommands)
+$CORE_ROOT/crossgen -readytorun -platform_assemblies_paths $CORE_ROOT:`pwd` -out fileversionpreservation.ni.exe fileversionpreservation.exe
+]]></BashCLRTestPreCommands>
+  </PropertyGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
\ No newline at end of file