Click here to Skip to main content
15,894,106 members
Articles / Programming Languages / C#

IconLib - Icons Unfolded (MultiIcon and Windows Vista supported)

Rate me:
Please Sign up or sign in to vote.
4.96/5 (671 votes)
14 Feb 2008CC (ASA 2.5)41 min read 1M   15K   661  
Library to manipulate icons and icons libraries with support to create, load, save, import and export icons in ico, icl, dll, exe, cpl and src format. (Windows Vista icons supported).
//  Copyright (c) 2006, Gustavo Franco
//  Email:  gustavo_franco@hotmail.com
//  All rights reserved.

//  Redistribution and use in source and binary forms, with or without modification, 
//  are permitted provided that the following conditions are met:

//  Redistributions of source code must retain the above copyright notice, 
//  this list of conditions and the following disclaimer. 
//  Redistributions in binary form must reproduce the above copyright notice, 
//  this list of conditions and the following disclaimer in the documentation 
//  and/or other materials provided with the distribution. 

//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER 
//  REMAINS UNCHANGED.
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing.IconLib.Exceptions;
using System.Drawing.Imaging;
using System.Collections;

namespace System.Drawing.IconLib.EncodingFormats
{
    [Author("Franco, Gustavo")]
    internal class PEFormat : ILibraryFormat
    {
        #region Variables Declaration
        private static List<string> mIconsIDs;
        #endregion

        #region Methods
        public bool IsRecognizedFormat(Stream stream)
        {
            stream.Position = 0;
            try
            {
                // Has a valid MS-DOS header?
                IMAGE_DOS_HEADER dos_header = new IMAGE_DOS_HEADER(stream);
                if (dos_header.e_magic != (int) HeaderSignatures.IMAGE_DOS_SIGNATURE) //MZ
                    return false;

                //Lets position over the "PE" header
                stream.Seek(dos_header.e_lfanew, SeekOrigin.Begin);

                //Lets read the NE header
                IMAGE_NT_HEADERS nt_header = new IMAGE_NT_HEADERS(stream);
                if (nt_header.Signature != (int) HeaderSignatures.IMAGE_NT_SIGNATURE) //PE
                    return false;

                return true;
            }
            catch(Exception){}
            return false;
        }

        public unsafe MultiIcon Load(Stream stream)
        {
            // LoadLibraryEx only can load files from File System, lets create a tmp file
            string tmpFile  = null;
            IntPtr hLib     = IntPtr.Zero;
            try
            {
                stream.Position = 0;

                // Find a tmp file where to dump the DLL stream, later we will remove this file
                tmpFile = Path.GetTempFileName();
                FileStream fs = new FileStream(tmpFile, FileMode.Create, FileAccess.Write);
                byte[] buffer = new byte[stream.Length];
                stream.Read(buffer, 0, buffer.Length);
                fs.Write(buffer, 0, buffer.Length);
                fs.Close();

                hLib = Win32.LoadLibraryEx(tmpFile, IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE);
                if (hLib == IntPtr.Zero)
                    throw new InvalidFileException();

                List<string> iconsIDs;
                lock (typeof(PEFormat))
                {
                    mIconsIDs = new List<string>();
                    bool bResult = Win32.EnumResourceNames(hLib, (IntPtr) ResourceType.RT_GROUP_ICON, new Win32.EnumResNameProc(EnumResNameProc), IntPtr.Zero);
                    if (bResult == false)
                    {
                        // No Resources in this file
                    }
                    iconsIDs = new List<string>(mIconsIDs);
                }

                MultiIcon multiIcon = new MultiIcon();
                for(int index=0; index<iconsIDs.Count; index++)
                {
                    string id = iconsIDs[index];
                    IntPtr hRsrc = IntPtr.Zero;

                    if (Win32.IS_INTRESOURCE(id))
                        hRsrc = Win32.FindResource(hLib, int.Parse(id), (IntPtr) ResourceType.RT_GROUP_ICON);
                    else
                        hRsrc = Win32.FindResource(hLib, id, (IntPtr) ResourceType.RT_GROUP_ICON);

                    if (hRsrc == IntPtr.Zero)
                        throw new InvalidFileException();
                     
                    IntPtr hGlobal = Win32.LoadResource(hLib, hRsrc);
                    if (hGlobal == IntPtr.Zero)
                        throw new InvalidFileException();

                    MEMICONDIR* pDirectory = (MEMICONDIR*) Win32.LockResource(hGlobal);
                    if (pDirectory->wCount != 0)
                    {
                        MEMICONDIRENTRY* pEntry = &(pDirectory->arEntries);

                        SingleIcon singleIcon = new SingleIcon(id);
                        for(int i=0;i<pDirectory->wCount; i++)
                        {
                            IntPtr hIconInfo = Win32.FindResource(hLib, (IntPtr) pEntry[i].wId, (IntPtr) ResourceType.RT_ICON);
                            if (hIconInfo == IntPtr.Zero)
                                throw new InvalidFileException();

                            IntPtr hIconRes = Win32.LoadResource(hLib, hIconInfo);
                            if (hIconRes == IntPtr.Zero)
                                throw new InvalidFileException();

                            IntPtr dibBits = Win32.LockResource(hIconRes);
                            if (dibBits == IntPtr.Zero)
                                throw new InvalidFileException();

                            buffer = new byte[Win32.SizeofResource(hLib, hIconInfo)];
                            Marshal.Copy(dibBits, buffer, 0, buffer.Length);

                            MemoryStream ms = new MemoryStream(buffer);
                            IconImage iconImage = new IconImage(ms, buffer.Length);
                            singleIcon.Add(iconImage);
                        }
                        multiIcon.Add(singleIcon);
                    }
                }

                // If everything went well then lets return the multiIcon.
                return multiIcon;
            }
            catch(Exception)
            {
                throw new InvalidFileException();
            }
            finally
            {
                if (hLib != null)
                    Win32.FreeLibrary(hLib);
                if (tmpFile != null)
                    File.Delete(tmpFile);
            }
        }

        public unsafe void Save(MultiIcon multiIcon, Stream stream)
        {
            // LoadLibraryEx only can load files from File System, lets create a tmp file
            string tmpFile  = null;
            IntPtr hLib     = IntPtr.Zero;
            MemoryStream ms;
            bool bResult;
            try
            {
                stream.Position = 0;

                // Find a tmp file where to dump the DLL stream, later we will remove this file
                tmpFile = Path.GetTempFileName();

                FileStream fs = new FileStream(tmpFile, FileMode.Create, FileAccess.Write);
                byte[] buffer = Resource.EmptyDll;
                stream.Read(buffer, 0, buffer.Length);
                fs.Write(buffer, 0, buffer.Length);
                fs.Close();

				// Begin the injection process
			    IntPtr updPtr = Win32.BeginUpdateResource(tmpFile, false);
                if (updPtr == IntPtr.Zero)
                    throw new InvalidFileException();

                ushort iconIndex = 1;
                foreach(SingleIcon singleIcon in multiIcon)
                {
                    // Lets scan all groups
                    GRPICONDIR grpIconDir   = GRPICONDIR.Initalizated;
                    grpIconDir.idCount      = (ushort) singleIcon.Count;
                    grpIconDir.idEntries    = new GRPICONDIRENTRY[grpIconDir.idCount];

                    for(int i=0;i<singleIcon.Count; i++)
                    {
                        // Inside every Icon let update every image format
                        IconImage iconImage = singleIcon[i];
                        grpIconDir.idEntries[i]     = iconImage.GRPICONDIRENTRY;
                        grpIconDir.idEntries[i].nID = iconIndex;

                        // Buffer creation with the same size of the icon to optimize write call
                        ms = new MemoryStream((int) grpIconDir.idEntries[i].dwBytesInRes);
                        iconImage.Write(ms);
                        buffer = ms.GetBuffer();

                        // Update resource but it doesn't write to disk yet
                        bResult = Win32.UpdateResource(updPtr, (int) ResourceType.RT_ICON, iconIndex, 0,  buffer, (uint) ms.Length);
                        
                        iconIndex++;

                        // For some reason Windows will fail if there are many calls to update resource and no call to endUpdateResource
                        // It is like there some internal buffer that gets full, after that all calls fail.
                        // This workaround will save the changes every 70 icons, for big files this slow the saving process significantly
                        // but I didn't find a way to make EndUpdateResource works without save frequently
                        if ((iconIndex % 70) == 0)
                        {
                            bResult = Win32.EndUpdateResource(updPtr, false);
                            updPtr = Win32.BeginUpdateResource(tmpFile, false);
                            if (updPtr == IntPtr.Zero)
                                throw new InvalidFileException();
                        }
                    }

                    // Buffer creation with the same size of the group to optimize write call
                    ms = new MemoryStream(grpIconDir.GroupDirSize);
                    grpIconDir.Write(ms);
                    buffer = ms.GetBuffer();

                    int id;
                    if (int.TryParse(singleIcon.Name, out id))
                    {
                        // Write id as an integer
                        bResult = Win32.UpdateResource(updPtr, (int) ResourceType.RT_GROUP_ICON, (IntPtr) id, 0, buffer, (uint) ms.Length);
                    }
                    else
                    {
                        // Write id as string
                        IntPtr pName = Marshal.StringToHGlobalAnsi(singleIcon.Name.ToUpper());
                        bResult = Win32.UpdateResource(updPtr, (int) ResourceType.RT_GROUP_ICON, pName, 0, buffer, (uint) ms.Length);
                        Marshal.FreeHGlobal(pName);
                    }
                }
                
                // Last call to update the file with the rest not that was not write before
                bResult = Win32.EndUpdateResource(updPtr, false);

                // Because Windows Resource functions requiere a filepath, and we need to return an string then lets open
                // the temporary file and dump it to the stream received as parameter.
                fs = new FileStream(tmpFile, FileMode.Open, FileAccess.Read);
                buffer = new byte[fs.Length];
                fs.Read(buffer, 0, buffer.Length);
                stream.Write(buffer, 0, buffer.Length);
                fs.Close();
            }
            catch(Exception)
            {
                throw new InvalidFileException();
            }
            finally
            {
                if (hLib != null)
                    Win32.FreeLibrary(hLib);
                if (tmpFile != null)
                    File.Delete(tmpFile);
            }
        }
        #endregion

        #region Private Methods
        private static unsafe bool EnumResNameProc(IntPtr hModule, IntPtr pType, IntPtr pName, IntPtr param)
        {
            if (Win32.IS_INTRESOURCE(pName))
            {
                mIconsIDs.Add(pName.ToString()); 
            }
            else
            {
                mIconsIDs.Add(Marshal.PtrToStringUni(pName)); 
            }
            return true;
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License


Written By
Software Developer Microsoft
United States United States
I started with programming about 19 years ago as a teenager, from my old Commodore moving to PC/Server environment Windows/UNIX SQLServer/Oracle doing gwBasic, QBasic, Turbo Pascal, Assembler, Turbo C, BC, Summer87, Clipper, Fox, SQL, C/C++, Pro*C, VB3/5/6, Java, and today loving C#.

Currently working as SDE on Failover Clustering team for Microsoft.

Passion for most programming languages and my kids Aidan&Nadia.

Comments and Discussions