| 1 | // 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. |
| 2 | |
| 3 | namespace WixToolset.Dtf.Resources |
| 4 | { |
| 5 | using System; |
| 6 | using System.IO; |
| 7 | using System.Text; |
| 8 | using System.Reflection; |
| 9 | using System.Collections; |
| 10 | using System.Collections.Generic; |
| 11 | using System.Globalization; |
| 12 | using System.Diagnostics.CodeAnalysis; |
| 13 | |
| 14 | /// <summary> |
| 15 | /// A subclass of Resource which provides specific methods for manipulating the resource data. |
| 16 | /// </summary> |
| 17 | /// <remarks> |
| 18 | /// The resource is of type <see cref="ResourceType.Version"/> (RT_VERSION). |
| 19 | /// </remarks> |
| 20 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] |
| 21 | public sealed class VersionResource : Resource, ICollection<VersionStringTable> |
| 22 | { |
| 23 | internal bool dirty; |
| 24 | private VersionInfo rawVersionInfo; |
| 25 | private FixedFileVersionInfo rawFileVersionInfo; |
| 26 | |
| 27 | /// <summary> |
| 28 | /// Creates a new VersionResource object without any data. The data can be later loaded from a file. |
| 29 | /// </summary> |
| 30 | /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param> |
| 31 | /// <param name="locale">Locale of the resource</param> |
| 32 | public VersionResource(string name, int locale) |
| 33 | : this(name, locale, null) |
| 34 | { |
| 35 | } |
| 36 | |
| 37 | /// <summary> |
| 38 | /// Creates a new VersionResource object with data. The data can be later saved to a file. |
| 39 | /// </summary> |
| 40 | /// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param> |
| 41 | /// <param name="locale">Locale of the resource</param> |
| 42 | /// <param name="data">Raw resource data</param> |
| 43 | public VersionResource(string name, int locale, byte[] data) |
| 44 | : base(ResourceType.Version, name, locale, data) |
| 45 | { |
| 46 | this.RefreshVersionInfo(data); |
| 47 | } |
| 48 | |
| 49 | /// <summary> |
| 50 | /// Gets or sets the raw data of the resource. The data is in the format of the VS_VERSIONINFO structure. |
| 51 | /// </summary> |
| 52 | public override byte[] Data |
| 53 | { |
| 54 | get |
| 55 | { |
| 56 | if (this.dirty) |
| 57 | { |
| 58 | this.rawVersionInfo.Data = (byte[]) this.rawFileVersionInfo; |
| 59 | base.Data = (byte[]) this.rawVersionInfo; |
| 60 | this.dirty = false; |
| 61 | } |
| 62 | |
| 63 | return base.Data; |
| 64 | } |
| 65 | set |
| 66 | { |
| 67 | base.Data = value; |
| 68 | this.RefreshVersionInfo(value); |
| 69 | this.dirty = false; |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | private void RefreshVersionInfo(byte[] refreshData) |
| 74 | { |
| 75 | if (refreshData == null) |
| 76 | { |
| 77 | this.rawVersionInfo = new VersionInfo("VS_VERSION_INFO"); |
| 78 | this.rawFileVersionInfo = new FixedFileVersionInfo(); |
| 79 | } |
| 80 | else |
| 81 | { |
| 82 | this.rawVersionInfo = (VersionInfo) refreshData; |
| 83 | this.rawFileVersionInfo = (FixedFileVersionInfo) this.rawVersionInfo.Data; |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /// <summary> |
| 88 | /// Gets or sets the binary locale-independent file version of the version resource. |
| 89 | /// </summary> |
| 90 | public Version FileVersion |
| 91 | { |
| 92 | get |
| 93 | { |
| 94 | return this.rawFileVersionInfo.FileVersion; |
| 95 | } |
| 96 | set |
| 97 | { |
| 98 | this.rawFileVersionInfo.FileVersion = value; |
| 99 | this.dirty = true; |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | /// <summary> |
| 104 | /// Gets or sets the binary locale-independent product version of the version resource. |
| 105 | /// </summary> |
| 106 | public Version ProductVersion |
| 107 | { |
| 108 | get |
| 109 | { |
| 110 | return this.rawFileVersionInfo.ProductVersion; |
| 111 | } |
| 112 | set |
| 113 | { |
| 114 | this.rawFileVersionInfo.ProductVersion = value; |
| 115 | this.dirty = true; |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | /// <summary> |
| 120 | /// Gets or sets a bitmask that specifies the build types of the file. |
| 121 | /// </summary> |
| 122 | public VersionBuildTypes BuildTypes |
| 123 | { |
| 124 | get |
| 125 | { |
| 126 | return this.rawFileVersionInfo.FileFlags & |
| 127 | this.rawFileVersionInfo.FileFlagsMask; |
| 128 | } |
| 129 | set |
| 130 | { |
| 131 | this.rawFileVersionInfo.FileFlags = value; |
| 132 | this.rawFileVersionInfo.FileFlagsMask = value; |
| 133 | this.dirty = true; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | /// <summary> |
| 138 | /// Gets or sets the general type of the file. |
| 139 | /// </summary> |
| 140 | public VersionFileType FileType |
| 141 | { |
| 142 | get |
| 143 | { |
| 144 | return this.rawFileVersionInfo.FileType; |
| 145 | } |
| 146 | set |
| 147 | { |
| 148 | this.rawFileVersionInfo.FileType = value; |
| 149 | this.dirty = true; |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | /// <summary> |
| 154 | /// Gets or sets the specific type of the file. |
| 155 | /// </summary> |
| 156 | public VersionFileSubtype FileSubtype |
| 157 | { |
| 158 | get |
| 159 | { |
| 160 | return this.rawFileVersionInfo.FileSubtype; |
| 161 | } |
| 162 | set |
| 163 | { |
| 164 | this.rawFileVersionInfo.FileSubtype = value; |
| 165 | this.dirty = true; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | /// <summary> |
| 170 | /// Gets or sets the binary creation date and time. |
| 171 | /// </summary> |
| 172 | public DateTime Timestamp |
| 173 | { |
| 174 | get |
| 175 | { |
| 176 | return this.rawFileVersionInfo.Timestamp; |
| 177 | } |
| 178 | set |
| 179 | { |
| 180 | this.rawFileVersionInfo.Timestamp = value; |
| 181 | this.dirty = true; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | /// <summary> |
| 186 | /// Gets the string table for a specific locale, or null if there is no table for that locale. |
| 187 | /// </summary> |
| 188 | /// <seealso cref="Add(int)"/> |
| 189 | /// <seealso cref="Remove(int)"/> |
| 190 | public VersionStringTable this[int locale] |
| 191 | { |
| 192 | get |
| 193 | { |
| 194 | VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; |
| 195 | if (svi != null) |
| 196 | { |
| 197 | foreach (VersionInfo strings in svi) |
| 198 | { |
| 199 | int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
| 200 | if (stringsLocale == locale) |
| 201 | { |
| 202 | return new VersionStringTable(this, strings); |
| 203 | } |
| 204 | } |
| 205 | } |
| 206 | return null; |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | /// <summary> |
| 211 | /// Adds a new version string table for a locale. |
| 212 | /// </summary> |
| 213 | /// <param name="locale">Locale of the table</param> |
| 214 | /// <returns>The new string table, or the existing table if the locale already existed.</returns> |
| 215 | public VersionStringTable Add(int locale) |
| 216 | { |
| 217 | VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; |
| 218 | if (svi == null) |
| 219 | { |
| 220 | svi = new VersionInfo("StringFileInfo"); |
| 221 | this.rawVersionInfo.Add(svi); |
| 222 | } |
| 223 | foreach (VersionInfo strings in svi) |
| 224 | { |
| 225 | int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
| 226 | if (stringsLocale == locale) |
| 227 | { |
| 228 | return new VersionStringTable(this, strings); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | VersionInfo newStrings = new VersionInfo( |
| 233 | ((ushort) locale).ToString("x4", CultureInfo.InvariantCulture) + ((ushort) 1200).ToString("x4", CultureInfo.InvariantCulture)); |
| 234 | svi.Add(newStrings); |
| 235 | this.dirty = true; |
| 236 | |
| 237 | VersionInfo vvi = this.rawVersionInfo["VarFileInfo"]; |
| 238 | if (vvi == null) |
| 239 | { |
| 240 | vvi = new VersionInfo("VarFileInfo"); |
| 241 | vvi.Add(new VersionInfo("Translation")); |
| 242 | this.rawVersionInfo.Add(vvi); |
| 243 | } |
| 244 | VersionInfo tVerInfo = vvi["Translation"]; |
| 245 | if (tVerInfo != null) |
| 246 | { |
| 247 | byte[] oldValue = tVerInfo.Data; |
| 248 | if (oldValue == null) oldValue = new byte[0]; |
| 249 | tVerInfo.Data = new byte[oldValue.Length + 4]; |
| 250 | Array.Copy(oldValue, tVerInfo.Data, oldValue.Length); |
| 251 | using (BinaryWriter bw = new BinaryWriter(new MemoryStream(tVerInfo.Data, oldValue.Length, 4, true))) |
| 252 | { |
| 253 | bw.Write((ushort) locale); |
| 254 | bw.Write((ushort) 1200); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | return new VersionStringTable(this, newStrings); |
| 259 | } |
| 260 | |
| 261 | /// <summary> |
| 262 | /// Removes a version string table for a locale. |
| 263 | /// </summary> |
| 264 | /// <param name="locale">Locale of the table</param> |
| 265 | public void Remove(int locale) |
| 266 | { |
| 267 | VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; |
| 268 | if (svi != null) |
| 269 | { |
| 270 | foreach (VersionInfo strings in svi) |
| 271 | { |
| 272 | int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
| 273 | if (stringsLocale == locale) |
| 274 | { |
| 275 | svi.Remove(strings); |
| 276 | this.dirty = true; |
| 277 | break; |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | } |
| 282 | |
| 283 | VersionInfo vvi = this.rawVersionInfo["VarFileInfo"]; |
| 284 | if (vvi != null) |
| 285 | { |
| 286 | VersionInfo tVerInfo = vvi["Translation"]; |
| 287 | if (tVerInfo != null) |
| 288 | { |
| 289 | byte[] newValue = new byte[tVerInfo.Data.Length]; |
| 290 | int j = 0; |
| 291 | using (BinaryWriter bw = new BinaryWriter(new MemoryStream(newValue, 0, newValue.Length, true))) |
| 292 | { |
| 293 | using (BinaryReader br = new BinaryReader(new MemoryStream(tVerInfo.Data))) |
| 294 | { |
| 295 | for (int i = tVerInfo.Data.Length / 4; i > 0; i--) |
| 296 | { |
| 297 | ushort tLocale = br.ReadUInt16(); |
| 298 | ushort cp = br.ReadUInt16(); |
| 299 | if (tLocale != locale) |
| 300 | { |
| 301 | bw.Write((ushort) tLocale); |
| 302 | bw.Write((ushort) cp); |
| 303 | j++; |
| 304 | } |
| 305 | } |
| 306 | } |
| 307 | } |
| 308 | tVerInfo.Data = new byte[j * 4]; |
| 309 | Array.Copy(newValue, tVerInfo.Data, tVerInfo.Data.Length); |
| 310 | } |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | /// <summary> |
| 315 | /// Checks if a version string table exists for a given locale. |
| 316 | /// </summary> |
| 317 | /// <param name="locale">Locale to search for</param> |
| 318 | /// <returns>True if a string table was found for the locale; false otherwise.</returns> |
| 319 | public bool Contains(int locale) |
| 320 | { |
| 321 | return this[locale] != null; |
| 322 | } |
| 323 | |
| 324 | /// <summary> |
| 325 | /// Gets the number string tables in the version resource. |
| 326 | /// </summary> |
| 327 | public int Count |
| 328 | { |
| 329 | get |
| 330 | { |
| 331 | VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; |
| 332 | return svi != null ? svi.Count : 0; |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | /// <summary> |
| 337 | /// Removes all string tables from the version resource. |
| 338 | /// </summary> |
| 339 | public void Clear() |
| 340 | { |
| 341 | VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; |
| 342 | if (svi != null) |
| 343 | { |
| 344 | svi.Clear(); |
| 345 | this.dirty = true; |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | bool ICollection<VersionStringTable>.IsReadOnly |
| 350 | { |
| 351 | get |
| 352 | { |
| 353 | return false; |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | void ICollection<VersionStringTable>.Add(VersionStringTable item) |
| 358 | { |
| 359 | throw new NotSupportedException(); |
| 360 | } |
| 361 | |
| 362 | bool ICollection<VersionStringTable>.Remove(VersionStringTable item) |
| 363 | { |
| 364 | throw new NotSupportedException(); |
| 365 | } |
| 366 | |
| 367 | bool ICollection<VersionStringTable>.Contains(VersionStringTable item) |
| 368 | { |
| 369 | throw new NotSupportedException(); |
| 370 | } |
| 371 | |
| 372 | /// <summary> |
| 373 | /// Copies the version string tables to an array, starting at a particular array index. |
| 374 | /// </summary> |
| 375 | /// <param name="array">The one-dimensional Array that is the destination of the elements copied |
| 376 | /// from the collection. The Array must have zero-based indexing.</param> |
| 377 | /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param> |
| 378 | public void CopyTo(VersionStringTable[] array, int arrayIndex) |
| 379 | { |
| 380 | VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; |
| 381 | if (svi != null) |
| 382 | { |
| 383 | foreach (VersionInfo strings in svi) |
| 384 | { |
| 385 | array[arrayIndex++] = new VersionStringTable(this, strings); |
| 386 | } |
| 387 | } |
| 388 | } |
| 389 | |
| 390 | /// <summary> |
| 391 | /// Gets an enumerator that can iterate over the version string tables in the collection. |
| 392 | /// </summary> |
| 393 | /// <returns>An enumerator that returns <see cref="VersionStringTable"/> objects.</returns> |
| 394 | public IEnumerator<VersionStringTable> GetEnumerator() |
| 395 | { |
| 396 | VersionInfo svi = this.rawVersionInfo["StringFileInfo"]; |
| 397 | if (svi != null) |
| 398 | { |
| 399 | foreach (VersionInfo strings in svi) |
| 400 | { |
| 401 | yield return new VersionStringTable(this, strings); |
| 402 | } |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | /// <summary> |
| 407 | /// Gets an enumerator that can iterate over the version string tables in the collection. |
| 408 | /// </summary> |
| 409 | /// <returns>An enumerator that returns <see cref="VersionStringTable"/> objects.</returns> |
| 410 | IEnumerator IEnumerable.GetEnumerator() |
| 411 | { |
| 412 | return this.GetEnumerator(); |
| 413 | } |
| 414 | } |
| 415 | } |