diff --git a/tools/ico_extractor/IconExtractor.cs b/tools/ico_extractor/IconExtractor.cs
new file mode 100644
index 0000000..21b4624
--- /dev/null
+++ b/tools/ico_extractor/IconExtractor.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.IO;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace AppStore.Tools.IconExtractor
+{
+ public static class IconExtractor
+ {
+ ///
+ /// 获取ICO文件中的实际尺寸列表
+ ///
+ public static List GetIconDimensions(string filePath)
+ {
+ var sizes = new List();
+ try
+ {
+ using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+ using (var reader = new BinaryReader(fs))
+ {
+ // 读取ICO文件头
+ reader.ReadUInt16(); // 保留字段
+ ushort type = reader.ReadUInt16(); // 1=ICO, 2=CUR
+ ushort count = reader.ReadUInt16(); // 图像数量
+
+ if (type != 1) return sizes; // 不是ICO文件
+
+ // 读取每个图像目录项
+ for (int i = 0; i < count; i++)
+ {
+ byte width = reader.ReadByte();
+ byte height = reader.ReadByte();
+ reader.ReadBytes(14); // 跳过其他字段
+
+ // 宽度/高度为0表示256像素
+ int size = width == 0 ? 256 : width;
+ sizes.Add(size);
+ }
+ }
+ }
+ catch
+ {
+ // 忽略所有错误,返回空列表
+ }
+ return sizes;
+ }
+ [DllImport("shell32.dll", CharSet = CharSet.Auto)]
+ private static extern int ExtractIconEx(
+ string lpszFile,
+ int nIconIndex,
+ [AllowNull] IntPtr[] phiconLarge,
+ [AllowNull] IntPtr[] phiconSmall,
+ int nIcons);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern bool DestroyIcon(IntPtr handle);
+
+ ///
+ /// 从文件中提取图标
+ ///
+ /// 文件路径
+ /// 图标索引
+ /// 是否提取大图标
+ /// 提取的图标
+ public static Icon ExtractIconFromFile(string filePath, int iconIndex = 0)
+ {
+ IntPtr[] hIcons = new IntPtr[1];
+ int extractedCount = ExtractIconEx(filePath, iconIndex, hIcons, null, 1);
+
+ if (extractedCount <= 0 || hIcons[0] == IntPtr.Zero)
+ throw new FileNotFoundException("无法从文件中提取图标");
+
+ // 直接返回原始图标
+ Icon icon = (Icon)Icon.FromHandle(hIcons[0]).Clone();
+ DestroyIcon(hIcons[0]);
+ return icon;
+ }
+
+ ///
+ /// 将图标保存为文件
+ ///
+ /// 图标对象
+ /// 输出路径
+ public static void SaveIconToFile(Icon icon, string outputPath)
+ {
+ using (FileStream fs = new FileStream(outputPath, FileMode.Create))
+ {
+ icon.Save(fs);
+ }
+ }
+
+ ///
+ /// 获取文件中的图标数量
+ ///
+ /// 文件路径
+ /// 图标数量
+ public static int GetIconCount(string filePath)
+ {
+ return ExtractIconEx(filePath, -1, null, null, 0);
+ }
+ }
+}
diff --git a/tools/ico_extractor/IconExtractorConstants.cs b/tools/ico_extractor/IconExtractorConstants.cs
new file mode 100644
index 0000000..f5def1d
--- /dev/null
+++ b/tools/ico_extractor/IconExtractorConstants.cs
@@ -0,0 +1,19 @@
+namespace AppStore.Tools.IconExtractor
+{
+ public static class IconExtractorConstants
+ {
+ public const string FileFilter = "可执行文件|*.exe;*.dll;*.ocx;*.cpl|所有文件|*.*";
+ public const string SaveFilter = "图标文件|*.ico|位图文件|*.bmp|PNG文件|*.png";
+
+ public static readonly int[] SupportedSizes = { 16, 24, 32, 48, 64, 128, 256, 512 };
+ public static readonly int DefaultExtractSize = 256;
+
+ public const string ErrorNoIconsFound = "文件不包含任何图标";
+ public const string ErrorExtractionFailed = "图标提取失败";
+ public const string ErrorInvalidIndex = "无效的图标索引";
+ public const string ErrorFileNotFound = "文件未找到";
+
+ public const int MaxRecentFiles = 5;
+ public const int DefaultPreviewSize = 128;
+ }
+}
diff --git a/tools/ico_extractor/IconExtractorForm.cs b/tools/ico_extractor/IconExtractorForm.cs
new file mode 100644
index 0000000..0b5dcc7
--- /dev/null
+++ b/tools/ico_extractor/IconExtractorForm.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+using System.IO;
+using AppStore.Tools.IconExtractor;
+
+namespace AppStore.Tools.IconExtractor
+{
+ public class IconExtractorForm : Form
+ {
+ private Button btnBrowse = new Button();
+ private Button btnSave = new Button();
+ private NumericUpDown numIconIndex = new NumericUpDown();
+ private PictureBox picIcon = new PictureBox();
+ private Label lblStatus = new Label();
+ private TextBox txtFilePath = new TextBox();
+ private ComboBox cmbIconSize = new ComboBox();
+ private string currentFilePath = string.Empty;
+
+ public IconExtractorForm()
+ {
+ this.Text = "图标提取器";
+ this.Size = new Size(500, 550);
+ this.StartPosition = FormStartPosition.CenterScreen;
+ InitializeComponents();
+ }
+
+ private void InitializeComponents()
+ {
+ // 文件路径文本框
+ txtFilePath.Location = new Point(20, 20);
+ txtFilePath.Size = new Size(300, 25);
+ txtFilePath.ReadOnly = true;
+ this.Controls.Add(txtFilePath);
+
+ // 浏览按钮
+ btnBrowse.Text = "浏览...";
+ btnBrowse.Location = new Point(330, 20);
+ btnBrowse.Size = new Size(80, 25);
+ btnBrowse.Click += BtnBrowse_Click;
+ this.Controls.Add(btnBrowse);
+
+ // 图标索引标签
+ Label lblIndex = new Label();
+ lblIndex.Text = "图标索引:";
+ lblIndex.Location = new Point(20, 60);
+ lblIndex.Size = new Size(80, 20);
+ this.Controls.Add(lblIndex);
+
+ // 图标索引选择器
+ numIconIndex.Location = new Point(100, 60);
+ numIconIndex.Size = new Size(80, 20);
+ numIconIndex.Minimum = 0;
+ numIconIndex.ValueChanged += NumIconIndex_ValueChanged;
+ this.Controls.Add(numIconIndex);
+
+ // 图标预览区域
+ picIcon.Location = new Point(20, 100);
+ picIcon.Size = new Size(256, 256);
+ picIcon.SizeMode = PictureBoxSizeMode.Zoom;
+ picIcon.BorderStyle = BorderStyle.FixedSingle;
+ this.Controls.Add(picIcon);
+
+ // 索引说明
+ Label lblIndexHelp = new Label();
+ lblIndexHelp.Text = "索引号表示文件中的第几个图标,从0开始";
+ lblIndexHelp.Location = new Point(20, 240);
+ lblIndexHelp.Size = new Size(300, 20);
+ this.Controls.Add(lblIndexHelp);
+
+ // 保存按钮
+ btnSave.Text = "保存图标";
+ btnSave.Location = new Point(20, 450);
+ btnSave.Size = new Size(100, 30);
+ btnSave.Click += BtnSave_Click;
+ this.Controls.Add(btnSave);
+
+ // 状态标签
+ lblStatus.Location = new Point(20, 490);
+ lblStatus.Size = new Size(400, 20);
+ lblStatus.Text = "请选择包含图标的文件";
+ this.Controls.Add(lblStatus);
+ }
+
+ private void BtnBrowse_Click(object sender, EventArgs e)
+ {
+ using (OpenFileDialog ofd = new OpenFileDialog())
+ {
+ ofd.Filter = "可执行文件|*.exe;*.dll;*.ocx;*.cpl|所有文件|*.*";
+ if (ofd.ShowDialog() == DialogResult.OK)
+ {
+ currentFilePath = ofd.FileName;
+ txtFilePath.Text = currentFilePath;
+ LoadIconInfo();
+ }
+ }
+ }
+
+ private void LoadIconInfo()
+ {
+ try
+ {
+ int iconCount = IconExtractor.GetIconCount(currentFilePath);
+ numIconIndex.Maximum = Math.Max(0, iconCount - 1);
+
+ // 如果是ICO文件,获取实际包含的尺寸
+ if (currentFilePath.EndsWith(".ico", StringComparison.OrdinalIgnoreCase))
+ {
+ var sizes = IconExtractor.GetIconDimensions(currentFilePath);
+ if (sizes.Count > 0)
+ {
+ cmbIconSize.Items.Clear();
+ foreach (var size in sizes)
+ {
+ cmbIconSize.Items.Add($"{size}x{size}");
+ }
+
+ // 默认选择最接近256的尺寸
+ int closest = sizes.OrderBy(s => Math.Abs(s - 256)).First();
+ cmbIconSize.SelectedIndex = sizes.IndexOf(closest);
+ }
+ }
+
+ lblStatus.Text = $"找到 {iconCount} 个图标";
+ ExtractAndDisplayIcon();
+ }
+ catch (Exception ex)
+ {
+ lblStatus.Text = $"错误: {ex.Message}";
+ picIcon.Image = null;
+ }
+ }
+
+ private void NumIconIndex_ValueChanged(object sender, EventArgs e)
+ {
+ if (!string.IsNullOrEmpty(currentFilePath))
+ {
+ ExtractAndDisplayIcon();
+ }
+ }
+
+ private void ExtractAndDisplayIcon()
+ {
+ try
+ {
+ Icon icon = IconExtractor.ExtractIconFromFile(currentFilePath, (int)numIconIndex.Value);
+ picIcon.Image = icon.ToBitmap();
+ lblStatus.Text = $"显示原始图标 #{numIconIndex.Value}";
+ }
+ catch (Exception ex)
+ {
+ lblStatus.Text = $"提取图标失败: {ex.Message}";
+ picIcon.Image = null;
+ }
+ }
+
+ private void BtnSave_Click(object sender, EventArgs e)
+ {
+ if (picIcon.Image == null)
+ {
+ MessageBox.Show("没有可保存的图标", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ return;
+ }
+
+ using (SaveFileDialog sfd = new SaveFileDialog())
+ {
+ sfd.Filter = "图标文件|*.ico|位图文件|*.bmp|PNG文件|*.png";
+ if (sfd.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ using (Icon icon = IconExtractor.ExtractIconFromFile(currentFilePath, (int)numIconIndex.Value))
+ {
+ IconExtractor.SaveIconToFile(icon, sfd.FileName);
+ lblStatus.Text = $"已保存原始图标到 {sfd.FileName}";
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"保存图标失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tools/ico_extractor/IconExtractorHelper.cs b/tools/ico_extractor/IconExtractorHelper.cs
new file mode 100644
index 0000000..9e3bfb7
--- /dev/null
+++ b/tools/ico_extractor/IconExtractorHelper.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Drawing;
+using System.IO;
+using System.Collections.Generic;
+
+namespace AppStore.Tools.IconExtractor
+{
+ public static class IconExtractorHelper
+ {
+ ///
+ /// 将图标转换为位图
+ ///
+ public static Bitmap ConvertIconToBitmap(Icon icon, Size size)
+ {
+ Bitmap bitmap = new Bitmap(size.Width, size.Height);
+ using (Graphics g = Graphics.FromImage(bitmap))
+ {
+ g.DrawIcon(icon, new Rectangle(0, 0, size.Width, size.Height));
+ }
+ return bitmap;
+ }
+
+ ///
+ /// 批量提取文件中的所有图标
+ ///
+ public static List ExtractAllIcons(string filePath)
+ {
+ List icons = new List();
+ int count = IconExtractor.GetIconCount(filePath);
+
+ for (int i = 0; i < count; i++)
+ {
+ try
+ {
+ Icon icon = IconExtractor.ExtractIconFromFile(filePath, i);
+ icons.Add(icon);
+ }
+ catch
+ {
+ // 忽略提取失败的图标
+ }
+ }
+
+ return icons;
+ }
+
+ ///
+ /// 将图标保存为PNG格式
+ ///
+ public static void SaveIconAsPng(Icon icon, string outputPath)
+ {
+ using (Bitmap bitmap = icon.ToBitmap())
+ {
+ bitmap.Save(outputPath, System.Drawing.Imaging.ImageFormat.Png);
+ }
+ }
+
+ ///
+ /// 检查文件是否包含图标
+ ///
+ public static bool HasIcons(string filePath)
+ {
+ try
+ {
+ return IconExtractor.GetIconCount(filePath) > 0;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/tools/ico_extractor/IconExtractorSettings.cs b/tools/ico_extractor/IconExtractorSettings.cs
new file mode 100644
index 0000000..8fb9c52
--- /dev/null
+++ b/tools/ico_extractor/IconExtractorSettings.cs
@@ -0,0 +1,123 @@
+using System;
+using System.IO;
+using System.Text.Json;
+
+namespace AppStore.Tools.IconExtractor
+{
+ public static class IconExtractorSettings
+ {
+ private static readonly string SettingsPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "kortapp-z", "icon_extractor_settings.json");
+
+ public static SettingsData CurrentSettings { get; private set; } = new SettingsData();
+
+ public class SettingsData
+ {
+ public string LastUsedDirectory { get; set; } = string.Empty;
+ public string DefaultSaveFormat { get; set; } = "ico";
+ public int DefaultIconSize { get; set; } = 128;
+ public string[] RecentFiles { get; set; } = Array.Empty();
+ }
+
+ ///
+ /// 加载设置
+ ///
+ public static void LoadSettings()
+ {
+ try
+ {
+ if (File.Exists(SettingsPath))
+ {
+ string json = File.ReadAllText(SettingsPath);
+ if (!string.IsNullOrWhiteSpace(json))
+ {
+ try
+ {
+ using (JsonDocument doc = JsonDocument.Parse(json))
+ {
+ var root = doc.RootElement;
+ CurrentSettings = new SettingsData
+ {
+ LastUsedDirectory = root.TryGetProperty("LastUsedDirectory", out var dir) ? dir.GetString() ?? string.Empty : string.Empty,
+ DefaultSaveFormat = root.TryGetProperty("DefaultSaveFormat", out var format) ? format.GetString() ?? "ico" : "ico",
+ DefaultIconSize = root.TryGetProperty("DefaultIconSize", out var size) ? size.GetInt32() : 128,
+ RecentFiles = root.TryGetProperty("RecentFiles", out var files) ?
+ JsonSerializer.Deserialize(files.GetRawText()) ?? Array.Empty() :
+ Array.Empty()
+ };
+ }
+ }
+ catch
+ {
+ CurrentSettings = new SettingsData();
+ }
+ }
+ }
+ }
+ catch
+ {
+ // 加载失败时使用默认设置
+ CurrentSettings = new SettingsData();
+ }
+ }
+
+ ///
+ /// 保存设置
+ ///
+ public static void SaveSettings()
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(SettingsPath))
+ return;
+
+ string directory = Path.GetDirectoryName(SettingsPath);
+ if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ string json = JsonSerializer.Serialize(CurrentSettings);
+ File.WriteAllText(SettingsPath, json);
+ }
+ catch
+ {
+ // 忽略保存错误
+ }
+ }
+
+ ///
+ /// 添加最近使用的文件
+ ///
+ public static void AddRecentFile(string filePath)
+ {
+ if (CurrentSettings.RecentFiles.Length >= 5)
+ {
+ var list = new List(CurrentSettings.RecentFiles);
+ list.RemoveAt(0);
+ CurrentSettings.RecentFiles = list.ToArray();
+ }
+
+ var newList = new List(CurrentSettings.RecentFiles);
+ if (!newList.Contains(filePath))
+ {
+ newList.Add(filePath);
+ CurrentSettings.RecentFiles = newList.ToArray();
+ SaveSettings();
+ }
+ }
+
+ ///
+ /// 更新最后使用的目录
+ ///
+ public static void UpdateLastUsedDirectory(string directory)
+ {
+ if (Directory.Exists(directory) && CurrentSettings.LastUsedDirectory != directory)
+ {
+ CurrentSettings.LastUsedDirectory = directory;
+ SaveSettings();
+ }
+ }
+ }
+}
diff --git a/tools/ico_extractor/IconExtractorToolCard.cs b/tools/ico_extractor/IconExtractorToolCard.cs
new file mode 100644
index 0000000..1e85f0b
--- /dev/null
+++ b/tools/ico_extractor/IconExtractorToolCard.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+using System.IO;
+
+namespace AppStore.Tools.IconExtractor
+{
+ public class IconExtractorToolCard : ToolCard
+ {
+ public IconExtractorToolCard()
+ {
+ ToolName = "图标提取器";
+ try
+ {
+ string iconPath = Path.Combine(Application.StartupPath, "img", "resource", "png", "QRcode.png");
+ if (File.Exists(iconPath))
+ {
+ ToolIcon = Image.FromFile(iconPath);
+ }
+ else
+ {
+ ToolIcon = SystemIcons.Application.ToBitmap();
+ }
+ }
+ catch
+ {
+ ToolIcon = SystemIcons.Application.ToBitmap();
+ }
+ this.ToolCardClicked += OnIconExtractorCardClicked;
+ UpdateDisplay();
+ }
+
+ private void OnIconExtractorCardClicked(object sender, EventArgs e)
+ {
+ var iconExtractorForm = new IconExtractorForm();
+ iconExtractorForm.ShowDialog();
+ }
+ }
+}