using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Windows.Forms;
namespace CrxConverterWinForms
{
internal static class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
public class MainForm : Form
{
private readonly RadioButton rbCrxToZip;
private readonly RadioButton rbZipToCrx;
private readonly TextBox txtCrxPath;
private readonly Button btnBrowseCrx;
private readonly TextBox txtZipPath;
private readonly Button btnBrowseZip;
private readonly TextBox txtFolderPath;
private readonly Button btnBrowseFolder;
private readonly Button btnConvert;
private readonly Label lblStatus;
public MainForm()
{
Text = "CRX ↔ ZIP Converter";
Width = 750;
Height = 340;
StartPosition = FormStartPosition.CenterScreen;
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
int y = 15;
int textBoxX = 80;
int textBoxW = 510;
int buttonX = 600;
int buttonW = 120;
// Режим работы
var lblMode = new Label
{
Text = "Режим:",
Location = new System.Drawing.Point(10, y),
AutoSize = true
};
Controls.Add(lblMode);
rbCrxToZip = new RadioButton
{
Text = "CRX → ZIP + папка",
Location = new System.Drawing.Point(80, y),
AutoSize = true,
Checked = true
};
rbCrxToZip.CheckedChanged += RbMode_CheckedChanged;
Controls.Add(rbCrxToZip);
rbZipToCrx = new RadioButton
{
Text = "ZIP → CRX",
Location = new System.Drawing.Point(260, y),
AutoSize = true
};
rbZipToCrx.CheckedChanged += RbMode_CheckedChanged;
Controls.Add(rbZipToCrx);
y += 35;
// CRX
var lblCrx = new Label
{
Text = "CRX:",
Location = new System.Drawing.Point(10, y),
AutoSize = true
};
Controls.Add(lblCrx);
txtCrxPath = new TextBox
{
Location = new System.Drawing.Point(textBoxX, y),
Width = textBoxW
};
Controls.Add(txtCrxPath);
btnBrowseCrx = new Button
{
Text = "Обзор...",
Location = new System.Drawing.Point(buttonX, y),
Width = buttonW
};
btnBrowseCrx.Click += BtnBrowseCrx_Click;
Controls.Add(btnBrowseCrx);
y += 35;
// ZIP
var lblZip = new Label
{
Text = "ZIP:",
Location = new System.Drawing.Point(10, y),
AutoSize = true
};
Controls.Add(lblZip);
txtZipPath = new TextBox
{
Location = new System.Drawing.Point(textBoxX, y),
Width = textBoxW
};
Controls.Add(txtZipPath);
btnBrowseZip = new Button
{
Text = "Обзор...",
Location = new System.Drawing.Point(buttonX, y),
Width = buttonW
};
btnBrowseZip.Click += BtnBrowseZip_Click;
Controls.Add(btnBrowseZip);
y += 35;
// Folder
var lblFolder = new Label
{
Text = "Папка:",
Location = new System.Drawing.Point(10, y),
AutoSize = true
};
Controls.Add(lblFolder);
txtFolderPath = new TextBox
{
Location = new System.Drawing.Point(textBoxX, y),
Width = textBoxW
};
Controls.Add(txtFolderPath);
btnBrowseFolder = new Button
{
Text = "Обзор...",
Location = new System.Drawing.Point(buttonX, y),
Width = buttonW
};
btnBrowseFolder.Click += BtnBrowseFolder_Click;
Controls.Add(btnBrowseFolder);
y += 45;
// Convert
btnConvert = new Button
{
Text = "Конвертировать",
Location = new System.Drawing.Point(260, y),
Width = 160,
Height = 30
};
btnConvert.Click += BtnConvert_Click;
Controls.Add(btnConvert);
y += 45;
// Status
lblStatus = new Label
{
Location = new System.Drawing.Point(10, y),
Width = Width - 40,
Height = 20,
AutoEllipsis = true
};
Controls.Add(lblStatus);
UpdateControlsByMode();
}
private void RbMode_CheckedChanged(object? sender, EventArgs e)
{
UpdateControlsByMode();
}
private void UpdateControlsByMode()
{
bool crxToZip = rbCrxToZip.Checked;
txtCrxPath.Enabled = crxToZip;
btnBrowseCrx.Enabled = crxToZip;
txtFolderPath.Enabled = crxToZip;
btnBrowseFolder.Enabled = crxToZip;
txtZipPath.Enabled = true;
btnBrowseZip.Enabled = true;
if (crxToZip)
{
// Подсказка: ZIP и папка используются
lblStatus.Text = "Режим: CRX → ZIP + папка";
}
else
{
// Режим ZIP → CRX: используется только ZIP и CRX
lblStatus.Text = "Режим: ZIP → CRX";
}
}
private void BtnBrowseCrx_Click(object? sender, EventArgs e)
{
using var dlg = new OpenFileDialog
{
Filter = "CRX файлы|*.crx|Все файлы|*.*",
Title = "Выберите CRX файл"
};
if (dlg.ShowDialog() == DialogResult.OK)
{
txtCrxPath.Text = dlg.FileName;
if (rbCrxToZip.Checked)
{
if (string.IsNullOrWhiteSpace(txtZipPath.Text))
txtZipPath.Text = Path.ChangeExtension(dlg.FileName, ".zip");
if (string.IsNullOrWhiteSpace(txtFolderPath.Text))
{
string baseName = Path.GetFileNameWithoutExtension(dlg.FileName);
string dir = Path.GetDirectoryName(dlg.FileName)!;
txtFolderPath.Text = Path.Combine(dir, baseName + "_extracted");
}
}
}
}
private void BtnBrowseZip_Click(object? sender, EventArgs e)
{
if (rbCrxToZip.Checked)
{
// Сохранение ZIP
using var dlg = new SaveFileDialog
{
Filter = "ZIP архив|*.zip|Все файлы|*.*",
Title = "Сохранить ZIP как",
FileName = Path.GetFileName(txtZipPath.Text)
};
if (dlg.ShowDialog() == DialogResult.OK)
{
txtZipPath.Text = dlg.FileName;
}
}
else
{
// Выбор исходного ZIP для упаковки в CRX
using var dlg = new OpenFileDialog
{
Filter = "ZIP архив|*.zip|Все файлы|*.*",
Title = "Выберите ZIP файл"
};
if (dlg.ShowDialog() == DialogResult.OK)
{
txtZipPath.Text = dlg.FileName;
if (string.IsNullOrWhiteSpace(txtCrxPath.Text))
{
string baseName = Path.GetFileNameWithoutExtension(dlg.FileName);
string dir = Path.GetDirectoryName(dlg.FileName)!;
txtCrxPath.Text = Path.Combine(dir, baseName + ".crx");
}
}
}
}
private void BtnBrowseFolder_Click(object? sender, EventArgs e)
{
using var dlg = new FolderBrowserDialog
{
Description = "Выберите папку для распаковки",
UseDescriptionForTitle = true
};
if (dlg.ShowDialog() == DialogResult.OK)
{
string crxName = Path.GetFileNameWithoutExtension(txtCrxPath.Text);
if (string.IsNullOrWhiteSpace(crxName))
crxName = "extension";
txtFolderPath.Text = Path.Combine(dlg.SelectedPath, crxName + "_extracted");
}
}
private void BtnConvert_Click(object? sender, EventArgs e)
{
if (rbCrxToZip.Checked)
ConvertCrxToZip();
else
ConvertZipToCrx();
}
private void ConvertCrxToZip()
{
string crxPath = txtCrxPath.Text.Trim();
string zipPath = txtZipPath.Text.Trim();
string folderPath = txtFolderPath.Text.Trim();
if (string.IsNullOrEmpty(crxPath))
{
MessageBox.Show(this, "Укажите путь к CRX файлу.", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (!File.Exists(crxPath))
{
MessageBox.Show(this, $"CRX файл не найден:\n{crxPath}", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (string.IsNullOrEmpty(zipPath))
zipPath = Path.ChangeExtension(crxPath, ".zip");
if (string.IsNullOrEmpty(folderPath))
{
string baseName = Path.GetFileNameWithoutExtension(crxPath);
string dir = Path.GetDirectoryName(crxPath)!;
folderPath = Path.Combine(dir, baseName + "_extracted");
}
try
{
lblStatus.Text = "Чтение CRX...";
Application.DoEvents();
byte[] zipData = ExtractZipFromCrx(crxPath);
lblStatus.Text = "Сохранение ZIP...";
Application.DoEvents();
File.WriteAllBytes(zipPath, zipData);
lblStatus.Text = "Распаковка в папку...";
Application.DoEvents();
Directory.CreateDirectory(folderPath);
ExtractZipToFolder(zipData, folderPath);
lblStatus.Text = "Готово.";
MessageBox.Show(this,
$"Конвертация завершена.\n\nZIP: {zipPath}\nПапка: {folderPath}",
"Успех",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (Exception ex)
{
lblStatus.Text = "Ошибка.";
MessageBox.Show(this,
$"Ошибка при конвертации:\n{ex.Message}",
"Ошибка",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
private void ConvertZipToCrx()
{
string zipPath = txtZipPath.Text.Trim();
string crxPath = txtCrxPath.Text.Trim();
if (string.IsNullOrEmpty(zipPath))
{
MessageBox.Show(this, "Укажите путь к ZIP файлу.", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (!File.Exists(zipPath))
{
MessageBox.Show(this, $"ZIP файл не найден:\n{zipPath}", "Ошибка",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (string.IsNullOrEmpty(crxPath))
{
string baseName = Path.GetFileNameWithoutExtension(zipPath);
string dir = Path.GetDirectoryName(zipPath)!;
crxPath = Path.Combine(dir, baseName + ".crx");
}
try
{
lblStatus.Text = "Чтение ZIP...";
Application.DoEvents();
byte[] zipData = File.ReadAllBytes(zipPath);
// Быстрая проверка, что это ZIP
if (zipData.Length < 4)
throw new InvalidDataException("ZIP файл слишком мал");
bool isZip =
(zipData[0] == 'P' && zipData[1] == 'K' && zipData[2] == 0x03 && zipData[3] == 0x04) ||
(zipData[0] == 'P' && zipData[1] == 'K' && zipData[2] == 0x01 && zipData[3] == 0x02) ||
(zipData[0] == 'P' && zipData[1] == 'K' && zipData[2] == 0x05 && zipData[3] == 0x06);
if (!isZip)
throw new InvalidDataException("Файл не похож на ZIP-архив");
lblStatus.Text = "Создание CRX...";
Application.DoEvents();
byte[] crxData = CreateCrxFromZip(zipData);
lblStatus.Text = "Сохранение CRX...";
Application.DoEvents();
File.WriteAllBytes(crxPath, crxData);
lblStatus.Text = "Готово.";
MessageBox.Show(this,
$"CRX создан:\n{crxPath}",
"Успех",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (Exception ex)
{
lblStatus.Text = "Ошибка.";
MessageBox.Show(this,
$"Ошибка при конвертации:\n{ex.Message}",
"Ошибка",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
// ---------- CRX → ZIP ----------
private static byte[] ExtractZipFromCrx(string crxPath)
{
byte[] data = File.ReadAllBytes(crxPath);
if (data.Length < 16)
throw new InvalidDataException("Файл слишком мал для CRX");
if (data[0] != 'C' || data[1] != 'r' || data[2] != '2' || data[3] != '4')
throw new InvalidDataException("Неверный CRX: отсутствует магическая сигнатура 'Cr24'");
uint version = BitConverter.ToUInt32(data, 4);
int headerSize;
if (version == 2)
{
uint pubkeyLen = BitConverter.ToUInt32(data, 8);
uint sigLen = BitConverter.ToUInt32(data, 12);
headerSize = 16 + (int)pubkeyLen + (int)sigLen;
}
else if (version == 3)
{
uint headerLen = BitConverter.ToUInt32(data, 8);
headerSize = 12 + (int)headerLen;
}
else
{
throw new InvalidDataException($"Неподдерживаемая версия CRX: {version}");
}
if (headerSize >= data.Length)
throw new InvalidDataException("Некорректный CRX: размер заголовка больше размера файла");
byte[] zipData = new byte[data.Length - headerSize];
Buffer.BlockCopy(data, headerSize, zipData, 0, zipData.Length);
if (zipData.Length < 4)
throw new InvalidDataException("CRX payload слишком мал для ZIP");
bool isZip =
(zipData[0] == 'P' && zipData[1] == 'K' && zipData[2] == 0x03 && zipData[3] == 0x04) ||
(zipData[0] == 'P' && zipData[1] == 'K' && zipData[2] == 0x01 && zipData[3] == 0x02) ||
(zipData[0] == 'P' && zipData[1] == 'K' && zipData[2] == 0x05 && zipData[3] == 0x06);
if (!isZip)
throw new InvalidDataException("CRX payload не похож на ZIP-архив");
return zipData;
}
private static void ExtractZipToFolder(byte[] zipData, string folderPath)
{
string tempZip = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".zip");
try
{
File.WriteAllBytes(tempZip, zipData);
ZipFile.ExtractToDirectory(tempZip, folderPath, overwriteFiles: true);
}
finally
{
if (File.Exists(tempZip))
File.Delete(tempZip);
}
}
// ---------- ZIP → CRX (версия 2) ----------
private static byte[] CreateCrxFromZip(byte[] zipData)
{
// Генерируем фиктивные ключ и подпись (по 512 байт, как в типичных CRX2)
// Это позволяет браузеру принять CRX как «расширение без проверки подписи»
// для локального использования (через загрузку unpacked / developer mode).
using var rng = RandomNumberGenerator.Create();
int keyLen = 512;
int sigLen = 512;
byte[] pubKey = new byte[keyLen];
byte[] signature = new byte[sigLen];
rng.GetBytes(pubKey);
rng.GetBytes(signature);
// CRX2 структура:
// 4 - magic "Cr24"
// 4 - version (2)
// 4 - pubkey_len
// 4 - sig_len
// N - pubkey
// M - signature
// K - ZIP data
byte[] header = new byte[16 + pubKey.Length + signature.Length];
// Magic
header[0] = (byte)'C';
header[1] = (byte)'r';
header[2] = (byte)'2';
header[3] = (byte)'4';
// Version = 2
BitConverter.GetBytes(2).CopyTo(header, 4);
// pubkey_len
BitConverter.GetBytes(pubKey.Length).CopyTo(header, 8);
// sig_len
BitConverter.GetBytes(signature.Length).CopyTo(header, 12);
// pubkey + signature
Buffer.BlockCopy(pubKey, 0, header, 16, pubKey.Length);
Buffer.BlockCopy(signature, 0, header, 16 + pubKey.Length, signature.Length);
byte[] crxData = new byte[header.Length + zipData.Length];
Buffer.BlockCopy(header, 0, crxData, 0, header.Length);
Buffer.BlockCopy(zipData, 0, crxData, header.Length, zipData.Length);
return crxData;
}
}
}