diff --git a/exeinfogui/NE/TabNeResources.xeto.cs b/exeinfogui/NE/TabNeResources.xeto.cs index 266d0e3..c26a50b 100644 --- a/exeinfogui/NE/TabNeResources.xeto.cs +++ b/exeinfogui/NE/TabNeResources.xeto.cs @@ -29,6 +29,7 @@ using System.Collections.Generic; using System.Linq; using exeinfogui.Os2; using exeinfogui.Win16; +using exeinfogui.Windows; using Eto.Forms; using Eto.Serialization.Xaml; @@ -41,6 +42,7 @@ namespace exeinfogui.NE PanelNeStrings panelNeStrings; PanelOs2Bitmap panelOs2Bitmap; PanelWin16Version panelWin16Version; + PanelWindowsIcon panelWindowsIcon; Panel pnlResource; TreeGridItemCollection treeData; TreeGridView treeResources; @@ -61,6 +63,7 @@ namespace exeinfogui.NE panelNeAccelerators = new PanelNeAccelerators(); panelHexDump = new PanelHexDump(); panelOs2Bitmap = new PanelOs2Bitmap(); + panelWindowsIcon = new PanelWindowsIcon(); } public void Update(IEnumerable resourceTypes, libexeinfo.NE.TargetOS os) @@ -131,6 +134,10 @@ namespace exeinfogui.NE } catch { goto default; } + break; + case "RT_ICON": + pnlResource.Content = panelWindowsIcon; + panelWindowsIcon.Update(data); break; default: pnlResource.Content = panelHexDump; diff --git a/exeinfogui/Os2/PanelOs2Bitmap.xeto.cs b/exeinfogui/Os2/PanelOs2Bitmap.xeto.cs index 9f5fd18..6e24930 100644 --- a/exeinfogui/Os2/PanelOs2Bitmap.xeto.cs +++ b/exeinfogui/Os2/PanelOs2Bitmap.xeto.cs @@ -86,6 +86,35 @@ namespace exeinfogui.Os2 Bitmap.DecodedBitmap[] icons = Bitmap.DecodeBitmap(data); + if(icons == null || icons.Length == 0) + try + { + libexeinfo.Windows.Bitmap.DecodedBitmap winIcon = null; + + if(BitConverter.ToUInt32(data, 4) == 40) + { + byte[] cursor = new byte[data.Length - 4]; + Array.Copy(data, 4, cursor, 0, cursor.Length); + winIcon = libexeinfo.Windows.Bitmap.DecodeIcon(cursor); + } + else if(BitConverter.ToUInt32(data, 0) == 40) + winIcon = libexeinfo.Windows.Bitmap.DecodeIcon(data); + + if(winIcon != null) + icons = new[] + { + new Bitmap.DecodedBitmap + { + BitsPerPixel = winIcon.BitsPerPixel, + Height = winIcon.Height, + Pixels = winIcon.Pixels, + Type = "Windows cursor", + Width = winIcon.Width + } + }; + } + catch { icons = null; } + if(icons == null || icons.Length == 0) { imgIcon.Image = null; diff --git a/exeinfogui/Windows/PanelWindowsIcon.xeto b/exeinfogui/Windows/PanelWindowsIcon.xeto new file mode 100644 index 0000000..7cc1ec9 --- /dev/null +++ b/exeinfogui/Windows/PanelWindowsIcon.xeto @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exeinfogui/Windows/PanelWindowsIcon.xeto.cs b/exeinfogui/Windows/PanelWindowsIcon.xeto.cs new file mode 100644 index 0000000..c3449f4 --- /dev/null +++ b/exeinfogui/Windows/PanelWindowsIcon.xeto.cs @@ -0,0 +1,70 @@ +using Eto.Drawing; +using Eto.Forms; +using Eto.Serialization.Xaml; +using Bitmap = libexeinfo.Windows.Bitmap; + +namespace exeinfogui.Windows +{ + public class PanelWindowsIcon : Panel + { + ImageView imgIcon; + Label lblColors; + Label lblSize; + PanelHexDump panelHexDump; + Panel pnlPanel; + TextBox txtColors; + TextBox txtSize; + + public PanelWindowsIcon() + { + XamlReader.Load(this); + panelHexDump = new PanelHexDump(); + pnlPanel.Content = panelHexDump; + } + + public void Update(byte[] data) + { + if(data == null) + { + imgIcon.Image = null; + lblSize.Text = "No data"; + lblColors.Visible = false; + lblSize.Visible = false; + txtColors.Visible = false; + txtSize.Visible = false; + pnlPanel.Visible = false; + return; + } + + Bitmap.DecodedBitmap icon; + + try { icon = Bitmap.DecodeIcon(data); } + catch { icon = null; } + + if(icon == null) + { + imgIcon.Image = null; + lblSize.Text = "Undecoded"; + lblColors.Visible = false; + lblSize.Visible = false; + txtColors.Visible = false; + txtSize.Visible = false; + pnlPanel.Visible = true; + panelHexDump.Update(data); + return; + } + + txtSize.Text = $"{icon.Width}x{icon.Height} pixels"; + txtColors.Text = $"{1 << (int)icon.BitsPerPixel} ({icon.BitsPerPixel} bpp)"; + imgIcon.Image = + new Eto.Drawing.Bitmap((int)icon.Width, (int)icon.Height, PixelFormat.Format32bppRgba, icon.Pixels); + + lblSize.Text = "Size"; + lblColors.Visible = true; + lblSize.Visible = true; + txtColors.Visible = true; + txtSize.Visible = true; + pnlPanel.Visible = false; + } + } +} \ No newline at end of file diff --git a/exeinfogui/exeinfogui.csproj b/exeinfogui/exeinfogui.csproj index afe9438..0ae3a11 100644 --- a/exeinfogui/exeinfogui.csproj +++ b/exeinfogui/exeinfogui.csproj @@ -23,5 +23,6 @@ + \ No newline at end of file diff --git a/libexeinfo/Os2/Bitmap.cs b/libexeinfo/Os2/Bitmap.cs index b9e8421..402540a 100644 --- a/libexeinfo/Os2/Bitmap.cs +++ b/libexeinfo/Os2/Bitmap.cs @@ -1,5 +1,5 @@ // -// Accelerator.cs +// Bitmap.cs // // Author: // Natalia Portillo @@ -225,7 +225,7 @@ namespace libexeinfo.Os2 for(int px = 0; px < bitmap.Pixels.Length; px++) bitmap.Pixels[px] = bitmap.Pixels[px] + VISIBLE; - // OS/2 coordinate 0,0 is lower,left, so need to reverse first all pixels then by line + // Need to reverse first all pixels then by line int[] pixels = bitmap.Pixels.Reverse().ToArray(); for(int y = 0; y < bitmap.Height; y++) for(int x = 0; x < bitmap.Width; x++) diff --git a/libexeinfo/Windows/Bitmap.cs b/libexeinfo/Windows/Bitmap.cs new file mode 100644 index 0000000..14624db --- /dev/null +++ b/libexeinfo/Windows/Bitmap.cs @@ -0,0 +1,225 @@ +// +// Bitmap.cs +// +// Author: +// Natalia Portillo +// +// Copyright (c) 2017 Copyright © Claunia.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace libexeinfo.Windows +{ + public class Bitmap + { + public enum Compression : uint + { + None = 0, + Rle8 = 1, + Rle4 = 2, + Bitfields = 3, + Jpeg = 4, + Png = 5, + AlphaBitfields = 6, + Cmyk = 11, + CmykRle8 = 12, + CmykRle4 = 13 + } + + const int VISIBLE = -16777216; + + /// + /// This will decode an icon + /// + /// Data + /// A with the icon, null if the icon could not be decoded + public static DecodedBitmap DecodeIcon(byte[] data) + { + long pos = 0; + byte[] buffer = new byte[Marshal.SizeOf(typeof(BitmapInfoHeader))]; + Array.Copy(data, pos, buffer, 0, buffer.Length); + BitmapInfoHeader bitmapFileHeader = + BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + + // Stop at unknown header + if(bitmapFileHeader.HeaderSize != 40) return null; + + // Multiplanes not supported + if(bitmapFileHeader.Planes != 1) return null; + + // TODO: Non paletted? + pos += bitmapFileHeader.HeaderSize; + RGB[] palette = new RGB[1 << bitmapFileHeader.BitsPerPlane]; + buffer = new byte[Marshal.SizeOf(typeof(RGB))]; + for(int i = 0; i < palette.Length; i++) + { + Array.Copy(data, pos, buffer, 0, buffer.Length); + pos += buffer.Length; + palette[i] = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + } + + // First let's do the icon itself + bitmapFileHeader.Height /= 2; + long dataLength = 0; + for(int y = 0; y < bitmapFileHeader.Height; y++) + { + int x = 0; + while(x < bitmapFileHeader.Width) + { + for(int k = 8 - bitmapFileHeader.BitsPerPlane; k >= 0; k -= (int)bitmapFileHeader.BitsPerPlane) x++; + + dataLength++; + } + + dataLength += dataLength % 2; + } + + buffer = new byte[dataLength]; + Array.Copy(data, pos, buffer, 0, buffer.Length); + + DecodedBitmap icon = DecodeBitmap(bitmapFileHeader, palette, buffer); + + // Then the mask + pos += dataLength; + bitmapFileHeader.BitsPerPlane = 1; + dataLength = 0; + for(int y = 0; y < bitmapFileHeader.Height; y++) + { + int x = 0; + while(x < bitmapFileHeader.Width) + { + for(int k = 8 - bitmapFileHeader.BitsPerPlane; k >= 0; k -= (int)bitmapFileHeader.BitsPerPlane) x++; + + dataLength++; + } + + dataLength += dataLength % 2; + } + + buffer = new byte[dataLength]; + Array.Copy(data, pos, buffer, 0, buffer.Length); + + DecodedBitmap mask = DecodeBitmap(bitmapFileHeader, palette, buffer); + + // Mask palette + int[] argbPalette = new int[palette.Length]; + for(int c = 0; c < palette.Length; c++) + argbPalette[c] = (palette[c].Red << 16) + (palette[c].Green << 8) + palette[c].Blue; + + DecodedBitmap bitmap = new DecodedBitmap + { + BitsPerPixel = icon.BitsPerPixel, + Height = icon.Height, + Width = icon.Width, + Pixels = new int[icon.Pixels.Length] + }; + + for(int px = 0; px < bitmap.Pixels.Length; px++) + bitmap.Pixels[px] = icon.Pixels[px] + (mask.Pixels[px] == argbPalette[0] ? VISIBLE : 0); + + // Need to reverse first all pixels then by line + int[] pixels = bitmap.Pixels.Reverse().ToArray(); + for(int y = 0; y < bitmap.Height; y++) + for(int x = 0; x < bitmap.Width; x++) + bitmap.Pixels[y * bitmap.Width + (bitmap.Width - x - 1)] = pixels[y * bitmap.Width + x]; + + return bitmap; + } + + static DecodedBitmap DecodeBitmap(BitmapInfoHeader header, IList palette, byte[] data) + { + if(header.Compression != Compression.None) return null; + + DecodedBitmap bitmap = new DecodedBitmap + { + BitsPerPixel = header.BitsPerPlane, + Height = header.Height, + Width = header.Width, + Pixels = new int[header.Width * header.Height] + }; + + int[] argbPalette = new int[palette.Count]; + + for(int c = 0; c < palette.Count; c++) + argbPalette[c] = (palette[c].Red << 16) + (palette[c].Green << 8) + palette[c].Blue; + + long pos = 0; + + for(int y = 0; y < bitmap.Height; y++) + { + int x = 0; + while(x < bitmap.Width) + { + for(int k = (int)(8 - bitmap.BitsPerPixel); k >= 0; k -= (int)bitmap.BitsPerPixel) + { + bitmap.Pixels[y * bitmap.Width + x] = + argbPalette[(data[pos] >> k) & ((1 << (int)bitmap.BitsPerPixel) - 1)]; + x++; + + if(x == bitmap.Width) break; + } + + pos++; + } + + pos += pos % 2; + } + + return bitmap; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BitmapInfoHeader + { + public uint HeaderSize; + public uint Width; + public uint Height; + public ushort Planes; + public ushort BitsPerPlane; + public Compression Compression; + public uint ImageSize; + public uint HorizontalResolution; + public uint VerticalResolution; + public uint ColorsInPalette; + public uint ImportantColors; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RGB + { + public byte Blue; + public byte Green; + public byte Red; + public byte Reserved; + } + + public class DecodedBitmap + { + public uint BitsPerPixel; + public uint Height; + public int[] Pixels; + public uint Width; + } + } +} \ No newline at end of file diff --git a/libexeinfo/libexeinfo.csproj b/libexeinfo/libexeinfo.csproj index 8400588..cb5ef5a 100644 --- a/libexeinfo/libexeinfo.csproj +++ b/libexeinfo/libexeinfo.csproj @@ -95,6 +95,7 @@ +