// 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();
}
}
}