// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using Mono.Cecil; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Mono.Cecil.Cil; namespace OpenTap.Package.SetAsmInfo { internal class SetAsmInfo { class VERSION_INFO_CHUNK { public UInt16 wType; public string Key; public byte[] Value; public string ValueText; public List Children; public byte[] GetData() { if (wType == 1) if (ValueText != null) Value = System.Text.Encoding.Unicode.GetBytes(ValueText + '\0'); using (var ms = new MemoryStream()) { ms.Write(BitConverter.GetBytes((UInt16)0), 0, 2); if (wType == 1) ms.Write(BitConverter.GetBytes((UInt16)(Value.Length / 2)), 0, 2); else ms.Write(BitConverter.GetBytes((UInt16)Value.Length), 0, 2); ms.Write(BitConverter.GetBytes((UInt16)wType), 0, 2); var bytes = System.Text.Encoding.Unicode.GetBytes(Key + '\0'); ms.Write(bytes, 0, bytes.Length); Align(ms); ms.Write(Value, 0, Value.Length); if (wType != 1) Align(ms); foreach (var ch in Children) { Align(ms); var data = ch.GetData(); ms.Write(data, 0, data.Length); } ms.Seek(0, 0); ms.Write(BitConverter.GetBytes((UInt16)ms.Length), 0, 2); return ms.ToArray(); } } private static void Align(Stream st) { int left = (4 - (int)st.Length) & 3; for (int i = 0; i < left; i++) st.WriteByte(0); } public VERSION_INFO_CHUNK(byte[] data) { var wLength = BitConverter.ToUInt16(data, 0); var wValueLength = BitConverter.ToUInt16(data, 2); wType = BitConverter.ToUInt16(data, 4); if (wType == 1) wValueLength *= 2; int end = -1; for (int i = 6; i < data.Length; i += 2) { if ((data[i] == 0) && (data[i + 1] == 0)) { end = i + 2; break; } } Key = System.Text.Encoding.Unicode.GetString(data, 6, end - 6 - 2); Align(ref end); Value = new byte[wValueLength]; Array.Copy(data, end, Value, 0, wValueLength); end = end + wValueLength; Align(ref end); if (wType == 1) { if (Value.Length >= 2) ValueText = System.Text.Encoding.Unicode.GetString(Value, 0, Value.Length - 2); else ValueText = null; } Children = new List(); while (end < (data.Length - 1)) { var left = data.Length - end; var subLen = BitConverter.ToUInt16(data, end); if (subLen == 0) break; else if (subLen <= left) Children.Add(new VERSION_INFO_CHUNK(data.Skip(end).Take(subLen).ToArray())); else throw new Exception("Invalid size"); end += subLen; Align(ref end); } } private static void Align(ref int end) { end = (end + 3) & ~3; } public override string ToString() { return Key; } } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct VS_FIXEDFILEINFO { public UInt32 dwSignature; public UInt32 dwStrucVersion; [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U2, SizeConst = 4)] public UInt16[] dwFileVersion; [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U2, SizeConst = 4)] public UInt16[] dwProductVersion; public UInt32 dwFileFlagsMask; public UInt32 dwFileFlags; public UInt32 dwFileOS; public UInt32 dwFileType; public UInt32 dwFileSubtype; public UInt32 dwFileDateMS; public UInt32 dwFileDateLS; } class Win32Resource { [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern IntPtr FreeLibrary(IntPtr lpModuleName); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr FindResource(IntPtr hModule, int lpType, int lpName); [DllImport("Kernel32.dll", EntryPoint = "SizeofResource", SetLastError = true)] private static extern uint SizeofResource(IntPtr hModule, IntPtr hResource); [DllImport("Kernel32.dll", EntryPoint = "LoadResource", SetLastError = true)] private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResource); [DllImport("Kernel32.dll", EntryPoint = "LockResource", SetLastError = true)] private static extern IntPtr LockResource(IntPtr hResource); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr BeginUpdateResource(string pFileName, [MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources); [DllImport("kernel32.dll", SetLastError = true)] static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard); [DllImport("kernel32.dll", SetLastError = true)] static extern bool UpdateResource(IntPtr hUpdate, int lpType, int lpName, ushort wLanguage, IntPtr lpData, uint cbData); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags); private static UInt16 MAKELANGID(UInt16 PrimaryLang, UInt16 SubLang) { return (UInt16)((SubLang << 10) | PrimaryLang); } [System.Flags] enum LoadLibraryFlags : uint { DONT_RESOLVE_DLL_REFERENCES = 0x00000001, LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, LOAD_LIBRARY_AS_DATAFILE = 0x00000002, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100, LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800, LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400, LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 } private const int LANGUAGE_NEUTRAL = 1; private const int VS_VERSION_INFO = 1; private const int LANG_NEUTRAL = 0; private const int SUBLANG_NEUTRAL = 0; private const int SUBLANG_DEFAULT = 1; private const int SUBLANG_SYS_DEFAULT = 2; private const int RT_VERSION = 16; public static byte[] ReadVersionResource(string libname) { var hModule = LoadLibraryEx(libname, IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE | LoadLibraryFlags.DONT_RESOLVE_DLL_REFERENCES); if (hModule == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(); try { IntPtr loc = FindResource(hModule, VS_VERSION_INFO, RT_VERSION); if (loc == IntPtr.Zero) throw new System.ComponentModel.Win32Exception("Could not find version information."); uint size = SizeofResource(hModule, loc); IntPtr hResource = LoadResource(hModule, loc); if (hResource == IntPtr.Zero) throw new System.ComponentModel.Win32Exception("Could not load version information."); IntPtr x = LockResource(hResource); if (x == IntPtr.Zero) throw new System.ComponentModel.Win32Exception("Could not get address of version information."); var dest = new byte[size]; Marshal.Copy(x, dest, 0, (int)size); return dest; } finally { FreeLibrary(hModule); } } internal static void WriteVersionResource(string filename, byte[] data) { IntPtr x = Marshal.AllocHGlobal(data.Length); try { var hResource = BeginUpdateResource(filename, false); if (hResource == IntPtr.Zero) throw new System.ComponentModel.Win32Exception("Could not begin the version information update procedure."); Marshal.Copy(data, 0, x, data.Length); if (!UpdateResource(hResource, RT_VERSION, VS_VERSION_INFO, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), x, (uint)data.Length)) throw new System.ComponentModel.Win32Exception(); if (!EndUpdateResource(hResource, false)) throw new System.ComponentModel.Win32Exception(); } finally { Marshal.FreeHGlobal(x); } } } public static void SetInfo(string filename, Version version, Version fileVersion, SemanticVersion infoVersion, bool writePdb = false) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { try { var data = Win32Resource.ReadVersionResource(filename); var versionInfo = new VERSION_INFO_CHUNK(data); // Update fixed file info var info = CopyFrom(versionInfo.Value); if (fileVersion != null) { EncodeVersion(ref info.dwFileVersion, fileVersion); EncodeVersion(ref info.dwProductVersion, fileVersion); versionInfo.Value = CopyTo(info); } var trl = versionInfo.Children.First(c => c.Key == "VarFileInfo").Children.First(c => c.Key == "Translation"); var translation = BitConverter.ToUInt16(trl.Value, 0); foreach (var sfi in versionInfo.Children.Where(c => c.Key == "StringFileInfo")) { foreach (var table in sfi.Children) { foreach (var str in table.Children) { switch (str.Key) { case "FileVersion": if (fileVersion != null) str.ValueText = fileVersion.ToString(3); break; case "ProductVersion": if (infoVersion != null) str.ValueText = infoVersion.ToString(); else if (fileVersion != null) str.ValueText = fileVersion.ToString(3); break; } } } } data = versionInfo.GetData(); Utils.Retry(() => Win32Resource.WriteVersionResource(filename, data)); } catch (Exception ex) { throw new Exception($"Error while updating version information for '{filename}'", ex); } } var resolver = new TapMonoResolver(); bool anyWritten = false; AssemblyDefinition asm; try { asm = AssemblyDefinition.ReadAssembly(filename, new ReaderParameters { AssemblyResolver = resolver, InMemory = true, ReadingMode = ReadingMode.Deferred, ReadSymbols = writePdb }); } catch (Exception ex) { var log = Log.CreateSource("Version Injector"); if (ex is SymbolsNotMatchingException) log.Debug($"Symbols were found but are not matching the assembly '{Path.GetFileName(filename)}'."); else if (ex is SymbolsNotFoundException) log.Debug($"No symbols found for the assembly '{Path.GetFileName(filename)}'."); else throw; // Continue updating the version without updating symbols writePdb = false; asm = AssemblyDefinition.ReadAssembly(filename, new ReaderParameters { AssemblyResolver = resolver, InMemory = true, ReadingMode = ReadingMode.Deferred, ReadSymbols = writePdb }); } if (version != null) { asm.Name.Version = version; anyWritten = true; } // the list of references before adding the versions. var preReferences = asm.MainModule.AssemblyReferences.Select(asm => asm.Name).ToHashSet(); // Set the file version if (fileVersion != null) { var current = asm.CustomAttributes.FirstOrDefault(c => c.AttributeType.FullName == typeof(AssemblyFileVersionAttribute).FullName); if (current != null) { current.ConstructorArguments[0] = new CustomAttributeArgument(current.ConstructorArguments[0].Type, fileVersion.ToString()); } else { // This action is never invoked; it is used to compute a reference to this particular constructor invocation Expression expr = () => new AssemblyFileVersionAttribute(""); var body = expr.Body as NewExpression; var ctorMethod = asm.MainModule.ImportReference(body.Constructor); var versionAttr = new CustomAttribute(ctorMethod); versionAttr.ConstructorArguments.Add( new CustomAttributeArgument(ctorMethod.Parameters.First().ParameterType, fileVersion.ToString())); asm.CustomAttributes.Add(versionAttr); } anyWritten = true; } // Set the semantic version if (infoVersion != null) { var current = asm.CustomAttributes.FirstOrDefault(c => c.AttributeType.FullName == typeof(AssemblyInformationalVersionAttribute).FullName); if (current != null) { current.ConstructorArguments[0] = new CustomAttributeArgument(current.ConstructorArguments[0].Type, infoVersion.ToString()); } else { // This action is never invoked; it is used to compute a reference to this particular constructor invocation Expression expr = () => new AssemblyInformationalVersionAttribute(""); var body = expr.Body as NewExpression; var ctorMethod = asm.MainModule.ImportReference(body.Constructor); var versionAttr = new CustomAttribute(ctorMethod); versionAttr.ConstructorArguments.Add( new CustomAttributeArgument(ctorMethod.Parameters.First().ParameterType, infoVersion.ToString())); asm.CustomAttributes.Add(versionAttr); } anyWritten = true; } // Adding any of these attributes should not result in new assembly references. // if that happened, it was likely a reference to // ...Private.Corelib or netstandard2.0, so we should remove these. foreach (var newReference in asm.MainModule.AssemblyReferences.Where(asm => preReferences.Contains(asm.Name) == false).ToArray()) { asm.MainModule.AssemblyReferences.Remove(newReference); } if (anyWritten) asm.Write(filename, new WriterParameters { WriteSymbols = writePdb }); asm.Dispose(); } private static T CopyFrom(byte[] data) { var size = Marshal.SizeOf(); var ptr = Marshal.AllocHGlobal(size); try { Marshal.Copy(data, 0, ptr, size); return Marshal.PtrToStructure(ptr); } finally { Marshal.FreeHGlobal(ptr); } } private static byte[] CopyTo(T x) { var size = Marshal.SizeOf(); var ptr = Marshal.AllocHGlobal(size); try { Marshal.StructureToPtr(x, ptr, false); var data = new byte[size]; Marshal.Copy(ptr, data, 0, size); return data; } finally { Marshal.FreeHGlobal(ptr); } } private static void EncodeVersion(ref UInt16[] dwVersion, Version fileVersion) { dwVersion[1] = (UInt16)fileVersion.Major; dwVersion[0] = (UInt16)fileVersion.Minor; dwVersion[3] = (UInt16)fileVersion.Build; } private class TapMonoResolver : BaseAssemblyResolver { readonly GacResolver gacResolver = new GacResolver(); ILookup searchedAssemblies = PluginManager.GetSearcher().Assemblies.ToLookup(asm => asm.Name); private ConditionalWeakTable lookup = new ConditionalWeakTable(); private AssemblyDefinition resolve(AssemblyNameReference name, ReaderParameters parameters) { var subset = searchedAssemblies[name.Name]; var found = subset.FirstOrDefault(asm => asm.Version == name.Version) ?? subset.FirstOrDefault(asm => OpenTap.Utils.Compatible(asm.Version, name.Version)); ReaderParameters customParameters = new ReaderParameters { AssemblyResolver = this }; if (found == null) // Try find dependency from already loaded assemblies { var neededAssembly = new AssemblyName(name.ToString()); var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(s => s.GetName().Name == neededAssembly.Name); if (loadedAssembly != null) return AssemblyDefinition.ReadAssembly(loadedAssembly.Location, customParameters); } try { var ld2 = gacResolver.Resolve(name, parameters); if (ld2 != null) return ld2; } catch { // An exception was thrown when trying to resolve the assembly in the GAC. // this is ok, if everything else fails too another exception will be thrown. } if (found != null) return AssemblyDefinition.ReadAssembly(found.Location, customParameters); return base.Resolve(name, parameters); } public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { return lookup.GetValue(name, _ => resolve(name, parameters)); } } } }