diff --git a/CommonShims/BinaryWriterExtensions.cs b/CommonShims/BinaryWriterExtensions.cs new file mode 100644 index 0000000..3a67c26 --- /dev/null +++ b/CommonShims/BinaryWriterExtensions.cs @@ -0,0 +1,29 @@ +using System.Buffers; + +namespace CommonShims; + +#if NETSTANDARD2_0 +public static class BinaryWriterExtensions +{ + public static void Write(this BinaryWriter writer, ReadOnlySpan buffer) + { + if (writer.GetType() == typeof(BinaryWriter)) + { + writer.BaseStream.Write(buffer); + } + else + { + var array = ArrayPool.Shared.Rent(buffer.Length); + try + { + buffer.CopyTo(array); + writer.Write(array, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + } +} +#endif diff --git a/CommonShims/CommonShims.csproj b/CommonShims/CommonShims.csproj new file mode 100644 index 0000000..5ff5946 --- /dev/null +++ b/CommonShims/CommonShims.csproj @@ -0,0 +1,24 @@ + + + + netstandard2.0 + enable + true + Latest + enable + false + + + + embedded + + + + embedded + + + + + + + diff --git a/CommonShims/DictionaryExtensions.cs b/CommonShims/DictionaryExtensions.cs new file mode 100644 index 0000000..014bcdc --- /dev/null +++ b/CommonShims/DictionaryExtensions.cs @@ -0,0 +1,16 @@ +namespace CommonShims; + +#if NETSTANDARD2_0 +public static class DictionaryExtensions +{ + public static bool TryAdd(this IDictionary dictionary, TKey key, TValue value) + { + if (dictionary.ContainsKey(key)) + return false; + + dictionary.Add(key, value); + + return true; + } +} +#endif diff --git a/CommonShims/EncodingExtensions.cs b/CommonShims/EncodingExtensions.cs new file mode 100644 index 0000000..a1db4c6 --- /dev/null +++ b/CommonShims/EncodingExtensions.cs @@ -0,0 +1,30 @@ +using System.Text; + +namespace CommonShims; + +#if NETSTANDARD2_0 +public static class EncodingExtensions +{ + public static string GetString(this Encoding encoding, ReadOnlySpan bytes) + { + if (bytes.Length == 0) + return string.Empty; + + return encoding.GetString(bytes.ToArray(), 0, bytes.Length); + } + + public static int GetChars(this Encoding encoding, ReadOnlySpan bytes, Span chars) + { + if (bytes.Length == 0 || chars.Length == 0) + return 0; + + var src = bytes.ToArray(); + var dst = new char[chars.Length]; + + var written = encoding.GetChars(src, 0, src.Length, dst, 0); + + dst.AsSpan(0, written).CopyTo(chars); + return written; + } +} +#endif diff --git a/CommonShims/SpanExtensions.cs b/CommonShims/SpanExtensions.cs new file mode 100644 index 0000000..06bce2d --- /dev/null +++ b/CommonShims/SpanExtensions.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace CommonShims; + +public static class SpanExtensions +{ +#if NETSTANDARD2_0 + public static unsafe string ToStringFast(this ReadOnlySpan span) + { + if (span.Length == 0) + return string.Empty; + + fixed (char* p = &MemoryMarshal.GetReference(span)) + { + return new string(p, 0, span.Length); + } + } + +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToStringFast(this ReadOnlySpan span) => new string(span); +#endif +} diff --git a/CommonShims/StreamExtensions.cs b/CommonShims/StreamExtensions.cs new file mode 100644 index 0000000..2a88c2d --- /dev/null +++ b/CommonShims/StreamExtensions.cs @@ -0,0 +1,40 @@ +using System.Buffers; + +namespace CommonShims; + +#if NETSTANDARD2_0 +public static class StreamExtensions +{ + public static int Read(this Stream stream, Span buffer) + { + var sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + var numRead = stream.Read(sharedBuffer, 0, buffer.Length); + if ((uint)numRead > (uint)buffer.Length) + throw new IOException("IOStream is too long."); + + new ReadOnlySpan(sharedBuffer, 0, numRead).CopyTo(buffer); + return numRead; + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } + } + + public static void Write(this Stream stream, ReadOnlySpan buffer) + { + var sharedBuffer = ArrayPool.Shared.Rent(buffer.Length); + try + { + buffer.CopyTo(sharedBuffer); + stream.Write(sharedBuffer, 0, buffer.Length); + } + finally + { + ArrayPool.Shared.Return(sharedBuffer); + } + } +} +#endif diff --git a/Ico.Reader.sln b/Ico.Reader.sln deleted file mode 100644 index f9c76e0..0000000 --- a/Ico.Reader.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34525.116 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ico.Reader", "Ico.Reader\Ico.Reader.csproj", "{63415B07-E691-4F66-8937-B43FFDDB5069}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PeDecoder", "PeDecoder\PeDecoder.csproj", "{05B9238B-028F-4CD4-B97B-44A8671FEC8C}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {63415B07-E691-4F66-8937-B43FFDDB5069}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63415B07-E691-4F66-8937-B43FFDDB5069}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63415B07-E691-4F66-8937-B43FFDDB5069}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63415B07-E691-4F66-8937-B43FFDDB5069}.Release|Any CPU.Build.0 = Release|Any CPU - {05B9238B-028F-4CD4-B97B-44A8671FEC8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {05B9238B-028F-4CD4-B97B-44A8671FEC8C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {05B9238B-028F-4CD4-B97B-44A8671FEC8C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {05B9238B-028F-4CD4-B97B-44A8671FEC8C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {361DC4F1-AD28-4426-8C22-6407605FD210} - EndGlobalSection -EndGlobal diff --git a/Ico.Reader.slnx b/Ico.Reader.slnx new file mode 100644 index 0000000..43d4a7f --- /dev/null +++ b/Ico.Reader.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/Ico.Reader/Creator/IPngCreator.cs b/Ico.Reader/Creator/IPngCreator.cs index bdde805..73e86bb 100644 --- a/Ico.Reader/Creator/IPngCreator.cs +++ b/Ico.Reader/Creator/IPngCreator.cs @@ -1,6 +1,7 @@ using Ico.Reader.Data; namespace Ico.Reader.Creator; + public interface IPngCreator { byte[] CreatePng(ReadOnlySpan rgba, BMP_Info_Header header); diff --git a/Ico.Reader/Creator/PngCreator.cs b/Ico.Reader/Creator/PngCreator.cs index a195cb6..a876280 100644 --- a/Ico.Reader/Creator/PngCreator.cs +++ b/Ico.Reader/Creator/PngCreator.cs @@ -1,9 +1,11 @@ -using Ico.Reader.Data; -using Ico.Reader.Extensions; -using System.IO.Compression; +using System.IO.Compression; using System.Text; +using Ico.Reader.Data; +using Ico.Reader.Extensions; + namespace Ico.Reader.Creator; + public class PngCreator : IPngCreator { private const uint cInit = 0xffffffff; @@ -11,16 +13,15 @@ public class PngCreator : IPngCreator private const string IHDR = "IHDR"; private const string IEND = "IEND"; - private static readonly byte[] _header = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; - private static readonly uint[] _crcTable = Enumerable.Range(0, 256).Select(n => + private static readonly byte[] _header = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; + private static readonly uint[] _crcTable = [.. Enumerable.Range(0, 256).Select(n => { var c = (uint)n; for (var k = 0; k < 8; k++) - c = (c & 1) == 1 ? 0xedb88320 ^ c >> 1 : c >> 1; + c = (c & 1) == 1 ? 0xedb88320 ^ (c >> 1) : c >> 1; return c; - }).ToArray(); - + })]; public byte[] CreatePng(ReadOnlySpan rgba, BMP_Info_Header header) { @@ -58,7 +59,7 @@ private static void WriteIdatChunks(BinaryWriter writer, ReadOnlySpan rgba var bytesPerRow = width * 4 + 1; var uncompressedData = new byte[height * bytesPerRow]; - for (int y = 0; y < height; y++) + for (var y = 0; y < height; y++) { uncompressedData[y * bytesPerRow] = 0; rgba.Slice(y * width * 4, width * 4).CopyTo(uncompressedData.AsSpan(y * bytesPerRow + 1)); @@ -73,7 +74,7 @@ private static void WriteIdatChunks(BinaryWriter writer, ReadOnlySpan rgba compressor.Flush(); compressor.Close(); - uint adler = CalculateAdler32(uncompressedData); + var adler = CalculateAdler32(uncompressedData); using var binaryWriter = new BinaryWriter(compressedDataStream); binaryWriter.WriteUInt32BigEndian(adler); @@ -100,9 +101,9 @@ private static void WriteChunk(BinaryWriter writer, string type, byte[] data) public static uint CalculateCrc32(byte[] data) { - uint crc = cInit; + var crc = cInit; foreach (var b in data) - crc = _crcTable[(crc ^ b) & 0xff] ^ crc >> 8; + crc = _crcTable[(crc ^ b) & 0xff] ^ (crc >> 8); return crc ^ cInit; } diff --git a/Ico.Reader/Data/BMP_Info_Header.cs b/Ico.Reader/Data/BMP_Info_Header.cs index ba98f11..994d468 100644 --- a/Ico.Reader/Data/BMP_Info_Header.cs +++ b/Ico.Reader/Data/BMP_Info_Header.cs @@ -68,15 +68,14 @@ public class BMP_Info_Header /// The number of colors in the color palette. public int CalculatePaletteSize() { - int maxColors = 1 << BitCount; - int paletteColors = (ClrUsed == 0 || ClrUsed > maxColors) ? maxColors : ClrUsed; + var maxColors = 1 << BitCount; + var paletteColors = (ClrUsed == 0 || ClrUsed > maxColors) ? maxColors : ClrUsed; return paletteColors; } - /// /// Calculates the offset to the beginning of bitmap data, taking into account the size of the header and the color palette. /// /// The offset to the bitmap data in bytes. - public int CalculateDataOffset() => Size + (1 << BitCount) * 4; + public int CalculateDataOffset() => Size + ((1 << BitCount) * 4); } \ No newline at end of file diff --git a/Ico.Reader/Data/CursorGroup.cs b/Ico.Reader/Data/CursorGroup.cs index 2f3bffe..f69b690 100644 --- a/Ico.Reader/Data/CursorGroup.cs +++ b/Ico.Reader/Data/CursorGroup.cs @@ -1,5 +1,9 @@ using System.Runtime.InteropServices; +using CommonShims; + +using Ico.Reader.Utils; + namespace Ico.Reader.Data; /// /// Represents a collection of within an CUR file. @@ -33,7 +37,7 @@ IIcoDirectoryEntry[] IIcoGroup.DirectoryEntries { var cursorDirectoryEntries = new CursorDirectoryEntry[value.Length]; - for (int i = 0; i < value.Length; i++) + for (var i = 0; i < value.Length; i++) { if (value[i] is CursorDirectoryEntry entry) cursorDirectoryEntries[i] = entry; @@ -49,27 +53,24 @@ IIcoDirectoryEntry[] IIcoGroup.DirectoryEntries public override string ToString() => $"[{nameof(CursorGroup)}] {Name} ({Size})"; - public CursorDirectoryEntry[] ReadEntriesFromEXEStream(Stream stream, IcoHeader icoHeader) { if (icoHeader.ImageType != CursorDirectoryEntry.ImageType) - { throw new Exception("The ico data does not contain cursor data."); - } var positionStart = stream.Position; - int byteSize = 14 * icoHeader.ImageCount; + var byteSize = 14 * icoHeader.ImageCount; var entries = new CursorDirectoryEntry[icoHeader.ImageCount]; Span entriesBuffer = stackalloc byte[byteSize]; stream.Read(entriesBuffer); ReadOnlySpan entriesBufferSpan = entriesBuffer; - for (int i = 0; i < icoHeader.ImageCount; i++) + for (var i = 0; i < icoHeader.ImageCount; i++) { var offset = i * 14; - ushort resourceID = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 2)); + var resourceID = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 2)); entries[i] = new CursorDirectoryEntry() { @@ -88,5 +89,6 @@ public CursorDirectoryEntry[] ReadEntriesFromEXEStream(Stream stream, IcoHeader return entries; } - IIcoDirectoryEntry[] IIcoGroup.ReadEntriesFromEXEStream(Stream stream, IcoHeader icoHeader) => IIcoGroup.ReadFromEXEStream(stream, icoHeader); + IIcoDirectoryEntry[] IIcoGroup.ReadEntriesFromEXEStream(Stream stream, IcoHeader icoHeader) + => IcoGroupUtils.ReadFromEXEStream(stream, icoHeader); } diff --git a/Ico.Reader/Data/DecodedIcoResult.cs b/Ico.Reader/Data/DecodedIcoResult.cs index 65cd4b8..d7a8eda 100644 --- a/Ico.Reader/Data/DecodedIcoResult.cs +++ b/Ico.Reader/Data/DecodedIcoResult.cs @@ -12,9 +12,9 @@ public sealed class DecodedIcoResult /// /// An array of image references, each pointing to an image extracted from the ico file. /// - public List References { get; set; } = new List(); + public List References { get; set; } = []; /// /// An array of ico groups, categorizing the extracted images into groups based on certain criteria, such as resolution or color depth. /// - public List IcoGroups { get; set; } = new List(); + public List IcoGroups { get; set; } = []; } diff --git a/Ico.Reader/Data/IIcoDirectoryEntry.cs b/Ico.Reader/Data/IIcoDirectoryEntry.cs index 2668fa2..c4bc4d8 100644 --- a/Ico.Reader/Data/IIcoDirectoryEntry.cs +++ b/Ico.Reader/Data/IIcoDirectoryEntry.cs @@ -1,6 +1,4 @@ -using System.Runtime.InteropServices; - -namespace Ico.Reader.Data; +namespace Ico.Reader.Data; /// /// Represents a base directory entry containing metadata for an image within an icon or cursor group. /// This serves as the base class for specific types of entries, such as for ICO files @@ -45,80 +43,4 @@ public interface IIcoDirectoryEntry /// The real offset of the image data in the file. /// uint RealImageOffset { get; internal set; } - - /// - /// Reads ico directory entries from a stream based on the specified ico header. This method is typically used when reading icos from ICO files. - /// - /// For more information, see Structure of image directory. - /// - /// - /// The stream from which to read the ico directory entries. - /// The header that provides information about the number of images. - /// An array of objects representing the ico directory entries read from the stream. - public static IIcoDirectoryEntry[] ReadEntriesFromStream(Stream stream, IcoHeader icoHeader) - { - var positionStart = stream.Position; - int byteSize = 16 * icoHeader.ImageCount; - var entries = new IIcoDirectoryEntry[icoHeader.ImageCount]; - - Span entriesBuffer = stackalloc byte[byteSize]; - stream.Read(entriesBuffer); - ReadOnlySpan entriesBufferSpan = entriesBuffer; - - for (int i = 0; i < icoHeader.ImageCount; i++) - { - var offset = i * 16; - - if (icoHeader.ImageType == 1) - { - entries[i] = new IconDirectoryEntry - { - Width = entriesBufferSpan[offset], - Height = entriesBufferSpan[offset + 1], - ColorCount = entriesBufferSpan[offset + 2], - Reserved = entriesBufferSpan[offset + 3], - Planes = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 4, 2)), - ColorDepth = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 6, 2)), - ImageSize = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 8, 4)), - ImageOffset = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 4)) - }; - } - else if (icoHeader.ImageType == 2) - { - entries[i] = new CursorDirectoryEntry - { - Width = entriesBufferSpan[offset], - Height = entriesBufferSpan[offset + 1], - Planes = 0, - HotspotX = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 4, 2)), - HotspotY = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 6, 2)), - ImageSize = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 8, 4)), - ImageOffset = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 4)) - }; - } - - entries[i].RealImageOffset = entries[i].ImageOffset; - } - - return entries; - } - - - public static T[] ReadEntriesFromEXEStream(Stream stream, IcoHeader icoHeader) where T : class, IIcoDirectoryEntry - { - if (icoHeader.ImageType == IconDirectoryEntry.ImageType && typeof(T) != typeof(IconDirectoryEntry)) - { - throw new Exception("Invalid entry type"); - } - else if (icoHeader.ImageType == CursorDirectoryEntry.ImageType && typeof(T) != typeof(CursorDirectoryEntry)) - { - throw new Exception("Invalid entry type"); - } - - var entries = ReadEntriesFromStream(stream, icoHeader); - return (T[])entries; - } - - - } diff --git a/Ico.Reader/Data/IIcoGroup.cs b/Ico.Reader/Data/IIcoGroup.cs index b48e254..dd2c9d8 100644 --- a/Ico.Reader/Data/IIcoGroup.cs +++ b/Ico.Reader/Data/IIcoGroup.cs @@ -1,70 +1,6 @@ -using System.Runtime.InteropServices; +namespace Ico.Reader.Data; -namespace Ico.Reader.Data; - -public interface IIcoGroup : IIcoGroup -{ - - /// - /// Reads ico directory entries from an executable file stream based on the specified ico header. This method adapts the process for the differences in ico data layout within EXE or DLL files. - /// - /// For more information, see Hacking Ico Resources. - /// - /// - /// The stream from which to read the ico directory entries, typically an EXE or DLL file stream. - /// The header that provides information about the number of images and their properties. - /// An array of objects representing the ico directory entries read from the executable file stream. - public static IIcoDirectoryEntry[] ReadFromEXEStream(Stream stream, IcoHeader icoHeader) - { - var positionStart = stream.Position; - - int byteSize = 14 * icoHeader.ImageCount; - var entries = new IIcoDirectoryEntry[icoHeader.ImageCount]; - - Span entriesBuffer = stackalloc byte[byteSize]; - stream.Read(entriesBuffer); - ReadOnlySpan entriesBufferSpan = entriesBuffer; - - for (int i = 0; i < icoHeader.ImageCount; i++) - { - var offset = i * 14; - ushort resourceID = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 2)); - - if (icoHeader.ImageType == 1) - { - entries[i] = new IconDirectoryEntry - { - Width = entriesBufferSpan[offset], - Height = entriesBufferSpan[offset + 1], - ColorCount = entriesBufferSpan[offset + 2], - Reserved = entriesBufferSpan[offset + 3], - Planes = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 4, 2)), - ColorDepth = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 6, 2)), - ImageSize = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 8, 4)), - ImageOffset = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 2)) - }; - } - else if (icoHeader.ImageType == 2) - { - entries[i] = new CursorDirectoryEntry() - { - Width = entriesBufferSpan[offset], - Height = entriesBufferSpan[offset + 1], - Planes = 0, - HotspotX = 0, - HotspotY = 0, - ColorDepth = 0, - RealImageOffset = 0, - ImageSize = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 8, 4)), - ImageOffset = resourceID - }; - - } - } - - return entries; - } -} +public interface IIcoGroup : IIcoGroup { } /// /// Represents a collection of ICO images, typically extracted from an executable file (EXE) or a dynamic link library (DLL). diff --git a/Ico.Reader/Data/IcoData.cs b/Ico.Reader/Data/IcoData.cs index b2b8179..d71f072 100644 --- a/Ico.Reader/Data/IcoData.cs +++ b/Ico.Reader/Data/IcoData.cs @@ -1,8 +1,10 @@ -using Ico.Reader.Data.IcoSources; +using System.Collections.ObjectModel; + +using Ico.Reader.Data.Source; using Ico.Reader.Decoder; -using System.Collections.ObjectModel; namespace Ico.Reader.Data; + public class IcoData { /// @@ -235,10 +237,10 @@ public async Task SaveImageAsync(ImageReference imageReference, string path) /// The index of the image with the highest calculated quality. public int PreferredImageIndex(float colorBitWeight = 2f) { - int bestIndex = 0; + var bestIndex = 0; float bestQuality = 0; - for (int i = 0; i < ImageReferences.Count; i++) + for (var i = 0; i < ImageReferences.Count; i++) { var imageReference = ImageReferences[i]; var qualityScore = imageReference.Width * imageReference.Height * (imageReference.BitCount * colorBitWeight); @@ -274,14 +276,14 @@ public int PreferredImageIndex(string groupName, IcoType icoType, float colorBit /// The index of the preferred image within the global image reference list (). public int PreferredImageIndex(IIcoGroup group, float colorBitWeight = 2f) { - int bestIndex = 0; + var bestIndex = 0; float bestQuality = 0; var imageReferences = new ImageReference[group.Size]; - for (int i = 0; i < group.Size; i++) + for (var i = 0; i < group.Size; i++) imageReferences[i] = GetImageReference(group, i); - for (int i = 0; i < imageReferences.Length; i++) + for (var i = 0; i < imageReferences.Length; i++) { var imageReference = imageReferences[i]; var qualityScore = imageReference.Width * imageReference.Height * (imageReference.BitCount * colorBitWeight); @@ -339,7 +341,7 @@ public async Task SaveGroupToDirectory(IIcoGroup group, string path) Directory.CreateDirectory(groupPath); var imageReferences = GetImageReferences(group); - for (int i = 0; i < imageReferences.Count; i++) + for (var i = 0; i < imageReferences.Count; i++) { var imageReference = imageReferences[i]; var filePath = GetImageFilePath(imageReference, groupPath); @@ -361,7 +363,7 @@ public async Task SaveAllImagesToDirectory(string path) var saveImageTasks = new List(); - for (int i = 0; i < ImageReferences.Count; i++) + for (var i = 0; i < ImageReferences.Count; i++) { var imageReference = ImageReferences[i]; var pathFile = GetImageFilePath(imageReference, rootPath); @@ -440,16 +442,13 @@ public ReadOnlyCollection GetImageReferences(IIcoGroup group) #endregion - /// /// Retrieves the ICO groups with the specified name. /// /// The name of the ICO group. /// public IEnumerable GetGroups(string groupName) - { - return Groups.Where(x => x.Name == groupName); - } + => Groups.Where(x => x.Name == groupName); /// /// Retrieves the ICO group with the specified name. @@ -459,9 +458,7 @@ public IEnumerable GetGroups(string groupName) /// /// public IIcoGroup GetGroup(string groupName, IcoType icoType) - { - return Groups.FirstOrDefault(x => x.IcoType == icoType && x.Name == groupName) ?? throw new InvalidOperationException("Group reference not found"); - } + => Groups.FirstOrDefault(x => x.IcoType == icoType && x.Name == groupName) ?? throw new InvalidOperationException("Group reference not found"); /// /// Retrieves the with the specified name. @@ -470,9 +467,7 @@ public IIcoGroup GetGroup(string groupName, IcoType icoType) /// /// public IconGroup GetIconGroup(string groupName) - { - return IconGroups.FirstOrDefault(x => x.Name == groupName) ?? throw new InvalidOperationException("Group reference not found"); - } + => IconGroups.FirstOrDefault(x => x.Name == groupName) ?? throw new InvalidOperationException("Group reference not found"); /// /// Retrieves the with the specified name. @@ -481,9 +476,7 @@ public IconGroup GetIconGroup(string groupName) /// /// public CursorGroup GetCursorGroup(string groupName) - { - return CursorGroups.FirstOrDefault(x => x.Name == groupName) ?? throw new InvalidOperationException("Group reference not found"); - } + => CursorGroups.FirstOrDefault(x => x.Name == groupName) ?? throw new InvalidOperationException("Group reference not found"); public override string ToString() => $"{Name} Groups[{Groups.Count}] Images[{ImageReferences.Count}] ({OriginFileType})"; diff --git a/Ico.Reader/Data/IcoHeader.cs b/Ico.Reader/Data/IcoHeader.cs index b8aa99f..1202a08 100644 --- a/Ico.Reader/Data/IcoHeader.cs +++ b/Ico.Reader/Data/IcoHeader.cs @@ -1,5 +1,7 @@ using System.Runtime.InteropServices; +using CommonShims; + namespace Ico.Reader.Data; /// /// Represents the header of an ico resource, detailing the ico's format and the number of images it contains. diff --git a/Ico.Reader/Data/IcoImageFormat.cs b/Ico.Reader/Data/IcoImageFormat.cs index 24d4264..376a256 100644 --- a/Ico.Reader/Data/IcoImageFormat.cs +++ b/Ico.Reader/Data/IcoImageFormat.cs @@ -1,4 +1,5 @@ namespace Ico.Reader.Data; + public enum IcoImageFormat { BMP, diff --git a/Ico.Reader/Data/IcoOriginFileType.cs b/Ico.Reader/Data/IcoOriginFileType.cs index 2513d69..1161ce6 100644 --- a/Ico.Reader/Data/IcoOriginFileType.cs +++ b/Ico.Reader/Data/IcoOriginFileType.cs @@ -1,4 +1,5 @@ namespace Ico.Reader.Data; + public enum IcoOriginFileType { /// diff --git a/Ico.Reader/Data/IcoReaderConfiguration.cs b/Ico.Reader/Data/IcoReaderConfiguration.cs index 9b66248..2bf0b8f 100644 --- a/Ico.Reader/Data/IcoReaderConfiguration.cs +++ b/Ico.Reader/Data/IcoReaderConfiguration.cs @@ -1,6 +1,7 @@ using Ico.Reader.Decoder; namespace Ico.Reader.Data; + public sealed class IcoReaderConfiguration { public IIcoDecoder IcoDecoder { get; set; } = new IcoDecoder(); diff --git a/Ico.Reader/Data/IconGroup.cs b/Ico.Reader/Data/IconGroup.cs index bd2eb20..c7ad547 100644 --- a/Ico.Reader/Data/IconGroup.cs +++ b/Ico.Reader/Data/IconGroup.cs @@ -1,5 +1,9 @@ using System.Runtime.InteropServices; +using CommonShims; + +using Ico.Reader.Utils; + namespace Ico.Reader.Data; /// /// Represents a collection of within an ICO file. @@ -36,7 +40,7 @@ IIcoDirectoryEntry[] IIcoGroup.DirectoryEntries { var cursorDirectoryEntries = new IconDirectoryEntry[value.Length]; - for (int i = 0; i < value.Length; i++) + for (var i = 0; i < value.Length; i++) { if (value[i] is IconDirectoryEntry entry) cursorDirectoryEntries[i] = entry; @@ -58,14 +62,14 @@ public IconDirectoryEntry[] ReadEntriesFromEXEStream(Stream stream, IcoHeader ic var positionStart = stream.Position; - int byteSize = 14 * icoHeader.ImageCount; + var byteSize = 14 * icoHeader.ImageCount; var entries = new IconDirectoryEntry[icoHeader.ImageCount]; Span entriesBuffer = stackalloc byte[byteSize]; stream.Read(entriesBuffer); ReadOnlySpan entriesBufferSpan = entriesBuffer; - for (int i = 0; i < icoHeader.ImageCount; i++) + for (var i = 0; i < icoHeader.ImageCount; i++) { var offset = i * 14; entries[i] = new IconDirectoryEntry @@ -84,5 +88,6 @@ public IconDirectoryEntry[] ReadEntriesFromEXEStream(Stream stream, IcoHeader ic return entries; } - IIcoDirectoryEntry[] IIcoGroup.ReadEntriesFromEXEStream(Stream stream, IcoHeader icoHeader) => IIcoGroup.ReadFromEXEStream(stream, icoHeader); + IIcoDirectoryEntry[] IIcoGroup.ReadEntriesFromEXEStream(Stream stream, IcoHeader icoHeader) + => IcoGroupUtils.ReadFromEXEStream(stream, icoHeader); } \ No newline at end of file diff --git a/Ico.Reader/Data/ImageReference.cs b/Ico.Reader/Data/ImageReference.cs index 6fb5aa5..16b6b0f 100644 --- a/Ico.Reader/Data/ImageReference.cs +++ b/Ico.Reader/Data/ImageReference.cs @@ -1,4 +1,6 @@ -using Ico.Reader.Decoder; +using CommonShims; + +using Ico.Reader.Decoder; namespace Ico.Reader.Data; diff --git a/Ico.Reader/Data/Source/IDataSource.cs b/Ico.Reader/Data/Source/IDataSource.cs index 2eb616c..b3660d8 100644 --- a/Ico.Reader/Data/Source/IDataSource.cs +++ b/Ico.Reader/Data/Source/IDataSource.cs @@ -1,4 +1,5 @@ -namespace Ico.Reader.Data.IcoSources; +namespace Ico.Reader.Data.Source; + public interface IDataSource { Stream GetStream(bool useAsync = false); diff --git a/Ico.Reader/Data/Source/MemorySource.cs b/Ico.Reader/Data/Source/MemorySource.cs index d72cf78..9bcce74 100644 --- a/Ico.Reader/Data/Source/MemorySource.cs +++ b/Ico.Reader/Data/Source/MemorySource.cs @@ -1,9 +1,13 @@ -using Ico.Reader.Data.IcoSources; +namespace Ico.Reader.Data.Source; -namespace Ico.Reader.Data.Source; public sealed class MemorySource : IDataSource { private readonly byte[] _data; - public MemorySource(byte[] data) => _data = data; + + public MemorySource(byte[] data) + { + _data = data; + } + public Stream GetStream(bool useAsync = false) => new MemoryStream(_data, false); } diff --git a/Ico.Reader/Data/Source/PathSource.cs b/Ico.Reader/Data/Source/PathSource.cs index 54e56a3..ef1befa 100644 --- a/Ico.Reader/Data/Source/PathSource.cs +++ b/Ico.Reader/Data/Source/PathSource.cs @@ -1,6 +1,5 @@ -using Ico.Reader.Data.IcoSources; +namespace Ico.Reader.Data.Source; -namespace Ico.Reader.Data.Source; public sealed class PathSource : IDataSource { private readonly string _originPath; @@ -15,5 +14,5 @@ public PathSource(string path) _originPath = path; } - public Stream GetStream(bool useAsync = false) => new FileStream(_originPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0, useAsync); + public Stream GetStream(bool useAsync = false) => new FileStream(_originPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, useAsync); } diff --git a/Ico.Reader/Data/Source/StreamBufferSource.cs b/Ico.Reader/Data/Source/StreamBufferSource.cs index 3c4f9ef..7b07b8f 100644 --- a/Ico.Reader/Data/Source/StreamBufferSource.cs +++ b/Ico.Reader/Data/Source/StreamBufferSource.cs @@ -1,13 +1,13 @@ -using Ico.Reader.Data.IcoSources; +namespace Ico.Reader.Data.Source; -namespace Ico.Reader.Data.Source; public sealed class StreamBufferSource : IDataSource { private readonly byte[] _buffer; public StreamBufferSource(Stream sourceStream) { - if (sourceStream is null) throw new ArgumentNullException(nameof(sourceStream)); + if (sourceStream is null) + throw new ArgumentNullException(nameof(sourceStream)); if (!sourceStream.CanRead) throw new ArgumentException("The source stream must be readable.", nameof(sourceStream)); diff --git a/Ico.Reader/Data/Source/StreamSource.cs b/Ico.Reader/Data/Source/StreamSource.cs index a0ddf35..c43b13a 100644 --- a/Ico.Reader/Data/Source/StreamSource.cs +++ b/Ico.Reader/Data/Source/StreamSource.cs @@ -1,6 +1,5 @@ -using Ico.Reader.Data.IcoSources; +namespace Ico.Reader.Data.Source; -namespace Ico.Reader.Data.Source; public sealed class StreamSource : IDataSource { private readonly Stream _sourceStream; diff --git a/Ico.Reader/Decoder/IcoDecoder.cs b/Ico.Reader/Decoder/IcoDecoder.cs index 97fdd1a..a56e961 100644 --- a/Ico.Reader/Decoder/IcoDecoder.cs +++ b/Ico.Reader/Decoder/IcoDecoder.cs @@ -8,19 +8,24 @@ namespace Ico.Reader.Decoder; public sealed class IcoDecoder : IIcoDecoder { private readonly IDecoder[] _decoders; - public IcoDecoder(IEnumerable decoders) => _decoders = decoders.ToArray(); + + public IcoDecoder(IEnumerable decoders) + { + _decoders = [.. decoders]; + } + public IcoDecoder() { - _decoders = new IDecoder[] - { + _decoders = + [ new BmpDecoder(), new PngDecoder() - }; + ]; } public byte[] GetImageData(ReadOnlySpan imageData, IcoImageFormat format) { - for (int i = 0; i < _decoders.Length; i++) + for (var i = 0; i < _decoders.Length; i++) { if (_decoders[i].SupportedFormat == format) return _decoders[i].Decode(imageData); @@ -31,7 +36,7 @@ public byte[] GetImageData(ReadOnlySpan imageData, IcoImageFormat format) public ImageReference? ReadImageMetadata(ReadOnlySpan imageData) { - for (int i = 0; i < _decoders.Length; i++) + for (var i = 0; i < _decoders.Length; i++) { if (_decoders[i].IsSupported(imageData)) return _decoders[i].ReadImageMetadata(imageData); @@ -42,7 +47,7 @@ public byte[] GetImageData(ReadOnlySpan imageData, IcoImageFormat format) public IcoImageFormat ReadFormat(ReadOnlySpan imageData) { - for (int i = 0; i < _decoders.Length; i++) + for (var i = 0; i < _decoders.Length; i++) { if (_decoders[i].IsSupported(imageData)) return _decoders[i].SupportedFormat; diff --git a/Ico.Reader/Decoder/IcoPeDecoder.cs b/Ico.Reader/Decoder/IcoPeDecoder.cs index 6e9d080..c726d03 100644 --- a/Ico.Reader/Decoder/IcoPeDecoder.cs +++ b/Ico.Reader/Decoder/IcoPeDecoder.cs @@ -1,13 +1,18 @@ -using Ico.Reader.Data; +using System.Runtime.InteropServices; + +using CommonShims; + +using Ico.Reader.Data; + using PeDecoder; using PeDecoder.Models; -using System.Runtime.InteropServices; namespace Ico.Reader.Decoder; /// public sealed class IcoPeDecoder : IIcoPeDecoder { private readonly IPeDecoder _peDecoder; + public IcoPeDecoder(IPeDecoder peDecoder) { _peDecoder = peDecoder; @@ -43,7 +48,7 @@ private void AddIcoGroups(DecodedIcoResult decodedIcoResult, ResourceDirectory r decodedIcoResult.References.Capacity = icoResource.Length; - for (int i = 0; i < icoResource.Length; i++) + for (var i = 0; i < icoResource.Length; i++) { var icoDataEntry = icoResource[i]; var fileOffset = icoDataEntry.GetFileOffset(stream, peHeader); @@ -63,7 +68,7 @@ private void AddIcoGroups(DecodedIcoResult decodedIcoResult, ResourceDirectory r if (icoResourceGroupDirectory is null) return; - for (int i = 0; i < icoResourceGroup.Length; i++) + for (var i = 0; i < icoResourceGroup.Length; i++) { var icoGroup = new IconGroup() { @@ -76,7 +81,7 @@ private void AddIcoGroups(DecodedIcoResult decodedIcoResult, ResourceDirectory r stream.Position = fileOffset + 6; var directoryEntries = icoGroup.ReadEntriesFromEXEStream(stream, icoGroup.Header).ToList(); - for (int x = 0; x < directoryEntries.Count; x++) + for (var x = 0; x < directoryEntries.Count; x++) { var reference = decodedIcoResult.References.FirstOrDefault(r => r.Id == directoryEntries[x].ImageOffset); if (reference is null) @@ -101,7 +106,7 @@ private void AddCurGroups(DecodedIcoResult decodedIcoResult, ResourceDirectory r decodedIcoResult.References.Capacity += curResource.Length; - for (int i = 0; i < curResource.Length; i++) + for (var i = 0; i < curResource.Length; i++) { var curDataEntry = curResource[i]; var fileOffset = curDataEntry.GetFileOffset(stream, peHeader); @@ -136,7 +141,7 @@ private void AddCurGroups(DecodedIcoResult decodedIcoResult, ResourceDirectory r decodedIcoResult.IcoGroups.Capacity += curResourceGroup.Length; - for (int i = 0; i < curResourceGroup.Length; i++) + for (var i = 0; i < curResourceGroup.Length; i++) { var curGroup = new CursorGroup() { @@ -148,7 +153,7 @@ private void AddCurGroups(DecodedIcoResult decodedIcoResult, ResourceDirectory r stream.Position = fileOffset + 6; var directoryEntries = curGroup.ReadEntriesFromEXEStream(stream, curGroup.Header).ToList(); - for (int x = 0; x < directoryEntries.Count; x++) + for (var x = 0; x < directoryEntries.Count; x++) { var reference = decodedIcoResult.References.FirstOrDefault(r => r.Id == directoryEntries[x].ImageOffset); if (reference is null) diff --git a/Ico.Reader/Decoder/ImageDecoder/Bmp/BmpDecoder.cs b/Ico.Reader/Decoder/ImageDecoder/Bmp/BmpDecoder.cs index a7dd32e..ed6e8fe 100644 --- a/Ico.Reader/Decoder/ImageDecoder/Bmp/BmpDecoder.cs +++ b/Ico.Reader/Decoder/ImageDecoder/Bmp/BmpDecoder.cs @@ -1,6 +1,9 @@ -using Ico.Reader.Creator; +using System.Runtime.InteropServices; + +using CommonShims; + +using Ico.Reader.Creator; using Ico.Reader.Data; -using System.Runtime.InteropServices; namespace Ico.Reader.Decoder.ImageDecoder.Bmp; /// @@ -13,7 +16,7 @@ public sealed class BmpDecoder : IDecoder /// public IcoImageFormat SupportedFormat => IcoImageFormat.BMP; - private readonly Dictionary _decoders = new(); + private readonly Dictionary _decoders = []; private readonly IPngCreator _pngCreator; /// @@ -57,7 +60,8 @@ public byte[] Decode(ReadOnlySpan data) if (!_decoders.TryGetValue(header.BitCount, out var decoder)) throw new NotSupportedException($"The bit count {header.BitCount} is not supported."); - if (header.Compression != 0) throw new NotSupportedException("Compressed BMP images are not supported yet."); + if (header.Compression != 0) + throw new NotSupportedException("Compressed BMP images are not supported yet."); var argbData = decoder.DecodeIcoBmpToRgba(data, header); diff --git a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp1Decoder.cs b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp1Decoder.cs index eeacdb2..8b32c66 100644 --- a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp1Decoder.cs +++ b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp1Decoder.cs @@ -1,39 +1,40 @@ -using Ico.Reader.Data; -using System.Drawing; +using System.Drawing; + +using Ico.Reader.Data; namespace Ico.Reader.Decoder.ImageDecoder.Bmp; + public sealed class IcoBmp1Decoder : IIcoBmpDecoder { public byte BitCountSupported => 1; public byte[] DecodeIcoBmpToRgba(ReadOnlySpan data, BMP_Info_Header header) { - int width = header.Width; - int height = header.Height / 2; + var width = header.Width; + var height = header.Height / 2; - byte[] rgbaData = new byte[width * height * 4]; - Color[] palette = CreateColorPalette(data, header); + var rgbaData = new byte[width * height * 4]; + var palette = CreateColorPalette(data, header); - int dataOffset = header.CalculateDataOffset(); - int bytesPerRowImage = (width + 7) / 8; - int imageRowPadding = (4 - (bytesPerRowImage % 4)) % 4; - int totalImageSize = (bytesPerRowImage + imageRowPadding) * height; + var dataOffset = header.CalculateDataOffset(); + var bytesPerRowImage = (width + 7) / 8; + var imageRowPadding = (4 - (bytesPerRowImage % 4)) % 4; + var totalImageSize = (bytesPerRowImage + imageRowPadding) * height; - int maskOffset = dataOffset + totalImageSize; - bool allTransparent = true; - for (int y = 0; y < height; y++) + var maskOffset = dataOffset + totalImageSize; + var allTransparent = true; + for (var y = 0; y < height; y++) { - for (int x = 0; x < width; x++) + for (var x = 0; x < width; x++) { - int pixelIndex = ((height - 1 - y) * width + x) * 4; - int byteIndex = dataOffset + y * (bytesPerRowImage + imageRowPadding) + x / 8; - int bitIndex = 7 - (x % 8); + var pixelIndex = (((height - 1 - y) * width) + x) * 4; + var byteIndex = dataOffset + (y * (bytesPerRowImage + imageRowPadding)) + (x / 8); + var bitIndex = 7 - (x % 8); - bool isSet = ((data[byteIndex] >> bitIndex) & 1) == 1; + var isSet = ((data[byteIndex] >> bitIndex) & 1) == 1; - - int maskByteIndex = maskOffset + y * (bytesPerRowImage + imageRowPadding) + x / 8; - bool isTransparent = ((data[maskByteIndex] >> bitIndex) & 1) == 1; + var maskByteIndex = maskOffset + (y * (bytesPerRowImage + imageRowPadding)) + (x / 8); + var isTransparent = ((data[maskByteIndex] >> bitIndex) & 1) == 1; rgbaData[pixelIndex] = isSet ? palette[1].R : palette[0].R; rgbaData[pixelIndex + 1] = isSet ? palette[1].G : palette[0].G; @@ -56,30 +57,26 @@ public byte[] DecodeIcoBmpToRgba(ReadOnlySpan data, BMP_Info_Header header private static void MakeImageVisible(Span rgbaData, ref Color[] palette) { var transparentColor = palette[0]; - for (int i = 0; i < rgbaData.Length; i += 4) + for (var i = 0; i < rgbaData.Length; i += 4) { - byte currentColorIndex = FindColorIndex(rgbaData, ref i, ref palette); - bool isVisible = palette[currentColorIndex] != transparentColor; + var currentColorIndex = FindColorIndex(rgbaData, ref i, ref palette); + var isVisible = palette[currentColorIndex] != transparentColor; var newColor = palette[currentColorIndex == 0 ? 1 : 0]; rgbaData[i + 0] = newColor.R; rgbaData[i + 1] = newColor.G; rgbaData[i + 2] = newColor.B; if (isVisible) - { rgbaData[i + 3] = 255; - } } } private static byte FindColorIndex(ReadOnlySpan rgbaData, ref int startIndex, ref Color[] palette) { - for (int i = 0; i < palette.Length; i++) + for (var i = 0; i < palette.Length; i++) { if (rgbaData[startIndex] == palette[i].R && rgbaData[startIndex + 1] == palette[i].G && rgbaData[startIndex + 2] == palette[i].B) - { return (byte)i; - } } throw new Exception("Color not found"); @@ -87,15 +84,15 @@ private static byte FindColorIndex(ReadOnlySpan rgbaData, ref int startInd private static Color[] CreateColorPalette(ReadOnlySpan data, BMP_Info_Header header) { - int paletteSize = header.CalculatePaletteSize(); - Color[] palette = new Color[paletteSize]; - int paletteOffset = header.Size; + var paletteSize = header.CalculatePaletteSize(); + var palette = new Color[paletteSize]; + var paletteOffset = header.Size; - for (int i = 0; i < paletteSize; i++) + for (var i = 0; i < paletteSize; i++) { - byte blue = data[paletteOffset + i * 4]; - byte green = data[paletteOffset + i * 4 + 1]; - byte red = data[paletteOffset + i * 4 + 2]; + var blue = data[paletteOffset + (i * 4)]; + var green = data[paletteOffset + (i * 4) + 1]; + var red = data[paletteOffset + (i * 4) + 2]; palette[i] = Color.FromArgb(255, red, green, blue); } diff --git a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp24Decoder.cs b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp24Decoder.cs index 7d2eb04..05b0de4 100644 --- a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp24Decoder.cs +++ b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp24Decoder.cs @@ -1,41 +1,41 @@ using Ico.Reader.Data; namespace Ico.Reader.Decoder.ImageDecoder.Bmp; + public sealed class IcoBmp24Decoder : IIcoBmpDecoder { public byte BitCountSupported => 24; public byte[] DecodeIcoBmpToRgba(ReadOnlySpan data, BMP_Info_Header header) { - int width = header.Width; - int height = header.Height / 2; - byte[] argbData = new byte[width * height * 4]; - - int dataOffset = header.Size + header.ClrUsed * 4; - int bytesPerRowImage = 3 * width; - int imageRowPadding = (4 - (bytesPerRowImage % 4)) % 4; - int totalImageSize = (bytesPerRowImage + imageRowPadding) * height; + var width = header.Width; + var height = header.Height / 2; + var argbData = new byte[width * height * 4]; - int maskOffset = dataOffset + totalImageSize; + var dataOffset = header.Size + (header.ClrUsed * 4); + var bytesPerRowImage = 3 * width; + var imageRowPadding = (4 - (bytesPerRowImage % 4)) % 4; + var totalImageSize = (bytesPerRowImage + imageRowPadding) * height; - int maskRowBytesActual = (width + 7) / 8; - int maskPadding = (4 - (maskRowBytesActual % 4)) % 4; + var maskOffset = dataOffset + totalImageSize; + var maskRowBytesActual = (width + 7) / 8; + var maskPadding = (4 - (maskRowBytesActual % 4)) % 4; - for (int y = 0; y < height; y++) + for (var y = 0; y < height; y++) { - for (int x = 0; x < width; x++) + for (var x = 0; x < width; x++) { - int pixelIndex = ((height - 1 - y) * width + x) * 4; - int dataRowOffset = dataOffset + y * (bytesPerRowImage + imageRowPadding) + x * 3; + var pixelIndex = (((height - 1 - y) * width) + x) * 4; + var dataRowOffset = dataOffset + (y * (bytesPerRowImage + imageRowPadding)) + (x * 3); argbData[pixelIndex] = data[dataRowOffset + 2]; argbData[pixelIndex + 1] = data[dataRowOffset + 1]; argbData[pixelIndex + 2] = data[dataRowOffset]; - int maskByteIndex = maskOffset + y * (maskRowBytesActual + maskPadding) + x / 8; - int maskBit = 7 - (x % 8); - bool isTransparent = ((data[maskByteIndex] >> maskBit) & 1) == 1; + var maskByteIndex = maskOffset + (y * (maskRowBytesActual + maskPadding)) + (x / 8); + var maskBit = 7 - (x % 8); + var isTransparent = ((data[maskByteIndex] >> maskBit) & 1) == 1; if (!isTransparent) argbData[pixelIndex + 3] = 255; diff --git a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp32Decoder.cs b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp32Decoder.cs index f30c14c..9cf5079 100644 --- a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp32Decoder.cs +++ b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp32Decoder.cs @@ -1,24 +1,25 @@ using Ico.Reader.Data; namespace Ico.Reader.Decoder.ImageDecoder.Bmp; + public sealed class IcoBmp32Decoder : IIcoBmpDecoder { public byte BitCountSupported => 32; public byte[] DecodeIcoBmpToRgba(ReadOnlySpan data, BMP_Info_Header header) { - int width = header.Width; - int height = header.Height / 2; - byte[] pixels = new byte[width * height * 4]; - int offset = header.Size + header.ClrUsed * 4; + var width = header.Width; + var height = header.Height / 2; + var pixels = new byte[width * height * 4]; + var offset = header.Size + (header.ClrUsed * 4); - for (int y = height - 1; y >= 0; y--) + for (var y = height - 1; y >= 0; y--) { - for (int x = 0; x < width; x++) + for (var x = 0; x < width; x++) { - int i = offset + ((height - 1 - y) * width + x) * 4; + var i = offset + ((((height - 1 - y) * width) + x) * 4); - int pixelIndex = (y * width + x) * 4; + var pixelIndex = ((y * width) + x) * 4; pixels[pixelIndex] = data[i + 2]; pixels[pixelIndex + 1] = data[i + 1]; diff --git a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp4Decoder.cs b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp4Decoder.cs index 216150e..98632d6 100644 --- a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp4Decoder.cs +++ b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp4Decoder.cs @@ -1,38 +1,40 @@ -using Ico.Reader.Data; -using System.Drawing; +using System.Drawing; + +using Ico.Reader.Data; namespace Ico.Reader.Decoder.ImageDecoder.Bmp; + public sealed class IcoBmp4Decoder : IIcoBmpDecoder { public byte BitCountSupported => 4; public byte[] DecodeIcoBmpToRgba(ReadOnlySpan data, BMP_Info_Header header) { - int width = header.Width; - int height = header.Height / 2; + var width = header.Width; + var height = header.Height / 2; - Color[] palette = CreateColorPalette(data, header); - byte[] rgbaData = new byte[width * height * 4]; + var palette = CreateColorPalette(data, header); + var rgbaData = new byte[width * height * 4]; - int dataOffset = header.CalculateDataOffset(); + var dataOffset = header.CalculateDataOffset(); - int bytesPerRow = (width + 1) / 2; - int paddedBytesPerRow = (bytesPerRow + 3) & ~3; - int maskOffset = dataOffset + paddedBytesPerRow * height; + var bytesPerRow = (width + 1) / 2; + var paddedBytesPerRow = (bytesPerRow + 3) & ~3; + var maskOffset = dataOffset + (paddedBytesPerRow * height); - int maskRowBytes = (width + 7) / 8; - int maskPadding = (4 - (maskRowBytes % 4)) % 4; - bool allTransparent = true; + var maskRowBytes = (width + 7) / 8; + var maskPadding = (4 - (maskRowBytes % 4)) % 4; + var allTransparent = true; - for (int y = 0; y < height; y++) + for (var y = 0; y < height; y++) { - for (int x = 0; x < width; x++) + for (var x = 0; x < width; x++) { - int dataIndex = dataOffset + y * paddedBytesPerRow + (x / 2); - bool isHighNibble = x % 2 == 0; - byte nibbleValue = isHighNibble ? (byte)(data[dataIndex] >> 4) : (byte)(data[dataIndex] & 0x0F); + var dataIndex = dataOffset + (y * paddedBytesPerRow) + (x / 2); + var isHighNibble = x % 2 == 0; + var nibbleValue = isHighNibble ? (byte)(data[dataIndex] >> 4) : (byte)(data[dataIndex] & 0x0F); - int pixelIndex = ((height - 1 - y) * width + x) * 4; - bool isTransparent = IsPixelTransparent(x, height - 1 - y, data, maskOffset, maskRowBytes, maskPadding, height); + var pixelIndex = (((height - 1 - y) * width) + x) * 4; + var isTransparent = IsPixelTransparent(x, height - 1 - y, data, maskOffset, maskRowBytes, maskPadding, height); rgbaData[pixelIndex] = palette[nibbleValue].R; rgbaData[pixelIndex + 1] = palette[nibbleValue].G; @@ -55,10 +57,10 @@ public byte[] DecodeIcoBmpToRgba(ReadOnlySpan data, BMP_Info_Header header private static void MakeImageVisible(Span rgbaData, ref Color[] palette) { var transparentColor = palette[0]; - for (int i = 0; i < rgbaData.Length; i += 4) + for (var i = 0; i < rgbaData.Length; i += 4) { - byte currentColorIndex = FindColorIndex(rgbaData, ref i, ref palette); - bool isVisible = palette[currentColorIndex] != transparentColor; + var currentColorIndex = FindColorIndex(rgbaData, ref i, ref palette); + var isVisible = palette[currentColorIndex] != transparentColor; var newColor = palette[currentColorIndex == 0 ? 1 : 0]; rgbaData[i + 0] = newColor.R; rgbaData[i + 1] = newColor.G; @@ -73,12 +75,10 @@ private static void MakeImageVisible(Span rgbaData, ref Color[] palette) private static byte FindColorIndex(ReadOnlySpan rgbaData, ref int startIndex, ref Color[] palette) { - for (int i = 0; i < palette.Length; i++) + for (var i = 0; i < palette.Length; i++) { if (rgbaData[startIndex] == palette[i].R && rgbaData[startIndex + 1] == palette[i].G && rgbaData[startIndex + 2] == palette[i].B) - { return (byte)i; - } } throw new Exception("Color not found"); @@ -86,23 +86,23 @@ private static byte FindColorIndex(ReadOnlySpan rgbaData, ref int startInd private static bool IsPixelTransparent(int x, int y, ReadOnlySpan data, int maskOffset, int maskRowBytes, int maskPadding, int height) { - int maskY = height - 1 - y; - int maskByteIndex = maskOffset + maskY * (maskRowBytes + maskPadding) + x / 8; - int maskBit = 7 - x % 8; - return (data[maskByteIndex] >> maskBit & 1) == 1; + var maskY = height - 1 - y; + var maskByteIndex = maskOffset + (maskY * (maskRowBytes + maskPadding)) + (x / 8); + var maskBit = 7 - (x % 8); + return ((data[maskByteIndex] >> maskBit) & 1) == 1; } private static Color[] CreateColorPalette(ReadOnlySpan data, BMP_Info_Header header) { - int paletteSize = header.CalculatePaletteSize(); - Color[] palette = new Color[paletteSize]; - int paletteOffset = header.Size; + var paletteSize = header.CalculatePaletteSize(); + var palette = new Color[paletteSize]; + var paletteOffset = header.Size; - for (int i = 0; i < paletteSize; i++) + for (var i = 0; i < paletteSize; i++) { - byte blue = data[paletteOffset + i * 4]; - byte green = data[paletteOffset + i * 4 + 1]; - byte red = data[paletteOffset + i * 4 + 2]; + var blue = data[paletteOffset + (i * 4)]; + var green = data[paletteOffset + (i * 4) + 1]; + var red = data[paletteOffset + (i * 4) + 2]; palette[i] = Color.FromArgb(255, red, green, blue); } diff --git a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp8Decoder.cs b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp8Decoder.cs index 12c9c89..2ed63f0 100644 --- a/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp8Decoder.cs +++ b/Ico.Reader/Decoder/ImageDecoder/Bmp/IcoBmp8Decoder.cs @@ -1,45 +1,47 @@ -using Ico.Reader.Data; -using System.Drawing; +using System.Drawing; + +using Ico.Reader.Data; namespace Ico.Reader.Decoder.ImageDecoder.Bmp; + public sealed class IcoBmp8Decoder : IIcoBmpDecoder { public byte BitCountSupported => 8; public byte[] DecodeIcoBmpToRgba(ReadOnlySpan data, BMP_Info_Header header) { - int width = header.Width; - int height = header.Height / 2; + var width = header.Width; + var height = header.Height / 2; - Color[] palette = CreateColorPalette(data, header); - byte[] rgbaData = new byte[width * height * 4]; + var palette = CreateColorPalette(data, header); + var rgbaData = new byte[width * height * 4]; - int bytesPerRowImage = width; - int imageRowPadding = (4 - (bytesPerRowImage % 4)) % 4; - int totalImageSize = (bytesPerRowImage + imageRowPadding) * height; + var bytesPerRowImage = width; + var imageRowPadding = (4 - (bytesPerRowImage % 4)) % 4; + var totalImageSize = (bytesPerRowImage + imageRowPadding) * height; - int dataOffset = CalculateDataOffset(header); - int maskOffset = dataOffset + totalImageSize; + var dataOffset = CalculateDataOffset(header); + var maskOffset = dataOffset + totalImageSize; - int maskRowBytesActual = (width + 7) / 8; - int maskPadding = (4 - (maskRowBytesActual % 4)) % 4; - bool allTransparent = true; - for (int y = 0; y < height; y++) + var maskRowBytesActual = (width + 7) / 8; + var maskPadding = (4 - (maskRowBytesActual % 4)) % 4; + var allTransparent = true; + for (var y = 0; y < height; y++) { - for (int x = 0; x < width; x++) + for (var x = 0; x < width; x++) { - int pixelIndex = ((height - 1 - y) * width + x) * 4; - int dataRowOffset = dataOffset + y * (width + imageRowPadding); + var pixelIndex = (((height - 1 - y) * width) + x) * 4; + var dataRowOffset = dataOffset + (y * (width + imageRowPadding)); - byte paletteIndex = data[dataRowOffset + x]; + var paletteIndex = data[dataRowOffset + x]; rgbaData[pixelIndex] = palette[paletteIndex].R; rgbaData[pixelIndex + 1] = palette[paletteIndex].G; rgbaData[pixelIndex + 2] = palette[paletteIndex].B; - int maskByteIndex = maskOffset + y * (maskRowBytesActual + maskPadding) + x / 8; - int maskBit = 7 - (x % 8); - bool isTransparent = true; + var maskByteIndex = maskOffset + (y * (maskRowBytesActual + maskPadding)) + (x / 8); + var maskBit = 7 - (x % 8); + var isTransparent = true; if (data.Length > maskByteIndex) { isTransparent = ((data[maskByteIndex] >> maskBit) & 1) == 1; @@ -62,34 +64,29 @@ public byte[] DecodeIcoBmpToRgba(ReadOnlySpan data, BMP_Info_Header header public int CalculateDataOffset(BMP_Info_Header header) => header.Size + (header.ClrUsed > 0 ? header.ClrUsed : (1 << header.BitCount)) * 4; - private static void MakeImageVisible(Span rgbaData, ref Color[] palette) { var transparentColor = palette[0]; - for (int i = 0; i < rgbaData.Length; i += 4) + for (var i = 0; i < rgbaData.Length; i += 4) { - byte currentColorIndex = FindColorIndex(rgbaData, ref i, ref palette); - bool isVisible = palette[currentColorIndex] != transparentColor; + var currentColorIndex = FindColorIndex(rgbaData, ref i, ref palette); + var isVisible = palette[currentColorIndex] != transparentColor; var newColor = palette[currentColorIndex == 0 ? 1 : 0]; rgbaData[i + 0] = newColor.R; rgbaData[i + 1] = newColor.G; rgbaData[i + 2] = newColor.B; if (isVisible) - { rgbaData[i + 3] = 255; - } } } private static byte FindColorIndex(ReadOnlySpan rgbaData, ref int startIndex, ref Color[] palette) { - for (int i = 0; i < palette.Length; i++) + for (var i = 0; i < palette.Length; i++) { if (rgbaData[startIndex] == palette[i].R && rgbaData[startIndex + 1] == palette[i].G && rgbaData[startIndex + 2] == palette[i].B) - { return (byte)i; - } } throw new Exception("Color not found"); @@ -97,15 +94,15 @@ private static byte FindColorIndex(ReadOnlySpan rgbaData, ref int startInd private static Color[] CreateColorPalette(ReadOnlySpan data, BMP_Info_Header header) { - int paletteSize = header.CalculatePaletteSize(); - Color[] palette = new Color[paletteSize]; - int paletteOffset = header.Size; + var paletteSize = header.CalculatePaletteSize(); + var palette = new Color[paletteSize]; + var paletteOffset = header.Size; - for (int i = 0; i < paletteSize; i++) + for (var i = 0; i < paletteSize; i++) { - byte blue = data[paletteOffset + i * 4]; - byte green = data[paletteOffset + i * 4 + 1]; - byte red = data[paletteOffset + i * 4 + 2]; + var blue = data[paletteOffset + (i * 4)]; + var green = data[paletteOffset + (i * 4) + 1]; + var red = data[paletteOffset + (i * 4) + 2]; palette[i] = Color.FromArgb(255, red, green, blue); } diff --git a/Ico.Reader/Decoder/ImageDecoder/PngDecoder.cs b/Ico.Reader/Decoder/ImageDecoder/PngDecoder.cs index e2e6e83..8538e6d 100644 --- a/Ico.Reader/Decoder/ImageDecoder/PngDecoder.cs +++ b/Ico.Reader/Decoder/ImageDecoder/PngDecoder.cs @@ -1,5 +1,6 @@ -using Ico.Reader.Data; -using System.Buffers.Binary; +using System.Buffers.Binary; + +using Ico.Reader.Data; namespace Ico.Reader.Decoder.ImageDecoder; /// @@ -8,7 +9,7 @@ namespace Ico.Reader.Decoder.ImageDecoder; /// public sealed class PngDecoder : IDecoder { - private static readonly byte[] _pngSignature = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + private static readonly byte[] _pngSignature = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; /// /// Specifies that this decoder supports the PNG image format. diff --git a/Ico.Reader/Extensions/BinaryWriterExtensions.cs b/Ico.Reader/Extensions/BinaryWriterExtensions.cs index a46735e..b70fba5 100644 --- a/Ico.Reader/Extensions/BinaryWriterExtensions.cs +++ b/Ico.Reader/Extensions/BinaryWriterExtensions.cs @@ -1,16 +1,19 @@ using System.Buffers.Binary; +using CommonShims; + namespace Ico.Reader.Extensions; -internal static class BinaryWriterExtensions + +public static class BinaryWriterExtensions { - internal static void WriteUInt32BigEndian(this BinaryWriter writer, uint value) + public static void WriteUInt32BigEndian(this BinaryWriter writer, uint value) { Span span = stackalloc byte[4]; BinaryPrimitives.WriteUInt32BigEndian(span, value); writer.Write(span); } - internal static void WriteInt32BigEndian(this BinaryWriter writer, int value) + public static void WriteInt32BigEndian(this BinaryWriter writer, int value) { Span span = stackalloc byte[4]; BinaryPrimitives.WriteInt32BigEndian(span, value); diff --git a/Ico.Reader/Ico.Reader.csproj b/Ico.Reader/Ico.Reader.csproj index 55fcab3..e444456 100644 --- a/Ico.Reader/Ico.Reader.csproj +++ b/Ico.Reader/Ico.Reader.csproj @@ -1,62 +1,71 @@  - - netstandard2.1 - enable - enable - true - 10 - Ico.Reader - Ico.Reader - `Ico.Reader` is a cross-platform library designed for extracting icons and cursors from `.ico` and `.cur` files, as well as from embedded resources** within `.exe` and `.dll` files. - CwistSilver - CwistSilver - https://github.com/CwistSilver/Ico.Reader - README.md - LICENSE.txt - icon.png - git - Icon;Reader;ICO;EXE;DLL - true - - - - - True - \ - - - True - \ - - - True - \ - - - - - - - - - - All - None - - - - - - - - - - - + + netstandard2.0 + enable + true + Latest + Ico.Reader + Ico.Reader + `Ico.Reader` is a cross-platform library designed for extracting icons and cursors from `.ico` and `.cur` files, as well as from embedded resources** within `.exe` and `.dll` files. + CwistSilver + CwistSilver + https://github.com/CwistSilver/Ico.Reader + README.md + LICENSE.txt + icon.png + git + Icon;Reader;ICO;EXE;DLL + true + https://github.com/CwistSilver/Ico.Reader + enable + true + + + + embedded + + + + embedded + + + + + True + \ + + + True + \ + + + True + \ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ico.Reader/IcoReader.cs b/Ico.Reader/IcoReader.cs index 56ce466..839911c 100644 --- a/Ico.Reader/IcoReader.cs +++ b/Ico.Reader/IcoReader.cs @@ -1,6 +1,6 @@ using Ico.Reader.Data; -using Ico.Reader.Data.IcoSources; using Ico.Reader.Data.Source; +using Ico.Reader.Utils; namespace Ico.Reader; /// @@ -14,12 +14,18 @@ public sealed class IcoReader /// Initializes a new instance of the icoReader class with a specific configuration. /// /// The configuration settings to use for reading ico's. - public IcoReader(IcoReaderConfiguration icoReaderConfiguration) => _icoReaderConfiguration = icoReaderConfiguration; + public IcoReader(IcoReaderConfiguration icoReaderConfiguration) + { + _icoReaderConfiguration = icoReaderConfiguration; + } /// /// Initializes a new instance of the icoReader class with default configuration settings. /// - public IcoReader() => _icoReaderConfiguration = new IcoReaderConfiguration(); + public IcoReader() + { + _icoReaderConfiguration = new IcoReaderConfiguration(); + } /// /// Reads ico data from a specified file path. @@ -32,7 +38,7 @@ public sealed class IcoReader return null; var icoSource = new PathSource(filePath); - using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0, false); + using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); var IcoData = ReadFromStream(stream, icoSource); if (IcoData is null) @@ -135,7 +141,7 @@ public sealed class IcoReader return null; } - decodedicoResult.IcoGroups[0].DirectoryEntries = IIcoDirectoryEntry.ReadEntriesFromStream(stream, decodedicoResult.IcoGroups[0].Header!); + decodedicoResult.IcoGroups[0].DirectoryEntries = IcoDirectoryEntryUtils.ReadEntriesFromStream(stream, decodedicoResult.IcoGroups[0].Header!); decodedicoResult.References = new List(decodedicoResult.IcoGroups[0].DirectoryEntries!.Length); for (var i = 0; i < decodedicoResult.IcoGroups[0].DirectoryEntries!.Length; i++) { @@ -177,5 +183,4 @@ public sealed class IcoReader return decodedicoResult; } - } diff --git a/Ico.Reader/ServiceCollectionExtensions.cs b/Ico.Reader/ServiceCollectionExtensions.cs index 2dabd33..c1d72ac 100644 --- a/Ico.Reader/ServiceCollectionExtensions.cs +++ b/Ico.Reader/ServiceCollectionExtensions.cs @@ -3,7 +3,9 @@ using Ico.Reader.Decoder; using Ico.Reader.Decoder.ImageDecoder; using Ico.Reader.Decoder.ImageDecoder.Bmp; + using Microsoft.Extensions.DependencyInjection; + using PeDecoder; namespace Ico.Reader; diff --git a/Ico.Reader/Utils/IcoDirectoryEntryUtils.cs b/Ico.Reader/Utils/IcoDirectoryEntryUtils.cs new file mode 100644 index 0000000..d296898 --- /dev/null +++ b/Ico.Reader/Utils/IcoDirectoryEntryUtils.cs @@ -0,0 +1,78 @@ +using System.Runtime.InteropServices; + +using CommonShims; + +using Ico.Reader.Data; + +namespace Ico.Reader.Utils; + +public static class IcoDirectoryEntryUtils +{ + /// + /// Reads ico directory entries from a stream based on the specified ico header. This method is typically used when reading icos from ICO files. + /// + /// For more information, see Structure of image directory. + /// + /// + /// The stream from which to read the ico directory entries. + /// The header that provides information about the number of images. + /// An array of objects representing the ico directory entries read from the stream. + public static IIcoDirectoryEntry[] ReadEntriesFromStream(Stream stream, IcoHeader icoHeader) + { + var positionStart = stream.Position; + var byteSize = 16 * icoHeader.ImageCount; + var entries = new IIcoDirectoryEntry[icoHeader.ImageCount]; + + Span entriesBuffer = stackalloc byte[byteSize]; + stream.Read(entriesBuffer); + ReadOnlySpan entriesBufferSpan = entriesBuffer; + + for (var i = 0; i < icoHeader.ImageCount; i++) + { + var offset = i * 16; + + if (icoHeader.ImageType == 1) + { + entries[i] = new IconDirectoryEntry + { + Width = entriesBufferSpan[offset], + Height = entriesBufferSpan[offset + 1], + ColorCount = entriesBufferSpan[offset + 2], + Reserved = entriesBufferSpan[offset + 3], + Planes = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 4, 2)), + ColorDepth = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 6, 2)), + ImageSize = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 8, 4)), + ImageOffset = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 4)) + }; + } + else if (icoHeader.ImageType == 2) + { + entries[i] = new CursorDirectoryEntry + { + Width = entriesBufferSpan[offset], + Height = entriesBufferSpan[offset + 1], + Planes = 0, + HotspotX = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 4, 2)), + HotspotY = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 6, 2)), + ImageSize = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 8, 4)), + ImageOffset = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 4)) + }; + } + + entries[i].RealImageOffset = entries[i].ImageOffset; + } + + return entries; + } + + public static T[] ReadEntriesFromEXEStream(Stream stream, IcoHeader icoHeader) where T : class, IIcoDirectoryEntry + { + if (icoHeader.ImageType == IconDirectoryEntry.ImageType && typeof(T) != typeof(IconDirectoryEntry)) + throw new Exception("Invalid entry type"); + else if (icoHeader.ImageType == CursorDirectoryEntry.ImageType && typeof(T) != typeof(CursorDirectoryEntry)) + throw new Exception("Invalid entry type"); + + var entries = ReadEntriesFromStream(stream, icoHeader); + return (T[])entries; + } +} diff --git a/Ico.Reader/Utils/IcoGroupUtils.cs b/Ico.Reader/Utils/IcoGroupUtils.cs new file mode 100644 index 0000000..eae07c8 --- /dev/null +++ b/Ico.Reader/Utils/IcoGroupUtils.cs @@ -0,0 +1,70 @@ +using System.Runtime.InteropServices; + +using CommonShims; + +using Ico.Reader.Data; + +namespace Ico.Reader.Utils; + +public static class IcoGroupUtils +{ + /// + /// Reads ico directory entries from an executable file stream based on the specified ico header. This method adapts the process for the differences in ico data layout within EXE or DLL files. + /// + /// For more information, see Hacking Ico Resources. + /// + /// + /// The stream from which to read the ico directory entries, typically an EXE or DLL file stream. + /// The header that provides information about the number of images and their properties. + /// An array of objects representing the ico directory entries read from the executable file stream. + public static IIcoDirectoryEntry[] ReadFromEXEStream(Stream stream, IcoHeader icoHeader) + { + var positionStart = stream.Position; + + var byteSize = 14 * icoHeader.ImageCount; + var entries = new IIcoDirectoryEntry[icoHeader.ImageCount]; + + Span entriesBuffer = stackalloc byte[byteSize]; + stream.Read(entriesBuffer); + ReadOnlySpan entriesBufferSpan = entriesBuffer; + + for (var i = 0; i < icoHeader.ImageCount; i++) + { + var offset = i * 14; + var resourceID = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 2)); + + if (icoHeader.ImageType == 1) + { + entries[i] = new IconDirectoryEntry + { + Width = entriesBufferSpan[offset], + Height = entriesBufferSpan[offset + 1], + ColorCount = entriesBufferSpan[offset + 2], + Reserved = entriesBufferSpan[offset + 3], + Planes = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 4, 2)), + ColorDepth = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 6, 2)), + ImageSize = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 8, 4)), + ImageOffset = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 12, 2)) + }; + } + else if (icoHeader.ImageType == 2) + { + entries[i] = new CursorDirectoryEntry() + { + Width = entriesBufferSpan[offset], + Height = entriesBufferSpan[offset + 1], + Planes = 0, + HotspotX = 0, + HotspotY = 0, + ColorDepth = 0, + RealImageOffset = 0, + ImageSize = MemoryMarshal.Read(entriesBufferSpan.Slice(offset + 8, 4)), + ImageOffset = resourceID + }; + + } + } + + return entries; + } +} diff --git a/PeDecoder/Models/COFF_Header.cs b/PeDecoder/Models/COFF_Header.cs index 1dc8e34..db37728 100644 --- a/PeDecoder/Models/COFF_Header.cs +++ b/PeDecoder/Models/COFF_Header.cs @@ -1,5 +1,7 @@ using System.Runtime.InteropServices; +using CommonShims; + namespace PeDecoder.Models; /// diff --git a/PeDecoder/Models/Characteristics.cs b/PeDecoder/Models/Characteristics.cs index f88a165..90ab51f 100644 --- a/PeDecoder/Models/Characteristics.cs +++ b/PeDecoder/Models/Characteristics.cs @@ -1,4 +1,5 @@ namespace PeDecoder.Models; + [Flags] public enum Characteristics : ushort { diff --git a/PeDecoder/Models/ImageDataDirectory.cs b/PeDecoder/Models/ImageDataDirectory.cs index 8af2b87..535aa6a 100644 --- a/PeDecoder/Models/ImageDataDirectory.cs +++ b/PeDecoder/Models/ImageDataDirectory.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; namespace PeDecoder.Models; + public class ImageDataDirectory { public uint VirtualAddress { get; set; } diff --git a/PeDecoder/Models/MZ_Header.cs b/PeDecoder/Models/MZ_Header.cs index f6aa412..8a652ff 100644 --- a/PeDecoder/Models/MZ_Header.cs +++ b/PeDecoder/Models/MZ_Header.cs @@ -1,6 +1,9 @@ using System.Buffers.Binary; +using CommonShims; + namespace PeDecoder.Models; + public struct MZ_Header { public char[] Signature; diff --git a/PeDecoder/Models/MachineType.cs b/PeDecoder/Models/MachineType.cs index 8583a0c..7751f8d 100644 --- a/PeDecoder/Models/MachineType.cs +++ b/PeDecoder/Models/MachineType.cs @@ -1,4 +1,5 @@ namespace PeDecoder.Models; + public enum MachineType : ushort { IMAGE_FILE_MACHINE_UNKNOWN = 0x0, diff --git a/PeDecoder/Models/MagicNumber.cs b/PeDecoder/Models/MagicNumber.cs index 8d83858..d2da3cb 100644 --- a/PeDecoder/Models/MagicNumber.cs +++ b/PeDecoder/Models/MagicNumber.cs @@ -1,4 +1,5 @@ namespace PeDecoder.Models; + public enum MagicNumber : ushort { PE32 = 0x10b, diff --git a/PeDecoder/Models/OptionalHeader.cs b/PeDecoder/Models/OptionalHeader.cs index a43e38e..230a8da 100644 --- a/PeDecoder/Models/OptionalHeader.cs +++ b/PeDecoder/Models/OptionalHeader.cs @@ -1,5 +1,7 @@ using System.Runtime.InteropServices; +using CommonShims; + namespace PeDecoder.Models; public class OptionalHeader @@ -36,7 +38,6 @@ public class OptionalHeader public uint LoaderFlags { get; set; } public uint NumberOfRvaAndSizes { get; set; } - public ImageDataDirectory? ExportTable { get; set; } public ImageDataDirectory? ImportTable { get; set; } public ImageDataDirectory? ResourceTable { get; set; } diff --git a/PeDecoder/Models/PE_Header.cs b/PeDecoder/Models/PE_Header.cs index c658f09..79d058b 100644 --- a/PeDecoder/Models/PE_Header.cs +++ b/PeDecoder/Models/PE_Header.cs @@ -1,6 +1,9 @@ using System.Runtime.InteropServices; +using CommonShims; + namespace PeDecoder.Models; + public class PE_Header { public const uint PeHeaderSize = 24; diff --git a/PeDecoder/Models/ResourceDataEntry.cs b/PeDecoder/Models/ResourceDataEntry.cs index 79ece58..2f0a202 100644 --- a/PeDecoder/Models/ResourceDataEntry.cs +++ b/PeDecoder/Models/ResourceDataEntry.cs @@ -1,6 +1,9 @@ using System.Runtime.InteropServices; +using CommonShims; + namespace PeDecoder.Models; + public class ResourceDataEntry { public uint ID { get; set; } diff --git a/PeDecoder/Models/ResourceDirectory.cs b/PeDecoder/Models/ResourceDirectory.cs index a755cc7..1769303 100644 --- a/PeDecoder/Models/ResourceDirectory.cs +++ b/PeDecoder/Models/ResourceDirectory.cs @@ -1,6 +1,9 @@ using System.Runtime.InteropServices; +using CommonShims; + namespace PeDecoder.Models; + public class ResourceDirectory { public string Name { get; set; } = string.Empty; @@ -12,8 +15,8 @@ public class ResourceDirectory public ushort NumberOfNamedEntries { get; set; } public ushort NumberOfIdEntries { get; set; } - public List Subdirectories { get; set; } = new List(); - public List DataEntries { get; set; } = new List(); + public List Subdirectories { get; set; } = []; + public List DataEntries { get; set; } = []; public override string ToString() => $"{Name} [DataEntries: {DataEntries.Count}] [Subdirectories: {Subdirectories.Count}]"; @@ -68,11 +71,9 @@ private static ResourceDirectory ReadResourceDirectory(Stream stream, long virtu var resourceDirectory = ReadResourceDirectoryBase(stream, virtualAddress, level); var resourceDirectoryEntries = ResourceDirectoryEntry.ReadFromStream(stream, resourceDirectory, virtualAddress); - foreach (var entry in resourceDirectoryEntries) ProcessResourceDirectoryEntry(stream, resourceDirectory, entry, rsrcSection); - return resourceDirectory; } @@ -128,7 +129,7 @@ private static void SetName(Stream stream, ResourceDirectory directory, Resource else { directory.Name = entry.IntegerID.ToString(); - for (int i = 0; i < directory.DataEntries.Count; i++) + for (var i = 0; i < directory.DataEntries.Count; i++) directory.DataEntries[i].ID = entry.IntegerID; } diff --git a/PeDecoder/Models/ResourceDirectoryEntry.cs b/PeDecoder/Models/ResourceDirectoryEntry.cs index 60ba14a..7579108 100644 --- a/PeDecoder/Models/ResourceDirectoryEntry.cs +++ b/PeDecoder/Models/ResourceDirectoryEntry.cs @@ -1,7 +1,10 @@ using System.Runtime.InteropServices; using System.Text; +using CommonShims; + namespace PeDecoder.Models; + public class ResourceDirectoryEntry { public const byte ResourceDirectoryEntrySize = 8; @@ -17,7 +20,7 @@ public string DecodeName(Stream resourceStream, long streamOffset) Span lengthBytes = stackalloc byte[2]; resourceStream.Read(lengthBytes); - ushort nameLength = MemoryMarshal.Read(lengthBytes); + var nameLength = MemoryMarshal.Read(lengthBytes); Span nameBytes = stackalloc byte[nameLength * 2]; resourceStream.Read(nameBytes); @@ -36,7 +39,7 @@ public static ResourceDirectoryEntry[] ReadFromStream(Stream stream, ResourceDir ReadOnlySpan readOnlyData = data; - for (int i = 0; i < total; i++) + for (var i = 0; i < total; i++) { var entrySpan = readOnlyData.Slice(ResourceDirectoryEntrySize * i, ResourceDirectoryEntrySize); entries[i] = new ResourceDirectoryEntry(); @@ -59,7 +62,7 @@ private static void AddNameOrId(ResourceDirectoryEntry entry, ReadOnlySpan private static void AddSubdirectoryOrDataEntry(ResourceDirectoryEntry entry, ReadOnlySpan entrySpan) { var offset = MemoryMarshal.Read(entrySpan.Slice(4, 4)); - bool isSubdirectoryOffset = (offset & 0x80000000) != 0; + var isSubdirectoryOffset = (offset & 0x80000000) != 0; if (isSubdirectoryOffset) entry.SubdirectoryOffset = offset & 0x7FFFFFFF; else diff --git a/PeDecoder/Models/SectionFlag.cs b/PeDecoder/Models/SectionFlag.cs index 4f349a3..95ca507 100644 --- a/PeDecoder/Models/SectionFlag.cs +++ b/PeDecoder/Models/SectionFlag.cs @@ -1,4 +1,5 @@ namespace PeDecoder.Models; + [Flags] public enum SectionFlag : uint { diff --git a/PeDecoder/Models/SectionHeader.cs b/PeDecoder/Models/SectionHeader.cs index e93f783..38be647 100644 --- a/PeDecoder/Models/SectionHeader.cs +++ b/PeDecoder/Models/SectionHeader.cs @@ -1,13 +1,15 @@ using System.Runtime.InteropServices; using System.Text; +using CommonShims; + namespace PeDecoder.Models; // https://learn.microsoft.com/en-gb/windows/win32/debug/pe-format?redirectedfrom=MSDN#section-table-section-headers public class SectionHeader { public const uint SectionSize = 40; - public string Name { get; set; } + public string Name { get; set; } = null!; public uint VirtualSize { get; set; } public uint VirtualAddress { get; set; } public uint SizeOfRawData { get; set; } @@ -33,14 +35,14 @@ public static SectionHeader[] ReadFromStream(Stream stream, PE_Header peHeader) ReadOnlySpan readOnlyData = data; Span nameChars = stackalloc char[8]; - for (int i = 0; i < peHeader.NumberOfSections; i++) + for (var i = 0; i < peHeader.NumberOfSections; i++) { var sectionIndex = i * (int)SectionSize; sections[i] = new SectionHeader(); Encoding.UTF8.GetChars(readOnlyData.Slice(sectionIndex, 8), nameChars); - sections[i].Name = new string(nameChars).Trim('\0'); + sections[i].Name = nameChars.ToStringFast().Trim('\0'); sections[i].VirtualSize = MemoryMarshal.Read(readOnlyData.Slice(sectionIndex + 8, 4)); sections[i].VirtualAddress = MemoryMarshal.Read(readOnlyData.Slice(sectionIndex + 12, 4)); diff --git a/PeDecoder/PeDecoder.cs b/PeDecoder/PeDecoder.cs index f795390..6c0719b 100644 --- a/PeDecoder/PeDecoder.cs +++ b/PeDecoder/PeDecoder.cs @@ -1,6 +1,7 @@ using PeDecoder.Models; namespace PeDecoder; + public interface IPeDecoder { MZ_Header DecodeMZ(Stream stream); diff --git a/PeDecoder/PeDecoder.csproj b/PeDecoder/PeDecoder.csproj index 9c25bac..ba57151 100644 --- a/PeDecoder/PeDecoder.csproj +++ b/PeDecoder/PeDecoder.csproj @@ -1,12 +1,28 @@  - - netstandard2.1 - enable - enable - true - 10 - false - + + netstandard2.0 + enable + true + Latest + enable + false + + + + embedded + + + + embedded + + + + + + + + + diff --git a/README.md b/README.md index c1150a4..cf8c039 100644 --- a/README.md +++ b/README.md @@ -18,26 +18,26 @@ ### Reading icoData ```cs -var IcoReader = new IcoReader(); +var icoReader = new IcoReader(); // Reading from a file path (most memory-efficient) -IcoData iconFromPath = IcoReader.Read("path/to/your/icon.ico"); -IcoData cursorFromPath = IcoReader.Read("path/to/your/cursor.cur"); -IcoData icoFromPathDll = IcoReader.Read("path/to/your/user32.dll"); -IcoData icoFromPathEXE = IcoReader.Read("path/to/your/regedit.exe"); +IcoData iconFromPath = icoReader.Read("path/to/your/icon.ico"); +IcoData cursorFromPath = icoReader.Read("path/to/your/cursor.cur"); +IcoData icoFromPathDll = icoReader.Read("path/to/your/user32.dll"); +IcoData icoFromPathEXE = icoReader.Read("path/to/your/regedit.exe"); // Reading from a byte array byte[] icoBytes = File.ReadAllBytes("path/to/your/icon.ico"); -IcoData icoFromBytes = IcoReader.Read(icoBytes); +IcoData icoFromBytes = icoReader.Read(icoBytes); // Reading from a stream (copies the stream for independent access) using var stream = File.OpenRead("path/to/your/icon.ico") -IcoData icoFromStream = IcoReader.Read(stream: stream, copyStream: true); +IcoData icoFromStream = icoReader.Read(stream: stream, copyStream: true); // Reading from a stream without copying (as efficient as direct file reading) using (var streamOrigin = File.OpenRead("path/to/your/icon.ico")) { - IcoData icoFromStreamDirect = IcoReader.Read(stream: streamOrigin, copyStream: false); + IcoData icoFromStreamDirect = icoReader.Read(stream: streamOrigin, copyStream: false); // ✅ This is as memory-efficient as reading directly from a file. // 🔴 WARNING: All images must be accessed before closing the stream, // otherwise an error will occur. @@ -123,9 +123,13 @@ You can retrieve the preferred image either **globally** (from all groups) or ** ```cs // Selecting the preferred image globally from all groups int preferredIndex = icoData.PreferredImageIndex(colorBitWeight: 2f); +var imageRef = icoData.ImageReferences[imageIndex]; +var imageData = icoData.GetImage(imageRef); // Selecting the preferred image from a specific group int preferredGroupIndex = icoData.PreferredImageIndex(selectedGroup, colorBitWeight: 2f); +var imageRef = icoData.ImageReferences[preferredGroupIndex]; +var imageData = icoData.GetImage(imageRef); ``` ## Dependency Injection Support