import 'dart:convert'; 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 updatePorts() async { // 检查端口占用 int newPort = await findAvailablePort(controllers.config.mixedPort.value+1); controllers.config.mixedPort.value = newPort; controllers.global.updateMsg("混合端口已更新为: $newPort"); // 检查API端口占用 int newApiPort = await findAvailablePort(controllers.config.apiAddressPort.value); controllers.config.apiAddressPort.value = newApiPort; controllers.global.updateMsg("API端口已更新为: $newApiPort"); int newDnsPort = await findAvailablePort(int.parse(controllers.config.dnsPort.value.split(':').last)); controllers.config.dnsPort.value = '${controllers.config.dnsPort.value.split(':').first}:$newDnsPort'; await controllers.config.saveConfig(); //await controllers.config.readClashCoreApi(); } Future makeInitConfig() async { //await updatePorts(); //await controllers.config.readClashCoreApi(); var mode = controllers.global.modesSelect.value; var clashConfig = ClashConfig( mixedPort: controllers.config.mixedPort.value, allowLan: true, bindAddress: '*', mode: mode, logLevel: 'debug', externalController: '127.0.0.1:${controllers.config.apiAddressPort.value}', unifiedDelay: false, geodataMode: true, tcpConcurrent: false, findProcessMode: 'strict', globalClientFingerprint: 'chrome', dns: DNS( enable: false, listen: controllers.config.dnsPort.value, ipv6: false, enhancedMode: kRedirHostMode, fakeIpFilter: null, nameserver: kDomesticDNS, proxyServerNameserver: kDomesticDNS, nameserverPolicy: { 'geosite:cn,private': kDomesticDNS, 'geosite:geolocation-!cn': kForeignDNS, }, ), tun: Tun( enable: false, stack: 'system', autoRoute: true, autoRedirect: true, autoDetectInterface: true, dnsHijack: ['any:53'], ), proxies: [], rules: [ 'GEOIP,CN,DIRECT', 'MATCH,DIRECT' ] ); try { final file = File(path.join(Paths.config.path, Files.makeInitProxyConfig.path )); await file.writeAsString(clashConfig.toYaml()); print('配置文件已成功保存到: ${file.path}'); } catch (e) { print('保存配置文件时发生错误: $e'); throw Exception('无法保存配置文件'); } } 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 == controllers.config.config.value.selected) { 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("点击连接 "); 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.makeProxyConfig.path; //await makeInitConfig(); 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 { // if(coreStatus.value == RunningState.stoped){ // await updatePorts(); // } 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) { // if(coreStatus.value == RunningState.stoped){ // await updatePorts(); // } 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 { // await updatePorts(); final config = ClashConfig( mixedPort: controllers.config.mixedPort.value, allowLan: true, bindAddress: '*', mode: controllers.global.modesSelect.value == "global" ? "global" : "rule", logLevel: 'info', externalController: '127.0.0.1:${controllers.config.apiAddressPort.value}', 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: 'gvisor', autoRoute: true, autoRedirect: false, autoDetectInterface: true, dnsHijack: ['any:53'], ), proxies: [], proxyGroups: [ ProxyGroup( name: 'proxy', type: 'select', proxies: [], ), ], rules: [ '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: '', 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(); } }