// 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.Globalization; using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; /// /// Represents a Win32 resource which can be loaded from and saved to a PE file. /// public class Resource { private ResourceType type; private string name; private int locale; private byte[] data; /// /// Creates a new Resource object without any data. The data can be later loaded from a file. /// /// Type of the resource; may be one of the ResourceType constants or a user-defined type. /// Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#". /// Locale of the resource public Resource(ResourceType type, string name, int locale) : this(type, name, locale, null) { } /// /// Creates a new Resource object with data. The data can be later saved to a file. /// /// Type of the resource; may be one of the ResourceType constants or a user-defined type. /// Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#". /// Locale of the resource /// Raw resource data public Resource(ResourceType type, string name, int locale, byte[] data) { if (name == null) { throw new ArgumentNullException("name"); } this.type = type; this.name = name; this.locale = locale; this.data = data; } /// /// Gets or sets the type of the resource. This may be one of the ResourceType constants /// or a user-defined type name. /// public ResourceType ResourceType { get { return this.type; } set { this.type = value; } } /// /// Gets or sets the name of the resource. For a numeric resource identifier, the decimal number is prefixed with a "#". /// public string Name { get { return this.name; } set { if (value == null) { throw new ArgumentNullException("value"); } this.name = value; } } /// /// Gets or sets the locale of the resource. /// public int Locale { get { return this.locale; } set { this.locale = value; } } /// /// Gets or sets the raw data of the resource. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public virtual byte[] Data { get { return this.data; } set { this.data = value; } } /// /// Loads the resource data from a file. The file is searched for a resource with matching type, name, and locale. /// /// Win32 PE file containing the resource public void Load(string file) { IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); try { this.Load(module); } finally { NativeMethods.FreeLibrary(module); } } internal void Load(IntPtr module) { IntPtr resourceInfo = NativeMethods.FindResourceEx(module, (string) this.ResourceType, this.Name, (ushort) this.Locale); if (resourceInfo != IntPtr.Zero) { uint resourceLength = NativeMethods.SizeofResource(module, resourceInfo); IntPtr resourceData = NativeMethods.LoadResource(module, resourceInfo); IntPtr resourcePtr = NativeMethods.LockResource(resourceData); byte[] resourceBytes = new byte[resourceLength]; Marshal.Copy(resourcePtr, resourceBytes, 0, resourceBytes.Length); this.Data = resourceBytes; } else { this.Data = null; } } /// /// Saves the resource to a file. Any existing resource data with matching type, name, and locale is overwritten. /// /// Win32 PE file to contain the resource public void Save(string file) { IntPtr updateHandle = IntPtr.Zero; try { updateHandle = NativeMethods.BeginUpdateResource(file, false); this.Save(updateHandle); if (!NativeMethods.EndUpdateResource(updateHandle, false)) { int err = Marshal.GetLastWin32Error(); throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error code: {0}", err)); } updateHandle = IntPtr.Zero; } finally { if (updateHandle != IntPtr.Zero) { NativeMethods.EndUpdateResource(updateHandle, true); } } } internal void Save(IntPtr updateHandle) { IntPtr dataPtr = IntPtr.Zero; try { int dataLength = 0; if (this.Data != null) { dataLength = this.Data.Length; dataPtr = Marshal.AllocHGlobal(dataLength); Marshal.Copy(this.Data, 0, dataPtr, dataLength); } bool updateSuccess; if (this.Name.StartsWith("#", StringComparison.Ordinal)) { // A numeric-named resource must be saved via the integer version of UpdateResource. IntPtr intName = new IntPtr(Int32.Parse(this.Name.Substring(1), CultureInfo.InvariantCulture)); updateSuccess = NativeMethods.UpdateResource(updateHandle, new IntPtr(this.ResourceType.IntegerValue), intName, (ushort) this.Locale, dataPtr, (uint) dataLength); } else { updateSuccess = NativeMethods.UpdateResource(updateHandle, (string) this.ResourceType, this.Name, (ushort) this.Locale, dataPtr, (uint) dataLength); } if (!updateSuccess) { throw new IOException("Failed to save resource. Error: " + Marshal.GetLastWin32Error()); } } finally { if (dataPtr != IntPtr.Zero) { Marshal.FreeHGlobal(dataPtr); } } } /// /// Tests if type, name, and locale of this Resource object match another Resource object. /// /// Resource object to be compared /// True if the objects represent the same resource; false otherwise. public override bool Equals(object obj) { Resource res = obj as Resource; if (res == null) return false; return this.ResourceType == res.ResourceType && this.Name == res.Name && this.Locale == res.Locale; } /// /// Gets a hash code for this Resource object. /// /// Hash code generated from the resource type, name, and locale. public override int GetHashCode() { return this.ResourceType.GetHashCode() ^ this.Name.GetHashCode() ^ this.Locale.GetHashCode(); } } }