Files
claude-proxy/native/MainForm.cs
T
gusadmin 39b58e1a77 Add AmneziaWG mode (wireproxy-awg userspace SOCKS)
Third mode alongside proxy and VLESS+Reality. Bundles wireproxy-awg built
from a pinned, audited source commit (fork of wireproxy over the official
amneziawg-go), exposing a local SOCKS5 tunnel with no TUN/driver/admin.
User picks their AmneziaWG .conf; the app appends a [Socks5] section and
routes Claude through it with the same fail-closed firewall.

build-wireproxy.ps1 reproducibly builds the binary (portable Go + pinned
commit); it is embedded as a resource and not stored in git.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 14:58:21 +03:00

442 lines
26 KiB
C#

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Windows.Forms;
namespace ClaudeProxy
{
class BufferedPanel : Panel { public BufferedPanel() { DoubleBuffered = true; } }
class MainForm : Form
{
// palette
static Color Cream = Color.FromArgb(250, 249, 245), Ink = Color.FromArgb(41, 40, 36),
Head = Color.FromArgb(26, 25, 21), Muted = Color.FromArgb(131, 129, 123),
Card = Color.FromArgb(255, 255, 255), Border = Color.FromArgb(232, 229, 220),
Coral = Color.FromArgb(193, 95, 60), CoralLo = Color.FromArgb(224, 137, 101),
CoralHi = Color.FromArgb(168, 79, 48), TrackOff = Color.FromArgb(206, 202, 192),
OkC = Color.FromArgb(74, 122, 80), RedC = Color.FromArgb(196, 74, 61),
Glyph = Color.FromArgb(248, 246, 239);
Font fReg = new Font("Segoe UI", 9.5f), fSemi = new Font("Segoe UI Semibold", 9.5f),
fHead = new Font("Segoe UI Semibold", 15f), fSub = new Font("Segoe UI", 9f),
fPill = new Font("Segoe UI Semibold", 12f), fMono = new Font("Consolas", 8.5f);
bool switchOn = false, busy = false;
RadioButton rbProxy, rbVless, rbAwg;
BufferedPanel mark, tgl, card;
Label title, subtitle, lblFieldCap, lblReach, rDot, lblFwCap, lblFw, lblClCap, lblCl, lblXrayCap, lblXray;
TextBox txtField, log;
CheckBox chkRestart, chkFail;
Button btnSave, btnLaunch, btnVerify, btnRefresh, btnXray;
Panel sep;
Timer timer;
string Mode { get { return rbAwg.Checked ? "awg" : (rbVless.Checked ? "vless" : "proxy"); } }
public MainForm()
{
Text = "Claude Proxy";
AutoScaleMode = AutoScaleMode.None;
ClientSize = new Size(452, 512);
StartPosition = FormStartPosition.CenterScreen;
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
BackColor = Cream; ForeColor = Ink; Font = fReg;
try { Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); } catch { }
mark = new BufferedPanel { Size = new Size(46, 46), Location = new Point(24, 22), BackColor = Cream };
mark.Paint += (s, e) => PaintTile(e.Graphics, 46);
Controls.Add(mark);
title = MkLabel("Claude Proxy", fHead, Head, 82, 22); title.AutoSize = true; Controls.Add(title);
subtitle = MkLabel("весь трафик Claude через прокси", fSub, Muted, 84, 48);
subtitle.AutoSize = false; subtitle.Size = new Size(196, 18); Controls.Add(subtitle);
rbProxy = MkRadio("Прямой прокси", 292, 14);
rbVless = MkRadio("VLESS + Reality", 292, 36);
rbAwg = MkRadio("AmneziaWG", 292, 58);
Controls.Add(rbProxy); Controls.Add(rbVless); Controls.Add(rbAwg);
tgl = new BufferedPanel { Size = new Size(172, 58), Location = new Point(24, 88), BackColor = Cream, Cursor = Cursors.Hand };
tgl.Paint += (s, e) => PaintToggle(e.Graphics, tgl.Width, tgl.Height);
tgl.Click += (s, e) => ToggleSwitch();
Controls.Add(tgl);
var hint = MkLabel("Включено = наружу только через прокси, всё остальное заблокировано", fSub, Muted, 208, 92);
hint.AutoSize = false; hint.Size = new Size(224, 52); Controls.Add(hint);
// status card
card = new BufferedPanel { Size = new Size(412, 158), Location = new Point(20, 162), BackColor = Cream };
card.Paint += (s, e) => { var g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias;
using (var path = Round(card.Width - 1, card.Height - 1, 14))
using (var b = new SolidBrush(Card)) using (var pen = new Pen(Border, 1))
{ g.FillPath(b, path); g.DrawPath(pen, path); } };
Controls.Add(card);
lblFieldCap = MkLabel("Прокси", fReg, Muted, 18, 18); lblFieldCap.AutoSize = true; card.Controls.Add(lblFieldCap);
txtField = new TextBox { Location = new Point(84, 15), Size = new Size(214, 24), BorderStyle = BorderStyle.FixedSingle, BackColor = Cream, ForeColor = Ink };
card.Controls.Add(txtField);
btnSave = MkButton("Сохранить", 308, 13, 88, 28, false); btnSave.Click += (s, e) => OnSave(); card.Controls.Add(btnSave);
rDot = MkLabel("●", new Font("Segoe UI", 11f), Muted, 17, 54); rDot.AutoSize = true; card.Controls.Add(rDot);
lblReach = MkLabel("", fReg, Ink, 38, 56); lblReach.AutoSize = true; card.Controls.Add(lblReach);
lblXrayCap = MkLabel("Xray-core", fReg, Muted, 18, 68); lblXrayCap.AutoSize = true; card.Controls.Add(lblXrayCap);
lblXray = MkLabel("", fSemi, Ink, 120, 68); lblXray.AutoSize = true; card.Controls.Add(lblXray);
btnXray = MkButton("Распаковать", 300, 64, 96, 26, false); btnXray.Click += (s, e) => { if (Mode == "awg") OnBrowseAwg(); else OnXray(); }; card.Controls.Add(btnXray);
sep = new Panel { Location = new Point(18, 86), Size = new Size(378, 1), BackColor = Border }; card.Controls.Add(sep);
lblFwCap = MkLabel("Firewall-лок", fReg, Muted, 18, 98); lblFwCap.AutoSize = true; card.Controls.Add(lblFwCap);
lblFw = MkLabel("", fSemi, Ink, 120, 98); lblFw.AutoSize = true; card.Controls.Add(lblFw);
lblClCap = MkLabel("Claude", fReg, Muted, 18, 122); lblClCap.AutoSize = true; card.Controls.Add(lblClCap);
lblCl = MkLabel("", fSemi, Ink, 120, 122); lblCl.AutoSize = true; card.Controls.Add(lblCl);
chkRestart = new CheckBox { Text = "Перезапускать Claude при переключении", ForeColor = Ink, AutoSize = true, Checked = true, Location = new Point(22, 324), Font = fReg };
chkFail = new CheckBox { Text = "Fail-closed: блокировать любой трафик мимо прокси (firewall)", ForeColor = Ink, AutoSize = true, Checked = true, Location = new Point(22, 348), Font = fReg };
Controls.Add(chkRestart); Controls.Add(chkFail);
btnLaunch = MkButton("Запустить Claude", 20, 384, 170, 36, true); btnLaunch.Click += (s, e) => LaunchProxied(); Controls.Add(btnLaunch);
btnVerify = MkButton("Проверить", 198, 386, 142, 32, false); btnVerify.Click += (s, e) => OnVerify(); Controls.Add(btnVerify);
btnRefresh = MkButton("Обновить", 348, 386, 84, 32, false); btnRefresh.Click += (s, e) => { UpdateUi(true); Log("статус обновлён."); }; Controls.Add(btnRefresh);
log = new TextBox { Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Vertical, Location = new Point(20, 430), Size = new Size(412, 66), BorderStyle = BorderStyle.FixedSingle, BackColor = Card, ForeColor = Muted, Font = fMono };
Controls.Add(log);
EventHandler modeCh = (s, e) => { if (!((RadioButton)s).Checked) return; ApplyModeLayout(); var c = Core.LoadConfig(); c.Mode = Mode; Core.SaveConfig(c); UpdateUi(true); };
rbVless.CheckedChanged += modeCh; rbAwg.CheckedChanged += modeCh; rbProxy.CheckedChanged += modeCh;
timer = new Timer { Interval = 4000 };
timer.Tick += (s, e) => { if (!busy) UpdateUi(false); };
timer.Start();
Load += (s, e) => {
var cfg = Core.LoadConfig();
chkFail.Checked = cfg.FailClosed;
if (cfg.Mode == "awg") rbAwg.Checked = true; else if (cfg.Mode == "vless") rbVless.Checked = true; else rbProxy.Checked = true;
ApplyModeLayout();
Log("готово. рубильник управляет проксированием Claude.");
UpdateUi(true);
};
}
// ---- factories ----
Label MkLabel(string t, Font f, Color c, int x, int y)
{ return new Label { Text = t, Font = f, ForeColor = c, Location = new Point(x, y), BackColor = Color.Transparent }; }
RadioButton MkRadio(string t, int x, int y)
{ return new RadioButton { Text = t, ForeColor = Ink, Font = fReg, AutoSize = false, TextAlign = ContentAlignment.MiddleLeft, BackColor = Cream, Size = new Size(150, 22), Location = new Point(x, y) }; }
Button MkButton(string t, int x, int y, int w, int h, bool primary)
{
var b = new Button { Text = t, Location = new Point(x, y), Size = new Size(w, h), FlatStyle = FlatStyle.Flat, Cursor = Cursors.Hand };
b.FlatAppearance.BorderSize = primary ? 0 : 1;
if (primary) { b.BackColor = Coral; b.ForeColor = Color.White; b.Font = fSemi; b.FlatAppearance.MouseOverBackColor = CoralHi; }
else { b.BackColor = Card; b.ForeColor = Ink; b.Font = fReg; b.FlatAppearance.BorderColor = Border; b.FlatAppearance.MouseOverBackColor = Cream; }
b.Region = new Region(Round(w, h, 9));
b.SizeChanged += (s, e) => { try { b.Region = new Region(Round(b.Width, b.Height, 9)); } catch { } };
return b;
}
static GraphicsPath Round(int w, int h, int r)
{
int d = r * 2; var p = new GraphicsPath();
p.AddArc(0, 0, d, d, 180, 90); p.AddArc(w - d, 0, d, d, 270, 90);
p.AddArc(w - d, h - d, d, d, 0, 90); p.AddArc(0, h - d, d, d, 90, 90);
p.CloseFigure(); return p;
}
// ---- painting ----
void PaintTile(Graphics g, int S)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
using (var path = Round(S - 1, S - 1, 12))
using (var br = new LinearGradientBrush(new Rectangle(0, 0, S, S), CoralLo, CoralHi, 90f))
g.FillPath(br, path);
DrawToggleGlyph(g, S);
}
void DrawToggleGlyph(Graphics g, float S)
{
float tw = S * 0.58f, th = S * 0.34f, tx = (S - tw) / 2f, ty = (S - th) / 2f;
using (var tp = new GraphicsPath())
{
tp.AddArc(tx, ty, th, th, 90, 180); tp.AddArc(tx + tw - th, ty, th, th, 270, 180); tp.CloseFigure();
using (var tb = new SolidBrush(Glyph)) g.FillPath(tb, tp);
}
float kd = th - S * 0.10f, kx = tx + tw - kd - S * 0.05f, ky = ty + (th - kd) / 2f;
using (var kb = new SolidBrush(CoralHi)) g.FillEllipse(kb, kx, ky, kd, kd);
}
void PaintToggle(Graphics g, int w, int h)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
using (var path = Round(w, h, h / 2))
{
if (switchOn) using (var br = new LinearGradientBrush(new Rectangle(0, 0, w, h), CoralLo, Coral, 0f)) g.FillPath(br, path);
else using (var br = new SolidBrush(TrackOff)) g.FillPath(br, path);
}
int knobD = h - 12, knobX = switchOn ? w - knobD - 6 : 6;
using (var sh = new SolidBrush(Color.FromArgb(40, 0, 0, 0))) g.FillEllipse(sh, knobX + 1, 7, knobD, knobD);
using (var kb = new SolidBrush(Color.White)) g.FillEllipse(kb, knobX, 6, knobD, knobD);
string txt = switchOn ? "ВКЛ" : "ВЫКЛ";
using (var tb = new SolidBrush(switchOn ? Color.White : Color.FromArgb(106, 104, 98)))
g.DrawString(txt, fPill, tb, switchOn ? 22 : 66, 16);
}
// ---- mode layout ----
void ApplyModeLayout()
{
var cfg = Core.LoadConfig();
bool tunnel = Mode == "vless" || Mode == "awg";
lblXrayCap.Visible = lblXray.Visible = btnXray.Visible = tunnel;
if (tunnel) { sep.Top = 100; lblFwCap.Top = lblFw.Top = 112; lblClCap.Top = lblCl.Top = 134; }
else { sep.Top = 74; lblFwCap.Top = lblFw.Top = 86; lblClCap.Top = lblCl.Top = 108; }
if (Mode == "vless")
{
lblFieldCap.Text = "VLESS"; subtitle.Text = "через VLESS + Reality тоннель";
txtField.UseSystemPasswordChar = true; txtField.Text = cfg.Vless ?? "";
lblXrayCap.Text = "Xray-core"; UpdateXrayStatus();
}
else if (Mode == "awg")
{
lblFieldCap.Text = "Конфиг"; subtitle.Text = "через AmneziaWG тоннель";
txtField.UseSystemPasswordChar = false; txtField.Text = cfg.Awg ?? "";
lblXrayCap.Text = "AmneziaWG"; UpdateAwgStatus();
}
else
{
lblFieldCap.Text = "Прокси"; subtitle.Text = "весь трафик Claude через прокси";
txtField.UseSystemPasswordChar = false; txtField.Text = cfg.Proxy ?? "";
}
}
void UpdateXrayStatus()
{
if (Core.XrayPresent()) { lblXray.Text = "готов (" + Core.XrayVersion + ")"; lblXray.ForeColor = OkC; btnXray.Text = "Переустановить"; }
else { lblXray.Text = "встроен (" + Core.XrayVersion + ")"; lblXray.ForeColor = OkC; btnXray.Text = "Распаковать"; }
btnXray.Width = 96; btnXray.Left = 300;
}
void UpdateAwgStatus()
{
bool ok = !string.IsNullOrWhiteSpace(txtField.Text) && System.IO.File.Exists(txtField.Text.Trim());
lblXray.Text = ok ? "конфиг выбран" : "не выбран"; lblXray.ForeColor = ok ? OkC : RedC;
btnXray.Text = "Обзор..."; btnXray.Width = 96; btnXray.Left = 300;
}
// ---- helpers ----
void Log(string m) { log.AppendText(DateTime.Now.ToString("HH:mm:ss") + " " + m + "\r\n"); }
void SetBusy(bool b)
{
busy = b;
btnSave.Enabled = btnRefresh.Enabled = btnVerify.Enabled = btnLaunch.Enabled = btnXray.Enabled = !b;
UpdateModeLock();
Cursor = b ? Cursors.WaitCursor : Cursors.Default;
}
// Mode can't be switched while the lock is active (ON) or during an operation.
void UpdateModeLock() { rbProxy.Enabled = rbVless.Enabled = rbAwg.Enabled = !switchOn && !busy; }
Config CaptureConfig()
{
var c = Core.LoadConfig();
c.Mode = Mode; c.FailClosed = chkFail.Checked;
if (Mode == "vless") c.Vless = txtField.Text;
else if (Mode == "awg") c.Awg = txtField.Text;
else c.Proxy = txtField.Text;
Core.SaveConfig(c); return c;
}
void UpdateUi(bool checkReach)
{
try
{
ClaudeApp app = null;
try { app = Core.GetClaudeApp(); } catch { }
string fw = app != null ? Core.FirewallState(app.Exe) : Core.FirewallState(null);
bool newOn = fw == "ok";
if (newOn != switchOn) { switchOn = newOn; tgl.Invalidate(); }
UpdateModeLock();
lblFw.Text = fw == "ok" ? "установлен" : (fw == "stale" ? "устарел (путь изменился)" : "нет");
lblFw.ForeColor = fw == "ok" ? Coral : (fw == "stale" ? RedC : Muted);
bool running = Core.ClaudeRunning();
if (!running) { lblCl.Text = "не запущен"; lblCl.ForeColor = Muted; }
else { lblCl.Text = "запущен"; lblCl.ForeColor = Ink; }
if (checkReach)
{
bool ok; string text;
if (Mode == "vless") { string ep; ok = Vless.ServerReachable(txtField.Text, out ep); text = (ok ? "сервер доступен " : "не отвечает ") + ep; }
else if (Mode == "awg") { string ep; ok = Core.AwgConfigInfo(txtField.Text, out ep); text = ok ? ("конфиг: " + ep) : "конфиг не выбран"; }
else { string h; int p; if (Core.TryParseProxy(txtField.Text, out h, out p)) { ok = Core.TcpReachable(h, p, 1500); text = (ok ? "сервер доступен " : "не отвечает ") + h + ":" + p; } else { ok = false; text = "некорректный адрес"; } }
rDot.ForeColor = ok ? OkC : RedC;
lblReach.Text = text;
}
if (Mode == "vless") UpdateXrayStatus();
else if (Mode == "awg") UpdateAwgStatus();
}
catch (Exception ex) { Log("ошибка обновления: " + ex.Message); }
}
// ---- orchestration ----
bool Preflight()
{
if (Mode == "vless")
{
if (string.IsNullOrWhiteSpace(txtField.Text)) { MessageBox.Show("Вставь VLESS-ссылку из 3x-ui.", "Нужна ссылка", MessageBoxButtons.OK, MessageBoxIcon.Warning); return false; }
try { string h, u, r; int p; Vless.Parse(txtField.Text, out h, out p, out u, out r); }
catch (Exception ex) { MessageBox.Show(ex.Message, "Некорректная ссылка", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; }
if (!Core.XrayPresent())
{
SetBusy(true);
try { Core.EnsureXray(); UpdateXrayStatus(); }
catch (Exception ex) { Log("ОШИБКА Xray: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка Xray", MessageBoxButtons.OK, MessageBoxIcon.Error); SetBusy(false); return false; }
SetBusy(false);
}
string ep; if (!Vless.ServerReachable(txtField.Text, out ep))
if (MessageBox.Show("Сервер " + ep + " не отвечает. Всё равно продолжить?", "Сервер недоступен", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return false;
}
else if (Mode == "awg")
{
if (string.IsNullOrWhiteSpace(txtField.Text) || !System.IO.File.Exists(txtField.Text.Trim()))
{ MessageBox.Show("Выбери файл AmneziaWG .conf (кнопка «Обзор...»).", "Нужен конфиг", MessageBoxButtons.OK, MessageBoxIcon.Warning); return false; }
if (!Core.AwgPresent())
{
SetBusy(true);
try { Core.EnsureAwg(); UpdateAwgStatus(); }
catch (Exception ex) { Log("ОШИБКА AmneziaWG: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); SetBusy(false); return false; }
SetBusy(false);
}
}
else
{
string h; int p;
if (!Core.TryParseProxy(txtField.Text, out h, out p)) { MessageBox.Show("Некорректный адрес прокси.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; }
if (!Core.TcpReachable(h, p, 1500))
if (MessageBox.Show("Прокси " + h + ":" + p + " не отвечает. Всё равно включить?", "Прокси недоступен", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return false;
}
CaptureConfig();
return true;
}
void DoEnable(bool restart)
{
var cfg = Core.LoadConfig();
string proxyArg;
if (cfg.Mode == "vless") { Core.EnsureXray(); Core.StartXray(cfg.Vless, cfg.LocalPort); proxyArg = "socks5://127.0.0.1:" + cfg.LocalPort; }
else if (cfg.Mode == "awg") { Core.EnsureAwg(); Core.StartAwg(cfg.Awg, cfg.LocalPort); proxyArg = "socks5://127.0.0.1:" + cfg.LocalPort; }
else proxyArg = Core.ProxyArg(cfg.Proxy);
if (cfg.FailClosed)
if (!Core.ElevateSelf("fwapply")) { Core.StopXray(); Core.StopAwg(); throw new Exception("Установка firewall отменена в UAC."); }
if (restart) { Core.StopClaude(); string a = "--proxy-server=" + proxyArg; Core.LaunchClaude(a); }
}
void DoDisable(bool restart)
{
string exe = null; try { exe = Core.GetClaudeApp().Exe; } catch { }
if (Core.FirewallState(exe) != "absent")
if (!Core.ElevateSelf("fwremove")) throw new Exception("Снятие firewall отменено в UAC (лок остался).");
Core.StopXray(); Core.StopAwg();
if (restart) { Core.StopClaude(); Core.LaunchClaude(""); }
}
void ToggleSwitch()
{
if (busy) return;
bool restart = chkRestart.Checked;
try
{
if (!switchOn)
{
if (!Preflight()) return;
bool running = Core.ClaudeRunning();
bool doRestart = false;
if (running) { if (restart) { if (MessageBox.Show("Claude запущен. Перезапустить его через прокси? (текущее окно закроется)", "Перезапуск Claude", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) return; doRestart = true; } }
else doRestart = true;
SetBusy(true); Log("включаю..."); DoEnable(doRestart);
Log("ВКЛ. " + (!running ? "Claude запущен через прокси." : (doRestart ? "Claude перезапущен." : "лок установлен; перезапусти Claude, чтобы применить.")));
}
else
{
bool restartOff = restart;
if (restartOff && MessageBox.Show("Выключение перезапустит Claude напрямую (текущее окно закроется). Продолжить?", "Перезапуск Claude", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) restartOff = false;
SetBusy(true); Log("выключаю прокси..."); DoDisable(restartOff); Log("прокси ВЫКЛ. firewall-лок снят.");
}
}
catch (Exception ex) { Log("ОШИБКА: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); }
finally { SetBusy(false); UpdateUi(true); }
}
void LaunchProxied()
{
if (busy) return;
try
{
if (!Preflight()) return;
if (Core.ClaudeRunning() && MessageBox.Show("Claude уже запущен. Перезапустить его через прокси? (текущее окно закроется)", "Перезапуск Claude", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) return;
SetBusy(true); Log("запускаю Claude через прокси..."); DoEnable(true); Log("Claude запущен через прокси.");
}
catch (Exception ex) { Log("ОШИБКА: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); }
finally { SetBusy(false); UpdateUi(true); }
}
void OnVerify()
{
try
{
var r = Verify.Run(Core.LoadConfig());
foreach (var l in r.Lines) Log(l);
string stamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
string p = Verify.WriteLog(r, stamp);
if (p.Length > 0) Log("лог: " + p);
MessageBox.Show(r.Verdict, "Результат проверки", MessageBoxButtons.OK,
r.Ok ? MessageBoxIcon.Information : (r.Verdict.StartsWith("ПРОВАЛ") ? MessageBoxIcon.Error : MessageBoxIcon.Warning));
}
catch (Exception ex) { MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); }
}
void OnSave()
{
try
{
if (Mode == "vless") { string h, u, rm; int p; Vless.Parse(txtField.Text, out h, out p, out u, out rm); CaptureConfig(); Log("VLESS сохранён: " + h + ":" + p + " [" + rm + "]"); }
else if (Mode == "awg") { if (string.IsNullOrWhiteSpace(txtField.Text) || !System.IO.File.Exists(txtField.Text.Trim())) throw new Exception("Файл AmneziaWG-конфига не найден."); CaptureConfig(); Log("AmneziaWG-конфиг сохранён: " + txtField.Text.Trim()); }
else { string h; int p; if (!Core.TryParseProxy(txtField.Text, out h, out p)) throw new Exception("Некорректный адрес."); CaptureConfig(); Log("прокси сохранён: " + Core.ProxyArg(txtField.Text)); }
if (switchOn) Log("ВНИМАНИЕ: режим активен со старой настройкой — переключи рубильник, чтобы применить.");
UpdateUi(true);
}
catch (Exception ex) { MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); }
}
void OnXray()
{
if (busy) return;
try
{
SetBusy(true); Core.StopXray();
try { if (Core.XrayPresent()) System.IO.File.Delete(Core.XrayExe); } catch { }
Log("готовлю Xray-core..."); Core.EnsureXray(); UpdateXrayStatus(); Log("Xray готов.");
}
catch (Exception ex) { Log("ОШИБКА: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка Xray", MessageBoxButtons.OK, MessageBoxIcon.Error); }
finally { SetBusy(false); }
}
void OnBrowseAwg()
{
using (var dlg = new OpenFileDialog())
{
dlg.Title = "Выбери AmneziaWG .conf";
dlg.Filter = "AmneziaWG config (*.conf)|*.conf|Все файлы (*.*)|*.*";
if (dlg.ShowDialog() != DialogResult.OK) return;
txtField.Text = dlg.FileName;
CaptureConfig();
UpdateAwgStatus();
Log("конфиг выбран: " + dlg.FileName);
UpdateUi(true);
}
}
}
}