import 'dart:io'; import 'package:speed_safe/app/util/log.dart'; import 'package:path/path.dart' as p; enum OS { windows, linux, macos } enum Architecture { x86_64, arm64 } const speedVersion = '0.7.6'; const speedBuildNumber = 7; const speedFullVersion = '$speedVersion+$speedBuildNumber'; const speedLastCommitHash = 'SELF_BUILD'; const String singBoxUrl = 'https://github.com/SagerNet/sing-box'; const String xrayCoreRepoUrl = 'https://github.com/xtls/xray-core'; const String shadowsocksRustUrl = 'https://github.com/shadowsocks/shadowsocks-rust'; const String hysteriaUrl = 'https://github.com/apernet/hysteria'; const String singBoxRulesRepoUrl = 'https://github.com/lyc8503/sing-box-rules'; const String geositeDBUrl = 'https://github.com/lyc8503/sing-box-rules/releases/latest/download/geosite.db'; const String geoipDBUrl = 'https://github.com/lyc8503/sing-box-rules/releases/latest/download/geoip.db'; const String v2rayRulesDatRepoUrl = 'https://github.com/Loyalsoldier/v2ray-rules-dat'; const String geositeDatUrl = 'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat'; const String geoipDatUrl = 'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat'; const String sphiaRepoUrl = 'https://github.com/YukidouSatoru/sphia'; final coreRepositories = { 'sing-box': singBoxUrl, 'xray-core': xrayCoreRepoUrl, 'shadowsocks-rust': shadowsocksRustUrl, 'hysteria': hysteriaUrl, 'sing-box-rules': singBoxRulesRepoUrl, 'v2ray-rules-dat': v2rayRulesDatRepoUrl, 'sphia': sphiaRepoUrl, // Add new core here }; class SystemUtil{ static late final OS os; static late final Architecture architecture; static late final bool isRoot; static void init() { os = determineOS(); architecture = determineArchitecture(); isRoot = determineIsRoot(); } static OS determineOS() { if(Platform.isWindows) { return OS.windows; } else if (Platform.isLinux) { return OS.linux; } else if (Platform.isMacOS){ return OS.macos; } else { logger.e('Unsupported OS'); throw Exception('Unsupported OS'); } } static Architecture determineArchitecture() { if (os == OS.windows) { final arch = Platform.environment['PROCESSOR_ARCHITECTURE']; // https://learn.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details if (arch == 'AMD64') { return Architecture.x86_64; } else if (arch == 'ARM64') { return Architecture.arm64; } else { logger.e('Unsupported Architecture'); throw Exception('Unsupported Architecture'); } } else { final result = Process.runSync('uname', ['-m']); if (result.exitCode == 0) { final arch = result.stdout.toString().trim(); if (arch == 'x86_64') { return Architecture.x86_64; } else if (arch == 'aarch64' || arch == 'arm64') { return Architecture.arm64; } else { logger.e('Unsupported Architecture'); throw Exception('Unsupported Architecture'); } } else { logger.e('Unsupported Architecture'); throw Exception('Unsupported Architecture'); } } } static bool determineIsRoot() { if (os == OS.windows) { final result = Process.runSync('net', ['session']); if (result.exitCode == 0) { return true; } else { return false; } } else { final result = Process.runSync('id', ['-u']); if (result.exitCode == 0) { return result.stdout.toString().trim() == '0'; } else { return false; } } } static void enableWindowsProxy(String listen, int httpPort) async { await runCommand('reg', [ 'add', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', '/v', 'ProxyEnable', '/t', 'REG_DWORD', '/d', '1', '/f' ]); await runCommand('reg', [ 'add', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', '/v', 'ProxyServer', '/d', '$listen:$httpPort', '/f' ]); await runCommand('reg', [ 'add', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', '/v', 'ProxyOverride', '/d', '127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*', '/f' ]); } static void disableWindowsProxy() async { await runCommand('reg', [ 'add', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', '/v', 'ProxyEnable', '/t', 'REG_DWORD', '/d', '0', '/f' ]); } static void enableMacOSProxy(String listen, int httpPort) async { await runCommand( 'networksetup', ['-setwebproxy', 'wi-fi', listen, httpPort.toString()]); await runCommand('networksetup', ['-setsecurewebproxy', 'wi-fi', listen, httpPort.toString()]); await runCommand('networksetup', ['-setwebproxystate', 'wi-fi', 'on']); await runCommand( 'networksetup', ['-setsecurewebproxystate', 'wi-fi', 'on']); } static void disableMacOSProxy() async { await runCommand('networksetup', ['-setwebproxystate', 'wi-fi', 'off']); await runCommand( 'networksetup', ['-setsecurewebproxystate', 'wi-fi', 'off']); } static Future runCommand( String executable, List arguments) async { final result = await Process.run(executable, arguments, runInShell: true); if (result.exitCode != 0) { logger.e('Failed to run command: $executable $arguments'); throw Exception('Failed to run command: $executable $arguments'); } } static String getCoreFileName(String coreName) { final ext = os == OS.windows ? '.exe' : ''; switch (coreName) { case 'sing-box': return 'sing-box$ext'; case 'xray-core': return 'xray$ext'; case 'shadowsocks-rust': return 'sslocal$ext'; case 'hysteria': final plat = os == OS.macos ? 'darwin' : os.name; final arch = architecture == Architecture.arm64 ? 'arm64' : 'amd64'; return 'hysteria-$plat-$arch$ext'; case 'sphia': return 'sphia$ext'; case 'upgradeAgent': return 'upgradeAgent$ext'; default: throw Exception('Unsupported core: $coreName'); } } static void setFilePermission(String fileName) { if (os != OS.windows) { Process.runSync('chmod', ['+x', fileName]); } } static void createDirectory(String dirName) { final dir = Directory(dirName); try { if (!dir.existsSync()) { logger.i('Creating directory: $dirName'); dir.createSync(); } } catch (e) { logger.e('Error creating directory: $e'); } } static bool fileExists(String fileName) { return File(p.join(binPath, fileName)).existsSync(); } static void deleteFileIfExists(String filePath, String logMessage) { final file = File(filePath); if (file.existsSync()) { logger.i(logMessage); file.deleteSync(); } } static bool coreExists(String coreName) { if (coreName == 'sing-box-rules') { return fileExists(p.join(binPath, 'geoip.db')) && fileExists(p.join(binPath, 'geosite.db')); } else if (coreName == 'v2ray-rules-dat') { return fileExists(p.join(binPath, 'geoip.dat')) && fileExists(p.join(binPath, 'geosite.dat')); } else { return fileExists(p.join(binPath, getCoreFileName(coreName))); } } static List getCoreFileNames() { List fileNames = []; coreRepositories.entries .toList() .sublist(0, coreRepositories.length - 3) .forEach((entry) { fileNames.add(getCoreFileName(entry.key)); }); return fileNames; } static void initPaths() { binPath = p.join(appPath, 'bin'); configPath = p.join(appPath, 'config'); logPath = p.join(appPath, 'log'); tempPath = p.join(appPath, 'temp'); } } late final String execPath; late final String appPath; late String binPath; late String configPath; late String logPath; late String tempPath;