File: Win32Res.cs
Web Access
Project: src\src\Compilers\Test\Core\Microsoft.CodeAnalysis.Test.Utilities.csproj (Microsoft.CodeAnalysis.Test.Utilities)
// 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.
 
#nullable disable
 
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using System.IO;
using System.Collections.Generic;
using System.Xml.Linq;
 
namespace Microsoft.CodeAnalysis.Test.Utilities
{
    public class Win32Res
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
        [DllImport("kernel32.dll")]
        private static extern IntPtr LockResource(IntPtr hResData);
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);
 
        public static IntPtr GetResource(IntPtr lib, string resourceId, string resourceType, out uint size)
        {
            IntPtr hrsrc = FindResource(lib, resourceId, resourceType);
            if (hrsrc == IntPtr.Zero)
                throw new Win32Exception(Marshal.GetLastWin32Error());
 
            size = SizeofResource(lib, hrsrc);
            IntPtr resource = LoadResource(lib, hrsrc);
            if (resource == IntPtr.Zero)
                throw new Win32Exception(Marshal.GetLastWin32Error());
 
            IntPtr manifest = LockResource(resource);
            if (resource == IntPtr.Zero)
                throw new Win32Exception(Marshal.GetLastWin32Error());
 
            return manifest;
        }
 
        private static string GetManifestString(IntPtr ptr, int offset, int length, Encoding encoding)
        {
            byte[] fullmanif = new byte[length];
            Marshal.Copy((IntPtr)(ptr.ToInt64() + offset), fullmanif, 0, length);
            return encoding.GetString(fullmanif, 0, length);
        }
 
        private static string GetDecodedManifest(IntPtr mfRsrc, uint rsrcSize)
        {
            byte[] ar = new byte[3];
            Marshal.Copy(mfRsrc, ar, 0, 3);
            string xmlManif;
            Encoding encoding;
            //If unicode (little endian)
            if (ar[0] == 0xFF && ar[1] == 0xFE)
            {
                encoding = Encoding.Unicode;
                xmlManif = GetManifestString(mfRsrc, 2, (int)rsrcSize - 2, encoding);
            }
            // if unicode (big endian)
            else if (ar[0] == 0xFE && ar[1] == 0xFF)
            {
                encoding = Encoding.BigEndianUnicode;
                xmlManif = GetManifestString(mfRsrc, 2, (int)rsrcSize - 2, encoding);
            }
            // if UTF-8
            else if (ar[0] == 0xEF && ar[1] == 0xBB && ar[2] == 0xBF)
            {
                encoding = Encoding.UTF8;
                xmlManif = GetManifestString(mfRsrc, 3, (int)rsrcSize - 3, encoding);
            }
            // We give up! Assume ASCII (which may or may not always be true...)
            else
            {
                encoding = Encoding.ASCII;
                xmlManif = GetManifestString(mfRsrc, 0, (int)rsrcSize, encoding);
            }
 
            return xmlManif;
        }
 
        public static string ManifestResourceToXml(IntPtr mftRsrc, uint size)
        {
            var doc = new XDocument();
            using (XmlWriter xw = doc.CreateWriter())
            {
                xw.WriteStartDocument();
                xw.WriteStartElement("ManifestResource");
                xw.WriteAttributeString("Size", size.ToString());
                xw.WriteStartElement("Contents");
                xw.WriteCData(GetDecodedManifest(mftRsrc, size));
                xw.WriteEndElement();
 
                //xw.WriteAttributeString("Data", GetDecodedManifest(mftRsrc, size));
 
                xw.WriteEndElement();
                xw.WriteEndDocument();
            }
            var sw = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
            doc.Save(sw);
            return sw.ToString();
        }
 
        private static string ReadString(BinaryReader reader)
        {
            int i = 0;
            var cbuffer = new char[16];
            do
            {
                cbuffer[i] = reader.ReadChar();
            }
            while (cbuffer[i] != '\0' && ++i < cbuffer.Length);
 
            return new string(cbuffer).TrimEnd(new char[] { '\0' });
        }
 
        private static void ReadVarFileInfo(BinaryReader reader)
        {
            ushort us;
            string s;
 
            us = reader.ReadUInt16();
            us = reader.ReadUInt16();    //0
            us = reader.ReadUInt16();    //1
            s = ReadString(reader);            //"Translation"
            reader.BaseStream.Position = (reader.BaseStream.Position + 3) & ~3; //round up to 32bit boundary
 
            us = reader.ReadUInt16();    //langId; MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)) = 0
            us = reader.ReadUInt16();    //codepage; 1200 = CP_WINUNICODE
        }
 
        public static IEnumerable<Tuple<string, string>> ReadStringFileInfo(BinaryReader reader, int sizeTotalStringFileInfo)
        {
            var result = new List<Tuple<string, string>>();
            int sizeConsumed = 2 + 2 + 2 + (16 * 2);
            long startPosition = reader.BaseStream.Position;
 
            string s;
 
            reader.ReadUInt16();   //length
            reader.ReadUInt16();    //0
            reader.ReadUInt16();    //1
            s = ReadString(reader);    //"12345678"
            reader.BaseStream.Position = (reader.BaseStream.Position + 3) & ~3; //round up to 32bit boundary
 
            while (reader.BaseStream.Position - startPosition + sizeConsumed < sizeTotalStringFileInfo)
            {
                result.Add(GetVerStringPair(reader));
                reader.BaseStream.Position = (reader.BaseStream.Position + 3) & ~3; //round up to 32bit boundary
            }
 
            return result;
        }
 
        public static string VersionResourceToXml(IntPtr versionRsrc)
        {
            var shortArray = new short[1];
            Marshal.Copy(versionRsrc, shortArray, 0, 1);
            int size = shortArray[0];
 
            var entireResourceBytes = new byte[size];
            Marshal.Copy(versionRsrc, entireResourceBytes, 0, entireResourceBytes.Length);
 
            var memoryStream = new MemoryStream(entireResourceBytes);
            var reader = new BinaryReader(memoryStream, Encoding.Unicode);
 
            var doc = new XDocument();
            using (XmlWriter xw = doc.CreateWriter())
            {
                xw.WriteStartDocument();
                xw.WriteStartElement("VersionResource");
                xw.WriteAttributeString("Size", size.ToString());
 
                //0x28 is the start of the VS_FIXEDFILEINFO
                reader.BaseStream.Seek(0x28, SeekOrigin.Begin);
                //skip the first two dwords of VS_FIXEDFILEINFO.
                reader.BaseStream.Seek(0x8, SeekOrigin.Current);
 
                xw.WriteStartElement("VS_FIXEDFILEINFO");
                xw.WriteAttributeString("FileVersionMS", String.Format("{0:x8}", reader.ReadUInt32()));
                xw.WriteAttributeString("FileVersionLS", String.Format("{0:x8}", reader.ReadUInt32()));
                xw.WriteAttributeString("ProductVersionMS", String.Format("{0:x8}", reader.ReadUInt32()));
                xw.WriteAttributeString("ProductVersionLS", String.Format("{0:x8}", reader.ReadUInt32()));
                xw.WriteEndElement();
 
                long l;
                l = reader.ReadUInt32(); //((DWORD)0x0000003F);   //VS_FFI_FILEFLAGSMASK  (EDMAURER) really? all these bits are valid?
                l = reader.ReadUInt32(); //((DWORD)0);    //file flags
                l = reader.ReadUInt32(); //((DWORD)0x00000004);   //VOS__WINDOWS32
                l = reader.ReadUInt32(); //((DWORD)this.FileType);
                l = reader.ReadUInt32(); //((DWORD)0);    //file subtype
                l = reader.ReadUInt32(); //((DWORD)0);    //date most sig
                l = reader.ReadUInt32(); //((DWORD)0);    //date least sig
 
                //VS_VERSIONINFO can have zero or one VarFileInfo
                //and zero or one StringFileInfo
                while (reader.BaseStream.Position < size)
                {
                    ushort us;
                    string s;
 
                    ushort length = reader.ReadUInt16();
                    us = reader.ReadUInt16();    //0
                    us = reader.ReadUInt16();    //1
                    //must decide if this is a VarFileInfo or a StringFileInfo
 
                    s = ReadString(reader);
                    reader.BaseStream.Position = (reader.BaseStream.Position + 3) & ~3; //round up to 32bit boundary
 
                    IEnumerable<Tuple<string, string>> keyValPairs;
                    if (s == "VarFileInfo")
                        ReadVarFileInfo(reader);
                    else if (s == "StringFileInfo")
                    {
                        keyValPairs = ReadStringFileInfo(reader, length);
                        foreach (var pair in keyValPairs)
                        {
                            xw.WriteStartElement("KeyValuePair");
                            xw.WriteAttributeString("Key", pair.Item1.TrimEnd(new char[] { '\0' }));
                            xw.WriteAttributeString("Value", pair.Item2.TrimEnd(new char[] { '\0' }));
                            xw.WriteEndElement();
                        }
                    }
                }
 
                xw.WriteEndElement();
                xw.WriteEndDocument();
            }
            var sw = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
            doc.Save(sw);
            return sw.ToString();
        }
 
        private static Tuple<string, string> GetVerStringPair(BinaryReader reader)
        {
            System.Diagnostics.Debug.Assert((reader.BaseStream.Position & 3) == 0);
            long startPos = reader.BaseStream.Position;
 
            int length = reader.ReadUInt16();       //the whole structure in bytes, does not include padding
                                                    //at the end to bring the structure to a dword boundary.
 
            int valueLength = reader.ReadUInt16();  //in words
 
            System.Diagnostics.Debug.Assert(length > valueLength);
 
            int type = reader.ReadUInt16();
 
            System.Diagnostics.Debug.Assert(type == 1); //string
 
            int keyLength = length - (valueLength * 2) - (3 * sizeof(short));
 
            char[] key = new char[keyLength / sizeof(char)];
            for (int i = 0; i < key.Length; i++)
                key[i] = reader.ReadChar();
 
            reader.BaseStream.Position = (reader.BaseStream.Position + 3) & ~3;
 
            char[] value = new char[valueLength];
            for (int i = 0; i < value.Length; i++)
                value[i] = reader.ReadChar();
 
            System.Diagnostics.Debug.Assert(length == (reader.BaseStream.Position - startPos));
            return new Tuple<string, string>(new string(key), new string(value));
        }
    }
}