import 'dart:io'; import 'dart:async'; import 'package:get/get.dart'; import 'package:yaml_edit/yaml_edit.dart'; import 'package:path/path.dart' as path; import '../../common/constants.dart'; import '../../const/const.dart'; import '../../controller/controllers.dart'; import '../../data/model/NodeMode.dart'; import '../../utils/shell.dart'; import '../../utils/utils.dart'; import '../mode/clash_config.dart'; class ClashService extends GetxController { Process? clashCoreProcess; final coreStatus = RunningState.stoped.obs; final serviceMode = false.obs; bool get clashServiceIsRuning => coreStatus.value == RunningState.running; Future startClashCore() async { final timeout = const Duration(seconds: 30); final checkInterval = const Duration(milliseconds: 200); var startTime = DateTime.now(); await controllers.config.readClashCoreApi(); try { controllers.global.updateMsg("启动内核---${controllers.config.config.value.selected}"); if (controllers.config.config.value.selected == 'init_proxy.yaml') { controllers.global.updateMsg("启动内核初始化"); } else { controllers.global.updateMsg("启动内核"); } coreStatus.value = RunningState.starting; int? exitCode; clashCoreProcess = await Process.start( Files.assetsCCore.path, ['-d', Paths.config.path, '-f', path.join(Paths.config.path, controllers.config.config.value.selected)], mode: ProcessStartMode.inheritStdio ); clashCoreProcess!.exitCode.then((code) => exitCode = code); if (exitCode != null && exitCode != 0) { controllers.global.updateMsg("启动内核错误,请重启点电脑测试"); return false; } controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value); while (DateTime.now().difference(startTime) < timeout) { try { controllers.global.updateMsg("等待内核启动.."); await controllers.core.fetchHello(); break; } catch (_) { await Future.delayed(checkInterval); } } if (DateTime.now().difference(startTime) >= timeout) { coreStatus.value = RunningState.error; controllers.global.updateMsg("内核启动超时,重新点击加速后尝试。"); return false; } await controllers.core.updateConfig(); coreStatus.value = RunningState.running; controllers.global.updateMsg("内核状态:${coreStatus.value == RunningState.running} "); return true; } catch (e) { controllers.global.updateMsg("启动内核错误"); coreStatus.value = RunningState.error; return false; } } Future stopClashCore() async { coreStatus.value = RunningState.stopping; await controllers.global.closeProxy(); clashCoreProcess?.kill(); killProcess(ClashName.name); coreStatus.value = RunningState.stoped; } Future initClashCoreConfig() async { controllers.config.config.value.selected = Files.makeInitProxyConfig.path; await startClashCore(); if (coreStatus.value == RunningState.error) { controllers.global.updateMsg("启动内核失败..."); return false; } else { await controllers.core.updateVersion(); controllers.global.updateMsg("启动内核成功..."); return true; } } Future stopClash() async { controllers.config.config.value.selected = Files.makeInitProxyConfig.path; if (coreStatus.value == RunningState.running) { await controllers.config.readClashCoreApi(); controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value); await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected)); } } Future reloadClashCore() async { try { controllers.config.config.value.selected = Files.makeProxyConfig.path; if (coreStatus.value == RunningState.running) { controllers.global.updateMsg("切换配置..."); await controllers.config.readClashCoreApi(); controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value); await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected)); controllers.global.updateMsg("fetchReloadConfig${controllers.config.clashCoreApiAddress.value}..."); } } catch (e) { controllers.global.updateMsg("重新配置..."); await controllers.config.readClashCoreApi(); controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value); await controllers.core.changeConfig(path.join(Paths.config.path, controllers.config.config.value.selected)); } } Future generateYamlConfig(List nodes) async { final config = ClashConfig( mixedPort: 9788, allowLan: true, bindAddress: '*', mode: controllers.global.modesSelect.value, logLevel: 'info', externalController: '127.0.0.1:9799', unifiedDelay: false, geodataMode: true, tcpConcurrent: false, findProcessMode: 'strict', globalClientFingerprint: 'chrome', profile: { 'store-selected': true, 'store-fake-ip': true, }, sniffer: Sniffer( enable: true, sniff: { 'HTTP': { 'ports': [80, '8080-8880'], 'override-destination': true, }, 'TLS': { 'ports': [443, 8443], }, 'QUIC': { 'ports': [443, 8443], }, }, skipDomain: ['www.baidu.com'], ), dns: DNS( enable: true, listen: kDnsListenPort, ipv6: false, enhancedMode: kRedirHostMode, fakeIpFilter: null, nameserver: kDomesticDNS, proxyServerNameserver: kDomesticDNS, nameserverPolicy: { 'geosite:cn,private': kDomesticDNS, 'geosite:geolocation-!cn': kForeignDNS, }, ), tun: Tun( enable: controllers.global.routeModesSelect.value == "tun" ? true:false, stack: 'system', autoRoute: true, autoRedirect: true, autoDetectInterface: true, dnsHijack: ['any:53'], ), proxies: [], proxyGroups: [ ProxyGroup( name: 'proxy', type: 'select', proxies: [], ), ], rules: [ 'GEOSITE,geolocation-!cn,proxy', 'GEOSITE,cn,DIRECT', 'GEOIP,CN,DIRECT', 'MATCH,proxy', ], ); for (final node in nodes) { BaseProxy proxy; switch (node.type) { case 'trojan': proxy = TrojanProxy( name: node.name ?? '', server: node.host ?? '', port: node.port ?? 0, password: node.passwd ?? '', udp: node.udp == 1, sni: node.sni, ); break; case 'shadowsocks': proxy = SSProxy( name: node.name ?? '', type: 'ss', server: node.host ?? '', port: node.port ?? 0, password: node.passwd ?? '', cipher: node.method ?? '', udp: node.udp == 1, ); break; case 'v2ray': final type = (node.vless == 1) ? 'vless' : 'vmess'; if (type == 'vless') { proxy = VlessProxy( name: node.name ?? '', uuid: node.uuid ?? '', server: node.host ?? '', port: node.port ?? 0, udp: node.udp == 1, flow: 'xtls-rprx-vision', tls: true, servername: node.v2Sni, realityOpts: {'public-key': node.vlessPulkey}, network: 'tcp', ); } else { proxy = VmessProxy( name: node.name ?? '', uuid: node.uuid ?? '', server: node.host ?? '', port: node.port ?? 0, alterId: node.v2AlterId, cipher: node.method, udp: node.udp == 1, ); } break; default: continue; } config.proxies?.add(proxy.toJson()); config.proxyGroups?[0].proxies.add(node.name ?? ''); } return config.toYaml(); } Future saveConfigToFile(String filePath, List nodes) async { try { final configYaml = await generateYamlConfig(nodes); final file = File(filePath); await file.writeAsString(configYaml); print('配置文件已成功保存到: $filePath'); } catch (e) { print('保存配置文件时发生错误: $e'); throw Exception('无法保存配置文件'); } } } extension ConfigToYaml on ClashConfig { String toYaml() { final yamlMap = toJson(); final yamlEditor = YamlEditor(''); yamlEditor.update([], yamlMap); return yamlEditor.toString(); } }