// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Dtf.Resources { using System; using System.IO; using System.Text; using System.Reflection; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Diagnostics.CodeAnalysis; /// /// A subclass of Resource which provides specific methods for manipulating the resource data. /// /// /// The resource is of type (RT_VERSION). /// [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] public sealed class VersionResource : Resource, ICollection { internal bool dirty; private VersionInfo rawVersionInfo; private FixedFileVersionInfo rawFileVersionInfo; /// /// Creates a new VersionResource object without any data. The data can be later loaded from a file. /// /// Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#". /// Locale of the resource public VersionResource(string name, int locale) : this(name, locale, null) { } /// /// Creates a new VersionResource object with data. The data can be later saved to a file. /// /// Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#". /// Locale of the resource /// Raw resource data public VersionResource(string name, int locale, byte[] data) : base(ResourceType.Version, name, locale, data) { this.RefreshVersionInfo(data); } /// /// Gets or sets the raw data of the resource. The data is in the format of the VS_VERSIONINFO structure. /// public override byte[] Data { get { if (this.dirty) { this.rawVersionInfo.Data = (byte[]) this.rawFileVersionInfo; base.Data = (byte[]) this.rawVersionInfo; this.dirty = false; } return base.Data; } set { base.Data = value; this.RefreshVersionInfo(value); this.dirty = false; } } private void RefreshVersionInfo(byte[] refreshData) { if (refreshData == null) { this.rawVersionInfo = new VersionInfo("VS_VERSION_INFO"); this.rawFileVersionInfo = new FixedFileVersionInfo(); } else { this.rawVersionInfo = (VersionInfo) refreshData; this.rawFileVersionInfo = (FixedFileVersionInfo) this.rawVersionInfo.Data; } } /// /// Gets or sets the binary locale-independent file version of the version resource. /// public Version FileVersion { get { return this.rawFileVersionInfo.FileVersion; } set { this.rawFileVersionInfo.FileVersion = value; this.dirty = true; } } /// /// Gets or sets the binary locale-independent product version of the version resource. /// public Version ProductVersion { get { return this.rawFileVersionInfo.ProductVersion; } set { this.rawFileVersionInfo.ProductVersion = value; this.dirty = true; } } /// /// Gets or sets a bitmask that specifies the build types of the file. /// public VersionBuildTypes BuildTypes { get { return this.rawFileVersionInfo.FileFlags & this.rawFileVersionInfo.FileFlagsMask; } set { this.rawFileVersionInfo.FileFlags = value; this.rawFileVersionInfo.FileFlagsMask = value; this.dirty = true; } } /// /// Gets or sets the general type of the file. /// public VersionFileType FileType { get { return this.rawFileVersionInfo.FileType; } set { this.rawFileVersionInfo.FileType = value; this.dirty = true; } } /// /// Gets or sets the specific type of the file. /// public VersionFileSubtype FileSubtype { get { return this.rawFileVersionInfo.FileSubtype; } set { this.rawFileVersionInfo.FileSubtype = value; this.dirty = true; } } /// /// Gets or sets the binary creation date and time. /// public DateTime Timestamp { get { return this.rawFileVersionInfo.Timestamp; } set { this.rawFileVersionInfo.Timestamp = value; this.dirty = true; } } /// /// Gets the string table for a specific locale, or null if there is no table for that locale. /// /// /// public VersionStringTable this[int locale] { get { VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; if (svi != null) { foreach (VersionInfo strings in svi) { int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture); if (stringsLocale == locale) { return new VersionStringTable(this, strings); } } } return null; } } /// /// Adds a new version string table for a locale. /// /// Locale of the table /// The new string table, or the existing table if the locale already existed. public VersionStringTable Add(int locale) { VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; if (svi == null) { svi = new VersionInfo("StringFileInfo"); this.rawVersionInfo.Add(svi); } foreach (VersionInfo strings in svi) { int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture); if (stringsLocale == locale) { return new VersionStringTable(this, strings); } } VersionInfo newStrings = new VersionInfo( ((ushort) locale).ToString("x4", CultureInfo.InvariantCulture) + ((ushort) 1200).ToString("x4", CultureInfo.InvariantCulture)); svi.Add(newStrings); this.dirty = true; VersionInfo vvi = this.rawVersionInfo["VarFileInfo"]; if (vvi == null) { vvi = new VersionInfo("VarFileInfo"); vvi.Add(new VersionInfo("Translation")); this.rawVersionInfo.Add(vvi); } VersionInfo tVerInfo = vvi["Translation"]; if (tVerInfo != null) { byte[] oldValue = tVerInfo.Data; if (oldValue == null) oldValue = new byte[0]; tVerInfo.Data = new byte[oldValue.Length + 4]; Array.Copy(oldValue, tVerInfo.Data, oldValue.Length); using (BinaryWriter bw = new BinaryWriter(new MemoryStream(tVerInfo.Data, oldValue.Length, 4, true))) { bw.Write((ushort) locale); bw.Write((ushort) 1200); } } return new VersionStringTable(this, newStrings); } /// /// Removes a version string table for a locale. /// /// Locale of the table public void Remove(int locale) { VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; if (svi != null) { foreach (VersionInfo strings in svi) { int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture); if (stringsLocale == locale) { svi.Remove(strings); this.dirty = true; break; } } } VersionInfo vvi = this.rawVersionInfo["VarFileInfo"]; if (vvi != null) { VersionInfo tVerInfo = vvi["Translation"]; if (tVerInfo != null) { byte[] newValue = new byte[tVerInfo.Data.Length]; int j = 0; using (BinaryWriter bw = new BinaryWriter(new MemoryStream(newValue, 0, newValue.Length, true))) { using (BinaryReader br = new BinaryReader(new MemoryStream(tVerInfo.Data))) { for (int i = tVerInfo.Data.Length / 4; i > 0; i--) { ushort tLocale = br.ReadUInt16(); ushort cp = br.ReadUInt16(); if (tLocale != locale) { bw.Write((ushort) tLocale); bw.Write((ushort) cp); j++; } } } } tVerInfo.Data = new byte[j * 4]; Array.Copy(newValue, tVerInfo.Data, tVerInfo.Data.Length); } } } /// /// Checks if a version string table exists for a given locale. /// /// Locale to search for /// True if a string table was found for the locale; false otherwise. public bool Contains(int locale) { return this[locale] != null; } /// /// Gets the number string tables in the version resource. /// public int Count { get { VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; return svi != null ? svi.Count : 0; } } /// /// Removes all string tables from the version resource. /// public void Clear() { VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; if (svi != null) { svi.Clear(); this.dirty = true; } } bool ICollection.IsReadOnly { get { return false; } } void ICollection.Add(VersionStringTable item) { throw new NotSupportedException(); } bool ICollection.Remove(VersionStringTable item) { throw new NotSupportedException(); } bool ICollection.Contains(VersionStringTable item) { throw new NotSupportedException(); } /// /// Copies the version string tables to an array, starting at a particular array index. /// /// The one-dimensional Array that is the destination of the elements copied /// from the collection. The Array must have zero-based indexing. /// The zero-based index in array at which copying begins. public void CopyTo(VersionStringTable[] array, int arrayIndex) { VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; if (svi != null) { foreach (VersionInfo strings in svi) { array[arrayIndex++] = new VersionStringTable(this, strings); } } } /// /// Gets an enumerator that can iterate over the version string tables in the collection. /// /// An enumerator that returns objects. public IEnumerator GetEnumerator() { VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; if (svi != null) { foreach (VersionInfo strings in svi) { yield return new VersionStringTable(this, strings); } } } /// /// Gets an enumerator that can iterate over the version string tables in the collection. /// /// An enumerator that returns objects. IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } }