using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; using System.Threading; namespace ClaudeProxy { // ---- MSIX activation (launch a packaged app with arguments) ------------------- [ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IApplicationActivationManager { int ActivateApplication([In, MarshalAs(UnmanagedType.LPWStr)] string appUserModelId, [In, MarshalAs(UnmanagedType.LPWStr)] string arguments, [In] int options, [Out] out uint processId); int ActivateForFile(); int ActivateForProtocol(); } [ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")] class ApplicationActivationManager { } class ClaudeApp { public string InstallLocation; public string Exe; public string Aumid; } static class Core { public const string XrayVersion = "v26.3.27"; public const string PackageFamily = "Claude_pzs8sxrjxfjjc"; public const string Aumid = "Claude_pzs8sxrjxfjjc!Claude"; public const string FwGroup = "ClaudeProxyLock"; public const string DefaultProxy = "socks5://127.0.0.1:1080"; public static string ConfigDir { get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ClaudeProxy"); } } public static string ConfigPath { get { return Path.Combine(ConfigDir, "config.ini"); } } public static string BinDir { get { return Path.Combine(ConfigDir, "bin"); } } public static string XrayExe { get { return Path.Combine(BinDir, "xray.exe"); } } public static string XrayCfgPath { get { return Path.Combine(ConfigDir, "xray-config.json"); } } // ---- admin ---------------------------------------------------------------- public static bool IsAdmin() { using (var id = WindowsIdentity.GetCurrent()) return new WindowsPrincipal(id).IsInRole(WindowsBuiltInRole.Administrator); } // Relaunch self elevated with a single token argument; wait for it. Returns false if cancelled. public static bool ElevateSelf(string arg) { try { var psi = new ProcessStartInfo { FileName = Process.GetCurrentProcess().MainModule.FileName, Arguments = arg, Verb = "runas", UseShellExecute = true, WindowStyle = ProcessWindowStyle.Hidden }; var p = Process.Start(psi); p.WaitForExit(); return true; } catch { return false; } // UAC cancelled / error } // ---- config --------------------------------------------------------------- public static Config LoadConfig() { var c = new Config(); try { if (File.Exists(ConfigPath)) foreach (var line in File.ReadAllLines(ConfigPath)) { int i = line.IndexOf('='); if (i <= 0) continue; string k = line.Substring(0, i).Trim(), v = line.Substring(i + 1); switch (k) { case "mode": c.Mode = v.Trim(); break; case "proxy": c.Proxy = v.Trim(); break; case "vless": c.Vless = v; break; case "localPort": int.TryParse(v.Trim(), out c.LocalPort); break; case "failClosed": c.FailClosed = v.Trim() == "1" || v.Trim().ToLower() == "true"; break; } } } catch { } if (c.LocalPort <= 0) c.LocalPort = 10808; if (string.IsNullOrEmpty(c.Mode)) c.Mode = "proxy"; if (string.IsNullOrEmpty(c.Proxy)) c.Proxy = DefaultProxy; return c; } public static void SaveConfig(Config c) { Directory.CreateDirectory(ConfigDir); var sb = new StringBuilder(); sb.AppendLine("mode=" + c.Mode); sb.AppendLine("proxy=" + c.Proxy); sb.AppendLine("vless=" + (c.Vless ?? "")); sb.AppendLine("localPort=" + c.LocalPort); sb.AppendLine("failClosed=" + (c.FailClosed ? "1" : "0")); File.WriteAllText(ConfigPath, sb.ToString()); } // ---- proxy parsing -------------------------------------------------------- public static bool TryParseProxy(string uri, out string host, out int port) { host = null; port = 0; if (string.IsNullOrWhiteSpace(uri)) return false; var m = System.Text.RegularExpressions.Regex.Match(uri.Trim(), @"^(socks5|socks5h|socks4|http|https)://([^:/]+):(\d+)/?$"); if (!m.Success) return false; host = m.Groups[2].Value; port = int.Parse(m.Groups[3].Value); return true; } public static string ProxyArg(string uri) { return uri.Trim().TrimEnd('/'); } public static bool TcpReachable(string host, int port, int timeoutMs) { try { using (var c = new TcpClient()) { var ar = c.BeginConnect(host, port, null, null); if (ar.AsyncWaitHandle.WaitOne(timeoutMs) && c.Connected) { c.EndConnect(ar); return true; } return false; } } catch { return false; } } // ---- Claude app discovery ------------------------------------------------- public static ClaudeApp GetClaudeApp() { string exe = null; // 1. from a running instance (works de-elevated) foreach (var p in Process.GetProcessesByName("claude")) { try { string fn = p.MainModule.FileName; if (fn.IndexOf("WindowsApps", StringComparison.OrdinalIgnoreCase) >= 0 && fn.IndexOf(PackageFamily.Split('_')[1], StringComparison.OrdinalIgnoreCase) >= 0) { exe = fn; break; } } catch { } } // 2. enumerate WindowsApps (works when elevated) if (exe == null) { try { string wa = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps"); foreach (var dir in Directory.GetDirectories(wa, "Claude_*__" + PackageFamily.Split('_')[1])) { string cand = Path.Combine(dir, "app", "Claude.exe"); if (File.Exists(cand)) { exe = cand; break; } } } catch { } } if (exe == null) throw new Exception("Не найден Claude (десктопный MSIX-пакет)."); string install = Directory.GetParent(Directory.GetParent(exe).FullName).FullName; return new ClaudeApp { Exe = exe, InstallLocation = install, Aumid = Aumid }; } public static bool ClaudeRunning() { foreach (var p in Process.GetProcessesByName("claude")) try { var fn = p.MainModule.FileName; if (fn.IndexOf("WindowsApps", StringComparison.OrdinalIgnoreCase) >= 0) return true; } catch { } return false; } public static void StopClaude() { foreach (var p in Process.GetProcessesByName("claude")) { try { string fn = p.MainModule.FileName; if (fn.IndexOf("WindowsApps", StringComparison.OrdinalIgnoreCase) >= 0) { p.Kill(); } } catch { } } Thread.Sleep(900); } public static uint LaunchClaude(string argLine) { var mgr = (IApplicationActivationManager)(new ApplicationActivationManager()); uint pid; int hr = mgr.ActivateApplication(Aumid, argLine ?? "", 0, out pid); if (hr < 0) throw new Exception("Не удалось запустить Claude (HRESULT 0x" + hr.ToString("X8") + ")."); return pid; } // ---- firewall (Windows Firewall COM API via dynamic late binding) --------- // Returns all IPv4 addresses EXCEPT the given ones, as "a-b" range strings. public static List Ipv4Complement(IEnumerable ips) { var vals = ips.Select(IpToUInt).Distinct().OrderBy(x => x).ToList(); var res = new List(); ulong cursor = 0, max = 4294967295UL; foreach (var v in vals) { if (v > cursor) res.Add(UIntToIp(cursor) + "-" + UIntToIp(v - 1)); cursor = v + 1; } if (cursor <= max) res.Add(UIntToIp(cursor) + "-" + UIntToIp(max)); return res; } static ulong IpToUInt(string ip) { var b = IPAddress.Parse(ip).GetAddressBytes(); return ((ulong)b[0] << 24) | ((ulong)b[1] << 16) | ((ulong)b[2] << 8) | b[3]; } static string UIntToIp(ulong v) { return string.Format("{0}.{1}.{2}.{3}", (v >> 24) & 255, (v >> 16) & 255, (v >> 8) & 255, v & 255); } const string V6All = "::-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"; static dynamic FwPolicy() { return Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2")); } static void AddBlockRule(dynamic policy, string name, string exe, string remote) { dynamic r = Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FWRule")); r.Name = name; r.Grouping = FwGroup; r.Direction = 2; // NET_FW_RULE_DIR_OUT r.Action = 0; // NET_FW_ACTION_BLOCK r.ApplicationName = exe; if (!string.IsNullOrEmpty(remote)) r.RemoteAddresses = remote; r.Profiles = 0x7FFFFFFF; // all profiles r.Enabled = true; policy.Rules.Add(r); } public static void RemoveFirewall() { try { dynamic policy = FwPolicy(); var names = new List(); foreach (dynamic r in policy.Rules) { try { if ((string)r.Grouping == FwGroup) names.Add((string)r.Name); } catch { } } foreach (var n in names) { try { policy.Rules.Remove(n); } catch { } } } catch { } } public static void ApplyFirewallForConfig() { var cfg = LoadConfig(); var app = GetClaudeApp(); RemoveFirewall(); dynamic policy = FwPolicy(); if (cfg.Mode == "vless") { // tunnel: block ALL external (loopback is exempt from firewall filtering) AddBlockRule(policy, "ClaudeProxyLock: tunnel", app.Exe, null); } else { string host; int port; if (!TryParseProxy(cfg.Proxy, out host, out port)) throw new Exception("Некорректный прокси."); var addrs = Dns.GetHostAddresses(host); var v4 = addrs.Where(a => a.AddressFamily == AddressFamily.InterNetwork).Select(a => a.ToString()).Distinct().ToList(); var v6 = addrs.Where(a => a.AddressFamily == AddressFamily.InterNetworkV6).Select(a => a.ToString()).Distinct().ToList(); if (v4.Count > 0) AddBlockRule(policy, "ClaudeProxyLock: IPv4", app.Exe, string.Join(",", Ipv4Complement(v4))); if (v6.Count == 0) AddBlockRule(policy, "ClaudeProxyLock: IPv6", app.Exe, V6All); else { // allow proxy v6, block the rest dynamic allow = Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FWRule")); allow.Name = "ClaudeProxyLock: IPv6 allow"; allow.Grouping = FwGroup; allow.Direction = 2; allow.Action = 1; allow.ApplicationName = app.Exe; allow.RemoteAddresses = string.Join(",", v6); allow.Profiles = 0x7FFFFFFF; allow.Enabled = true; policy.Rules.Add(allow); AddBlockRule(policy, "ClaudeProxyLock: IPv6 block", app.Exe, V6All); } } } // absent | stale | ok (path mismatch -> stale) public static string FirewallState(string exe) { try { dynamic policy = FwPolicy(); int count = 0; bool stale = false; foreach (dynamic r in policy.Rules) { try { if ((string)r.Grouping != FwGroup) continue; count++; string prog = null; try { prog = (string)r.ApplicationName; } catch { } if (!string.IsNullOrEmpty(prog) && exe != null && !prog.Equals(exe, StringComparison.OrdinalIgnoreCase)) stale = true; } catch { } } if (count == 0) return "absent"; return stale ? "stale" : "ok"; } catch { return "absent"; } } // ---- Xray ----------------------------------------------------------------- public static bool XrayPresent() { return File.Exists(XrayExe); } public static void EnsureXray() { if (XrayPresent()) return; Directory.CreateDirectory(BinDir); // embedded resource "ClaudeProxy.xray.zip" var asm = Assembly.GetExecutingAssembly(); string resName = asm.GetManifestResourceNames().FirstOrDefault(n => n.EndsWith("xray.zip", StringComparison.OrdinalIgnoreCase)); if (resName != null) { string tmp = Path.Combine(Path.GetTempPath(), "claudeproxy-xray.zip"); using (var rs = asm.GetManifestResourceStream(resName)) using (var fs = File.Create(tmp)) rs.CopyTo(fs); using (var z = ZipFile.OpenRead(tmp)) foreach (var e in z.Entries) { if (string.IsNullOrEmpty(e.Name)) continue; string dest = Path.Combine(BinDir, e.Name); e.ExtractToFile(dest, true); } try { File.Delete(tmp); } catch { } } if (!XrayPresent()) throw new Exception("Не удалось распаковать встроенный Xray."); } public static void StopXray() { foreach (var p in Process.GetProcessesByName("xray")) { try { if (p.MainModule.FileName.StartsWith(BinDir, StringComparison.OrdinalIgnoreCase)) p.Kill(); } catch { } } Thread.Sleep(300); } public static bool LocalSocksUp(int port) { return TcpReachable("127.0.0.1", port, 500); } public static void StartXray(string vlessLink, int localPort) { EnsureXray(); StopXray(); File.WriteAllText(XrayCfgPath, Vless.BuildXrayConfig(vlessLink, localPort)); var psi = new ProcessStartInfo { FileName = XrayExe, Arguments = "run -config \"" + XrayCfgPath + "\"", UseShellExecute = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden }; Process.Start(psi); for (int i = 0; i < 30; i++) { if (LocalSocksUp(localPort)) return; Thread.Sleep(200); } StopXray(); throw new Exception("Xray не поднял локальный SOCKS на 127.0.0.1:" + localPort + ". Проверь ссылку и сервер."); } public static bool XrayRunning() { return Process.GetProcessesByName("xray").Any(p => { try { return p.MainModule.FileName.StartsWith(BinDir, StringComparison.OrdinalIgnoreCase); } catch { return false; } }); } } }