Skip to content

Instantly share code, notes, and snippets.

@fearthecowboy
Last active August 29, 2015 14:12
Show Gist options
  • Save fearthecowboy/6393cdc9510b6ab6267a to your computer and use it in GitHub Desktop.
Save fearthecowboy/6393cdc9510b6ab6267a to your computer and use it in GitHub Desktop.
Very Type-safe APIs for getting at resources in a PE file
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
/*
NOTES:
The APIs here are using strongly typed parameters:
1st, we use Enums for the flags, instead of the more commonly used const uint values that are everywhere.
2nd, we use structs instead of IntPtrs, which ensures that APIs are never passed an arbitrary IntPtr.
Use of these techniques in p/invokes ensure strong-typing of parameters, and far less fat-fingered
mistakes that could otherwise cause a nightmare to debug.
(Plus, with the DisposableModule class, you can leverage using() to ensure that the module gets unloaded)
*/
namespace Microsoft.OneGet.Utility.Platform {
using System;
using System.Runtime.InteropServices;
using System.Text;
[Flags]
internal enum LoadLibraryFlags : uint {
DontResolveDllReferences = 0x00000001,
AsDatafile = 0x00000002,
LoadWithAlteredSearchPath = 0x00000008,
LoadIgnoreCodeAuthzLevel = 0x00000010,
AsImageResource = 0x00000020,
}
[Flags]
internal enum ResourceEnumFlags : uint {
None = 0x00000000,
LanguageNeutral = 0x00000001,
Mui = 0x00000002,
Validate = 0x00000008,
}
public class DisposableModule : IDisposable {
private Module _module;
public bool IsInvalid {
get {
return _module.IsInvalid;
}
}
public void Dispose() {
_module.Free();
}
public static implicit operator Module(DisposableModule instance) {
return instance._module;
}
public static implicit operator DisposableModule(Module module) {
return new DisposableModule {
_module = module
};
}
}
[StructLayout(LayoutKind.Explicit)]
public struct Module {
[FieldOffset(0)]
public IntPtr handle;
public Module(IntPtr ptr) {
handle = ptr;
}
public bool IsInvalid {
get {
return handle == IntPtr.Zero;
}
}
public void Free() {
if (!IsInvalid) {
NativeMethods.FreeLibrary(handle);
}
handle = IntPtr.Zero;
}
}
[StructLayout(LayoutKind.Explicit)]
public struct ResourceType {
public static ResourceType None = new ResourceType(0);
public static ResourceType Cursor = new ResourceType(1);
public static ResourceType Bitmap = new ResourceType(2);
public static ResourceType Icon = new ResourceType(3);
public static ResourceType Menu = new ResourceType(4);
public static ResourceType Dialog = new ResourceType(5);
public static ResourceType String = new ResourceType(6);
public static ResourceType FontDir = new ResourceType(7);
public static ResourceType Font = new ResourceType(8);
public static ResourceType Accelerator = new ResourceType(9);
public static ResourceType RCData = new ResourceType(10);
public static ResourceType MessageTable = new ResourceType(11);
public static ResourceType GroupCursor = new ResourceType(12);
public static ResourceType GroupIcon = new ResourceType(14);
public static ResourceType Version = new ResourceType(16);
public static ResourceType DialogInclud = new ResourceType(17);
public static ResourceType PlugPlay = new ResourceType(19);
public static ResourceType Vxd = new ResourceType(20);
public static ResourceType AniCursor = new ResourceType(21);
public static ResourceType AniIcon = new ResourceType(22);
public static ResourceType Html = new ResourceType(23);
public static ResourceType Manifest = new ResourceType(24);
[FieldOffset(0)]
public IntPtr handle;
public ResourceType(IntPtr ptr) {
handle = ptr;
}
public ResourceType(int ptr) {
handle = (IntPtr)ptr;
}
public bool IsInvalid {
get {
return handle == IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct ResourceId {
[FieldOffset(0)]
public IntPtr handle;
public ResourceId(IntPtr ptr) {
handle = ptr;
}
public bool IsInvalid {
get {
return handle == IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct Resource {
[FieldOffset(0)]
public IntPtr handle;
public Resource(IntPtr ptr) {
handle = ptr;
}
public bool IsInvalid {
get {
return handle == IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct ResourceData {
[FieldOffset(0)]
public IntPtr handle;
public ResourceData(IntPtr ptr) {
handle = ptr;
}
public bool IsInvalid {
get {
return handle == IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct Unused {
internal static Unused Nothing;
[FieldOffset(0)]
public IntPtr handle;
public Unused(IntPtr ptr) {
handle = ptr;
}
public bool IsInvalid {
get {
return handle == IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Explicit)]
internal struct LanguageId {
internal static LanguageId None;
[FieldOffset(0)]
private UInt16 value;
}
public delegate bool EnumResourceTypes([MarshalAs(UnmanagedType.SysInt)] Module module, ResourceType type, Unused unused);
public delegate bool EnumResourceNames(Module module, ResourceType type, ResourceId resourceId, Unused unused);
public delegate bool EnumResourceLanguages(Module module, ResourceType type, ResourceId resourceId, LanguageId language, Unused unused);
public static class NativeMethods {
/// <summary>
/// Enumerates resource types within a binary.
/// </summary>
/// <param name="module">Handle to a module to search.</param>
/// <param name="callback">Pointer to the function to be called for each resource type.</param>
/// <param name="unused">Value passed to the callback function.</param>
/// <param name="flags">The type of file to be searched.</param>
/// <param name="langid">Language ID</param>
/// <returns>Returns TRUE if successful, otherwise, FALSE.</returns>
[DllImport("kernel32.dll", EntryPoint = "EnumResourceTypesExW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool EnumResourceTypesEx(Module module, EnumResourceTypes callback, Unused unused, ResourceEnumFlags flags, LanguageId langid);
[DllImport("kernel32.dll", EntryPoint = "EnumResourceNamesExW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool EnumResourceNamesEx(Module module, ResourceType resourceType, EnumResourceNames callback, Unused unused, ResourceEnumFlags flags, uint langid);
[DllImport("kernel32.dll", EntryPoint = "EnumResourceLanguagesExW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool EnumResourceLanguagesEx(Module module, ResourceType resourceType, ResourceId resourceId, EnumResourceLanguages callback, Unused unused, ResourceEnumFlags flags, LanguageId language);
[DllImport("kernel32.dll", EntryPoint = "FindResourceExW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern Resource FindResourceEx(Module module, ResourceType resourceType, ResourceId resourceId, LanguageId language);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern ResourceData LoadResource(Module module, Resource resource);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr LockResource(ResourceData data);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern int SizeofResource(Module module, Resource hResInfo);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern Module LoadLibraryEx(string filename, Unused unused, LoadLibraryFlags dwFlags);
[DllImport("kernel32")]
internal static extern bool FreeLibrary(Module instance);
}
}
// Sample usage Code:
public static class Manifest {
private static readonly byte[] _utf = { 0xef, 0xbb, 0xbf };
public static IEnumerable<XElement> LoadFrom(string filename) {
var manifests = new List<XElement>();
using (DisposableModule dll = NativeMethods.LoadLibraryEx(filename, Unused.Nothing, LoadLibraryFlags.AsImageResource | LoadLibraryFlags.AsDatafile)) {
// if we get back a valid module handle
if (!dll.IsInvalid) {
// search all the 'manifest' resources
if (NativeMethods.EnumResourceNamesEx(dll, ResourceType.Manifest, (m, type, id, param) => {
// for each manifest, check the language
NativeMethods.EnumResourceLanguagesEx(m, type, id, (m1, resourceType, resourceId, language, unused) => {
// find the specific resource
var resource = NativeMethods.FindResourceEx(m1, resourceType, resourceId, language);
if (!resource.IsInvalid) {
// get a handle to the resource data
var resourceData = NativeMethods.LoadResource(m1, resource);
if (!resourceData.IsInvalid) {
// copy the resource text out of the resource data
try {
var dataSize = NativeMethods.SizeofResource(m1, resource);
var dataPointer = NativeMethods.LockResource(resourceData);
// make sure that the pointer and size are legit.
if (dataSize > 0 && dataPointer != IntPtr.Zero) {
var data = new byte[dataSize];
Marshal.Copy(dataPointer, data, 0, data.Length);
var bomPresent = (data.Length >= 3 && data[0] == _utf[0] && data[1] == _utf[1] && data[2] == _utf[2]);
// create an XElement for the data returned.
// IIRC, manifests are always UTF-8, n'est-ce pas?
manifests.Add(XElement.Parse(Encoding.UTF8.GetString(data, bomPresent ? 3 : 0, bomPresent ? data.Length - 3 : data.Length)));
}
} catch {
// skip it if it doesn't load.
}
}
}
return true;
}, Unused.Nothing, ResourceEnumFlags.None, LanguageId.None);
return true;
}, Unused.Nothing, ResourceEnumFlags.None, 0)) {
}
}
}
return manifests;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment