using System; using System.Runtime.InteropServices; using System.Security; namespace xServer.Core.Build { public static class IconInjector { // Basically, you can change icons with the UpdateResource api call. // When you make the call you say "I'm updating an icon", and you send the icon data. // The main problem is that ICO files store the icons in one set of structures, and exe/dll files store them in // another set of structures. So you have to translate between the two -- you can't just load the ICO file as // bytes and send them with the UpdateResource api call. [SuppressUnmanagedCodeSecurity()] private class NativeMethods { [DllImport("kernel32")] public static extern IntPtr BeginUpdateResource(string fileName, [MarshalAs(UnmanagedType.Bool)] bool deleteExistingResources); [DllImport("kernel32")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool UpdateResource(IntPtr hUpdate, IntPtr type, IntPtr name, short language, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)] byte[] data, int dataSize); [DllImport("kernel32")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool EndUpdateResource(IntPtr hUpdate, [MarshalAs(UnmanagedType.Bool)] bool discard); } // The first structure in an ICO file lets us know how many images are in the file. [StructLayout(LayoutKind.Sequential)] private struct ICONDIR { // Reserved, must be 0 public ushort Reserved; // Resource type, 1 for icons. public ushort Type; // How many images. public ushort Count; // The native structure has an array of ICONDIRENTRYs as a final field. } // Each ICONDIRENTRY describes one icon stored in the ico file. The offset says where the icon image data // starts in the file. The other fields give the information required to turn that image data into a valid // bitmap. [StructLayout(LayoutKind.Sequential)] private struct ICONDIRENTRY { // Width, in pixels, of the image public byte Width; // Height, in pixels, of the image public byte Height; // Number of colors in image (0 if >=8bpp) public byte ColorCount; // Reserved ( must be 0) public byte Reserved; // Color Planes public ushort Planes; // Bits per pixel public ushort BitCount; // Length in bytes of the pixel data public int BytesInRes; // Offset in the file where the pixel data starts. public int ImageOffset; } // Each image is stored in the file as an ICONIMAGE structure: //typdef struct //{ // BITMAPINFOHEADER icHeader; // DIB header // RGBQUAD icColors[1]; // Color table // BYTE icXOR[1]; // DIB bits for XOR mask // BYTE icAND[1]; // DIB bits for AND mask //} ICONIMAGE, *LPICONIMAGE; [StructLayout(LayoutKind.Sequential)] private struct BITMAPINFOHEADER { public uint Size; public int Width; public int Height; public ushort Planes; public ushort BitCount; public uint Compression; public uint SizeImage; public int XPelsPerMeter; public int YPelsPerMeter; public uint ClrUsed; public uint ClrImportant; } // The icon in an exe/dll file is stored in a very similar structure: [StructLayout(LayoutKind.Sequential, Pack = 2)] private struct GRPICONDIRENTRY { public byte Width; public byte Height; public byte ColorCount; public byte Reserved; public ushort Planes; public ushort BitCount; public int BytesInRes; public ushort ID; } public static void InjectIcon(string exeFileName, string iconFileName) { InjectIcon(exeFileName, iconFileName, 1, 1); } public static void InjectIcon(string exeFileName, string iconFileName, uint iconGroupID, uint iconBaseID) { const uint RT_ICON = 3u; const uint RT_GROUP_ICON = 14u; IconFile iconFile = IconFile.FromFile(iconFileName); var hUpdate = NativeMethods.BeginUpdateResource(exeFileName, false); var data = iconFile.CreateIconGroupData(iconBaseID); NativeMethods.UpdateResource(hUpdate, new IntPtr(RT_GROUP_ICON), new IntPtr(iconGroupID), 0, data, data.Length); for (int i = 0; i <= iconFile.ImageCount - 1; i++) { var image = iconFile.ImageData(i); NativeMethods.UpdateResource(hUpdate, new IntPtr(RT_ICON), new IntPtr(iconBaseID + i), 0, image, image.Length); } NativeMethods.EndUpdateResource(hUpdate, false); } private class IconFile { private ICONDIR iconDir = new ICONDIR(); private ICONDIRENTRY[] iconEntry; private byte[][] iconImage; public int ImageCount { get { return iconDir.Count; } } public byte[] ImageData(int index) { return iconImage[index]; } public static IconFile FromFile(string filename) { IconFile instance = new IconFile(); // Read all the bytes from the file. byte[] fileBytes = System.IO.File.ReadAllBytes(filename); // First struct is an ICONDIR // Pin the bytes from the file in memory so that we can read them. // If we didn't pin them then they could move around (e.g. when the // garbage collector compacts the heap) GCHandle pinnedBytes = GCHandle.Alloc(fileBytes, GCHandleType.Pinned); // Read the ICONDIR instance.iconDir = (ICONDIR) Marshal.PtrToStructure(pinnedBytes.AddrOfPinnedObject(), typeof (ICONDIR)); // which tells us how many images are in the ico file. For each image, there's a ICONDIRENTRY, and associated pixel data. instance.iconEntry = new ICONDIRENTRY[instance.iconDir.Count]; instance.iconImage = new byte[instance.iconDir.Count][]; // The first ICONDIRENTRY will be immediately after the ICONDIR, so the offset to it is the size of ICONDIR int offset = Marshal.SizeOf(instance.iconDir); // After reading an ICONDIRENTRY we step forward by the size of an ICONDIRENTRY var iconDirEntryType = typeof (ICONDIRENTRY); var size = Marshal.SizeOf(iconDirEntryType); for (int i = 0; i <= instance.iconDir.Count - 1; i++) { // Grab the structure. var entry = (ICONDIRENTRY) Marshal.PtrToStructure(new IntPtr(pinnedBytes.AddrOfPinnedObject().ToInt64() + offset), iconDirEntryType); instance.iconEntry[i] = entry; // Grab the associated pixel data. instance.iconImage[i] = new byte[entry.BytesInRes]; Buffer.BlockCopy(fileBytes, entry.ImageOffset, instance.iconImage[i], 0, entry.BytesInRes); offset += size; } pinnedBytes.Free(); return instance; } public byte[] CreateIconGroupData(uint iconBaseID) { // This will store the memory version of the icon. int sizeOfIconGroupData = Marshal.SizeOf(typeof (ICONDIR)) + Marshal.SizeOf(typeof (GRPICONDIRENTRY))*ImageCount; byte[] data = new byte[sizeOfIconGroupData]; var pinnedData = GCHandle.Alloc(data, GCHandleType.Pinned); Marshal.StructureToPtr(iconDir, pinnedData.AddrOfPinnedObject(), false); var offset = Marshal.SizeOf(iconDir); for (int i = 0; i <= ImageCount - 1; i++) { GRPICONDIRENTRY grpEntry = new GRPICONDIRENTRY(); BITMAPINFOHEADER bitmapheader = new BITMAPINFOHEADER(); var pinnedBitmapInfoHeader = GCHandle.Alloc(bitmapheader, GCHandleType.Pinned); Marshal.Copy(ImageData(i), 0, pinnedBitmapInfoHeader.AddrOfPinnedObject(), Marshal.SizeOf(typeof (BITMAPINFOHEADER))); pinnedBitmapInfoHeader.Free(); grpEntry.Width = iconEntry[i].Width; grpEntry.Height = iconEntry[i].Height; grpEntry.ColorCount = iconEntry[i].ColorCount; grpEntry.Reserved = iconEntry[i].Reserved; grpEntry.Planes = bitmapheader.Planes; grpEntry.BitCount = bitmapheader.BitCount; grpEntry.BytesInRes = iconEntry[i].BytesInRes; grpEntry.ID = Convert.ToUInt16(iconBaseID + i); Marshal.StructureToPtr(grpEntry, new IntPtr(pinnedData.AddrOfPinnedObject().ToInt64() + offset), false); offset += Marshal.SizeOf(typeof (GRPICONDIRENTRY)); } pinnedData.Free(); return data; } } } }