File: CvtRes.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.Text;
using System.Diagnostics;
using BYTE = System.Byte;
using DWORD = System.UInt32;
using WCHAR = System.Char;
using WORD = System.UInt16;
using System.Reflection.PortableExecutable;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    internal class RESOURCE
    {
        internal RESOURCE_STRING? pstringType;
        internal RESOURCE_STRING? pstringName;
 
        internal DWORD DataSize;               // size of data without header
        internal DWORD HeaderSize;     // Length of the header
        // [Ordinal or Name TYPE]
        // [Ordinal or Name NAME]
        internal DWORD DataVersion;    // version of data struct
        internal WORD MemoryFlags;    // state of the resource
        internal WORD LanguageId;     // Unicode support for NLS
        internal DWORD Version;        // Version of the resource data
        internal DWORD Characteristics;        // Characteristics of the data
        internal byte[]? data;       //data
    };
 
    internal class RESOURCE_STRING
    {
        internal WORD Ordinal;
        internal string? theString;
    };
 
    /// <summary>
    /// Parses .RES a file into its constituent resource elements.
    /// Mostly translated from cvtres.cpp.
    /// </summary>
    internal class CvtResFile
    {
        private const WORD RT_DLGINCLUDE = 17;
 
        internal static List<RESOURCE> ReadResFile(Stream stream)
        {
            var reader = new BinaryReader(stream, Encoding.Unicode);
            var resourceNames = new List<RESOURCE>();
 
            var startPos = stream.Position;
 
            var initial32Bits = reader.ReadUInt32();
 
            //RC.EXE output starts with a resource that contains no data.
            if (initial32Bits != 0)
                throw new ResourceException("Stream does not begin with a null resource and is not in .RES format.");
 
            stream.Position = startPos;
 
            // Build up Type and Name directories
 
            while (stream.Position < stream.Length)
            {
                // Get the sizes from the file
 
                var cbData = reader.ReadUInt32();
                var cbHdr = reader.ReadUInt32();
 
                if (cbHdr < 2 * sizeof(DWORD))
                {
                    throw new ResourceException(String.Format("Resource header beginning at offset 0x{0:x} is malformed.", stream.Position - 8));
                    //ErrorPrint(ERR_FILECORRUPT, szFilename);
                }
 
                // Discard null resource
 
                if (cbData == 0)
                {
                    stream.Position += cbHdr - 2 * sizeof(DWORD);
                    continue;
                }
 
                var pAdditional = new RESOURCE()
                {
                    HeaderSize = cbHdr,
                    DataSize = cbData
                };
 
                // Read the TYPE and NAME
 
                pAdditional.pstringType = ReadStringOrID(reader);
                pAdditional.pstringName = ReadStringOrID(reader);
 
                //round up to dword boundary.
                stream.Position = (stream.Position + 3) & ~3;
 
                // Read the rest of the header
                pAdditional.DataVersion = reader.ReadUInt32();
                pAdditional.MemoryFlags = reader.ReadUInt16();
                pAdditional.LanguageId = reader.ReadUInt16();
                pAdditional.Version = reader.ReadUInt32();
                pAdditional.Characteristics = reader.ReadUInt32();
 
                pAdditional.data = new byte[pAdditional.DataSize];
                reader.Read(pAdditional.data, 0, pAdditional.data.Length);
 
                stream.Position = (stream.Position + 3) & ~3;
 
                if (pAdditional.pstringType.theString == null && (pAdditional.pstringType.Ordinal == (WORD)RT_DLGINCLUDE))
                {
                    // Ignore DLGINCLUDE resources
                    continue;
                }
 
                resourceNames.Add(pAdditional);
            }
 
            return resourceNames;
        }
 
        private static RESOURCE_STRING ReadStringOrID(BinaryReader fhIn)
        {
            // Reads a String structure from fhIn
            // If the first word is 0xFFFF then this is an ID
            // return the ID instead
 
            RESOURCE_STRING pstring = new RESOURCE_STRING();
 
            WCHAR firstWord = fhIn.ReadChar();
 
            if (firstWord == 0xFFFF)
            {
                // An ID
 
                pstring.Ordinal = fhIn.ReadUInt16();
            }
            else
            {
                // A string
                pstring.Ordinal = 0xFFFF;
 
                //keep reading until null reached.
 
                StringBuilder sb = new StringBuilder();
 
                WCHAR curChar = firstWord;
 
                do
                {
                    sb.Append(curChar);
                    curChar = fhIn.ReadChar();
                }
                while (curChar != 0);
 
                pstring.theString = sb.ToString();
            }
 
            return (pstring);
        }
    }
 
    internal static class COFFResourceReader
    {
        private static void ConfirmSectionValues(SectionHeader hdr, long fileSize)
        {
            if ((long)hdr.PointerToRawData + hdr.SizeOfRawData > fileSize)
                throw new ResourceException(CodeAnalysisResources.CoffResourceInvalidSectionSize);
        }
 
        internal static Microsoft.Cci.ResourceSection ReadWin32ResourcesFromCOFF(Stream stream)
        {
            var peHeaders = new PEHeaders(stream);
            var rsrc1 = new SectionHeader();
            var rsrc2 = new SectionHeader();
 
            int foundCount = 0;
            foreach (var sectionHeader in peHeaders.SectionHeaders)
            {
                if (sectionHeader.Name == ".rsrc$01")
                {
                    rsrc1 = sectionHeader;
                    foundCount++;
                }
                else if (sectionHeader.Name == ".rsrc$02")
                {
                    rsrc2 = sectionHeader;
                    foundCount++;
                }
            }
 
            if (foundCount != 2)
                throw new ResourceException(CodeAnalysisResources.CoffResourceMissingSection);
 
            ConfirmSectionValues(rsrc1, stream.Length);
            ConfirmSectionValues(rsrc2, stream.Length);
 
            //This will be the final resource section bytes without a header. It contains the concatenation
            //of .rsrc$02 on to the end of .rsrc$01.
            var imageResourceSectionBytes = new byte[checked(rsrc1.SizeOfRawData + rsrc2.SizeOfRawData)];
 
            stream.Seek(rsrc1.PointerToRawData, SeekOrigin.Begin);
            stream.TryReadAll(imageResourceSectionBytes, 0, rsrc1.SizeOfRawData); // ConfirmSectionValues ensured that data are available
            stream.Seek(rsrc2.PointerToRawData, SeekOrigin.Begin);
            stream.TryReadAll(imageResourceSectionBytes, rsrc1.SizeOfRawData, rsrc2.SizeOfRawData); // ConfirmSectionValues ensured that data are available
 
            const int SizeOfRelocationEntry = 10;
 
            try
            {
                var relocLastAddress = checked(rsrc1.PointerToRelocations + (rsrc1.NumberOfRelocations * SizeOfRelocationEntry));
 
                if (relocLastAddress > stream.Length)
                    throw new ResourceException(CodeAnalysisResources.CoffResourceInvalidRelocation);
            }
            catch (OverflowException)
            {
                throw new ResourceException(CodeAnalysisResources.CoffResourceInvalidRelocation);
            }
 
            //.rsrc$01 contains the directory tree. .rsrc$02 contains the raw resource data.
            //.rsrc$01 has references to spots in .rsrc$02. Those spots are expressed as relocations.
            //These will need to be fixed up when the RVA of the .rsrc section in the final image is known.
            var relocationOffsets = new uint[rsrc1.NumberOfRelocations];    //offsets into .rsrc$01
 
            var relocationSymbolIndices = new uint[rsrc1.NumberOfRelocations];
 
            var reader = new BinaryReader(stream, Encoding.Unicode);
            stream.Position = rsrc1.PointerToRelocations;
 
            for (int i = 0; i < rsrc1.NumberOfRelocations; i++)
            {
                relocationOffsets[i] = reader.ReadUInt32();
                //What is being read and stored is the reloc's "Value"
                //This is the symbol's index.
                relocationSymbolIndices[i] = reader.ReadUInt32();
                reader.ReadUInt16(); //we do nothing with the "Type"
            }
 
            //now that symbol indices are gathered, begin indexing the symbols
            stream.Position = peHeaders.CoffHeader.PointerToSymbolTable;
            const uint ImageSizeOfSymbol = 18;
 
            try
            {
                var lastSymAddress = checked(peHeaders.CoffHeader.PointerToSymbolTable + peHeaders.CoffHeader.NumberOfSymbols * ImageSizeOfSymbol);
 
                if (lastSymAddress > stream.Length)
                    throw new ResourceException(CodeAnalysisResources.CoffResourceInvalidSymbol);
            }
            catch (OverflowException)
            {
                throw new ResourceException(CodeAnalysisResources.CoffResourceInvalidSymbol);
            }
 
            var outputStream = new MemoryStream(imageResourceSectionBytes);
            var writer = new BinaryWriter(outputStream);  //encoding shouldn't matter. There are no strings being written.
 
            for (int i = 0; i < relocationSymbolIndices.Length; i++)
            {
                if (relocationSymbolIndices[i] > peHeaders.CoffHeader.NumberOfSymbols)
                    throw new ResourceException(CodeAnalysisResources.CoffResourceInvalidRelocation);
 
                var offsetOfSymbol = peHeaders.CoffHeader.PointerToSymbolTable + relocationSymbolIndices[i] * ImageSizeOfSymbol;
 
                stream.Position = offsetOfSymbol;
                stream.Position += 8; //skip over symbol name
                var symValue = reader.ReadUInt32();
                var symSection = reader.ReadInt16();
                var symType = reader.ReadUInt16();
                //ignore the rest of the fields.
 
                const ushort IMAGE_SYM_TYPE_NULL = 0x0000;
 
                if (symType != IMAGE_SYM_TYPE_NULL ||
                    symSection != 3)  //3rd section is .rsrc$02
                    throw new ResourceException(CodeAnalysisResources.CoffResourceInvalidSymbol);
 
                //perform relocation. We are concatenating the contents of .rsrc$02 (the raw resource data)
                //on to the end of .rsrc$01 (the directory tree) to yield the final resource section for the image.
                //The directory tree has references into the raw resource data. These references are expressed
                //in the final image as file positions, not positions relative to the beginning of the section.
                //First make the resources be relative to the beginning of the section by adding the size
                //of .rsrc$01 to them. They will ultimately need the RVA of the final image resource section added 
                //to them. We don't know that yet. That is why the array of offsets is preserved. 
 
                outputStream.Position = relocationOffsets[i];
                writer.Write((uint)(symValue + rsrc1.SizeOfRawData));
            }
 
            return new Cci.ResourceSection(imageResourceSectionBytes, relocationOffsets);
        }
    }
 
    internal static class Win32ResourceConversions
    {
        private struct ICONDIRENTRY
        {
            internal BYTE bWidth;
            internal BYTE bHeight;
            internal BYTE bColorCount;
            internal BYTE bReserved;
            internal WORD wPlanes;
            internal WORD wBitCount;
            internal DWORD dwBytesInRes;
            internal DWORD dwImageOffset;
        };
 
        internal static void AppendIconToResourceStream(Stream resStream, Stream iconStream)
        {
            var iconReader = new BinaryReader(iconStream);
 
            //read magic reserved WORD
            var reserved = iconReader.ReadUInt16();
            if (reserved != 0)
                throw new ResourceException(CodeAnalysisResources.IconStreamUnexpectedFormat);
 
            var type = iconReader.ReadUInt16();
            if (type != 1)
                throw new ResourceException(CodeAnalysisResources.IconStreamUnexpectedFormat);
 
            var count = iconReader.ReadUInt16();
            if (count == 0)
                throw new ResourceException(CodeAnalysisResources.IconStreamUnexpectedFormat);
 
            var iconDirEntries = new ICONDIRENTRY[count];
            for (ushort i = 0; i < count; i++)
            {
                // Read the Icon header
                iconDirEntries[i].bWidth = iconReader.ReadByte();
                iconDirEntries[i].bHeight = iconReader.ReadByte();
                iconDirEntries[i].bColorCount = iconReader.ReadByte();
                iconDirEntries[i].bReserved = iconReader.ReadByte();
                iconDirEntries[i].wPlanes = iconReader.ReadUInt16();
                iconDirEntries[i].wBitCount = iconReader.ReadUInt16();
                iconDirEntries[i].dwBytesInRes = iconReader.ReadUInt32();
                iconDirEntries[i].dwImageOffset = iconReader.ReadUInt32();
            }
 
            // Because Icon files don't seem to record the actual w and BitCount in
            // the ICONDIRENTRY, get the info from the BITMAPINFOHEADER at the beginning
            // of the data here:
            //EDMAURER: PNG compressed icons must be treated differently. Do what has always
            //been done for uncompressed icons. Assume modern, compressed icons set the 
            //ICONDIRENTRY fields correctly.
            //if (*(DWORD*)icoBuffer == sizeof(BITMAPINFOHEADER))
            //{
            //    grp[i].Planes = ((BITMAPINFOHEADER*)icoBuffer)->biPlanes;
            //    grp[i].BitCount = ((BITMAPINFOHEADER*)icoBuffer)->biBitCount;
            //}
 
            for (ushort i = 0; i < count; i++)
            {
                iconStream.Position = iconDirEntries[i].dwImageOffset;
                if (iconReader.ReadUInt32() == 40)
                {
                    iconStream.Position += 8;
                    iconDirEntries[i].wPlanes = iconReader.ReadUInt16();
                    iconDirEntries[i].wBitCount = iconReader.ReadUInt16();
                }
            }
 
            //read everything and no exceptions. time to write.
            var resWriter = new BinaryWriter(resStream);
 
            //write all of the icon images as individual resources, then follow up with
            //a resource that groups them.
            const WORD RT_ICON = 3;
 
            for (ushort i = 0; i < count; i++)
            {
                /* write resource header.
                struct RESOURCEHEADER
                {
                    DWORD DataSize;
                    DWORD HeaderSize;
                    WORD Magic1;
                    WORD Type;
                    WORD Magic2;
                    WORD Name;
                    DWORD DataVersion;
                    WORD MemoryFlags;
                    WORD LanguageId;
                    DWORD Version;
                    DWORD Characteristics;
                };
                */
 
                resStream.Position = (resStream.Position + 3) & ~3; //headers begin on 4-byte boundaries.
                resWriter.Write((DWORD)iconDirEntries[i].dwBytesInRes);
                resWriter.Write((DWORD)0x00000020);
                resWriter.Write((WORD)0xFFFF);
                resWriter.Write((WORD)RT_ICON);
                resWriter.Write((WORD)0xFFFF);
                resWriter.Write((WORD)(i + 1));       //EDMAURER this is not general. Implies you can only append one icon to the resources.
                                                      //This icon ID would seem to be global among all of the icons not just this group.
                                                      //Zero appears to not be an acceptable ID. Note that this ID is referred to below.
                resWriter.Write((DWORD)0x00000000);
                resWriter.Write((WORD)0x1010);
                resWriter.Write((WORD)0x0000);
                resWriter.Write((DWORD)0x00000000);
                resWriter.Write((DWORD)0x00000000);
 
                //write the data.
                iconStream.Position = iconDirEntries[i].dwImageOffset;
                resWriter.Write(iconReader.ReadBytes(checked((int)iconDirEntries[i].dwBytesInRes)));
            }
 
            /*
            
            struct ICONDIR
            {
                WORD           idReserved;   // Reserved (must be 0)
                WORD           idType;       // Resource Type (1 for icons)
                WORD           idCount;      // How many images?
                ICONDIRENTRY   idEntries[1]; // An entry for each image (idCount of 'em)
            }/
             
            struct ICONRESDIR
            {
                BYTE Width;        // = ICONDIRENTRY.bWidth;
                BYTE Height;       // = ICONDIRENTRY.bHeight;
                BYTE ColorCount;   // = ICONDIRENTRY.bColorCount;
                BYTE reserved;     // = ICONDIRENTRY.bReserved;
                WORD Planes;       // = ICONDIRENTRY.wPlanes;
                WORD BitCount;     // = ICONDIRENTRY.wBitCount;
                DWORD BytesInRes;   // = ICONDIRENTRY.dwBytesInRes;
                WORD IconId;       // = RESOURCEHEADER.Name
            };
            */
 
            const WORD RT_GROUP_ICON = RT_ICON + 11;
 
            resStream.Position = (resStream.Position + 3) & ~3; //align 4-byte boundary
            //write the icon group. first a RESOURCEHEADER. the data is the ICONDIR
            resWriter.Write((DWORD)(3 * sizeof(WORD) + count * /*sizeof(ICONRESDIR)*/ 14));
            resWriter.Write((DWORD)0x00000020);
            resWriter.Write((WORD)0xFFFF);
            resWriter.Write((WORD)RT_GROUP_ICON);
            resWriter.Write((WORD)0xFFFF);
            resWriter.Write((WORD)0x7F00);  //IDI_APPLICATION
            resWriter.Write((DWORD)0x00000000);
            resWriter.Write((WORD)0x1030);
            resWriter.Write((WORD)0x0000);
            resWriter.Write((DWORD)0x00000000);
            resWriter.Write((DWORD)0x00000000);
 
            //the ICONDIR
            resWriter.Write((WORD)0x0000);
            resWriter.Write((WORD)0x0001);
            resWriter.Write((WORD)count);
 
            for (ushort i = 0; i < count; i++)
            {
                resWriter.Write((BYTE)iconDirEntries[i].bWidth);
                resWriter.Write((BYTE)iconDirEntries[i].bHeight);
                resWriter.Write((BYTE)iconDirEntries[i].bColorCount);
                resWriter.Write((BYTE)iconDirEntries[i].bReserved);
                resWriter.Write((WORD)iconDirEntries[i].wPlanes);
                resWriter.Write((WORD)iconDirEntries[i].wBitCount);
                resWriter.Write((DWORD)iconDirEntries[i].dwBytesInRes);
                resWriter.Write((WORD)(i + 1));   //ID
            }
        }
 
        /*
         * Dev10 alink had the following fallback behavior.
                private uint[] FileVersion
                {
                    get
                    {
                        if (fileVersionContents != null)
                            return fileVersionContents;
                        else
                        {
                            System.Diagnostics.Debug.Assert(assemblyVersionContents != null);
                            return assemblyVersionContents;
                        }
                    }
                }
 
                private uint[] ProductVersion
                {
                    get
                    {
                        if (productVersionContents != null)
                            return productVersionContents;
                        else
                            return this.FileVersion;
                    }
                }
                */
 
        internal static void AppendVersionToResourceStream(Stream resStream, bool isDll,
            string fileVersion, //should be [major.minor.build.rev] but doesn't have to be
            string originalFileName,
            string internalName,
            string productVersion,  //4 ints
            Version assemblyVersion, //individual values must be smaller than 65535
            string fileDescription = " ",   //the old compiler put blank here if nothing was user-supplied
            string legalCopyright = " ",    //the old compiler put blank here if nothing was user-supplied
            string? legalTrademarks = null,
            string? productName = null,
            string? comments = null,
            string? companyName = null)
        {
            var resWriter = new BinaryWriter(resStream, Encoding.Unicode);
            resStream.Position = (resStream.Position + 3) & ~3;
 
            const DWORD RT_VERSION = 16;
 
            var ver = new VersionResourceSerializer(isDll,
                comments,
                companyName,
                fileDescription,
                fileVersion,
                internalName,
                legalCopyright,
                legalTrademarks,
                originalFileName,
                productName,
                productVersion,
                assemblyVersion);
 
            var startPos = resStream.Position;
            var dataSize = ver.GetDataSize();
            const int headerSize = 0x20;
 
            resWriter.Write((DWORD)dataSize);    //data size
            resWriter.Write((DWORD)headerSize);                 //header size
            resWriter.Write((WORD)0xFFFF);                      //identifies type as ordinal.
            resWriter.Write((WORD)RT_VERSION);                 //type
            resWriter.Write((WORD)0xFFFF);                      //identifies name as ordinal.
            resWriter.Write((WORD)0x0001);                      //only ever 1 ver resource (what Dev10 does)
            resWriter.Write((DWORD)0x00000000);                 //data version
            resWriter.Write((WORD)0x0030);                      //memory flags (this is what the Dev10 compiler uses)
            resWriter.Write((WORD)0x0000);                      //languageId
            resWriter.Write((DWORD)0x00000000);                 //version
            resWriter.Write((DWORD)0x00000000);                 //characteristics
 
            ver.WriteVerResource(resWriter);
 
            System.Diagnostics.Debug.Assert(resStream.Position - startPos == dataSize + headerSize);
        }
 
        internal static void AppendManifestToResourceStream(Stream resStream, Stream manifestStream, bool isDll)
        {
            resStream.Position = (resStream.Position + 3) & ~3;
            const WORD RT_MANIFEST = 24;
 
            var resWriter = new BinaryWriter(resStream);
            resWriter.Write((DWORD)(manifestStream.Length));    //data size
            resWriter.Write((DWORD)0x00000020);                 //header size
            resWriter.Write((WORD)0xFFFF);                      //identifies type as ordinal.
            resWriter.Write((WORD)RT_MANIFEST);                 //type
            resWriter.Write((WORD)0xFFFF);                      //identifies name as ordinal.
            resWriter.Write((WORD)((isDll) ? 0x0002 : 0x0001));  //EDMAURER executables are named "1", DLLs "2"
            resWriter.Write((DWORD)0x00000000);                 //data version
            resWriter.Write((WORD)0x1030);                      //memory flags
            resWriter.Write((WORD)0x0000);                      //languageId
            resWriter.Write((DWORD)0x00000000);                 //version
            resWriter.Write((DWORD)0x00000000);                 //characteristics
 
            manifestStream.CopyTo(resStream);
        }
 
        private class VersionResourceSerializer
        {
            private readonly string? _commentsContents;
            private readonly string? _companyNameContents;
            private readonly string _fileDescriptionContents;
            private readonly string _fileVersionContents;
            private readonly string _internalNameContents;
            private readonly string _legalCopyrightContents;
            private readonly string? _legalTrademarksContents;
            private readonly string _originalFileNameContents;
            private readonly string? _productNameContents;
            private readonly string _productVersionContents;
            private readonly Version _assemblyVersionContents;
 
            private const string vsVersionInfoKey = "VS_VERSION_INFO";
            private const string varFileInfoKey = "VarFileInfo";
            private const string translationKey = "Translation";
            private const string stringFileInfoKey = "StringFileInfo";
            private readonly string _langIdAndCodePageKey; //should be 8 characters
            private const DWORD CP_WINUNICODE = 1200;
 
            private const ushort sizeVS_FIXEDFILEINFO = sizeof(DWORD) * 13;
            private readonly bool _isDll;
 
            internal VersionResourceSerializer(bool isDll, string? comments, string? companyName, string fileDescription, string fileVersion,
                string internalName, string legalCopyright, string? legalTrademark, string originalFileName, string? productName, string productVersion,
                Version assemblyVersion)
            {
                _isDll = isDll;
                _commentsContents = comments;
                _companyNameContents = companyName;
                _fileDescriptionContents = fileDescription;
                _fileVersionContents = fileVersion;
                _internalNameContents = internalName;
                _legalCopyrightContents = legalCopyright;
                _legalTrademarksContents = legalTrademark;
                _originalFileNameContents = originalFileName;
                _productNameContents = productName;
                _productVersionContents = productVersion;
                _assemblyVersionContents = assemblyVersion;
                _langIdAndCodePageKey = System.String.Format("{0:x4}{1:x4}", 0 /*langId*/, CP_WINUNICODE /*codepage*/);
            }
 
            private const uint VFT_APP = 0x00000001;
            private const uint VFT_DLL = 0x00000002;
 
            private IEnumerable<KeyValuePair<string, string>> GetVerStrings()
            {
                if (_commentsContents != null) yield return new KeyValuePair<string, string>("Comments", _commentsContents);
                if (_companyNameContents != null) yield return new KeyValuePair<string, string>("CompanyName", _companyNameContents);
                if (_fileDescriptionContents != null) yield return new KeyValuePair<string, string>("FileDescription", _fileDescriptionContents);
 
                yield return new KeyValuePair<string, string>("FileVersion", _fileVersionContents);
 
                if (_internalNameContents != null) yield return new KeyValuePair<string, string>("InternalName", _internalNameContents);
                if (_legalCopyrightContents != null) yield return new KeyValuePair<string, string>("LegalCopyright", _legalCopyrightContents);
                if (_legalTrademarksContents != null) yield return new KeyValuePair<string, string>("LegalTrademarks", _legalTrademarksContents);
                if (_originalFileNameContents != null) yield return new KeyValuePair<string, string>("OriginalFilename", _originalFileNameContents);
                if (_productNameContents != null) yield return new KeyValuePair<string, string>("ProductName", _productNameContents);
 
                yield return new KeyValuePair<string, string>("ProductVersion", _productVersionContents);
 
                if (_assemblyVersionContents != null) yield return new KeyValuePair<string, string>("Assembly Version", _assemblyVersionContents.ToString());
            }
 
            private uint FileType { get { return (_isDll) ? VFT_DLL : VFT_APP; } }
 
            private void WriteVSFixedFileInfo(BinaryWriter writer)
            {
                //There's nothing guaranteeing that these are n.n.n.n format.
                //The documentation says that if they're not that format the behavior is undefined.
                Version fileVersion;
                VersionHelper.TryParse(_fileVersionContents, version: out fileVersion);
 
                Version productVersion;
                VersionHelper.TryParse(_productVersionContents, version: out productVersion);
 
                writer.Write((DWORD)0xFEEF04BD);
                writer.Write((DWORD)0x00010000);
                writer.Write((DWORD)((uint)fileVersion.Major << 16) | (uint)fileVersion.Minor);
                writer.Write((DWORD)((uint)fileVersion.Build << 16) | (uint)fileVersion.Revision);
                writer.Write((DWORD)((uint)productVersion.Major << 16) | (uint)productVersion.Minor);
                writer.Write((DWORD)((uint)productVersion.Build << 16) | (uint)productVersion.Revision);
                writer.Write((DWORD)0x0000003F);   //VS_FFI_FILEFLAGSMASK  (EDMAURER) really? all these bits are valid?
                writer.Write((DWORD)0);    //file flags
                writer.Write((DWORD)0x00000004);   //VOS__WINDOWS32
                writer.Write((DWORD)this.FileType);
                writer.Write((DWORD)0);    //file subtype
                writer.Write((DWORD)0);    //date most sig
                writer.Write((DWORD)0);    //date least sig
            }
 
            /// <summary>
            /// Assume that 3 WORDs preceded this string and that they began 32-bit aligned.
            /// Given the string length compute the number of bytes that should be written to end
            /// the buffer on a 32-bit boundary</summary>
            /// <param name="cb"></param>
            /// <returns></returns>
            private static int PadKeyLen(int cb)
            {
                //add previously written 3 WORDS, round up, then subtract the 3 WORDS.
                return PadToDword(cb + 3 * sizeof(WORD)) - 3 * sizeof(WORD);
            }
            /// <summary>
            /// assuming the length of bytes submitted began on a 32-bit boundary,
            /// round up this length as necessary so that it ends at a 32-bit boundary.
            /// </summary>
            /// <param name="cb"></param>
            /// <returns></returns>
            private static int PadToDword(int cb)
            {
                return (cb + 3) & ~3;
            }
 
            private const int HDRSIZE = 3 * sizeof(ushort);
 
            private static ushort SizeofVerString(string lpszKey, string lpszValue)
            {
                int cbKey, cbValue;
 
                cbKey = (lpszKey.Length + 1) * 2;  // Make room for the NULL
                cbValue = (lpszValue.Length + 1) * 2;
 
                return checked((ushort)(PadKeyLen(cbKey) +    // key, 0 padded to DWORD boundary
                                cbValue +               // value
                                HDRSIZE));             // block header.
            }
 
            private static void WriteVersionString(KeyValuePair<string, string> keyValuePair, BinaryWriter writer)
            {
                RoslynDebug.Assert(keyValuePair.Value != null);
 
                ushort cbBlock = SizeofVerString(keyValuePair.Key, keyValuePair.Value);
                int cbKey = (keyValuePair.Key.Length + 1) * 2;     // includes terminating NUL
                int cbVal = (keyValuePair.Value.Length + 1) * 2;     // includes terminating NUL
 
                var startPos = writer.BaseStream.Position;
                Debug.Assert((startPos & 3) == 0);
 
                writer.Write((WORD)cbBlock);
                writer.Write((WORD)(keyValuePair.Value.Length + 1)); //add 1 for nul
                writer.Write((WORD)1);
                writer.Write(keyValuePair.Key.ToCharArray());
                writer.Write((WORD)'\0');
                writer.Write(new byte[PadKeyLen(cbKey) - cbKey]);
                Debug.Assert((writer.BaseStream.Position & 3) == 0);
                writer.Write(keyValuePair.Value.ToCharArray());
                writer.Write((WORD)'\0');
                //writer.Write(new byte[PadToDword(cbVal) - cbVal]);
 
                System.Diagnostics.Debug.Assert(cbBlock == writer.BaseStream.Position - startPos);
            }
 
            /// <summary>
            /// compute number of chars needed to end up on a 32-bit boundary assuming that three
            /// WORDS preceded this string.
            /// </summary>
            /// <param name="sz"></param>
            /// <returns></returns>
            private static int KEYSIZE(string sz)
            {
                return PadKeyLen((sz.Length + 1) * sizeof(WCHAR)) / sizeof(WCHAR);
            }
            private static int KEYBYTES(string sz)
            {
                return KEYSIZE(sz) * sizeof(WCHAR);
            }
 
            private int GetStringsSize()
            {
                int sum = 0;
 
                foreach (var verString in GetVerStrings())
                {
                    sum = (sum + 3) & ~3;   //ensure that each String data structure starts on a 32bit boundary.
                    sum += SizeofVerString(verString.Key, verString.Value);
                }
 
                return sum;
            }
 
            internal int GetDataSize()
            {
                int sizeEXEVERRESOURCE = sizeof(WORD) * 3 * 5 + 2 * sizeof(WORD) + //five headers + two words for CP and lang
                    KEYBYTES(vsVersionInfoKey) +
                    KEYBYTES(varFileInfoKey) +
                    KEYBYTES(translationKey) +
                    KEYBYTES(stringFileInfoKey) +
                    KEYBYTES(_langIdAndCodePageKey) +
                    sizeVS_FIXEDFILEINFO;
 
                return GetStringsSize() + sizeEXEVERRESOURCE;
            }
 
            internal void WriteVerResource(BinaryWriter writer)
            {
                /*
                    must be assumed to start on a 32-bit boundary.
                 * 
                 * the sub-elements of the VS_VERSIONINFO consist of a header (3 WORDS) a string
                 * and then beginning on the next 32-bit boundary, the elements children
                 
                    struct VS_VERSIONINFO
                    {
                        WORD cbRootBlock;                                     // size of whole resource
                        WORD cbRootValue;                                     // size of VS_FIXEDFILEINFO structure
                        WORD fRootText;                                       // root is text?
                        WCHAR szRootKey[KEYSIZE("VS_VERSION_INFO")];          // Holds "VS_VERSION_INFO"
                        VS_FIXEDFILEINFO vsFixed;                             // fixed information.
                          WORD cbVarBlock;                                      //   size of VarFileInfo block
                          WORD cbVarValue;                                      //   always 0
                          WORD fVarText;                                        //   VarFileInfo is text?
                          WCHAR szVarKey[KEYSIZE("VarFileInfo")];               //   Holds "VarFileInfo"
                            WORD cbTransBlock;                                    //     size of Translation block
                            WORD cbTransValue;                                    //     size of Translation value
                            WORD fTransText;                                      //     Translation is text?
                            WCHAR szTransKey[KEYSIZE("Translation")];             //     Holds "Translation"
                              WORD langid;                                          //     language id
                              WORD codepage;                                        //     codepage id
                          WORD cbStringBlock;                                   //   size of StringFileInfo block
                          WORD cbStringValue;                                   //   always 0
                          WORD fStringText;                                     //   StringFileInfo is text?
                          WCHAR szStringKey[KEYSIZE("StringFileInfo")];         //   Holds "StringFileInfo"
                            WORD cbLangCpBlock;                                   //     size of language/codepage block
                            WORD cbLangCpValue;                                   //     always 0
                            WORD fLangCpText;                                     //     LangCp is text?
                            WCHAR szLangCpKey[KEYSIZE("12345678")];               //     Holds hex version of language/codepage
                        // followed by strings
                    };
                */
 
                var debugPos = writer.BaseStream.Position;
                var dataSize = GetDataSize();
 
                writer.Write((WORD)dataSize);
                writer.Write((WORD)sizeVS_FIXEDFILEINFO);
                writer.Write((WORD)0);
                writer.Write(vsVersionInfoKey.ToCharArray());
                writer.Write(new byte[KEYBYTES(vsVersionInfoKey) - vsVersionInfoKey.Length * 2]);
                System.Diagnostics.Debug.Assert((writer.BaseStream.Position & 3) == 0);
                WriteVSFixedFileInfo(writer);
                writer.Write((WORD)(sizeof(WORD) * 2 + 2 * HDRSIZE + KEYBYTES(varFileInfoKey) + KEYBYTES(translationKey)));
                writer.Write((WORD)0);
                writer.Write((WORD)1);
                writer.Write(varFileInfoKey.ToCharArray());
                writer.Write(new byte[KEYBYTES(varFileInfoKey) - varFileInfoKey.Length * 2]);   //padding
                System.Diagnostics.Debug.Assert((writer.BaseStream.Position & 3) == 0);
                writer.Write((WORD)(sizeof(WORD) * 2 + HDRSIZE + KEYBYTES(translationKey)));
                writer.Write((WORD)(sizeof(WORD) * 2));
                writer.Write((WORD)0);
                writer.Write(translationKey.ToCharArray());
                writer.Write(new byte[KEYBYTES(translationKey) - translationKey.Length * 2]);   //padding
                System.Diagnostics.Debug.Assert((writer.BaseStream.Position & 3) == 0);
                writer.Write((WORD)0);      //langId; MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)) = 0
                writer.Write((WORD)CP_WINUNICODE);   //codepage; 1200 = CP_WINUNICODE
                System.Diagnostics.Debug.Assert((writer.BaseStream.Position & 3) == 0);
                writer.Write((WORD)(2 * HDRSIZE + KEYBYTES(stringFileInfoKey) + KEYBYTES(_langIdAndCodePageKey) + GetStringsSize()));
                writer.Write((WORD)0);
                writer.Write((WORD)1);
                writer.Write(stringFileInfoKey.ToCharArray());      //actually preceded by 5 WORDS so not consistent with the
                                                                    //assumptions of KEYBYTES, but equivalent.
                writer.Write(new byte[KEYBYTES(stringFileInfoKey) - stringFileInfoKey.Length * 2]); //padding. 
                System.Diagnostics.Debug.Assert((writer.BaseStream.Position & 3) == 0);
                writer.Write((WORD)(HDRSIZE + KEYBYTES(_langIdAndCodePageKey) + GetStringsSize()));
                writer.Write((WORD)0);
                writer.Write((WORD)1);
                writer.Write(_langIdAndCodePageKey.ToCharArray());
                writer.Write(new byte[KEYBYTES(_langIdAndCodePageKey) - _langIdAndCodePageKey.Length * 2]); //padding
                System.Diagnostics.Debug.Assert((writer.BaseStream.Position & 3) == 0);
 
                System.Diagnostics.Debug.Assert(writer.BaseStream.Position - debugPos == dataSize - GetStringsSize());
                debugPos = writer.BaseStream.Position;
 
                foreach (var entry in GetVerStrings())
                {
                    var writerPos = writer.BaseStream.Position;
 
                    //write any padding necessary to align the String struct on a 32 bit boundary.
                    writer.Write(new byte[((writerPos + 3) & ~3) - writerPos]);
 
                    System.Diagnostics.Debug.Assert(entry.Value != null);
                    WriteVersionString(entry, writer);
                }
 
                System.Diagnostics.Debug.Assert(writer.BaseStream.Position - debugPos == GetStringsSize());
            }
        }
    }
}