Files
MagSec-Encrypter/Form1.cs
Claude 4744315fdd Harden encryption and add key UX improvements
Security:
- Versioned, self-describing payload format (v2) that stores KDF
  parameters, enabling future crypto agility
- Upgrade key derivation to PBKDF2-HMAC-SHA512 at 600k iterations
  (was SHA256 at 200k)
- Bind the full header (prefix, version, KDF params, salt, nonce)
  into the AES-GCM tag as associated data so header tampering is detected
- Zero derived keys and secret bytes from memory after use
- Enforce a minimum combined key length
- Keep backwards-compatible decryption for legacy v1 payloads

UX:
- Add a Generate button that creates a strong random key with a
  transcription-friendly alphabet
- Add a Show/Hide keys toggle
- Add a live key-strength indicator
- Replace leftover Dutch file-dialog strings with English
2026-06-01 10:08:31 +00:00

1479 lines
50 KiB
C#

using System.Buffers.Binary;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace TextEncryptorApp;
public partial class Form1 : Form
{
private const int SaltSize = 16;
private const int NonceSize = 12;
private const int TagSize = 16;
private const int KeySize = 32;
private const int Pbkdf2Iterations = 600_000;
private const int LegacyPbkdf2Iterations = 200_000;
private const int MinimumKeyLength = 8;
private const byte FormatVersion = 2;
private const byte KdfSha256 = 1;
private const byte KdfSha512 = 2;
// V2 self-describing format. V1 prefixes are still decryptable for backwards compatibility.
private const string TextPrefix = "TXT2";
private const string ImagePrefix = "IMG2";
private const string LegacyTextPrefix = "TXT1";
private const string LegacyImagePrefix = "IMG1";
// prefix(4) + version(1) + kdfId(1) + iterations(4) + salt(16) + nonce(12)
private const int V2HeaderLength = 4 + 1 + 1 + 4 + SaltSize + NonceSize;
private const int LegacyHeaderLength = 4 + SaltSize + NonceSize + TagSize;
private static readonly Color VoidColor = Color.FromArgb(3, 0, 20);
private static readonly Color PanelColor = Color.FromArgb(12, 17, 39);
private static readonly Color TextMainColor = Color.FromArgb(245, 248, 255);
private static readonly Color TextMutedColor = Color.FromArgb(148, 163, 184);
private static readonly Color CyanColor = Color.FromArgb(14, 165, 233);
private static readonly Color TealColor = Color.FromArgb(45, 212, 191);
private static readonly Color IndigoColor = Color.FromArgb(99, 102, 241);
private static readonly Color EmeraldColor = Color.FromArgb(16, 185, 129);
private static readonly Color DangerColor = Color.FromArgb(244, 63, 94);
private readonly List<Star> stars = [];
private readonly List<ShootingStar> shootingStars = [];
private readonly System.Windows.Forms.Timer starfieldTimer = new() { Interval = 33 };
private readonly Random random = new();
private float nebulaPhase;
private string? selectedImagePath;
private string? selectedEncryptedImagePath;
private bool keysVisible;
public Form1()
{
InitializeComponent();
ApplyTheme();
LoadBrandImage();
ConfigureDragAndDrop();
ConfigureKeyFeedback();
InitializeStarfield();
}
private void ConfigureKeyFeedback()
{
foreach (TextBox keyBox in new[] { key1TextBox, key2TextBox, key3TextBox, key4TextBox })
{
keyBox.TextChanged += (_, _) => UpdateKeyStrength();
}
UpdateKeyStrength();
}
private void ApplyTheme()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
DoubleBuffered = true;
heroPanel.AccentColor = IndigoColor;
keyRailPanel.AccentColor = CyanColor;
textPanel.AccentColor = TealColor;
imagePanel.AccentColor = IndigoColor;
previewPanel.AccentColor = IndigoColor;
ConfigureButton(encryptTextButton, CyanColor);
ConfigureButton(decryptTextButton, TealColor);
ConfigureButton(copyTextButton, IndigoColor);
ConfigureButton(clearAllButton, Color.FromArgb(30, 35, 62), isSecondary: true);
ConfigureButton(selectImageButton, CyanColor);
ConfigureButton(encryptImageButton, TealColor);
ConfigureButton(selectEncryptedImageButton, IndigoColor);
ConfigureButton(decryptImageButton, CyanColor);
ConfigureButton(generateKeyButton, EmeraldColor);
ConfigureButton(toggleKeysButton, Color.FromArgb(36, 45, 72), isSecondary: true);
toggleKeysButton.GlowColor = CyanColor;
StyleTextBox(key1TextBox);
StyleTextBox(key2TextBox);
StyleTextBox(key3TextBox);
StyleTextBox(key4TextBox);
StyleTextBox(inputTextBox);
StyleTextBox(outputTextBox);
inputTextBox.ApplyTheme(PanelColor, TextMainColor, Color.FromArgb(18, 28, 52), Color.FromArgb(95, 108, 255));
outputTextBox.ApplyTheme(PanelColor, TextMainColor, Color.FromArgb(18, 28, 52), Color.FromArgb(65, 200, 190));
brandPictureBox.BackColor = Color.Transparent;
imagePreviewBox.BackColor = Color.Transparent;
imagePathValueLabel.ForeColor = TextMutedColor;
encryptedImagePathValueLabel.ForeColor = TextMutedColor;
sideInfoLabel.ForeColor = TextMutedColor;
heroMetaLabel.ForeColor = TextMutedColor;
imageDropHintLabel.ForeColor = TextMutedColor;
titleLabel.Font = new Font("Century Gothic", 25F, FontStyle.Bold);
heroKickerLabel.Font = new Font("Segoe UI Semibold", 9.5F, FontStyle.Bold);
subtitleLabel.Font = new Font("Segoe UI", 10.5F, FontStyle.Regular);
railTitleLabel.Font = new Font("Century Gothic", 16.5F, FontStyle.Bold);
textPanelDescription.Font = new Font("Century Gothic", 16.5F, FontStyle.Bold);
imagePanelTitle.Font = new Font("Century Gothic", 16.5F, FontStyle.Bold);
inputLabel.Font = new Font("Segoe UI Semibold", 10.5F, FontStyle.Bold);
outputLabel.Font = new Font("Segoe UI Semibold", 10.5F, FontStyle.Bold);
clearAllButton.FillColor = Color.FromArgb(36, 45, 72);
clearAllButton.GlowColor = IndigoColor;
heroPanel.AccentColor = Color.FromArgb(80, 120, 255);
keyRailPanel.AccentColor = Color.FromArgb(40, 180, 255);
textPanel.AccentColor = Color.FromArgb(60, 200, 190);
imagePanel.AccentColor = Color.FromArgb(90, 110, 255);
previewPanel.AccentColor = Color.FromArgb(80, 120, 255);
heroPanel.Padding = new Padding(0);
brandPictureBox.Padding = new Padding(0);
}
private void InitializeStarfield()
{
CreateStars();
starfieldTimer.Tick += (_, _) =>
{
AdvanceStars();
AdvanceShootingStars();
Invalidate();
};
starfieldTimer.Start();
Resize += (_, _) => CreateStars();
}
private void CreateStars()
{
stars.Clear();
if (ClientSize.Width <= 0 || ClientSize.Height <= 0)
{
return;
}
int starCount = Math.Max(70, (ClientSize.Width * ClientSize.Height) / 12000);
for (int index = 0; index < starCount; index++)
{
stars.Add(CreateStar(random.NextSingle() * ClientSize.Width, random.NextSingle() * ClientSize.Height));
}
shootingStars.Clear();
}
private Star CreateStar(float x, float y)
{
float depth = 0.45F + (random.NextSingle() * 1.35F);
return new Star(
x,
y,
depth,
0.6F + (depth * 1.5F),
65 + random.Next(125),
random.NextSingle() * MathF.Tau);
}
private void AdvanceStars()
{
if (stars.Count == 0)
{
return;
}
for (int index = 0; index < stars.Count; index++)
{
Star star = stars[index];
float nextY = star.Y + (0.18F * star.Depth);
float drift = MathF.Sin(star.Phase) * 0.12F * star.Depth;
float nextX = star.X + drift;
float nextPhase = star.Phase + (0.035F * star.Depth);
if (nextY > ClientSize.Height + 6)
{
stars[index] = CreateStar(random.NextSingle() * ClientSize.Width, -8F);
continue;
}
if (nextX < -6F)
{
nextX = ClientSize.Width + 6F;
}
else if (nextX > ClientSize.Width + 6F)
{
nextX = -6F;
}
stars[index] = star with { X = nextX, Y = nextY, Phase = nextPhase };
}
nebulaPhase += 0.0055F;
}
private void AdvanceShootingStars()
{
if (shootingStars.Count < 3 && random.NextDouble() < 0.028)
{
shootingStars.Add(new ShootingStar(
random.NextSingle() * ClientSize.Width * 0.8F,
random.NextSingle() * ClientSize.Height * 0.35F,
5.5F + (random.NextSingle() * 3.4F),
2.5F + (random.NextSingle() * 1.6F),
90F + (random.NextSingle() * 35F),
130 + random.Next(90)));
}
for (int index = shootingStars.Count - 1; index >= 0; index--)
{
ShootingStar star = shootingStars[index];
float nextX = star.X + star.Speed;
float nextY = star.Y + (star.Speed * 0.46F);
float nextLife = star.Life - 2.2F;
if (nextLife <= 0 || nextX > ClientSize.Width + 140 || nextY > ClientSize.Height + 140)
{
shootingStars.RemoveAt(index);
continue;
}
shootingStars[index] = star with { X = nextX, Y = nextY, Life = nextLife };
}
}
private void ConfigureDragAndDrop()
{
AllowDrop = true;
imagePanel.AllowDrop = true;
previewPanel.AllowDrop = true;
imagePreviewBox.AllowDrop = true;
DragEnter += SharedDragEnter;
imagePanel.DragEnter += SharedDragEnter;
previewPanel.DragEnter += SharedDragEnter;
imagePreviewBox.DragEnter += SharedDragEnter;
DragDrop += SharedDragDrop;
imagePanel.DragDrop += SharedDragDrop;
previewPanel.DragDrop += SharedDragDrop;
imagePreviewBox.DragDrop += SharedDragDrop;
}
private void ConfigureButton(NeonButton button, Color color, bool isSecondary = false)
{
button.FillColor = color;
button.GlowColor = color;
button.IsSecondary = isSecondary;
}
private void LoadBrandImage()
{
string imagePath = Path.Combine(AppContext.BaseDirectory, "magsec_logo_transparent.png");
if (!File.Exists(imagePath))
{
imagePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "magsec_logo_transparent.png"));
}
if (File.Exists(imagePath))
{
using Image original = Image.FromFile(imagePath);
brandPictureBox.Image = new Bitmap(original);
}
}
private void StyleTextBox(TextBox textBox)
{
textBox.BorderStyle = BorderStyle.FixedSingle;
textBox.BackColor = PanelColor;
textBox.ForeColor = TextMainColor;
textBox.Font = textBox.Multiline
? new Font("Segoe UI", 10.75F, FontStyle.Regular)
: new Font("Segoe UI Semibold", 10.25F, FontStyle.Regular);
}
private void StyleTextBox(StyledEditor editor)
{
editor.Font = new Font("Segoe UI", 10.75F, FontStyle.Regular);
}
private void EncryptTextButton_Click(object sender, EventArgs e)
{
try
{
string secret = BuildSecret();
if (string.IsNullOrWhiteSpace(inputTextBox.Text))
{
ShowStatus("Enter text to encrypt.", isError: true);
return;
}
byte[] encrypted = EncryptBytes(Encoding.UTF8.GetBytes(inputTextBox.Text), secret, TextPrefix);
outputTextBox.Text = Convert.ToBase64String(encrypted);
ShowStatus("Text encrypted successfully.", isError: false);
}
catch (Exception ex)
{
ShowStatus(ex.Message, isError: true);
}
}
private void DecryptTextButton_Click(object sender, EventArgs e)
{
try
{
string secret = BuildSecret();
if (string.IsNullOrWhiteSpace(inputTextBox.Text))
{
ShowStatus("Enter encrypted text to decrypt.", isError: true);
return;
}
byte[] payload = Convert.FromBase64String(inputTextBox.Text.Trim());
byte[] plain = DecryptBytes(payload, secret, TextPrefix, LegacyTextPrefix);
outputTextBox.Text = Encoding.UTF8.GetString(plain);
ShowStatus("Text decrypted successfully.", isError: false);
}
catch (FormatException)
{
ShowStatus("The encrypted text is not valid Base64.", isError: true);
}
catch (Exception ex)
{
ShowStatus(ex.Message, isError: true);
}
}
private void CopyTextButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(outputTextBox.Text))
{
ShowStatus("There is no output to copy yet.", isError: true);
return;
}
Clipboard.SetText(outputTextBox.Text);
ShowStatus("Output copied.", isError: false);
}
private void ClearAllButton_Click(object sender, EventArgs e)
{
inputTextBox.Clear();
outputTextBox.Clear();
key1TextBox.Clear();
key2TextBox.Clear();
key3TextBox.Clear();
key4TextBox.Clear();
selectedImagePath = null;
selectedEncryptedImagePath = null;
imagePreviewBox.Image = null;
imagePathValueLabel.Text = "No image selected";
encryptedImagePathValueLabel.Text = "No encrypted file selected";
ShowStatus("Workspace cleared.", isError: false);
}
private void GenerateKeyButton_Click(object sender, EventArgs e)
{
TextBox target = new[] { key1TextBox, key2TextBox, key3TextBox, key4TextBox }
.FirstOrDefault(box => string.IsNullOrWhiteSpace(box.Text)) ?? key1TextBox;
target.Text = GenerateStrongKey(24);
if (!keysVisible)
{
SetKeysVisible(true);
}
target.Focus();
ShowStatus("Strong key generated. Store it somewhere safe.", isError: false);
}
private void ToggleKeysButton_Click(object sender, EventArgs e)
{
SetKeysVisible(!keysVisible);
}
private void SetKeysVisible(bool visible)
{
keysVisible = visible;
char passwordChar = visible ? '\0' : '*';
foreach (TextBox keyBox in new[] { key1TextBox, key2TextBox, key3TextBox, key4TextBox })
{
keyBox.PasswordChar = passwordChar;
}
toggleKeysButton.Text = visible ? "Hide keys" : "Show keys";
}
private static string GenerateStrongKey(int length)
{
// Unambiguous alphabet (no 0/O/1/l/I) so generated keys are easy to transcribe.
const string alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*-_=+";
char[] result = new char[length];
for (int index = 0; index < length; index++)
{
result[index] = alphabet[RandomNumberGenerator.GetInt32(alphabet.Length)];
}
return new string(result);
}
private void UpdateKeyStrength()
{
int totalLength = new[] { key1TextBox.Text, key2TextBox.Text, key3TextBox.Text, key4TextBox.Text }
.Where(key => !string.IsNullOrWhiteSpace(key))
.Sum(key => key.Trim().Length);
(string label, Color color) = totalLength switch
{
0 => ("Strength: —", TextMutedColor),
< MinimumKeyLength => ("Strength: Weak", DangerColor),
< 16 => ("Strength: Fair", Color.FromArgb(250, 204, 21)),
< 28 => ("Strength: Strong", EmeraldColor),
_ => ("Strength: Excellent", TealColor)
};
keyStrengthLabel.Text = label;
keyStrengthLabel.ForeColor = color;
}
private void SelectImageButton_Click(object sender, EventArgs e)
{
using OpenFileDialog dialog = new()
{
Filter = "Images|*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.webp|All files|*.*",
Title = "Select an image"
};
if (dialog.ShowDialog(this) == DialogResult.OK)
{
LoadSelectedImage(dialog.FileName);
}
}
private void EncryptImageButton_Click(object sender, EventArgs e)
{
try
{
string secret = BuildSecret();
if (string.IsNullOrWhiteSpace(selectedImagePath) || !File.Exists(selectedImagePath))
{
ShowStatus("Select an image before encrypting.", isError: true);
return;
}
using SaveFileDialog dialog = new()
{
Filter = "MagSec encrypted image|*.mseimg",
FileName = $"{Path.GetFileNameWithoutExtension(selectedImagePath)}.mseimg",
Title = "Save encrypted image"
};
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
byte[] fileBytes = File.ReadAllBytes(selectedImagePath);
byte[] imagePackage = BuildImagePackage(Path.GetExtension(selectedImagePath), fileBytes);
byte[] encrypted = EncryptBytes(imagePackage, secret, ImagePrefix);
File.WriteAllBytes(dialog.FileName, encrypted);
selectedEncryptedImagePath = dialog.FileName;
encryptedImagePathValueLabel.Text = Path.GetFileName(dialog.FileName);
ShowStatus("Image encrypted and saved.", isError: false);
}
catch (Exception ex)
{
ShowStatus(ex.Message, isError: true);
}
}
private void SelectEncryptedImageButton_Click(object sender, EventArgs e)
{
using OpenFileDialog dialog = new()
{
Filter = "MagSec encrypted image|*.mseimg|All files|*.*",
Title = "Select an encrypted image"
};
if (dialog.ShowDialog(this) == DialogResult.OK)
{
LoadEncryptedImage(dialog.FileName);
}
}
private void DecryptImageButton_Click(object sender, EventArgs e)
{
try
{
string secret = BuildSecret();
if (string.IsNullOrWhiteSpace(selectedEncryptedImagePath) || !File.Exists(selectedEncryptedImagePath))
{
ShowStatus("Select an encrypted image before decrypting.", isError: true);
return;
}
byte[] encrypted = File.ReadAllBytes(selectedEncryptedImagePath);
byte[] decryptedPackage = DecryptBytes(encrypted, secret, ImagePrefix, LegacyImagePrefix);
(string extension, byte[] imageBytes) = ReadImagePackage(decryptedPackage);
using SaveFileDialog dialog = new()
{
Filter = $"Original file|*{extension}|All files|*.*",
FileName = $"{Path.GetFileNameWithoutExtension(selectedEncryptedImagePath)}_restored{extension}",
Title = "Save decrypted image"
};
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
File.WriteAllBytes(dialog.FileName, imageBytes);
imagePreviewBox.Image?.Dispose();
using MemoryStream stream = new(imageBytes);
using Image restored = Image.FromStream(stream);
imagePreviewBox.Image = new Bitmap(restored);
imagePathValueLabel.Text = Path.GetFileName(dialog.FileName);
ShowStatus("Image decrypted and saved.", isError: false);
}
catch (Exception ex)
{
ShowStatus(ex.Message, isError: true);
}
}
private string BuildSecret()
{
string[] keys =
[
key1TextBox.Text.Trim(),
key2TextBox.Text.Trim(),
key3TextBox.Text.Trim(),
key4TextBox.Text.Trim()
];
string[] filledKeys = keys.Where(key => !string.IsNullOrWhiteSpace(key)).ToArray();
if (filledKeys.Length == 0)
{
throw new InvalidOperationException("Enter at least one key.");
}
if (filledKeys.Sum(key => key.Length) < MinimumKeyLength)
{
throw new InvalidOperationException($"Use at least {MinimumKeyLength} characters across your keys for a secure result.");
}
return string.Join("|", filledKeys);
}
private static byte[] EncryptBytes(byte[] plainBytes, string secret, string prefix)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
byte[] nonce = RandomNumberGenerator.GetBytes(NonceSize);
byte[] key = DeriveKey(secret, salt, Pbkdf2Iterations, KdfSha512);
byte[] payload = new byte[V2HeaderLength + TagSize + plainBytes.Length];
Encoding.ASCII.GetBytes(prefix).CopyTo(payload, 0);
payload[4] = FormatVersion;
payload[5] = KdfSha512;
BinaryPrimitives.WriteInt32BigEndian(payload.AsSpan(6, 4), Pbkdf2Iterations);
salt.CopyTo(payload, 10);
nonce.CopyTo(payload, 10 + SaltSize);
// The whole header (prefix, version, KDF params, salt, nonce) is bound into the
// authentication tag as associated data so tampering with it is detected on decrypt.
ReadOnlySpan<byte> associatedData = payload.AsSpan(0, V2HeaderLength);
Span<byte> tag = payload.AsSpan(V2HeaderLength, TagSize);
Span<byte> cipherBytes = payload.AsSpan(V2HeaderLength + TagSize);
try
{
using var aes = new AesGcm(key, TagSize);
aes.Encrypt(nonce, plainBytes, cipherBytes, tag, associatedData);
}
finally
{
CryptographicOperations.ZeroMemory(key);
}
return payload;
}
private static byte[] DecryptBytes(byte[] payload, string secret, string expectedPrefix, string legacyPrefix)
{
if (payload.Length < 4)
{
throw new InvalidOperationException("The encrypted content is incomplete.");
}
string prefix = Encoding.ASCII.GetString(payload, 0, 4);
if (string.Equals(prefix, expectedPrefix, StringComparison.Ordinal))
{
return DecryptV2(payload, secret);
}
if (string.Equals(prefix, legacyPrefix, StringComparison.Ordinal))
{
return DecryptLegacy(payload, secret);
}
throw new InvalidOperationException("Unknown encryption format.");
}
private static byte[] DecryptV2(byte[] payload, string secret)
{
if (payload.Length < V2HeaderLength + TagSize)
{
throw new InvalidOperationException("The encrypted content is incomplete.");
}
byte version = payload[4];
if (version != FormatVersion)
{
throw new InvalidOperationException("Unsupported encryption version.");
}
byte kdfId = payload[5];
int iterations = BinaryPrimitives.ReadInt32BigEndian(payload.AsSpan(6, 4));
if (iterations < 1 || iterations > 10_000_000)
{
throw new InvalidOperationException("Invalid key derivation parameters.");
}
byte[] salt = payload[10..(10 + SaltSize)];
byte[] nonce = payload[(10 + SaltSize)..V2HeaderLength];
byte[] tag = payload[V2HeaderLength..(V2HeaderLength + TagSize)];
byte[] cipherBytes = payload[(V2HeaderLength + TagSize)..];
byte[] plainBytes = new byte[cipherBytes.Length];
byte[] key = DeriveKey(secret, salt, iterations, kdfId);
try
{
using var aes = new AesGcm(key, TagSize);
aes.Decrypt(nonce, cipherBytes, tag, plainBytes, payload.AsSpan(0, V2HeaderLength));
}
catch (CryptographicException)
{
CryptographicOperations.ZeroMemory(plainBytes);
throw new InvalidOperationException("The keys do not match or the data is corrupted.");
}
finally
{
CryptographicOperations.ZeroMemory(key);
}
return plainBytes;
}
private static byte[] DecryptLegacy(byte[] payload, string secret)
{
if (payload.Length <= LegacyHeaderLength)
{
throw new InvalidOperationException("The encrypted content is incomplete.");
}
byte[] salt = payload[4..(4 + SaltSize)];
byte[] nonce = payload[(4 + SaltSize)..(4 + SaltSize + NonceSize)];
byte[] tag = payload[(4 + SaltSize + NonceSize)..LegacyHeaderLength];
byte[] cipherBytes = payload[LegacyHeaderLength..];
byte[] plainBytes = new byte[cipherBytes.Length];
byte[] key = DeriveKey(secret, salt, LegacyPbkdf2Iterations, KdfSha256);
try
{
using var aes = new AesGcm(key, TagSize);
aes.Decrypt(nonce, cipherBytes, tag, plainBytes);
}
catch (CryptographicException)
{
CryptographicOperations.ZeroMemory(plainBytes);
throw new InvalidOperationException("The keys do not match or the data is corrupted.");
}
finally
{
CryptographicOperations.ZeroMemory(key);
}
return plainBytes;
}
private static byte[] BuildImagePackage(string extension, byte[] imageBytes)
{
byte[] extensionBytes = Encoding.UTF8.GetBytes(extension);
using MemoryStream stream = new();
using BinaryWriter writer = new(stream, Encoding.UTF8, leaveOpen: true);
writer.Write((ushort)extensionBytes.Length);
writer.Write(extensionBytes);
writer.Write(imageBytes.Length);
writer.Write(imageBytes);
writer.Flush();
return stream.ToArray();
}
private static (string extension, byte[] imageBytes) ReadImagePackage(byte[] package)
{
using MemoryStream stream = new(package);
using BinaryReader reader = new(stream, Encoding.UTF8, leaveOpen: true);
int extensionLength = reader.ReadUInt16();
string extension = Encoding.UTF8.GetString(reader.ReadBytes(extensionLength));
int imageLength = reader.ReadInt32();
byte[] imageBytes = reader.ReadBytes(imageLength);
if (imageBytes.Length != imageLength)
{
throw new InvalidOperationException("The encrypted image file is incomplete.");
}
return (extension, imageBytes);
}
private static byte[] DeriveKey(string secret, byte[] salt, int iterations, byte kdfId)
{
HashAlgorithmName hash = kdfId == KdfSha512 ? HashAlgorithmName.SHA512 : HashAlgorithmName.SHA256;
byte[] secretBytes = Encoding.UTF8.GetBytes(secret);
try
{
return Rfc2898DeriveBytes.Pbkdf2(secretBytes, salt, iterations, hash, KeySize);
}
finally
{
CryptographicOperations.ZeroMemory(secretBytes);
}
}
private void ShowStatus(string message, bool isError)
{
heroMetaLabel.Text = message;
heroMetaLabel.ForeColor = isError ? Color.FromArgb(255, 205, 214) : Color.FromArgb(176, 230, 230, 240);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using LinearGradientBrush backgroundBrush = new(
ClientRectangle,
Color.FromArgb(4, 8, 22),
Color.FromArgb(9, 16, 38),
90F);
e.Graphics.FillRectangle(backgroundBrush, ClientRectangle);
Rectangle upperGlow = new(-180, -120, 580, 380);
using GraphicsPath upperPath = new();
upperPath.AddEllipse(upperGlow);
using PathGradientBrush upperBrush = new(upperPath)
{
CenterColor = Color.FromArgb(58 + (int)(18 * MathF.Sin(nebulaPhase)), 56, 189, 248),
SurroundColors = [Color.Transparent]
};
e.Graphics.FillEllipse(upperBrush, upperGlow);
Rectangle lowerGlow = new(ClientSize.Width - 430, ClientSize.Height - 340, 500, 320);
using GraphicsPath lowerPath = new();
lowerPath.AddEllipse(lowerGlow);
using PathGradientBrush lowerBrush = new(lowerPath)
{
CenterColor = Color.FromArgb(44 + (int)(14 * MathF.Cos(nebulaPhase * 1.2F)), 94, 92, 230),
SurroundColors = [Color.Transparent]
};
e.Graphics.FillEllipse(lowerBrush, lowerGlow);
Rectangle centerGlow = new((ClientSize.Width / 2) - 280, -40, 560, 220);
using GraphicsPath centerPath = new();
centerPath.AddEllipse(centerGlow);
using PathGradientBrush centerBrush = new(centerPath)
{
CenterColor = Color.FromArgb(24 + (int)(10 * MathF.Sin(nebulaPhase * 1.8F)), 120, 160, 255),
SurroundColors = [Color.Transparent]
};
e.Graphics.FillEllipse(centerBrush, centerGlow);
foreach (Star star in stars)
{
int alpha = (int)(star.Alpha * (0.65F + (0.35F * MathF.Sin(star.Phase))));
using SolidBrush starBrush = new(Color.FromArgb(alpha, 235, 244, 255));
e.Graphics.FillEllipse(starBrush, star.X, star.Y, star.Size, star.Size);
if (star.Depth > 1.35F)
{
using Pen trailPen = new(Color.FromArgb(alpha / 3, 145, 220, 255), 1F);
e.Graphics.DrawLine(trailPen, star.X + (star.Size / 2F), star.Y - (star.Size * 1.8F), star.X + (star.Size / 2F), star.Y);
}
}
foreach (ShootingStar shootingStar in shootingStars)
{
using Pen trailPen = new(Color.FromArgb((int)shootingStar.Life, 210, 240, 255), shootingStar.Thickness)
{
StartCap = LineCap.Round,
EndCap = LineCap.Round
};
float tailX = shootingStar.X - shootingStar.TailLength;
float tailY = shootingStar.Y - (shootingStar.TailLength * 0.46F);
e.Graphics.DrawLine(trailPen, tailX, tailY, shootingStar.X, shootingStar.Y);
}
}
private void SharedDragEnter(object? sender, DragEventArgs e)
{
if (e.Data?.GetDataPresent(DataFormats.FileDrop) == true)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop)!;
if (files.Length > 0 && IsSupportedDropFile(files[0]))
{
e.Effect = DragDropEffects.Copy;
return;
}
}
e.Effect = DragDropEffects.None;
}
private void SharedDragDrop(object? sender, DragEventArgs e)
{
if (e.Data?.GetDataPresent(DataFormats.FileDrop) != true)
{
return;
}
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop)!;
if (files.Length == 0)
{
return;
}
string filePath = files[0];
if (string.Equals(Path.GetExtension(filePath), ".mseimg", StringComparison.OrdinalIgnoreCase))
{
LoadEncryptedImage(filePath);
}
else
{
LoadSelectedImage(filePath);
}
}
private static bool IsSupportedDropFile(string filePath)
{
string extension = Path.GetExtension(filePath).ToLowerInvariant();
return extension is ".png" or ".jpg" or ".jpeg" or ".bmp" or ".gif" or ".webp" or ".mseimg";
}
private void LoadSelectedImage(string filePath)
{
selectedImagePath = filePath;
imagePathValueLabel.Text = Path.GetFileName(filePath);
imagePreviewBox.Image?.Dispose();
using Image original = Image.FromFile(filePath);
imagePreviewBox.Image = new Bitmap(original);
ShowStatus("Image loaded.", isError: false);
}
private void LoadEncryptedImage(string filePath)
{
selectedEncryptedImagePath = filePath;
encryptedImagePathValueLabel.Text = Path.GetFileName(filePath);
ShowStatus("Encrypted image loaded.", isError: false);
}
private sealed record Star(float X, float Y, float Depth, float Size, int Alpha, float Phase);
private sealed record ShootingStar(float X, float Y, float Speed, float Thickness, float TailLength, float Life);
protected override void OnFormClosed(FormClosedEventArgs e)
{
starfieldTimer.Stop();
starfieldTimer.Dispose();
base.OnFormClosed(e);
}
}
internal sealed class GlassPanel : Panel
{
private Color accentColor = Color.FromArgb(14, 165, 233);
[DefaultValue(typeof(Color), "14, 165, 233")]
public Color AccentColor
{
get => accentColor;
set
{
accentColor = value;
Invalidate();
}
}
public GlassPanel()
{
BackColor = Color.Transparent;
DoubleBuffered = true;
ResizeRedraw = true;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = new(0, 0, Width - 1, Height - 1);
using GraphicsPath path = CreateRoundedRectangle(rect, 18);
Rectangle shineRect = new(0, 0, Width, Math.Max(42, Height / 2));
using LinearGradientBrush fillBrush = new(
rect,
Color.FromArgb(44, 16, 24, 46),
Color.FromArgb(32, 11, 16, 34),
90F);
using GraphicsPath shinePath = CreateRoundedRectangle(shineRect, 18);
using PathGradientBrush shineBrush = new(shinePath)
{
CenterColor = Color.FromArgb(34, accentColor),
SurroundColors = [Color.Transparent]
};
using Pen borderPen = new(Color.FromArgb(54, 255, 255, 255));
using Pen accentPen = new(Color.FromArgb(110, accentColor), 1.15F);
e.Graphics.FillPath(fillBrush, path);
e.Graphics.FillPath(shineBrush, shinePath);
e.Graphics.DrawPath(borderPen, path);
e.Graphics.DrawArc(accentPen, 12, 10, Math.Max(56, Width / 4), 24, 200, 120);
base.OnPaint(e);
}
private static GraphicsPath CreateRoundedRectangle(Rectangle rect, int radius)
{
int diameter = radius * 2;
GraphicsPath path = new();
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
path.CloseFigure();
return path;
}
}
internal sealed class StyledEditor : UserControl
{
private readonly RichTextBox editor;
private readonly SlimScrollBar scrollBar;
private Color surfaceColor = Color.FromArgb(12, 17, 39);
private Color borderColor = Color.FromArgb(62, 120, 140, 176);
public StyledEditor()
{
SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
DoubleBuffered = true;
BackColor = Color.Transparent;
Padding = new Padding(1);
editor = new RichTextBox
{
BorderStyle = BorderStyle.None,
Dock = DockStyle.Fill,
ScrollBars = RichTextBoxScrollBars.None,
Multiline = true,
WordWrap = true,
DetectUrls = false,
AcceptsTab = true
};
scrollBar = new SlimScrollBar
{
Dock = DockStyle.Right,
Width = 14
};
Controls.Add(editor);
Controls.Add(scrollBar);
editor.VScroll += (_, _) => SyncScrollBar();
editor.TextChanged += (_, _) => SyncScrollBar();
editor.Resize += (_, _) => SyncScrollBar();
editor.SelectionChanged += (_, _) => SyncScrollBar();
editor.MouseWheel += (_, _) => BeginInvoke(new Action(SyncScrollBar));
editor.KeyUp += (_, _) => BeginInvoke(new Action(SyncScrollBar));
scrollBar.ValueChanged += (_, _) => ScrollToBarValue();
}
public override string Text
{
get => editor.Text;
set
{
editor.Text = value;
SyncScrollBar();
}
}
public override Font Font
{
get => editor.Font;
set => editor.Font = value;
}
public override Color ForeColor
{
get => editor.ForeColor;
set => editor.ForeColor = value;
}
public override Color BackColor
{
get => base.BackColor;
set => base.BackColor = value;
}
public void Clear()
{
editor.Clear();
SyncScrollBar();
}
public void ApplyTheme(Color editorBackColor, Color editorForeColor, Color trackColor, Color thumbColor)
{
surfaceColor = editorBackColor;
editor.BackColor = editorBackColor;
editor.ForeColor = editorForeColor;
borderColor = Color.FromArgb(72, ControlPaint.Light(trackColor, 0.35F));
scrollBar.TrackColor = Color.FromArgb(38, trackColor);
scrollBar.TrackGlowColor = Color.FromArgb(26, thumbColor);
scrollBar.ThumbColor = thumbColor;
scrollBar.ThumbHighlightColor = ControlPaint.Light(thumbColor, 0.16F);
Invalidate();
}
protected override void OnCreateControl()
{
base.OnCreateControl();
SyncScrollBar();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = new(0, 0, Width - 1, Height - 1);
using GraphicsPath path = CreateRoundedRectangle(rect, 14);
using LinearGradientBrush fillBrush = new(
rect,
Color.FromArgb(238, surfaceColor),
Color.FromArgb(228, ControlPaint.Dark(surfaceColor, 0.06F)),
90F);
using Pen borderPen = new(borderColor);
e.Graphics.FillPath(fillBrush, path);
e.Graphics.DrawPath(borderPen, path);
base.OnPaint(e);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
SyncScrollBar();
}
private void SyncScrollBar()
{
if (!IsHandleCreated || !editor.IsHandleCreated)
{
return;
}
int lineCount = Math.Max(1, editor.GetLineFromCharIndex(editor.TextLength) + 1);
int visibleLines = Math.Max(1, editor.ClientSize.Height / Math.Max(1, TextRenderer.MeasureText("Ag", editor.Font).Height));
int maxOffset = Math.Max(0, lineCount - visibleLines);
int firstVisibleLine = GetFirstVisibleLine(editor.Handle);
scrollBar.ViewSize = visibleLines;
scrollBar.Maximum = lineCount;
scrollBar.Enabled = maxOffset > 0;
scrollBar.SetValueSilently(Math.Min(firstVisibleLine, maxOffset));
}
private void ScrollToBarValue()
{
if (!editor.IsHandleCreated)
{
return;
}
int firstVisibleLine = GetFirstVisibleLine(editor.Handle);
int delta = scrollBar.Value - firstVisibleLine;
if (delta != 0)
{
SendMessage(editor.Handle, EmLineScroll, IntPtr.Zero, (IntPtr)delta);
}
}
private static int GetFirstVisibleLine(IntPtr handle)
{
return SendMessage(handle, EmGetFirstVisibleLine, IntPtr.Zero, IntPtr.Zero).ToInt32();
}
private static GraphicsPath CreateRoundedRectangle(Rectangle rect, int radius)
{
int diameter = radius * 2;
GraphicsPath path = new();
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
path.CloseFigure();
return path;
}
private const int EmGetFirstVisibleLine = 0x00CE;
private const int EmLineScroll = 0x00B6;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
}
internal sealed class SlimScrollBar : Control
{
private const int MinimumThumbHeight = 36;
private int maximum = 1;
private int value;
private int viewSize = 1;
private bool dragging;
private bool hovered;
private int dragOffset;
public SlimScrollBar()
{
SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
DoubleBuffered = true;
BackColor = Color.Transparent;
Cursor = Cursors.Hand;
Width = 14;
}
public event EventHandler? ValueChanged;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Color TrackColor { get; set; } = Color.FromArgb(18, 28, 52);
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Color TrackGlowColor { get; set; } = Color.FromArgb(22, 95, 108, 255);
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Color ThumbColor { get; set; } = Color.FromArgb(95, 108, 255);
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Color ThumbHighlightColor { get; set; } = Color.FromArgb(128, 148, 160, 255);
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int Maximum
{
get => maximum;
set
{
maximum = Math.Max(1, value);
Invalidate();
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int ViewSize
{
get => viewSize;
set
{
viewSize = Math.Max(1, value);
Invalidate();
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int Value
{
get => value;
set => SetValue(value, true);
}
public void SetValueSilently(int newValue)
{
SetValue(newValue, false);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.Clear(Color.Transparent);
Rectangle trackRect = new(3, 6, Width - 6, Height - 12);
using GraphicsPath trackPath = CreateRoundedRectangle(trackRect, 4);
using SolidBrush trackBrush = new(Enabled ? TrackColor : Color.FromArgb(10, TrackColor));
e.Graphics.FillPath(trackBrush, trackPath);
Rectangle glowRect = new(trackRect.X - 1, trackRect.Y, trackRect.Width + 2, trackRect.Height);
using GraphicsPath glowPath = CreateRoundedRectangle(glowRect, 5);
using PathGradientBrush glowBrush = new(glowPath)
{
CenterColor = Enabled ? TrackGlowColor : Color.Transparent,
SurroundColors = [Color.Transparent]
};
e.Graphics.FillPath(glowBrush, glowPath);
if (!Enabled)
{
return;
}
Rectangle thumbRect = GetThumbRectangle(trackRect);
using GraphicsPath thumbPath = CreateRoundedRectangle(thumbRect, 4);
using LinearGradientBrush thumbBrush = new(
thumbRect,
hovered || dragging ? ControlPaint.Light(ThumbHighlightColor, 0.06F) : ThumbHighlightColor,
hovered || dragging ? ControlPaint.Light(ThumbColor, 0.02F) : ThumbColor,
90F);
using Pen thumbBorderPen = new(Color.FromArgb(110, ControlPaint.Light(ThumbColor, 0.18F)));
e.Graphics.FillPath(thumbBrush, thumbPath);
e.Graphics.DrawPath(thumbBorderPen, thumbPath);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (!Enabled)
{
return;
}
Rectangle trackRect = new(3, 6, Width - 6, Height - 12);
Rectangle thumbRect = GetThumbRectangle(trackRect);
if (thumbRect.Contains(e.Location))
{
dragging = true;
dragOffset = e.Y - thumbRect.Y;
return;
}
int pageStep = Math.Max(1, ViewSize - 1);
Value += e.Y < thumbRect.Y ? -pageStep : pageStep;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (Enabled)
{
Rectangle trackRect = new(3, 6, Width - 6, Height - 12);
bool isHovering = GetThumbRectangle(trackRect).Contains(e.Location);
if (hovered != isHovering)
{
hovered = isHovering;
Invalidate();
}
}
if (!dragging || !Enabled)
{
return;
}
Rectangle dragTrackRect = new(3, 6, Width - 6, Height - 12);
int thumbHeight = GetThumbHeight(dragTrackRect.Height);
int movableHeight = Math.Max(1, dragTrackRect.Height - thumbHeight);
int top = Math.Clamp(e.Y - dragOffset - dragTrackRect.Y, 0, movableHeight);
float ratio = top / (float)movableHeight;
int maxValue = Math.Max(0, Maximum - ViewSize);
Value = (int)Math.Round(ratio * maxValue);
}
protected override void OnMouseUp(MouseEventArgs e)
{
dragging = false;
Invalidate();
base.OnMouseUp(e);
}
protected override void OnMouseLeave(EventArgs e)
{
hovered = false;
Invalidate();
base.OnMouseLeave(e);
}
private void SetValue(int newValue, bool raiseEvent)
{
int maxValue = Math.Max(0, Maximum - ViewSize);
int clamped = Math.Clamp(newValue, 0, maxValue);
if (this.value == clamped)
{
return;
}
this.value = clamped;
Invalidate();
if (raiseEvent)
{
ValueChanged?.Invoke(this, EventArgs.Empty);
}
}
private Rectangle GetThumbRectangle(Rectangle trackRect)
{
int thumbHeight = GetThumbHeight(trackRect.Height);
int movableHeight = Math.Max(1, trackRect.Height - thumbHeight);
int maxValue = Math.Max(1, Maximum - ViewSize);
int top = (int)Math.Round((Value / (float)maxValue) * movableHeight);
return new Rectangle(trackRect.X, trackRect.Y + top, trackRect.Width, thumbHeight);
}
private int GetThumbHeight(int trackHeight)
{
float ratio = Math.Clamp(ViewSize / (float)Math.Max(ViewSize, Maximum), 0.1F, 1F);
return Math.Max(MinimumThumbHeight, (int)Math.Round(trackHeight * ratio));
}
private static GraphicsPath CreateRoundedRectangle(Rectangle rect, int radius)
{
int diameter = radius * 2;
GraphicsPath path = new();
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
path.CloseFigure();
return path;
}
}
internal sealed class NeonButton : Button
{
private Color fillColor = Color.FromArgb(14, 165, 233);
private Color glowColor = Color.FromArgb(14, 165, 233);
private bool isSecondary;
private bool isHovered;
private bool isPressed;
[DefaultValue(typeof(Color), "14, 165, 233")]
public Color FillColor
{
get => fillColor;
set
{
fillColor = value;
Invalidate();
}
}
[DefaultValue(typeof(Color), "14, 165, 233")]
public Color GlowColor
{
get => glowColor;
set
{
glowColor = value;
Invalidate();
}
}
[DefaultValue(false)]
public bool IsSecondary
{
get => isSecondary;
set
{
isSecondary = value;
Invalidate();
}
}
public NeonButton()
{
FlatStyle = FlatStyle.Flat;
FlatAppearance.BorderSize = 0;
ForeColor = Color.White;
Font = new Font("Segoe UI Semibold", 10F, FontStyle.Bold);
Cursor = Cursors.Hand;
DoubleBuffered = true;
}
protected override void OnMouseEnter(EventArgs e)
{
isHovered = true;
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
isHovered = false;
isPressed = false;
Invalidate();
base.OnMouseLeave(e);
}
protected override void OnMouseDown(MouseEventArgs mevent)
{
isPressed = true;
Invalidate();
base.OnMouseDown(mevent);
}
protected override void OnMouseUp(MouseEventArgs mevent)
{
isPressed = false;
Invalidate();
base.OnMouseUp(mevent);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.Clear(Color.Transparent);
Rectangle rect = new(0, 0, Width - 1, Height - 1);
using GraphicsPath path = CreateRoundedRectangle(rect, 14);
Color baseColor = FillColor;
Color currentColor = isPressed
? ControlPaint.Dark(baseColor, 0.14F)
: isHovered ? ControlPaint.Light(baseColor, 0.08F) : baseColor;
using LinearGradientBrush fillBrush = new(
rect,
ControlPaint.Light(currentColor, isSecondary ? 0.02F : 0.08F),
ControlPaint.Dark(currentColor, 0.06F),
90F);
using Pen borderPen = new(IsSecondary ? Color.FromArgb(80, 148, 163, 184) : Color.FromArgb(80, GlowColor), 1.1F);
e.Graphics.FillPath(fillBrush, path);
e.Graphics.DrawPath(borderPen, path);
TextRenderer.DrawText(
e.Graphics,
Text,
Font,
rect,
ForeColor,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis);
}
private static GraphicsPath CreateRoundedRectangle(Rectangle rect, int radius)
{
int diameter = radius * 2;
GraphicsPath path = new();
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
path.CloseFigure();
return path;
}
}