Last active
September 7, 2026 12:36
-
-
Save scriptingstudio/0b15927ac5cf9016d0fdcd0dc6e101e6 to your computer and use it in GitHub Desktop.
Yet another icon extractor from DLL,EXE,ICO for C#, PowerShell, and Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| SYNOPSIS : Icon Reader for C# | |
| DESCRIPTION : A utility class that extracts the list of all icons handles (all sizes, all color counts) for a binary file like DLL, EXE, or ICO | |
| UTILITIES : GetTotal, ImportIcons, ExportIcons, Dispose | |
| FEATURES : Export: by size and index, multisize icons, split icon groups by size | |
| VERSION : 4.0 | |
| LASTUPDATE : 2026-Sep-5 | |
| LINK : https://en.wikipedia.org/wiki/ICO_(file_format) | |
| NOTES : Only builtin sizes are exported; GIF,BMP,JPEG always have the background in black | |
| BACKLOG : | |
| // get amount of icons stored in a dll | |
| var icons = IconReader.GetTotal(@"c:\windows\system32\imageres.dll"); | |
| // get all icons handles with index 0 from a dll | |
| var icons = IconReader.ImportIcons(@"c:\windows\system32\shell32.dll", 0); | |
| // get all icons handles from a dll | |
| var icons = IconReader.ImportIcons(@"c:\windows\system32\shell32.dll"); | |
| // get all icons handles from an ico | |
| var icons = IconReader.ImportIcons(@"<your_ico_filepath>"); | |
| // dispose imported icons | |
| icons.ForEach(i => i.Dispose()); | |
| */ | |
| // .NET 8+ Edition | |
| using System; | |
| using System.IO; | |
| using System.Runtime.InteropServices; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Globalization; | |
| using System.Text.RegularExpressions; | |
| using System.Drawing; | |
| using System.Drawing.Imaging; | |
| using System.Drawing.Drawing2D; | |
| using System.Reflection; | |
| using System.Linq; | |
| using System.Net; | |
| using System.Net.Http; | |
| public sealed class IconReader : IDisposable | |
| { | |
| public IntPtr Handle { get; } // primary ID for icon | |
| public int Index { get; } // global index, inc multi sizing | |
| public int GroupIndex { get; } // EXE,DLL - group (multi-size icon) index; ICO - 0 | |
| public string Id { get; } // primarily for EXE,DLL | |
| public string GroupId { get; } // primarily for EXE,DLL | |
| public int Width { get; } | |
| public int Height { get; } | |
| public int Bpp { get; } | |
| public int Colors { get; } | |
| public int ImgLength { get; } | |
| public object? IconData { get; set; } // icon image/object storage; by default contains image bytes | |
| private IconReader(int groupIndex, string groupId, int index, string id, int width, int height, int bpp, int colors, int len, IntPtr handle, object? imgData=null, bool rawData=true) | |
| { | |
| GroupIndex = groupIndex; | |
| GroupId = groupId; | |
| Index = index; | |
| Id = id; | |
| Width = width; // width == 0 ? 256 : width; | |
| Height = height; // height == 0 ? 256 : height; | |
| Bpp = bpp; | |
| Colors = colors; | |
| ImgLength = len; | |
| Handle = handle; | |
| IconData = rawData ? imgData : Icon.FromHandle(handle); | |
| } | |
| // Utilities | |
| //public override string ToString() => Index.ToString(); | |
| public void Dispose() | |
| { | |
| if (IconData != null && IconData.GetType() == typeof(Icon)) ((Icon)IconData).Dispose(); | |
| DestroyIcon(Handle); | |
| } | |
| // DLL,EXE: the total doesn't take into account multi sizing | |
| public static int GetTotal(string iconFilePath) | |
| { | |
| iconFilePath = ResolveInputURI(iconFilePath); | |
| if (string.IsNullOrEmpty(iconFilePath)) return 0; // or ArgumentNullException? | |
| if (Path.GetExtension(iconFilePath).ToLower() == ".ico") | |
| { | |
| byte[] signature; | |
| FileStream stream; | |
| try { | |
| using (stream = File.Open(iconFilePath, FileMode.Open)) | |
| { | |
| using (var reader = new BinaryReader(stream, System.Text.Encoding.UTF8, false)) | |
| { // don't modify "using" syntax | |
| signature = reader.ReadBytes(6); // 4+2 | |
| if (signature.Length < 6) // corrupted | |
| throw new ArgumentException($"'{iconFilePath}' is not a valid ICO file."); | |
| } | |
| } | |
| } | |
| catch {throw new ArgumentException($"Unable to read '{iconFilePath}' file.");} | |
| if (BitConverter.ToInt32(signature[..4], 0) != 0x10000) // Basic validation | |
| throw new ArgumentException($"'{iconFilePath}' is not a valid ICO file."); | |
| return BitConverter.ToInt16(signature[4..6], 0); | |
| } | |
| else // dll or exe | |
| { // version 2, uniform; version 1 is simpler and faster | |
| int icount = 0; | |
| var handle = LoadLibraryEx(iconFilePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); | |
| if (handle != IntPtr.Zero) | |
| { | |
| EnumResourceNames(handle, new IntPtr(RT_GROUP_ICON), (m, t, n, lp) => { icount++; return true; }, IntPtr.Zero); | |
| FreeLibrary(handle); | |
| } | |
| return icount; | |
| } | |
| } // GetTotal | |
| public static List<IconReader> ImportIcons(string iconFilePath, int[]? byIndex=null, int[]? bySize=null, bool rawData=true) | |
| { | |
| iconFilePath = ResolveInputURI(iconFilePath); | |
| if (string.IsNullOrEmpty(iconFilePath)) | |
| throw new ArgumentNullException(nameof(iconFilePath)); | |
| // Validate input filters | |
| if (bySize != null && bySize.Length > 0) | |
| { | |
| bySize = bySize.Where(n => n > 15 && n < 257).ToArray(); | |
| if (bySize.Length == 0) bySize = new[]{-1000}; // stop value | |
| } | |
| else bySize = null; | |
| if (byIndex != null && byIndex.Length > 0) | |
| { | |
| byIndex = byIndex.Where(n => n > -1).ToArray(); | |
| if (byIndex.Length == 0) byIndex = new[]{-1000}; // stop value | |
| } | |
| else byIndex = null; | |
| if (Path.GetExtension(iconFilePath).ToLower() == ".ico") | |
| return ImportIco(iconFilePath, byIndex, bySize, rawData); | |
| var icons = new List<IconReader>(); | |
| var handle = LoadLibraryEx(iconFilePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); | |
| if (handle != IntPtr.Zero) | |
| { | |
| try | |
| { | |
| icons = ImportIcons(handle, byIndex, bySize, rawData); | |
| } | |
| finally | |
| { | |
| FreeLibrary(handle); | |
| } | |
| } | |
| return icons; | |
| } // ImportIcons | |
| private static List<IconReader> ImportIcons(IntPtr handle, int[]? byIndex=null, int[]? bySize=null, bool rawData=true) | |
| { | |
| var icons = new List<IconReader>(); | |
| var entries = new Dictionary<ushort, GRPICONDIRENTRY>(); | |
| var groupIndices = new Dictionary<ushort, int>(); | |
| var groupIds = new Dictionary<ushort, string>(); | |
| var groupIndex = 0; | |
| if (EnumResourceNames(handle, new IntPtr(RT_GROUP_ICON), (m, t, n, lp) => | |
| { | |
| if (byIndex != null && !Array.Exists(byIndex, el => el > -1 && el == groupIndex)) | |
| { | |
| groupIndex++; | |
| return true; | |
| } | |
| try | |
| { | |
| ExtractIconGroupEntries(handle, n, t, groupIndex, entries, groupIndices, groupIds); | |
| groupIndex++; | |
| } | |
| catch | |
| { | |
| // do nothing | |
| } | |
| return true; | |
| }, IntPtr.Zero)) | |
| { | |
| EnumResourceNames(handle, new IntPtr(RT_ICON), (m, t, n, lp) => | |
| { | |
| if (!entries.TryGetValue((ushort)n.ToInt32(), out _)) return true; | |
| var imgbytes = GetImageBytes(handle, n, t); | |
| if (imgbytes == null) return true; | |
| var iconHandle = CreateIconFromResourceEx(imgbytes, imgbytes.Length, true, 0x00030000,0,0,0); | |
| if (iconHandle != IntPtr.Zero) | |
| { | |
| var id = (ushort)n.ToInt32(); | |
| //var ent = entries[id]; // ICONDIR | |
| var w = entries[id].bWidth == 0 ? 256 : entries[id].bWidth; | |
| var h = entries[id].bHeight == 0 ? 256 : entries[id].bHeight; | |
| var l = entries[id].dwBytesInRes; | |
| var bpp = entries[id].wBitCount; | |
| int colors = entries[id].bColorCount; | |
| if (colors == 0 && bpp == 8) colors = 256; | |
| if (bySize == null || Array.Exists(bySize, el => el == w)) | |
| { | |
| var info = new IconReader(groupIndices[id], groupIds[id], n.ToInt32() - 1, n.ToString(), w,h,bpp,colors,l, iconHandle,imgbytes,rawData); | |
| icons.Add(info); | |
| } | |
| } | |
| return true; | |
| }, IntPtr.Zero); | |
| } | |
| return icons; | |
| } // ImportIcons | |
| public static bool ExportIcons(string iconFilePath=null, List<IconReader>? iconInfo=null, string? outputPath=null, int[]? bySize=null, int[]? byIndex=null, bool split=false, string? type="ico", Color? background=null) | |
| { | |
| // Resolve parameter sets | |
| iconFilePath = ResolveInputURI(iconFilePath); | |
| var ii = iconInfo == null || iconInfo.Count == 0; | |
| if (string.IsNullOrEmpty(iconFilePath) && ii) | |
| throw new ArgumentNullException("Filename and icon collection are empty."); | |
| if (string.IsNullOrEmpty(iconFilePath) && string.IsNullOrEmpty(outputPath)) return false; | |
| try { iconFilePath = Path.GetFullPath(iconFilePath); } catch { return false; } | |
| var src = Path.GetExtension(iconFilePath).ToLower().Substring(1); // exe,dll,ico | |
| if (src == "ico" && !split && (bySize == null || bySize.Length == 0)) split = true; | |
| if (string.IsNullOrEmpty(type)) type = "ico"; | |
| //var raw = type.ToLower() == "ico"; | |
| if (ii) iconInfo = ImportIcons(iconFilePath,byIndex,bySize); //,raw | |
| if (iconInfo == null || iconInfo.Count == 0) return false; | |
| // Create output file name template | |
| var fn = Path.GetFileNameWithoutExtension(iconFilePath); | |
| if (string.IsNullOrEmpty(outputPath)) | |
| { | |
| var sfx = src == "ico" ? "-extract-%index%.ico" : "-%index%.ico"; | |
| outputPath = string.Format(@"{0}\{1}{2}", Path.GetDirectoryName(iconFilePath),fn,sfx); | |
| } | |
| else | |
| { | |
| try { outputPath = Path.GetFullPath(outputPath); } catch { return false; } | |
| outputPath = string.Format(@"{0}\{1}-%index%.ico", outputPath,fn); | |
| } | |
| if (Regex.IsMatch(outputPath,@"c:\\(windows.+|program.+|[^\\]+)$",RegexOptions.IgnoreCase)) | |
| Console.WriteLine("WARNING: System location specified as output. Export may fail. Make sure that you have write access rights."); | |
| // Validate input filters | |
| if (bySize != null && bySize.Length > 0) | |
| bySize = bySize.Where(n => n > 15 && n < 257).OrderByDescending(n => n).ToArray(); | |
| else bySize = null; | |
| if (byIndex != null && byIndex.Length > 0) | |
| byIndex = byIndex.Where(n => n > -1).OrderByDescending(n => n).ToArray(); | |
| else byIndex = null; | |
| // Other formats | |
| if (type.ToLower() != "ico") return ExportImages(iconInfo,outputPath,type,bySize,byIndex,background); | |
| // Group by GroupIndex first | |
| var igroups = iconInfo.Where(i => i.IconData != null).GroupBy(i => i.GroupIndex).OrderBy(i => i.Key); | |
| foreach (var gr in igroups) | |
| { | |
| if (byIndex != null && !Array.Exists(byIndex, el => el == gr.Key)) continue; // filter | |
| var iconGroup = gr.ToList(); // select a group | |
| var series = split ? iconGroup.Count : 1; // mode selector | |
| for (var s = 0; s < series; s++) | |
| { | |
| // select the next chunk to work with | |
| var icoWork = split ? new List<IconReader>{iconGroup[s]} : iconGroup; | |
| // resolve output file name | |
| // PROBLEM: size may be wrong because of filtering (below); TEST!!! | |
| var icofile = outputPath.Replace("%index%",string.Format("{0}%sz%%color%",gr.Key)); | |
| if (split) { | |
| if (icoWork[0].Colors == 0) | |
| icofile = icofile.Replace("%sz%%color%",string.Format("-{0}",icoWork[0].Width)); | |
| else | |
| icofile = icofile.Replace("%sz%%color%",string.Format("-{0}-{1}",icoWork[0].Width,icoWork[0].Colors)); | |
| } | |
| else | |
| icofile = icofile.Replace("%sz%%color%",""); | |
| FileStream outputStream; | |
| try {outputStream = new FileStream(icofile, FileMode.OpenOrCreate);} | |
| catch {continue;} | |
| if (outputStream == null) continue; | |
| var iconWriter = new BinaryWriter(outputStream); | |
| if (iconWriter == null) | |
| { | |
| outputStream.Close(); | |
| outputStream.Dispose(); | |
| continue; | |
| } | |
| bool inew = false; // no icons to export | |
| // 0-1 reserved, 0 | |
| iconWriter.Write((byte)0); | |
| iconWriter.Write((byte)0); | |
| // 2-3 image type, 1 = icon, 2 = cursor | |
| iconWriter.Write((short)1); | |
| // 4-5 number of images | |
| iconWriter.Write((short)icoWork.Count); | |
| //var natives = icoWork.Select(n => n.Width).Distinct(); // builtin sizes | |
| int offset = 6 + (16 * icoWork.Count); | |
| short icocount = 0; | |
| foreach (var icon in icoWork) | |
| { | |
| if (bySize != null && !Array.Exists(bySize, el => el == icon.Width)) continue; // filter | |
| var w = icon.Width == 256 ? 0 : icon.Width; | |
| var h = icon.Height == 256 ? 0 : icon.Height; | |
| // image entry | |
| // 0 image width | |
| iconWriter.Write((byte)w); | |
| // 1 image height | |
| iconWriter.Write((byte)h); | |
| // 2 number of colors | |
| iconWriter.Write((byte)0); | |
| // 3 reserved | |
| iconWriter.Write((byte)0); | |
| // 4-5 color planes | |
| iconWriter.Write((short)0); | |
| // 6-7 bits per pixel | |
| iconWriter.Write((short)32); | |
| // 8-11 size of image data | |
| iconWriter.Write((int)icon.ImgLength); | |
| // 12-15 offset of image data | |
| iconWriter.Write((int)offset); | |
| offset += icon.ImgLength; | |
| inew = true; | |
| icocount++; | |
| } | |
| foreach (var icon in icoWork) | |
| { | |
| // image data | |
| // png data must contain the whole png data file | |
| if (bySize == null || Array.Exists(bySize, el => el == icon.Width)) | |
| iconWriter.Write(icon.IconData as byte[]); | |
| } | |
| if (inew && icocount != icoWork.Count) | |
| { | |
| // if a filter happend update icon amount value with actual | |
| iconWriter.Write(BitConverter.GetBytes(icocount), 4,2); // TEST!!!!! | |
| } | |
| if (inew) iconWriter.Flush(); // flush buffer to a file | |
| // Cleanup | |
| iconWriter.Close(); | |
| iconWriter.Dispose(); | |
| outputStream.Close(); | |
| outputStream.Dispose(); | |
| if (!inew) File.Delete(icofile); | |
| else Console.WriteLine($"Icon '{icofile}' successfully exported."); | |
| //else Console.WriteLine(string.Format("Icon '{0}' successfully exported.",icofile)); | |
| } // split mode | |
| } // groups | |
| iconInfo.ForEach(i => i.Dispose()); // cleanup | |
| return true; | |
| } // ExportIcons | |
| // Icon helpers | |
| private static List<IconReader> ImportIco(string iconFilePath, int[]? byIndex, int[]? bySize, bool rawData=true) | |
| { | |
| byte[] bytes; | |
| try {bytes = File.ReadAllBytes(iconFilePath);} catch | |
| { | |
| throw new ArgumentException($"Unable to read '{iconFilePath}' file."); | |
| } | |
| // Basic validation | |
| if (bytes.Length < 6 || BitConverter.ToInt32(bytes[..4],0) != 0x10000) | |
| throw new ArgumentException($"'{iconFilePath}' is not a valid ICO file."); | |
| var icons = new List<IconReader>(); | |
| int dirend = 16 * BitConverter.ToInt16(bytes[4..6],0); // dirlen * amount | |
| for (int i = 6, k = 0; i < dirend; i += 16, k++) | |
| { | |
| if (byIndex != null && !Array.Exists(byIndex, el => el >= 0 && el == k)) continue; | |
| var direntry = bytes[i..(i + 16)]; // raw icon resource | |
| /* No advantages over regular byte indexing | |
| GCHandle enthandle = GCHandle.Alloc(direntry, GCHandleType.Pinned); | |
| var entry = Marshal.PtrToStructure<ICONDIRENTRY>(enthandle.AddrOfPinnedObject()); | |
| enthandle.Free();*/ | |
| int w = direntry[0] == 0 ? 256 : direntry[0]; | |
| int h = direntry[1] == 0 ? 256 : direntry[1]; | |
| if (bySize != null && !Array.Exists(bySize, el => el >= 0 && el == w)) continue; | |
| var l = (int)BitConverter.ToUInt32(direntry[8..12],0); | |
| var bpp = BitConverter.ToUInt16(direntry[6..8],0); | |
| var col = direntry[2] == 0 && bpp == 8 ? 256 : direntry[2]; | |
| var dataoffset = (int)BitConverter.ToUInt32(direntry[12..16],0); | |
| var imgbytes = bytes[dataoffset .. (dataoffset + l)]; | |
| var iconHandle = CreateIconFromResourceEx(imgbytes,l,true,0x00030000,w,h,0); | |
| var info = new IconReader(0,"", k,(k+1).ToString(), w,h,bpp,col,l, iconHandle,rawData); | |
| icons.Add(info); | |
| } | |
| return icons; | |
| } // ImportIco | |
| private static bool ExportImages(List<IconReader> iconInfo, string outputPath, string type, int[]? bySize=null, int[]? byIndex=null, Color? background=null) | |
| { | |
| string[] imgType = {"png","jpeg","tiff","gif","bmp","emf","exif","webp","heif"}; // available formats | |
| type = type.ToLower(); | |
| if (type == "jpg") type = "jpeg"; | |
| else if (type == "tif") type = "tiff"; | |
| if (!Array.Exists(imgType, el => el == type)) return false; // input validation | |
| imgType = new[] {"jpeg","bmp","gif"}; // no alpha channel list | |
| if (background is null) background = Color.Transparent; | |
| if (Array.Exists(imgType, el => el == type) && background == Color.Transparent) background = Color.White; | |
| foreach (var icon in iconInfo) | |
| { | |
| if (icon.IconData is null || icon.IconData.GetType() != typeof(IconReader)) | |
| icon.IconData = Icon.FromHandle(icon.Handle); | |
| if (icon.IconData is null) continue; // test for image data | |
| var ii = icon.GroupIndex; | |
| // cleanup | |
| if (byIndex != null && byIndex.Length != 0 && !Array.Exists(byIndex, el => el == ii)) continue; | |
| if (bySize != null && bySize.Length != 0 && !Array.Exists(bySize, el => el == icon.Width)) continue; | |
| // resolve file name | |
| var icofile = outputPath.Replace("%index%",string.Format("{0}%sz%%color%",ii)); | |
| icofile = Regex.Replace(icofile,@"\.ico$",@"."+type); | |
| if (icon.Colors == 0) | |
| icofile = icofile.Replace("%sz%%color%",string.Format("-{0}",icon.Width)); | |
| else | |
| icofile = icofile.Replace("%sz%%color%",string.Format("-{0}-{1}",icon.Width,icon.Colors)); | |
| Icon oIcon = (Icon)icon.IconData; | |
| Bitmap bitmap = oIcon.ToBitmap(); | |
| // Replace black (primarily) or any background | |
| if (Array.Exists(imgType, el => el == type) && background != Color.Transparent) | |
| { | |
| int w = icon.Width; | |
| int h = icon.Height; | |
| var destRect = new Rectangle(0,0,w,h); | |
| Bitmap destImage = new Bitmap(w,h); //,PixelFormat.Format32bppPArgb | |
| destImage.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution); | |
| /////destImage.MakeTransparent(); | |
| var graphics = Graphics.FromImage(destImage); | |
| graphics.Clear((Color)background); | |
| graphics.CompositingMode = CompositingMode.SourceOver; | |
| graphics.CompositingQuality = CompositingQuality.HighQuality; | |
| graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; | |
| graphics.SmoothingMode = SmoothingMode.HighQuality; | |
| graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; | |
| var wrapMode = new ImageAttributes(); | |
| wrapMode.SetWrapMode(WrapMode.TileFlipXY); | |
| graphics.DrawImage(bitmap, destRect, 0, 0, w, h, GraphicsUnit.Pixel, wrapMode); | |
| wrapMode.Dispose(); | |
| graphics.Dispose(); | |
| bitmap = destImage; | |
| } | |
| var ifmt = (ImageFormat)typeof(ImageFormat) | |
| .GetProperty(type, BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase) | |
| .GetValue(type,null); | |
| try { | |
| bitmap.Save(icofile,ifmt); | |
| } finally { | |
| bitmap.Dispose(); | |
| } | |
| } | |
| iconInfo.ForEach(i => i.Dispose()); // cleanup | |
| return true; | |
| } // ExportImages | |
| private static void ExtractIconGroupEntries(IntPtr module, IntPtr name, IntPtr type, int index, Dictionary<ushort, GRPICONDIRENTRY> entries, Dictionary<ushort, int> groupIndices, Dictionary<ushort, string> groupIds) | |
| { | |
| var handle = FindResource(module, name, type); | |
| if (handle == IntPtr.Zero) return; | |
| var resource = LoadResource(module, handle); | |
| if (resource == IntPtr.Zero) return; | |
| var ptr = LockResource(resource); | |
| if (ptr == IntPtr.Zero) return; | |
| // GRPICONDIR | |
| ptr += 2; // idReserved | |
| var idtype = Marshal.ReadInt16(ptr); | |
| if (idtype != 1) return; // idType: 1 - ICO, 2 - CUR | |
| var entrySize = Marshal.SizeOf<GRPICONDIRENTRY>(); | |
| ptr += 2; | |
| var count = Marshal.ReadInt16(ptr); | |
| ptr += 2; | |
| for (var i = 0; i < count; i++) | |
| { | |
| // restore ICONDIR | |
| var entry = Marshal.PtrToStructure<GRPICONDIRENTRY>(ptr); | |
| ptr += entrySize; | |
| entries[entry.nId] = entry; | |
| // GroupId, is it a string or an id? | |
| groupIndices[entry.nId] = index; | |
| if (name.ToInt64() > ushort.MaxValue) | |
| { | |
| groupIds[entry.nId] = Marshal.PtrToStringAuto(name); | |
| } | |
| else | |
| { | |
| groupIds[entry.nId] = "#" + name.ToInt32(); | |
| } | |
| } | |
| } // ExtractIconGroupEntries | |
| private static byte[]? GetImageBytes(IntPtr module, IntPtr name, IntPtr type) | |
| { | |
| // https://devblogs.microsoft.com/oldnewthing/?p=7083 | |
| var hres = FindResource(module, name, type); | |
| if (hres == IntPtr.Zero) return null; | |
| var size = SizeofResource(module, hres); | |
| if (size == 0) return null; | |
| var res = LoadResource(module, hres); | |
| if (res == IntPtr.Zero) return null; | |
| var ptr = LockResource(res); | |
| if (ptr == IntPtr.Zero) return null; | |
| byte[] imgbytes = new byte[size]; | |
| Marshal.Copy(ptr, imgbytes, 0, size); | |
| return imgbytes; | |
| } // GetImageBytes | |
| private static string? ResolveInputURI(string iconFilePath) // experimental | |
| { | |
| if (string.IsNullOrEmpty(iconFilePath)) return null; | |
| if (!Regex.IsMatch(iconFilePath,@"^https?://",RegexOptions.IgnoreCase)) return iconFilePath; | |
| // TODO: resolve aliases | |
| var target = Path.GetTempPath() + Path.GetFileName(iconFilePath); | |
| try | |
| { | |
| using (var webclient = new HttpClient()) | |
| { | |
| webclient.Timeout = TimeSpan.FromMilliseconds(4500); // can parameterize | |
| using (var response = webclient.GetStreamAsync(iconFilePath)) | |
| { | |
| using (var fs = new FileStream(target, FileMode.OpenOrCreate)) | |
| { | |
| response.Result.CopyTo(fs); | |
| //if (!File.Exists(target)) target = null; | |
| } | |
| } | |
| } | |
| } catch {target = null;} //if (File.Exists(target)) File.Delete(target); | |
| return target; | |
| } // ResolveInputURI | |
| // Generic helpers | |
| private delegate bool EnumResNameProc(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam); | |
| [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] | |
| private static extern bool EnumResourceNames(IntPtr hModule, IntPtr lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam); | |
| [DllImport("kernel32", CharSet = CharSet.Unicode)] | |
| private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType); | |
| [DllImport("kernel32")] | |
| private static extern int SizeofResource(IntPtr hModule, IntPtr hResInfo); | |
| [DllImport("kernel32")] | |
| private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo); | |
| [DllImport("user32")] | |
| private static extern IntPtr CreateIconFromResourceEx(IntPtr presbits, int dwResSize, bool fIcon, int dwVer, int cxDesired, int cyDesired, int flags); | |
| [DllImport("user32")] | |
| private static extern IntPtr CreateIconFromResourceEx(byte[] presbits, int dwResSize, bool fIcon, int dwVer, int cxDesired, int cyDesired, int flags); | |
| [DllImport("user32")] | |
| private static extern bool DestroyIcon(IntPtr handle); | |
| [DllImport("kernel32", CharSet = CharSet.Unicode)] | |
| private static extern IntPtr LockResource(IntPtr hResData); | |
| [DllImport("kernel32", CharSet = CharSet.Unicode)] | |
| private static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, int dwFlags); | |
| [DllImport("kernel32")] | |
| private static extern bool FreeLibrary(IntPtr hModule); | |
| private const int LOAD_LIBRARY_AS_DATAFILE = 0x2; | |
| private const int LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20; | |
| private const int RT_ICON = 3; | |
| private const int RT_GROUP_ICON = RT_ICON + 11; | |
| [StructLayout(LayoutKind.Sequential, Pack = 1)] | |
| private struct GRPICONDIRENTRY | |
| { | |
| public byte bWidth; | |
| public byte bHeight; | |
| public byte bColorCount; | |
| public byte bReserved; | |
| public short wPlanes; | |
| public short wBitCount; | |
| public int dwBytesInRes; | |
| public ushort nId; | |
| } | |
| /*[StructLayout(LayoutKind.Sequential, Pack = 1)] | |
| private struct ICONDIRENTRY | |
| { | |
| public byte bWidth; | |
| public byte bHeight; | |
| public byte bColorCount; | |
| public byte bReserved; | |
| public short wPlanes; | |
| public short wBitCount; | |
| public int dwBytesInRes; | |
| public int dwImageOffset; | |
| }*/ | |
| } // IconReader |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SYNOPSIS : Icon Reader for PowerShell | |
| # DESCRIPTION : A utility class that extracts the list of all icons handles (all sizes, all color counts) for a binary file like DLL, EXE, or ICO | |
| # UTILITIES : GetTotal, ImportIcons, ExportIcons, Dispose | |
| # FEATURES : Export: by size and index, multisize icons, split icon groups by size | |
| # VERSION : 4.0 | |
| # LASTUPDATE : 2026-Sep-5 | |
| # LINK : https://en.wikipedia.org/wiki/ICO_(file_format) | |
| # NOTES : Only builtin sizes are exported; GIF,BMP,JPEG always have the background in black | |
| # BACKLOG : | |
| $IconReader = @" | |
| // .NET Framework Edition | |
| using System; | |
| using System.IO; | |
| using System.Runtime.InteropServices; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Globalization; | |
| using System.Text.RegularExpressions; | |
| using System.Linq; | |
| using System.Drawing; | |
| using System.Drawing.Imaging; | |
| using System.Drawing.Drawing2D; | |
| using System.Reflection; | |
| using System.Net; | |
| using System.Net.Http; | |
| public sealed class IconReader : IDisposable | |
| { | |
| public IntPtr Handle { get; private set; } // primary ID for icon | |
| public int Index { get; private set; } // global index, inc multi sizing | |
| public int GroupIndex { get; private set; } // EXE,DLL - group (multi-size icon) index; ICO - 0 | |
| public string Id { get; private set; } // primarily for EXE,DLL | |
| public string GroupId { get; private set; } // primarily for EXE,DLL | |
| public int Width { get; private set; } | |
| public int Height { get; private set; } | |
| public int Bpp { get; private set; } | |
| public int Colors { get; private set; } | |
| public int ImgLength { get; private set; } | |
| public object IconData { get; set; } // icon image/object storage; by default contains image bytes | |
| private IconReader(int groupIndex, string groupId, int index, string id, int width, int height, int bpp, int colors, int len, IntPtr handle, object imgData=null, bool rawData=true) | |
| { | |
| GroupIndex = groupIndex; | |
| GroupId = groupId; | |
| Index = index; | |
| Id = id; | |
| Width = width; // width == 0 ? 256 : width; | |
| Height = height; // height == 0 ? 256 : height; | |
| Bpp = bpp; | |
| Colors = colors; | |
| ImgLength = len; | |
| Handle = handle; | |
| IconData = rawData ? imgData : Icon.FromHandle(handle); | |
| } | |
| // Utilities | |
| //public override string ToString() => Index.ToString(); | |
| public void Dispose() | |
| { | |
| if (IconData != null && IconData.GetType() == typeof(Icon)) ((Icon)IconData).Dispose(); | |
| DestroyIcon(Handle); | |
| } | |
| // DLL,EXE: the total doesn't take into account multi sizing | |
| public static int GetTotal(string iconFilePath) | |
| { | |
| iconFilePath = ResolveInputURI(iconFilePath); | |
| if (string.IsNullOrEmpty(iconFilePath)) return 0; // or ArgumentNullException? | |
| if (Path.GetExtension(iconFilePath).ToLower() == ".ico") | |
| { | |
| FileStream stream; | |
| try {stream = File.Open(iconFilePath, FileMode.Open);} catch {return 0;} | |
| var reader = new BinaryReader(stream, System.Text.Encoding.UTF8, false); | |
| byte[] bytes = reader.ReadBytes(6); // 4+2 | |
| stream.Close(); | |
| stream.Dispose(); | |
| reader.Close(); | |
| reader.Dispose(); | |
| if (bytes.Length < 6) | |
| throw new ArgumentException("'"+iconFilePath+"' is not a valid ICO file."); | |
| var signature = new byte[4]; | |
| var total = new byte[2]; | |
| Array.Copy(bytes,0, signature,0,4); | |
| Array.Copy(bytes,4, total,0,2); | |
| if (BitConverter.ToInt32(signature,0) != 0x10000) // Basic validation | |
| throw new ArgumentException("'"+iconFilePath+"' is not a valid ICO file."); | |
| return BitConverter.ToInt16(total,0); | |
| } | |
| else // dll or exe | |
| { // version 2, uniform; version 1 is simpler and faster | |
| int icount = 0; | |
| var handle = LoadLibraryEx(iconFilePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); | |
| if (handle != IntPtr.Zero) | |
| { | |
| EnumResourceNames(handle, new IntPtr(RT_GROUP_ICON), (m, t, n, lp) => { icount++; return true; }, IntPtr.Zero); | |
| FreeLibrary(handle); | |
| } | |
| return icount; | |
| } | |
| } // GetTotal | |
| public static List<IconReader> ImportIcons(string iconFilePath, int[] byIndex=null, int[] bySize=null, bool rawData=true) | |
| { | |
| iconFilePath = ResolveInputURI(iconFilePath); | |
| if (string.IsNullOrEmpty(iconFilePath)) | |
| throw new ArgumentNullException("Filename is empty."); | |
| // Validate input filters | |
| if (bySize != null && bySize.Length > 0) | |
| { | |
| bySize = bySize.Where(n => n > 15 && n < 257).ToArray(); | |
| if (bySize.Length == 0) bySize = new[]{-1000}; // stop value | |
| } | |
| else bySize = null; | |
| if (byIndex != null && byIndex.Length > 0) | |
| { | |
| byIndex = byIndex.Where(n => n > -1).ToArray(); | |
| if (byIndex.Length == 0) byIndex = new[]{-1000}; // stop value | |
| } | |
| else byIndex = null; | |
| if (Path.GetExtension(iconFilePath).ToLower() == ".ico") | |
| return ImportIco(iconFilePath, byIndex, bySize, rawData); | |
| var icons = new List<IconReader>(); | |
| var handle = LoadLibraryEx(iconFilePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); | |
| if (handle != IntPtr.Zero) | |
| { | |
| try | |
| { | |
| icons = ImportIcons(handle, byIndex, bySize, rawData); | |
| } | |
| finally | |
| { | |
| FreeLibrary(handle); | |
| } | |
| } | |
| return icons; | |
| } // ImportIcons | |
| public static bool ExportIcons(string iconFilePath=null, List<IconReader> iconInfo=null, string outputPath=null, int[] bySize=null, int[] byIndex=null, bool split=false, string type="ico", Color? background=null) | |
| { | |
| // Resolve parameter sets | |
| iconFilePath = ResolveInputURI(iconFilePath); | |
| var ii = iconInfo == null || iconInfo.Count == 0; | |
| if (string.IsNullOrEmpty(iconFilePath) && ii) | |
| throw new ArgumentNullException("Filename and icon collection are empty."); | |
| if (string.IsNullOrEmpty(iconFilePath) && string.IsNullOrEmpty(outputPath)) return false; | |
| try { iconFilePath = Path.GetFullPath(iconFilePath); } catch { return false; } | |
| var src = Path.GetExtension(iconFilePath).ToLower().Substring(1); // exe,dll,ico | |
| if (src == "ico" && !split && (bySize == null || bySize.Length == 0)) split = true; | |
| if (string.IsNullOrEmpty(type)) type = "ico"; | |
| //var raw = type.ToLower() == "ico"; | |
| if (ii) iconInfo = ImportIcons(iconFilePath,byIndex,bySize); //,raw | |
| if (iconInfo == null || iconInfo.Count == 0) return false; | |
| // Create output file name template | |
| var fn = Path.GetFileNameWithoutExtension(iconFilePath); | |
| if (string.IsNullOrEmpty(outputPath)) | |
| { | |
| var sfx = src == "ico" ? "-extract-%index%.ico" : "-%index%.ico"; | |
| outputPath = string.Format(@"{0}\{1}{2}", Path.GetDirectoryName(iconFilePath),fn,sfx); | |
| } | |
| else | |
| { | |
| try { outputPath = Path.GetFullPath(outputPath); } catch { return false; } | |
| outputPath = string.Format(@"{0}\{1}-%index%.ico", outputPath,fn); | |
| } | |
| if (Regex.IsMatch(outputPath,@"c:\\(windows.+|program.+|[^\\]+)$",RegexOptions.IgnoreCase)) | |
| Console.WriteLine("WARNING: System location specified as output. Export may fail. Make sure that you have write access."); | |
| // Validate input filters | |
| if (bySize != null && bySize.Length > 0) | |
| bySize = bySize.Where(n => n > 15 && n < 257).OrderByDescending(n => n).ToArray(); | |
| else bySize = null; | |
| if (byIndex != null && byIndex.Length > 0) | |
| byIndex = byIndex.Where(n => n > -1).ToArray(); | |
| else byIndex = null; | |
| // Other formats | |
| if (type.ToLower() != "ico") return ExportImages(iconInfo,outputPath,type,bySize,byIndex,background); | |
| // Group by GroupIndex first | |
| var igroups = iconInfo.Where(i => i.IconData != null).GroupBy(i => i.GroupIndex).OrderBy(i => i.Key); | |
| foreach (var gr in igroups) | |
| { | |
| if (byIndex != null && !Array.Exists(byIndex, el => el == gr.Key)) continue; // filter | |
| var iconGroup = gr.ToList(); // select a group | |
| var series = split ? iconGroup.Count : 1; // mode selector | |
| for (var s=0; s < series; s++) | |
| { | |
| // select the next chunk to work with | |
| var icoWork = split ? new List<IconReader>{iconGroup[s]} : iconGroup; | |
| // resolve output file name | |
| // PROBLEM: size may be wrong because of filtering (below); TEST!!! | |
| var icofile = outputPath.Replace("%index%",string.Format("{0}%sz%%color%",gr.Key)); | |
| if (split) { | |
| if (icoWork[0].Colors == 0) | |
| icofile = icofile.Replace("%sz%%color%",string.Format("-{0}",icoWork[0].Width)); | |
| else | |
| icofile = icofile.Replace("%sz%%color%",string.Format("-{0}-{1}",icoWork[0].Width,icoWork[0].Colors)); | |
| } | |
| else | |
| icofile = icofile.Replace("%sz%%color%",""); | |
| FileStream outputStream; | |
| try {outputStream = new FileStream(icofile, FileMode.OpenOrCreate);} | |
| catch {continue;} | |
| if (outputStream == null) continue; | |
| var iconWriter = new BinaryWriter(outputStream); | |
| if (iconWriter == null) | |
| { | |
| outputStream.Close(); | |
| outputStream.Dispose(); | |
| continue; | |
| } | |
| bool inew = false; // no icons to export | |
| // 0-1 reserved, 0 | |
| iconWriter.Write((byte)0); | |
| iconWriter.Write((byte)0); | |
| // 2-3 image type, 1 = icon, 2 = cursor | |
| iconWriter.Write((short)1); | |
| // 4-5 number of images | |
| iconWriter.Write((short)icoWork.Count); | |
| //var natives = icoWork.Select(n => n.Width).Distinct(); // builtin sizes | |
| int offset = 6 + (16 * icoWork.Count); | |
| short icocount = 0; | |
| foreach (var icon in icoWork) | |
| { | |
| if (bySize != null && !Array.Exists(bySize, el => el == icon.Width)) continue; // filter | |
| var w = icon.Width == 256 ? 0 : icon.Width; | |
| var h = icon.Height == 256 ? 0 : icon.Height; | |
| // image entry | |
| // 0 image width | |
| iconWriter.Write((byte)w); | |
| // 1 image height | |
| iconWriter.Write((byte)h); | |
| // 2 number of colors | |
| iconWriter.Write((byte)0); | |
| // 3 reserved | |
| iconWriter.Write((byte)0); | |
| // 4-5 color planes | |
| iconWriter.Write((short)0); | |
| // 6-7 bits per pixel | |
| iconWriter.Write((short)32); | |
| // 8-11 size of image data | |
| iconWriter.Write((int)icon.ImgLength); | |
| // 12-15 offset of image data | |
| iconWriter.Write((int)offset); | |
| offset += icon.ImgLength; | |
| inew = true; | |
| icocount++; | |
| } | |
| foreach (var icon in icoWork) | |
| { | |
| // image data | |
| // png data must contain the whole png data file | |
| if (bySize == null || Array.Exists(bySize, el => el == icon.Width)) | |
| iconWriter.Write(icon.IconData as byte[]); | |
| } | |
| if (inew && icocount != icoWork.Count) | |
| { | |
| // if a filter happend update icon amount value with actual | |
| iconWriter.Write(BitConverter.GetBytes(icocount), 4,2); // TEST!!!!! | |
| } | |
| if (inew) iconWriter.Flush(); // flush buffer to a file | |
| // Cleanup | |
| iconWriter.Close(); | |
| iconWriter.Dispose(); | |
| outputStream.Close(); | |
| outputStream.Dispose(); | |
| if (!inew) File.Delete(icofile); | |
| else Console.WriteLine(string.Format("Icon '{0}' successfully exported.",icofile)); | |
| } // split mode | |
| } // groups | |
| iconInfo.ForEach(i => i.Dispose()); // cleanup | |
| return true; | |
| } // ExportIcons | |
| // Icon helpers | |
| private static List<IconReader> ImportIcons(IntPtr handle, int[] byIndex=null, int[] bySize=null, bool rawData=true) | |
| { | |
| var icons = new List<IconReader>(); | |
| var entries = new Dictionary<ushort, GRPICONDIRENTRY>(); | |
| var groupIndices = new Dictionary<ushort, int>(); | |
| var groupIds = new Dictionary<ushort, string>(); | |
| var groupIndex = 0; | |
| if (EnumResourceNames(handle, new IntPtr(RT_GROUP_ICON), (m, t, n, lp) => | |
| { | |
| if (byIndex != null && !Array.Exists(byIndex, el => el > -1 && el == groupIndex)) //el.GetType() == typeof(int) && | |
| { | |
| groupIndex++; | |
| return true; | |
| } | |
| /* | |
| // experimental; not very useful option; | |
| // will require to replace all corresponding "byIndex" parameters with dynamic[] type and type checking | |
| string name; | |
| if (n.ToInt64() > ushort.MaxValue) | |
| name = Marshal.PtrToStringAuto(n); // alfabet string | |
| else | |
| name = n.ToInt32().ToString(CultureInfo.InvariantCulture); // decimal string | |
| if (byIndex != null && !Array.Exists(byIndex, el => el.GetType() == typeof(string) && string.Equals(el, name, StringComparison.OrdinalIgnoreCase))) | |
| { | |
| groupIndex++; | |
| return true; | |
| }*/ | |
| try | |
| { | |
| ExtractIconGroupEntries(handle, n, t, groupIndex, entries, groupIndices, groupIds); | |
| groupIndex++; | |
| } | |
| catch | |
| { | |
| // do nothing | |
| } | |
| return true; | |
| }, IntPtr.Zero)) | |
| { | |
| EnumResourceNames(handle, new IntPtr(RT_ICON), (m, t, n, lp) => | |
| { | |
| GRPICONDIRENTRY x; | |
| if (!entries.TryGetValue((ushort)n.ToInt32(), out x)) return true; | |
| var imgbytes = GetImageBytes(handle, n, t); | |
| if (imgbytes == null) return true; | |
| var iconHandle = CreateIconFromResourceEx(imgbytes, imgbytes.Length, true, 0x00030000,0,0,0); | |
| if (iconHandle != IntPtr.Zero) | |
| { | |
| var id = (ushort)n.ToInt32(); | |
| //var ent = entries[id]; // ICONDIR | |
| var w = entries[id].bWidth == 0 ? 256 : entries[id].bWidth; | |
| var h = entries[id].bHeight == 0 ? 256 : entries[id].bHeight; | |
| var l = entries[id].dwBytesInRes; | |
| var bpp = entries[id].wBitCount; | |
| int colors = entries[id].bColorCount; | |
| if (colors == 0 && bpp == 8) colors = 256; | |
| if (bySize == null || Array.Exists(bySize, el => el == w)) | |
| { | |
| var info = new IconReader(groupIndices[id], groupIds[id], n.ToInt32() - 1, n.ToString(), w,h,bpp,colors,l, iconHandle,imgbytes,rawData); | |
| icons.Add(info); | |
| } | |
| } | |
| return true; | |
| }, IntPtr.Zero); | |
| } | |
| return icons; | |
| } // ImportIcons | |
| private static List<IconReader> ImportIco(string iconFilePath, int[] byIndex, int[] bySize, bool rawData) | |
| { | |
| byte[] bytes; | |
| try {bytes = File.ReadAllBytes(iconFilePath);} catch { | |
| throw new ArgumentException("Unable to read '"+iconFilePath+"' file."); | |
| } | |
| if (bytes.Length < 6) // corrupted | |
| throw new ArgumentException("'"+iconFilePath+"' is not a valid ICO file."); | |
| var signature = new byte[4]; | |
| Array.Copy(bytes,0, signature,0,4); | |
| if (BitConverter.ToInt32(signature,0) != 0x10000) // basic validation | |
| throw new ArgumentException("'"+iconFilePath+"' is not a valid ICO file."); | |
| var icons = new List<IconReader>(); | |
| var direntry = new byte[16]; | |
| var total = new byte[2]; | |
| Array.Copy(bytes,4, total,0,2); | |
| int dirend = 16 * BitConverter.ToInt16(total,0); // dirlen * amount | |
| ICONDIRENTRY entry; | |
| for (int i = 6, k = 0; i < dirend; i += 16, k++) | |
| { | |
| if (byIndex != null && !Array.Exists(byIndex, el => el >= 0 && el == k)) continue; | |
| Array.Copy(bytes,i, direntry,0,16); | |
| GCHandle enthandle = GCHandle.Alloc(direntry, GCHandleType.Pinned); | |
| try { // get icon resource structure | |
| entry = Marshal.PtrToStructure<ICONDIRENTRY>(enthandle.AddrOfPinnedObject()); | |
| } catch {continue;} | |
| finally {enthandle.Free();} | |
| var w = entry.bWidth == 0 ? 256 : entry.bWidth; | |
| var h = entry.bHeight == 0 ? 256 : entry.bHeight; | |
| if (bySize != null && !Array.Exists(bySize, el => el >= 0 && el == w)) continue; | |
| var bpp = entry.wBitCount; | |
| var col = entry.bColorCount == 0 && bpp == 8 ? 256 : entry.bColorCount; | |
| var l = entry.dwBytesInRes; | |
| var imgbytes = new byte[l]; | |
| Array.Copy(bytes,entry.dwImageOffset, imgbytes,0,l); | |
| //overhead - var imgbytes = bytes.Skip(entry.dwImageOffset-1).Take(l).ToArray(); | |
| var iconHandle = CreateIconFromResourceEx(imgbytes,l,true,0x00030000,w,h,0); | |
| var info = new IconReader(0,"",k,(k+1).ToString(), w,h,bpp,col,l, iconHandle,imgbytes,rawData); | |
| icons.Add(info); | |
| } | |
| return icons; | |
| } // ImportIco | |
| private static bool ExportImages(List<IconReader> iconInfo, string outputPath, string type, int[] bySize=null, int[] byIndex=null, Color? background=null) | |
| { | |
| string[] imgType = {"png","jpeg","tiff","gif","bmp","emf","exif"}; // available formats | |
| type = type.ToLower(); | |
| if (type == "jpg") type = "jpeg"; | |
| else if (type == "tif") type = "tiff"; | |
| if (!Array.Exists(imgType, el => el == type)) return false; // input validation | |
| imgType = new[] {"jpeg","bmp","gif"}; // no alpha channel list | |
| if (background == null) background = Color.Transparent; | |
| if (Array.Exists(imgType, el => el == type) && background == Color.Transparent) background = Color.White; | |
| foreach (var icon in iconInfo) | |
| { | |
| if (icon.IconData == null || icon.IconData.GetType() != typeof(IconReader)) | |
| icon.IconData = Icon.FromHandle(icon.Handle); | |
| if (icon.IconData == null) continue; // test for image data | |
| var ii = icon.GroupIndex; | |
| // filters | |
| if (byIndex != null && byIndex.Length != 0 && !Array.Exists(byIndex, el => el == ii)) continue; | |
| if (bySize != null && bySize.Length != 0 && !Array.Exists(bySize, el => el == icon.Width)) continue; | |
| // resolve file name | |
| var icofile = outputPath.Replace("%index%",string.Format("{0}%sz%%color%",ii)); | |
| icofile = Regex.Replace(icofile,@"\.ico$",@"."+type); | |
| if (icon.Colors == 0) | |
| icofile = icofile.Replace("%sz%%color%",string.Format("-{0}",icon.Width)); | |
| else | |
| icofile = icofile.Replace("%sz%%color%",string.Format("-{0}-{1}",icon.Width,icon.Colors)); | |
| Icon oIcon = (Icon)icon.IconData; | |
| Bitmap bitmap = oIcon.ToBitmap(); | |
| // Replace black (primarily) or any background | |
| if (Array.Exists(imgType, el => el == type) && background != Color.Transparent) | |
| { | |
| int w = icon.Width; | |
| int h = icon.Height; | |
| var destRect = new Rectangle(0,0,w,h); | |
| Bitmap destImage = new Bitmap(w,h); //,PixelFormat.Format32bppPArgb | |
| destImage.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution); | |
| /////destImage.MakeTransparent(); | |
| var graphics = Graphics.FromImage(destImage); | |
| graphics.Clear((Color)background); | |
| graphics.CompositingMode = CompositingMode.SourceOver; | |
| graphics.CompositingQuality = CompositingQuality.HighQuality; | |
| graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; | |
| graphics.SmoothingMode = SmoothingMode.HighQuality; | |
| graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; | |
| var wrapMode = new ImageAttributes(); | |
| wrapMode.SetWrapMode(WrapMode.TileFlipXY); | |
| graphics.DrawImage(bitmap, destRect, 0, 0, w, h, GraphicsUnit.Pixel, wrapMode); | |
| wrapMode.Dispose(); | |
| graphics.Dispose(); | |
| bitmap = destImage; | |
| } | |
| var ifmt = (ImageFormat)typeof(ImageFormat) | |
| .GetProperty(type, BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase) | |
| .GetValue(type,null); | |
| try { | |
| bitmap.Save(icofile,ifmt); | |
| } finally { | |
| bitmap.Dispose(); | |
| } | |
| } | |
| iconInfo.ForEach(i => i.Dispose()); // cleanup | |
| return true; | |
| } // ExportImages | |
| private static void ExtractIconGroupEntries(IntPtr module, IntPtr name, IntPtr type, int index, Dictionary<ushort, GRPICONDIRENTRY> entries, Dictionary<ushort, int> groupIndices, Dictionary<ushort, string> groupIds) | |
| { | |
| var handle = FindResource(module, name, type); | |
| if (handle == IntPtr.Zero) return; | |
| var resource = LoadResource(module, handle); | |
| if (resource == IntPtr.Zero) return; | |
| var ptr = LockResource(resource); | |
| if (ptr == IntPtr.Zero) return; | |
| // GRPICONDIR | |
| ptr += 2; // idReserved | |
| var idtype = Marshal.ReadInt16(ptr); | |
| if (idtype != 1) return; // idType: 1 - ICO, 2 - CUR | |
| var entrySize = Marshal.SizeOf<GRPICONDIRENTRY>(); | |
| ptr += 2; | |
| var count = Marshal.ReadInt16(ptr); | |
| ptr += 2; | |
| for (var i = 0; i < count; i++) | |
| { | |
| // restore ICONDIR | |
| var entry = Marshal.PtrToStructure<GRPICONDIRENTRY>(ptr); | |
| ptr += entrySize; | |
| entries[entry.nId] = entry; | |
| // GroupId, is it a string or an id? | |
| groupIndices[entry.nId] = index; | |
| if (name.ToInt64() > ushort.MaxValue) | |
| { | |
| groupIds[entry.nId] = Marshal.PtrToStringAuto(name); | |
| } | |
| else | |
| { | |
| groupIds[entry.nId] = "#" + name.ToInt32(); | |
| } | |
| } | |
| } // ExtractIconGroupEntries | |
| private static byte[] GetImageBytes(IntPtr module, IntPtr name, IntPtr type) | |
| { | |
| // https://devblogs.microsoft.com/oldnewthing/?p=7083 | |
| var hres = FindResource(module, name, type); | |
| if (hres == IntPtr.Zero) return null; | |
| var size = SizeofResource(module, hres); | |
| if (size == 0) return null; | |
| var res = LoadResource(module, hres); | |
| if (res == IntPtr.Zero) return null; | |
| var ptr = LockResource(res); | |
| if (ptr == IntPtr.Zero) return null; | |
| byte[] imgbytes = new byte[size]; | |
| Marshal.Copy(ptr, imgbytes, 0, size); | |
| return imgbytes; | |
| } // GetImageBytes | |
| private static string ResolveInputURI(string iconFilePath) // experimental | |
| { | |
| if (string.IsNullOrEmpty(iconFilePath)) return null; | |
| if (!Regex.IsMatch(iconFilePath,@"^https?://",RegexOptions.IgnoreCase)) | |
| { | |
| // resolve aliases; can add more | |
| switch (iconFilePath.ToLower()) | |
| { | |
| case "%shell32": return @"C:\Windows\System32\shell32.dll"; | |
| case "%imageres": return @"C:\Windows\System32\imageres.dll"; | |
| case "%ddo": return @"C:\Windows\System32\ddores.dll"; | |
| } | |
| return iconFilePath; | |
| } | |
| var target = Path.GetTempPath() + Path.GetFileName(iconFilePath); | |
| try | |
| { | |
| using (var webclient = new HttpClient()) | |
| { | |
| webclient.Timeout = TimeSpan.FromMilliseconds(4500); // can parameterize | |
| using (var response = webclient.GetStreamAsync(iconFilePath)) | |
| { | |
| using (var fs = new FileStream(target, FileMode.OpenOrCreate)) | |
| { | |
| response.Result.CopyTo(fs); | |
| //if (!File.Exists(target)) target = null; | |
| } | |
| } | |
| } | |
| } catch {target = null;} //if (File.Exists(target)) File.Delete(target); | |
| return target; | |
| } // ResolveInputURI | |
| // Generic helpers | |
| private delegate bool EnumResNameProc(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam); | |
| [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] | |
| private static extern bool EnumResourceNames(IntPtr hModule, IntPtr lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam); | |
| [DllImport("kernel32", CharSet = CharSet.Unicode)] | |
| private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType); | |
| [DllImport("kernel32")] | |
| private static extern int SizeofResource(IntPtr hModule, IntPtr hResInfo); | |
| [DllImport("kernel32")] | |
| private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo); | |
| [DllImport("user32")] | |
| private static extern IntPtr CreateIconFromResourceEx(IntPtr presbits, int dwResSize, bool fIcon, int dwVer, int cxDesired, int cyDesired, int flags); | |
| [DllImport("user32")] | |
| private static extern IntPtr CreateIconFromResourceEx(byte[] presbits, int dwResSize, bool fIcon, int dwVer, int cxDesired, int cyDesired, int flags); | |
| [DllImport("user32")] | |
| private static extern bool DestroyIcon(IntPtr handle); | |
| [DllImport("kernel32", CharSet = CharSet.Unicode)] | |
| private static extern IntPtr LockResource(IntPtr hResData); | |
| [DllImport("kernel32", CharSet = CharSet.Unicode)] | |
| private static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, int dwFlags); | |
| [DllImport("kernel32")] | |
| private static extern bool FreeLibrary(IntPtr hModule); | |
| private const int LOAD_LIBRARY_AS_DATAFILE = 0x2; | |
| private const int LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20; | |
| private const int RT_ICON = 3; | |
| private const int RT_GROUP_ICON = RT_ICON + 11; | |
| [StructLayout(LayoutKind.Sequential, Pack = 1)] | |
| private struct GRPICONDIRENTRY | |
| { | |
| public byte bWidth; | |
| public byte bHeight; | |
| public byte bColorCount; | |
| public byte bReserved; | |
| public short wPlanes; | |
| public short wBitCount; | |
| public int dwBytesInRes; | |
| public ushort nId; | |
| } | |
| [StructLayout(LayoutKind.Sequential, Pack = 1)] | |
| private struct ICONDIRENTRY | |
| { | |
| public byte bWidth; | |
| public byte bHeight; | |
| public byte bColorCount; | |
| public byte bReserved; | |
| public short wPlanes; | |
| public short wBitCount; | |
| public int dwBytesInRes; | |
| public int dwImageOffset; | |
| } | |
| } // END class IconReader | |
| "@ # IconReader | |
| # Compilation | |
| $loadlib = if ($PSVersionTable.PSEdition -eq 'Core') { 'System','System.Management.Automation','System.Collections','System.Linq','System.Text.RegularExpressions','System.Drawing.Common','System.Drawing.Primitives','System.Console','System.Net.Http' | |
| } else {'System.Drawing','System.Net.Http'} | |
| Add-Type -TypeDefinition $IconReader -ReferencedAssemblies $loadlib -IgnoreWarnings | |
| # Powershell session references | |
| Add-Type -AssemblyName System.Drawing |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ''' | |
| SYNOPSIS : Icon Reader for Win32 | |
| DESCRIPTION : A utility class that extracts the list of all icons handles (all sizes, all color counts) for a binary file like DLL, EXE, or ICO. | |
| UTILITIES : GetTotal, ImportIcons, ExportIcons | |
| FEATURES : Export: by size and index, multisize icons, split icon groups by size, various formats including ICO,PNG,JPG,WEBP,TIF; PIL for none-ICO images | |
| VERSION : 2.0 | |
| LASTUPDATE : 2026-Sep-6 | |
| STATUS : Demo as Python learning track; tested with 3.14.7 | |
| LINK : https://en.wikipedia.org/wiki/ICO_(file_format) | |
| NOTES : Only builtin sizes can be exported, th's no native way to resize | |
| BACKLOG : | |
| ISSUES : PIL sucks: bmp, no transparency, no antialising; gif, no antialising | |
| ''' | |
| import pathlib,struct,os,re | |
| import ctypes | |
| from ctypes import wintypes | |
| try: | |
| from PIL import Image # external package :-( | |
| pilpkg = True | |
| except ImportError: pilpkg = False | |
| USHORT_MAX = ctypes.c_uint16(-1).value # 65535 | |
| class IconReader: | |
| @property | |
| def Handle(self): return self.__handle # for .NET | |
| @property | |
| def Index(self): return self.__index | |
| @property | |
| def GroupIndex(self): return self.__groupIndex | |
| @property | |
| def Id(self): return self.__id | |
| @property | |
| def GroupId(self): return self.__groupId | |
| @property | |
| def Width(self): return self.__width | |
| @property | |
| def Height(self): return self.__height | |
| @property | |
| def Bpp(self): return self.__bpp | |
| @property | |
| def Colors(self): return self.__colors | |
| @property | |
| def ImgLength(self): return self.__imgLength | |
| def __init__(self,groupIndex,groupId,index,iid,width,height,bpp,colors,ilen,handle,imgdata=None): | |
| self.__handle = handle # pri dyn ID for icon in .NET; useless in Python | |
| self.__index = index # global index, inc multi sizing | |
| self.__groupIndex = groupIndex # EXE,DLL: icon group (multi-size icon) index; ICO: always 0 | |
| self.__id = iid # for EXE,DLL | |
| self.__groupId = groupId # for EXE,DLL | |
| self.__width = width # 256 if width == 0 else width | |
| self.__height = height # 256 if height == 0 else height | |
| self.__bpp = bpp | |
| self.__colors = colors | |
| self.__imgLength = ilen | |
| self.Icon = imgdata # icon image/object storage | |
| # Utilities | |
| @staticmethod | |
| def GetTotal(iconFilePath) -> int: | |
| if os.name != 'nt': return 0 | |
| iconFilePath = IconReader.__resolve_inputuri(iconFilePath) | |
| if iconFilePath == '' or iconFilePath is None: return 0 | |
| #if pathlib.Path(iconFilePath).exists() == False: return 0 | |
| # FileNotFoundError: No such file or directory | |
| class_ = IconReader # work around: surrogate "self" | |
| icount = 0 | |
| if pathlib.Path(iconFilePath).suffix.lower() == '.ico': | |
| try: | |
| with open(iconFilePath, 'rb') as f: | |
| data = f.read(6) | |
| if len(data) < 6: return 0 | |
| isignature,icount = struct.unpack(class_.__ICOHEADER, data[0:6]) | |
| if isignature != 0x10000: return 0 | |
| except: | |
| print('Error reading file.') | |
| return None | |
| else: | |
| handle = class_.__load_library(class_,iconFilePath) | |
| if handle != 0 and handle is not None: | |
| # enumerator callback | |
| def lpEnumFunc(m,t,n,lp): nonlocal icount; icount += 1; return True | |
| class_.__enum_resource_names(class_,handle,class_.__RT_GROUP_ICON,lpEnumFunc) | |
| class_.__kernel32.FreeLibrary(ctypes.c_ulong(handle)) | |
| return icount | |
| # END GetTotal | |
| @staticmethod | |
| def ImportIcons(iconFilePath, byIndex=[], bySize=[]): | |
| if os.name != 'nt': return None | |
| class_ = IconReader # work around: surrogate "self" | |
| iconFilePath = class_.__resolve_inputuri(iconFilePath) | |
| if iconFilePath == '' or iconFilePath is None: return None | |
| #if pathlib.Path(iconFilePath).exists() == False: return None | |
| if byIndex is not None and len(byIndex) > 0: | |
| byIndex = set([n for n in byIndex if type(n).__name__ == 'int' and n > -1]) # or type(n).__name__ == 'str' | |
| if len(byIndex) == 0: byIndex = [-1000] # stop value | |
| if bySize is not None and len(bySize) > 0: | |
| bySize = set([n for n in bySize if type(n).__name__ == 'int' and n > -1]) | |
| if len(bySize) == 0: bySize = [-1000] # stop value | |
| if pathlib.Path(iconFilePath).suffix.lower() == '.ico': | |
| return class_.__import_ico(class_, iconFilePath, byIndex, bySize) | |
| icons = [] # output | |
| handle = class_.__load_library(class_,iconFilePath) | |
| if handle != 0 and handle is not None: | |
| #icons = class_.__extract_icons(class_, handle, byIndex, bySize) | |
| #class_.__kernel32.FreeLibrary(ctypes.c_ulong(handle)) | |
| try: icons = class_.__extract_icons(class_, handle, byIndex, bySize) | |
| finally: class_.__kernel32.FreeLibrary(ctypes.c_ulong(handle)) | |
| return icons | |
| # END ImportIcons | |
| @staticmethod | |
| def ExportIcons(iconFilePath, iconInfo=None, outputPath=None, byIndex=[], bySize=[], split=False, itype='ico', background='#ffffff'): | |
| if os.name != 'nt': return False | |
| if iconInfo is not None and type(iconInfo).__name__ != 'IconReader': return False | |
| class_ = IconReader | |
| iconFilePath = class_.__resolve_inputuri(iconFilePath) | |
| if iconInfo is None and (iconFilePath == '' or iconFilePath is None): return False | |
| if (outputPath == '' or outputPath is None) and (iconFilePath == '' or iconFilePath is None): return False | |
| src = str(pathlib.Path(iconFilePath).resolve().suffix)[1:].lower() # exe,dll,ico | |
| if src == 'ico' and not split and (bySize is None or len(bySize) == 0): split = True | |
| if iconInfo is None or len(iconInfo) == 0: iconInfo = class_.ImportIcons(iconFilePath,byIndex,bySize) | |
| if iconInfo is None or len(iconInfo) == 0: return False | |
| # Resolve output file name and create a template | |
| if outputPath == '' or outputPath is None: | |
| sfx = r'-extract-%index%.ico' if src == 'ico' else r'-%index%.ico' | |
| outputPath = str(pathlib.Path(iconFilePath).resolve().with_suffix('')) + sfx | |
| else: | |
| fn = pathlib.Path(iconFilePath).resolve().stem | |
| outputPath = r'{0}\{1}-%index%.ico'.format(pathlib.Path(outputPath).resolve(), fn) | |
| if re.match(r'c:\\(windows.+|program.+|[^\\]+)$',outputPath,re.I) is not None: | |
| print('WARNING: System location specified as output. Export may fail. Make sure that you have write access.') | |
| # Validate input filters | |
| if byIndex is not None and len(byIndex) > 0: | |
| byIndex = sorted( | |
| list(set([n for n in byIndex if type(n).__name__ == 'int' and n > -1])), reverse=True | |
| ) | |
| if bySize is not None and len(bySize) > 0: | |
| bySize = sorted( | |
| list(set([n for n in bySize if type(n).__name__ == 'int' and n > 15 and n < 257])), reverse=True | |
| ) | |
| # experimental; png,jpg,... | |
| if itype.lower() != 'ico': | |
| return class_.__export_image(class_, iconInfo, outputPath, itype, byIndex, bySize,background) | |
| # Group by GroupIndex first; extract indices | |
| gi_list = set([n.GroupIndex for n in iconInfo if n.Icon is not None]) | |
| for i in gi_list: # foreach index of icon resource as a group of sizes | |
| if len(byIndex) != 0 and i not in byIndex: continue # filter | |
| iconGroup = [n for n in iconInfo if i == n.GroupIndex] # select a group by index | |
| series = len(iconGroup) if split else 1 # mode selector | |
| for s in range(series): | |
| icoWork = [iconGroup[s]] if split else iconGroup # select a chunk to work with | |
| # resolve output file name | |
| # PROBLEM: size may be wrong because of filtering (below); TEST!!! | |
| icofile = outputPath.replace('%index%',f'{str(i)}%sz%%color%') | |
| if split: | |
| if icoWork[0].Colors == 0: | |
| icofile = icofile.replace('%sz%%color%',f'-{icoWork[0].Width}') | |
| else: | |
| icofile = icofile.replace('%sz%%color%',f'-{icoWork[0].Width}-{icoWork[0].Colors}') | |
| else: | |
| icofile = icofile.replace('%sz%%color%','') | |
| try: fhandle = open(icofile,'wb') | |
| except: | |
| print(f"ERROR: Failed to create file '{icofile}'. Check filepath, write access, file lock and try again.") | |
| #if pathlib.Path(icofile).exists(): os.remove(icofile) | |
| continue | |
| inew = False # no icons to export | |
| memstream = bytearray() # memory buffer | |
| memstream.extend(struct.pack('@BBHH',0,0,1,len(icoWork))) # ICO file header | |
| offset = 6 + (16 * len(icoWork)) | |
| # foreach size in icon (group of sizes) | |
| #natives = set([n.Width for n in icoWork]) # builtin sizes | |
| for icocount,icon in enumerate(icoWork): | |
| #if icon.Icon is None: continue # already filtered in gi_list | |
| if len(bySize) != 0 and icon.Width not in bySize: continue # filter | |
| w = 0 if icon.Width == 256 else icon.Width | |
| h = 0 if icon.Height == 256 else icon.Height | |
| entry = struct.pack('@BBBBHHII',w,h,0,0,0,32,icon.ImgLength,offset) | |
| memstream.extend(entry) | |
| offset += icon.ImgLength | |
| inew = True | |
| for icon in icoWork: | |
| #if icon.Icon is not None: # already filtered in gi_list | |
| if len(bySize) == 0 or icon.Width in bySize: memstream.extend(icon.Icon) | |
| icocount += 1 # because it starts with 0 | |
| if inew and icocount != len(icoWork): | |
| # if a filter happend update icon amount value with actual | |
| memstream[4],memstream[5] = struct.pack('@H',icocount) | |
| if inew: fhandle.write(memstream) # flush buffer to a file | |
| # Cleanup | |
| fhandle.close() | |
| memstream.clear() | |
| icoWork.clear() | |
| if not inew: os.remove(icofile) | |
| else: print(f"Icon '{icofile}' successfully exported.") | |
| iconGroup.clear() | |
| if len(gi_list) > 0: return True | |
| else: return False | |
| # END ExportIcons | |
| # Icon helpers | |
| def __extract_icons(self, handle, byIndex=[], bySize=[]): | |
| icons = [] # output | |
| entries = {} # directory entry table | |
| groupIndices = {} # for DLL,EXE | |
| groupIds = {} # for DLL,EXE | |
| groupIndex = 0 # batch index | |
| def lpEnumFunc(m,t,n,lp): # enumerator callback | |
| nonlocal groupIndex, entries, groupIndices, groupIds | |
| isIndex = byIndex is not None and len(byIndex) != 0 | |
| #iNum = [n for n in byIndex if type(n).__name__ == 'int'] if isIndex else [] | |
| #if isIndex and len(iNum) != 0 and groupIndex not in iNum: | |
| if isIndex and groupIndex not in byIndex: | |
| groupIndex += 1 | |
| return True | |
| #experimental; not very useful option | |
| '''if int(n) > USHORT_MAX: | |
| name = ctypes.cast(n, ctypes.c_wchar_p).value # alfabet string | |
| else: | |
| name = str(n) # decimal string | |
| iStr = [n for n in byIndex if type(n).__name__ == 'str'] if isIndex else [] | |
| if isIndex and len(iStr) != 0 and name not in iStr: | |
| groupIndex += 1 | |
| return True''' | |
| try: | |
| self.__extract_icon_groups(self,handle, n, t, groupIndex, entries, groupIndices, groupIds) | |
| groupIndex += 1 | |
| except: pass # do nothing | |
| return True | |
| if self.__enum_resource_names(self,handle,self.__RT_GROUP_ICON,lpEnumFunc): | |
| def lpEnumFunc(m,t,n,lp): # enumerator callback | |
| nonlocal icons, groupIndices, groupIds | |
| if len(entries) == 0 or int(n) not in entries: return True | |
| imgbytes = self.__get_imgbytes(self,handle,n,t) # th's no ImageOffset in RESDIR | |
| if len(imgbytes) > 0: | |
| iconHandle = self.__user32.CreateIconFromResourceEx(imgbytes, len(imgbytes), True, 0x00030000, 0, 0, 0) | |
| if iconHandle != 0 and iconHandle is not None: | |
| idx = int(n) | |
| w,h = entries[idx]['bWidth'],entries[idx]['bHeight'] | |
| l = entries[idx]['dwBytesInRes'] | |
| bpp = entries[idx]['wBitCount'] | |
| colors = entries[idx]['bColorCount'] | |
| if colors == 0 and bpp == 8: colors = 256 # normalize | |
| if 0 == w: w = 256 # normalize | |
| if 0 == h: h = 256 # normalize | |
| if bySize is None or len(bySize) == 0 or w in bySize: | |
| info = IconReader(groupIndices[idx], groupIds[idx], int(n) - 1, str(n), w,h,bpp,colors,l, iconHandle,imgbytes) | |
| icons.append(info) | |
| return True | |
| self.__enum_resource_names(self,handle, self.__RT_ICON, lpEnumFunc) | |
| return icons | |
| # END __extract_icons | |
| # experimental; requires PIL package, sucks | |
| def __export_image(self, iconInfo, outputPath, itype, byIndex, bySize, background): | |
| if not pilpkg: | |
| print('ERROR: PIL package not installed. Installation command is "python -m pip install pillow".') | |
| return False | |
| import io | |
| # normalize itype and background | |
| itype = itype.lower() # bmp,gif are unreliable | |
| if itype not in ['png','jpg','jpeg','tif','tiff','webp','bmp','gif']: return False | |
| if itype == 'jpg': itype = 'jpeg' | |
| elif itype == 'tif': itype = 'tiff' | |
| if background is None or background == '': background = '#ffffff' | |
| bmphdr = struct.pack('@BBHH',0,0,1,1) # image header | |
| for icon in iconInfo: | |
| if icon.Icon == None or len(icon.Icon) == 0: continue # test for image data | |
| ii = icon.GroupIndex | |
| if len(byIndex) != 0 and ii not in byIndex: continue # filter | |
| # for resizing; experimental | |
| #iconGroup = [n for n in iconInfo if ii == n['GroupIndex']] # select a group by index | |
| #natives = set([n['Width'] for n in iconGroup]) # builtin sizes | |
| if len(bySize) != 0 and icon.Width not in bySize: continue # filter | |
| # resolve file name | |
| icofile = outputPath.replace('%index%',f'{str(ii)}%sz%%color%') | |
| icofile = re.sub(r'\.ico$',f'.{itype}',icofile) | |
| if icon.Colors == 0: | |
| icofile = icofile.replace('%sz%%color%',f'-{icon.Width}') | |
| else: | |
| icofile = icofile.replace('%sz%%color%',f'-{icon.Width}-{icon.Colors}') | |
| w = 0 if icon.Width == 256 else icon.Width | |
| h = 0 if icon.Height == 256 else icon.Height | |
| direntry = struct.pack('@BBBBHHII', w, h, 0, 0, 0, 32, icon.ImgLength, 22) | |
| inMemoryICO = io.BytesIO(bmphdr + direntry + icon.Icon) | |
| img = Image.open(inMemoryICO) | |
| if itype in ['jpeg','bmp']: | |
| bg = Image.new('RGBA',(icon.Width,icon.Height),background) | |
| if itype == 'jpeg': img = Image.alpha_composite(bg,img).convert('RGB') | |
| else: | |
| bg.putalpha(255) # adds(255)/removes(0) antialiasing | |
| img = Image.alpha_composite(bg,img) # this adds antialiasing | |
| #elif itype in ['gif']: # TODO add antialiasing; how??? | |
| params = {'quality':100} | |
| if itype in ['jpeg','bmp']: params['transparency'] = 0 | |
| img.save(icofile, **params) | |
| img.close() | |
| inMemoryICO.close() | |
| '''temporarily disabled | |
| try: | |
| img = Image.open(inMemoryICO) | |
| img.save(icofile,itype) | |
| print(f"Icon '{icofile}' successfully exported.") | |
| #except Exception as e: | |
| # print(e) | |
| except PIL.UnidentifiedImageError: | |
| print(f"ERROR: Failed to open image.") | |
| except OSError: | |
| print(f"ERROR: Failed to create file '{icofile}'. Check filepath, write access, file lock and try again.") | |
| finally: | |
| if img != None: img.close() | |
| inMemoryICO.close()''' | |
| return True | |
| # END __export_image | |
| def __import_ico(self, iconFilePath, byIndex=[], bySize=[]): | |
| try: | |
| with open(iconFilePath, 'rb') as f: | |
| data = f.read() | |
| if len(data) < 6: return None | |
| isignature,icount = struct.unpack(self.__ICOHEADER, data[0:6]) | |
| if isignature != 0x10000 or icount == 0: return None | |
| except: | |
| print('Error reading file.') | |
| return None | |
| ENTRYFORMAT = '@BBBBHHII' | |
| icons = [] # output icon storage | |
| #entrysize = struct.calcsize(ENTRYFORMAT) # must be 16 | |
| diroffset = range(6,16*icount,16) | |
| isIndex = byIndex is not None and len(byIndex) > 0 | |
| isSize = bySize is not None and len(bySize) > 0 | |
| for k,i in enumerate(diroffset): | |
| if isIndex and k not in byIndex: continue | |
| w,h,colors,_,_,bpp,l,dataoffset = struct.unpack(ENTRYFORMAT, data[i:i+16]) | |
| if 0 == w: w = 256 # normalize | |
| if 0 == h: h = 256 # normalize | |
| if isSize and w not in bySize: continue | |
| imgbytes = data[dataoffset:(dataoffset+l)] | |
| iconHandle = self.__user32.CreateIconFromResourceEx(imgbytes, l, True, 0x0030000, w, h, 0) | |
| info = IconReader(0, "", k, k+1, w,h,bpp,colors,l, iconHandle,imgbytes) | |
| icons.append(info) | |
| return icons | |
| # END __import_ico | |
| def __extract_icon_groups(self, module, name, itype, index, entries, groupIndices, groupIds): | |
| hres = self.__find_resource(self, module, name, itype) | |
| if hres == 0 or hres is None: return | |
| resource = self.__load_resource(self, module, hres) | |
| if resource == 0 or resource is None: return | |
| ptr = self.__lock_resource(self, resource) | |
| if ptr == 0 or ptr is None: return | |
| # GRPICONDIR | |
| ptr += 2 # idReserved | |
| idtype = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_uint16)).contents.value | |
| if idtype != 1: return # idType: 1 - ICO, 2 - CUR | |
| ENTRYFORMAT = '@BBBBhhIH' | |
| entrySize = struct.calcsize(ENTRYFORMAT) # must be 14 | |
| ptr += 2 | |
| count = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_uint16)).contents.value | |
| ptr += 2 | |
| for i in range(count): | |
| # restore ICONDIR | |
| edata = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_byte * entrySize)).contents # raw entry | |
| ptr += entrySize | |
| values = struct.unpack(ENTRYFORMAT, edata) | |
| entry = dict(zip(self.__GRPICONDIRENTRY, values)) | |
| ##if entry['bWidth'] == 0: entry['bWidth'] = 256 # normalize later | |
| ##if entry['bHeight'] == 0: entry['bHeight'] = 256 | |
| entries[entry['nId']] = entry | |
| # GroupId, is it a string or an id? | |
| groupIndices[entry['nId']] = index | |
| if int(name) > USHORT_MAX: | |
| groupIds[entry['nId']] = ctypes.cast(name, ctypes.c_wchar_p).value # alfabet string | |
| else: | |
| groupIds[entry['nId']] = f'#{name}' # decimal string | |
| # END __extract_icon_groups | |
| def __get_imgbytes(self, module, name, itype): | |
| # https://devblogs.microsoft.com/oldnewthing/?p=7083 | |
| hres = self.__find_resource(self, module, name, itype) | |
| if hres == 0 or hres is None: return 0 | |
| size = self.__sizeof_resource(self, module, hres) | |
| if size == 0: return 0 | |
| resource = self.__load_resource(self, module, hres) | |
| if resource == 0 or resource is None: return 0 | |
| ptr = self.__lock_resource(self, resource) | |
| if ptr == 0 or ptr is None: return 0 | |
| return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_byte * size)).contents # why "POINTER" is deprecated here??? | |
| # END __get_imgbytes | |
| # Generic helpers | |
| def __resolve_inputuri(filepath): | |
| if filepath is None or not filepath or type(filepath).__name__ != 'str': return None | |
| if not filepath.lower().startswith('http'): | |
| # resolve aliases; can add more | |
| match filepath.lower(): | |
| case '%shell32': filepath = r'C:\Windows\System32\shell32.dll' | |
| case '%imageres': filepath = r'C:\Windows\System32\imageres.dll' | |
| case '%ddo': filepath = r'C:\windows\System32\ddores.dll' | |
| return filepath | |
| import urllib.request,tempfile | |
| target = '\\'.join([tempfile.gettempdir(), pathlib.Path(filepath).name]) | |
| try: | |
| with urllib.request.urlopen(filepath,timeout=3.5) as response, open(target, 'wb') as out_file: | |
| out_file.write(response.read()) | |
| #if not pathlib.Path(target).exists(): target = None | |
| except: | |
| #if pathlib.Path(target).exists(): os.remove(target) | |
| target = None | |
| return target | |
| def __load_library(self, lpFileName): | |
| LoadLibraryExW = self.__kernel32.LoadLibraryExW | |
| LoadLibraryExW.argtypes = [wintypes.LPCWSTR, wintypes.HANDLE, wintypes.DWORD] | |
| LoadLibraryExW.restype = wintypes.HMODULE | |
| flags = self.__LOAD_LIBRARY_AS_DATAFILE | self.__LOAD_LIBRARY_AS_IMAGE_RESOURCE | |
| return LoadLibraryExW(lpFileName, 0, flags) | |
| def __enum_resource_names(self, handle, lpType, lpEnumFunc): | |
| ENUMFUNC = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HMODULE, wintypes.LPVOID, wintypes.LPVOID, wintypes.LPARAM) | |
| enum_func = ENUMFUNC(lpEnumFunc) | |
| enum_func.restype = ctypes.c_bool | |
| EnumResourceNames = self.__kernel32.EnumResourceNamesW | |
| EnumResourceNames.argtypes = [wintypes.HMODULE, wintypes.LPVOID, ENUMFUNC, wintypes.LPARAM] | |
| EnumResourceNames.restype = ctypes.c_bool | |
| return EnumResourceNames(handle, lpType, enum_func, 0) | |
| def __find_resource(self, hModule, lpName, lpType): | |
| FindResource = self.__kernel32.FindResourceW | |
| FindResource.argtypes = [wintypes.HMODULE, wintypes.LPVOID, wintypes.LPVOID] | |
| FindResource.restype = wintypes.HRSRC | |
| return FindResource(hModule, lpName, lpType) | |
| def __sizeof_resource(self, hModule, hResInfo): | |
| SizeofResource = self.__kernel32.SizeofResource | |
| SizeofResource.argtypes = [wintypes.HMODULE, wintypes.HRSRC] | |
| SizeofResource.restype = wintypes.DWORD | |
| return SizeofResource(hModule, hResInfo) | |
| def __load_resource(self, hModule, hResInfo): | |
| LoadResource = self.__kernel32.LoadResource | |
| LoadResource.argtypes = [wintypes.HMODULE, wintypes.HRSRC] | |
| LoadResource.restype = wintypes.HGLOBAL | |
| return LoadResource(hModule, hResInfo) | |
| def __lock_resource(self, hResData): | |
| LockResource = self.__kernel32.LockResource | |
| LockResource.argtypes = [wintypes.HGLOBAL] | |
| LockResource.restype = ctypes.c_void_p | |
| return LockResource(hResData) | |
| __user32 = ctypes.WinDLL(r'C:\Windows\System32\user32.dll') | |
| __kernel32 = ctypes.WinDLL(r'C:\Windows\System32\kernel32.dll') | |
| __LOAD_LIBRARY_AS_DATAFILE = 0x2 | |
| __LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20 | |
| __RT_ICON = 3 | |
| __RT_GROUP_ICON = __RT_ICON + 11 | |
| __GRPICONDIRENTRY = ["bWidth","bHeight","bColorCount","bReserved","wPlanes","wBitCount","dwBytesInRes","nId"] | |
| #__ICONDIRENTRY = ["bWidth","bHeight","bColorCount","bReserved","wPlanes","wBitCount","dwBytesInRes","dwImageOffset"] | |
| __ICOHEADER = '@IH' | |
| # END class IconReader | |
| icons = IconReader.ImportIcons(r'C:\windows\system32\WindowsPowerShell\v1.0\PowerShell.exe') | |
| fmtR = '{0:>{1}}' | |
| fmtL = '{0:<{1}}' | |
| for i in icons: | |
| print( | |
| fmtR.format(i.Handle,11), | |
| fmtR.format(i.Index,3), | |
| fmtR.format(i.Width,4), | |
| fmtR.format(i.Bpp,3), | |
| fmtR.format(i.Colors,3), | |
| fmtR.format(i.ImgLength,8), | |
| fmtR.format(i.GroupIndex,3), | |
| fmtL.format(i.GroupId,13) | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment