├── src-tauri ├── .gitignore ├── icons │ ├── 32x32.png │ ├── 64x64.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── FuckACE.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square310x310Logo.png │ └── android │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── values │ │ └── ic_launcher_background.xml │ │ └── mipmap-anydpi-v26 │ │ └── ic_launcher.xml ├── capabilities │ └── default.json ├── build.rs ├── src │ ├── mainfest.xml │ ├── main.rs │ └── lib.rs ├── Cargo.toml └── tauri.conf.json ├── public └── logo.png ├── .vscode └── extensions.json ├── src ├── main.tsx ├── vite-env.d.ts ├── main.ts ├── styles.css └── App.tsx ├── index.html ├── .gitignore ├── tsconfig.json ├── package.json ├── README.md ├── vite.config.ts └── LICENSE /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /gen/schemas 3 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/public/logo.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/64x64.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/FuckACE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/FuckACE.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shshouse/FuckACE/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #fff 4 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | 5 | ReactDOM.createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ) -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_SUPABASE_URL: string 5 | readonly VITE_SUPABASE_ANON_KEY: string 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv 10 | } 11 | -------------------------------------------------------------------------------- /src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": ["main"], 6 | "permissions": [ 7 | "core:default", 8 | "opener:default", 9 | "shell:allow-open" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let mut windows = tauri_build::WindowsAttributes::new(); 3 | windows = windows.app_manifest(include_str!("src/mainfest.xml")); 4 | 5 | tauri_build::try_build( 6 | tauri_build::Attributes::new() 7 | .windows_attributes(windows) 8 | ).expect("failed to run tauri-build"); 9 | } 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FuckACE 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | info 26 | src/utils 27 | src/hooks 28 | src/supabaseClient.ts 29 | 30 | 便携版 -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | 16 | /* React */ 17 | "jsx": "react-jsx", 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /src-tauri/src/mainfest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | FuckACE Application 10 | 11 | 12 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FuckACE", 3 | "private": true, 4 | "version": "0.5.2", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "tauri": "tauri", 11 | "build:portable": "powershell -ExecutionPolicy Bypass -File ./build-portable.ps1" 12 | }, 13 | "dependencies": { 14 | "@emotion/react": "^11.14.0", 15 | "@emotion/styled": "^11.14.1", 16 | "@mui/icons-material": "^7.3.5", 17 | "@mui/material": "^7.3.5", 18 | "@supabase/supabase-js": "^2.81.1", 19 | "@tauri-apps/api": "^2", 20 | "@tauri-apps/plugin-opener": "^2", 21 | "@tauri-apps/plugin-process": "^2.3.1", 22 | "@tauri-apps/plugin-shell": "^2.3.3", 23 | "@types/react": "^19.2.2", 24 | "@types/react-dom": "^19.2.2", 25 | "@vitejs/plugin-react": "^4.7.0", 26 | "react": "^19.2.0", 27 | "react-dom": "^19.2.0" 28 | }, 29 | "devDependencies": { 30 | "@tauri-apps/cli": "^2", 31 | "typescript": "~5.6.2", 32 | "vite": "^6.0.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | 3 | fn main() { 4 | #[cfg(target_os = "windows")] 5 | { 6 | setup_portable_webview2(); 7 | } 8 | 9 | fuck_ace_lib::run() 10 | } 11 | 12 | #[cfg(target_os = "windows")] 13 | fn setup_portable_webview2() { 14 | use std::env; 15 | use std::path::PathBuf; 16 | let exe_path = env::current_exe().unwrap_or_else(|_| PathBuf::from(".")); 17 | let default_path = PathBuf::from("."); 18 | let exe_dir = exe_path.parent().unwrap_or(&default_path); 19 | let webview2_dir = exe_dir.join("webview2"); 20 | if webview2_dir.exists() && webview2_dir.is_dir() { 21 | env::set_var("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", webview2_dir.to_str().unwrap_or("")); 22 | 23 | let user_data_dir = exe_dir.join(".webview2-data"); 24 | env::set_var("WEBVIEW2_USER_DATA_FOLDER", user_data_dir.to_str().unwrap_or("")); 25 | 26 | env::set_var("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--no-sandbox"); 27 | 28 | println!("Using portable WebView2 from: {:?}", webview2_dir); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FuckACE 2 | ## FuckACE是一个用来优化所有使用ACE的游戏的免安装软件,可以一键设置ACE进程为效率模式和绑定到最后一个小核心,以降低ACE对电脑的性能影响。 3 | ### 喜欢的话请点点star,谢谢!Ciallo~(∠・ω<)⌒☆ 4 | 5 | 最新版本下载(github的一般会延后发布): 6 | - [前往MikuGame下载最新版本](https://www.mikugame.icu/modpacks) 7 | 8 | 相关使用教程请看B站视频: 9 | - [FuckACE使用教程](https://www.bilibili.com/video/BV1ePCnBpEWp/) 10 | 11 | 反馈问题,或者有什么建议都可以说喵>﹏< 12 | - [提出建议!](https://github.com/shshouse/FuckACE/issues/new) 13 | 14 | image 15 | 16 | 注意:
17 | 1.软件不会彻底关闭ACE,只是对其占用进行限制,别开桂嗷。
18 | 2.主包测试了大战场是没事的,但是不排除会误封的可能,请充分了解潜在风险,使用即代表已经了解并接受这些风险/(ㄒoㄒ)/~~ 19 | 20 | ## 核心机制 21 | ### 1.被动限制: 22 | 通过注册表修改,一键降低ACE的CPU优先级和I/O优先级,同时提高对应游戏优先级。 23 | 24 | ### 2.主动限制: 25 | 在主动限制下,可以额外对ACE进行限制:
26 | 1.绑定到最后一个核心(一般是小核)
27 | 2.将ACE设置为效率模式(减低占用)
28 | 3.降低ACE的内存优先性
29 | 30 | 将被执行限制的进程:
31 | 1.SGuard64.exe
32 | 2.SGuardSvc64.exe
33 | 3.Weixin.exe
34 | 35 | ## 开发者 36 | - 开发者: [shshouse](https://github.com/shshouse) 37 | - Bilibili: [shshouse](https://space.bilibili.com/3493127123897196) 38 | - 爱发电: [shshouse](https://afdian.com/a/shshouse) 39 | 40 | ## 免责声明 41 | 本软件仅供技术研究和学习使用,使用本软件造成的任何后果由使用者自行承担。 42 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "FuckACE" 3 | version = "0.5.2" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | edition = "2021" 7 | 8 | [package.metadata.windows.manifest] 9 | path = "src/mainfest.xml" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [lib] 14 | # The `_lib` suffix may seem redundant but it is necessary 15 | # to make the lib name unique and wouldn't conflict with the bin name. 16 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 17 | name = "fuck_ace_lib" 18 | crate-type = ["staticlib", "cdylib", "rlib"] 19 | 20 | [build-dependencies] 21 | tauri-build = { version = "2", features = [] } 22 | 23 | [dependencies] 24 | tauri = { version = "2", features = ["tray-icon"] } 25 | tauri-plugin-opener = "2" 26 | tauri-plugin-shell = "2" 27 | tauri-plugin-process = "2" 28 | serde = { version = "1", features = ["derive"] } 29 | serde_json = "1" 30 | sysinfo = "0.30" 31 | windows = { version = "0.58", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_System_Diagnostics_ToolHelp", "Win32_System_SystemInformation", "Win32_Security"] } 32 | tokio = { version = "1", features = ["full"] } 33 | winreg = "0.52" 34 | 35 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // @ts-expect-error process is a nodejs global 5 | const host = process.env.TAURI_DEV_HOST; 6 | 7 | // https://vite.dev/config/ 8 | export default defineConfig(async () => ({ 9 | plugins: [react()], 10 | 11 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 12 | // 13 | // 1. prevent Vite from obscuring rust errors 14 | clearScreen: false, 15 | // 2. tauri expects a fixed port, fail if that port is not available 16 | server: { 17 | port: 8787, 18 | strictPort: true, 19 | host: host || false, 20 | hmr: host 21 | ? { 22 | protocol: "ws", 23 | host, 24 | port: 8788, 25 | } 26 | : undefined, 27 | watch: { 28 | // 3. tell Vite to ignore watching `src-tauri` 29 | ignored: ["**/src-tauri/**"], 30 | }, 31 | }, 32 | build: { 33 | chunkSizeWarningLimit: 1000, 34 | rollupOptions: { 35 | output: { 36 | manualChunks: { 37 | vendor: ['react', 'react-dom'], 38 | ui: ['@mui/material', '@mui/icons-material'], 39 | supabase: ['@supabase/supabase-js'], 40 | } 41 | } 42 | } 43 | } 44 | })); 45 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2", 3 | "productName": "FuckACE", 4 | "version": "0.1.0", 5 | "identifier": "com.shshouse.FuckACE", 6 | "build": { 7 | "beforeDevCommand": "npm run dev", 8 | "devUrl": "http://localhost:8787", 9 | "beforeBuildCommand": "npm run build", 10 | "frontendDist": "../dist" 11 | }, 12 | "app": { 13 | "withGlobalTauri": true, 14 | "windows": [ 15 | { 16 | "title": "FuckACE", 17 | "width": 900, 18 | "height": 700, 19 | "resizable": true, 20 | "fullscreen": false, 21 | "closable": true, 22 | "skipTaskbar": false 23 | } 24 | ], 25 | "security": { 26 | "csp": null, 27 | "capabilities": [ 28 | { 29 | "identifier": "main-capability", 30 | "windows": ["main"], 31 | "permissions": [ 32 | "core:default", 33 | "shell:default", 34 | "process:allow-exit" 35 | ] 36 | } 37 | ] 38 | } 39 | }, 40 | "plugins": { 41 | "shell": { 42 | "open": true 43 | } 44 | }, 45 | "bundle": { 46 | "active": true, 47 | "targets": "all", 48 | "icon": [ 49 | "icons/32x32.png", 50 | "icons/128x128.png", 51 | "icons/128x128@2x.png", 52 | "icons/icon.icns", 53 | "icons/icon.ico" 54 | ] 55 | }, 56 | "app": { 57 | "withGlobalTauri": true, 58 | "windows": [ 59 | { 60 | "title": "FuckACE", 61 | "width": 900, 62 | "height": 700, 63 | "resizable": true, 64 | "fullscreen": false, 65 | "closable": true, 66 | "skipTaskbar": false 67 | } 68 | ], 69 | "security": { 70 | "csp": null, 71 | "capabilities": [ 72 | { 73 | "identifier": "main-capability", 74 | "windows": ["main"], 75 | "permissions": [ 76 | "core:default", 77 | "shell:default", 78 | "process:allow-exit" 79 | ] 80 | } 81 | ] 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | let startBtn: HTMLButtonElement | null; 4 | let stopBtn: HTMLButtonElement | null; 5 | let manualBtn: HTMLButtonElement | null; 6 | let monitorStatusEl: HTMLElement | null; 7 | let countdownEl: HTMLElement | null; 8 | let targetCoreEl: HTMLElement | null; 9 | let sguard64StatusEl: HTMLElement | null; 10 | let sguardsvc64StatusEl: HTMLElement | null; 11 | let logContainerEl: HTMLElement | null; 12 | 13 | let isMonitoring = false; 14 | let countdownTimer: number | null = null; 15 | let countdownSeconds = 60; 16 | 17 | function addLogEntry(message: string) { 18 | if (!logContainerEl) return; 19 | 20 | const logEntry = document.createElement('div'); 21 | logEntry.className = 'log-entry'; 22 | logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; 23 | 24 | logContainerEl.appendChild(logEntry); 25 | logContainerEl.scrollTop = logContainerEl.scrollHeight; 26 | } 27 | 28 | 29 | function updateCountdown(seconds: number) { 30 | if (countdownEl) { 31 | countdownEl.textContent = `${seconds}秒`; 32 | } 33 | } 34 | 35 | function startCountdown() { 36 | if (countdownTimer) { 37 | clearInterval(countdownTimer); 38 | } 39 | 40 | countdownSeconds = 60; 41 | updateCountdown(countdownSeconds); 42 | 43 | countdownTimer = window.setInterval(() => { 44 | countdownSeconds--; 45 | updateCountdown(countdownSeconds); 46 | 47 | if (countdownSeconds <= 0) { 48 | if (isMonitoring) { 49 | executeProcessRestriction(); 50 | countdownSeconds = 60; 51 | updateCountdown(countdownSeconds); 52 | } 53 | } 54 | }, 1000); 55 | } 56 | async function executeProcessRestriction() { 57 | try { 58 | addLogEntry('进程限制开始b( ̄▽ ̄)d '); 59 | 60 | const result = await invoke('restrict_processes') as { 61 | target_core: number; 62 | sguard64_found: boolean; 63 | sguard64_restricted: boolean; 64 | sguardsvc64_found: boolean; 65 | sguardsvc64_restricted: boolean; 66 | message: string; 67 | }; 68 | 69 | if (targetCoreEl) { 70 | targetCoreEl.textContent = `核心 ${result.target_core}`; 71 | } 72 | 73 | if (sguard64StatusEl) { 74 | if (result.sguard64_found) { 75 | sguard64StatusEl.textContent = result.sguard64_restricted ? '已限制' : '已发现,未限制'; 76 | sguard64StatusEl.setAttribute('data-status', result.sguard64_restricted ? 'restricted' : 'running'); 77 | } else { 78 | sguard64StatusEl.textContent = '未找到'; 79 | sguard64StatusEl.removeAttribute('data-status'); 80 | } 81 | } 82 | 83 | if (sguardsvc64StatusEl) { 84 | if (result.sguardsvc64_found) { 85 | sguardsvc64StatusEl.textContent = result.sguardsvc64_restricted ? '已限制' : '已发现,未限制'; 86 | sguardsvc64StatusEl.setAttribute('data-status', result.sguardsvc64_restricted ? 'restricted' : 'running'); 87 | } else { 88 | sguardsvc64StatusEl.textContent = '未找到'; 89 | sguardsvc64StatusEl.removeAttribute('data-status'); 90 | } 91 | } 92 | 93 | addLogEntry(result.message); 94 | 95 | } catch (error) { 96 | addLogEntry(`执行失败: ${error}`); 97 | console.error('执行进程限制失败/(ㄒoㄒ)/~~', error); 98 | } 99 | } 100 | 101 | async function startMonitoring() { 102 | if (isMonitoring) return; 103 | 104 | isMonitoring = true; 105 | 106 | try { 107 | await invoke('start_timer'); 108 | if (startBtn) startBtn.disabled = true; 109 | if (stopBtn) stopBtn.disabled = false; 110 | if (monitorStatusEl) monitorStatusEl.textContent = '监控中'; 111 | addLogEntry('启动进程监控'); 112 | await executeProcessRestriction(); 113 | startCountdown(); 114 | } catch (error) { 115 | addLogEntry(`启动监控失败: ${error}`); 116 | isMonitoring = false; 117 | if (startBtn) startBtn.disabled = false; 118 | if (stopBtn) stopBtn.disabled = true; 119 | if (monitorStatusEl) monitorStatusEl.textContent = '已停止'; 120 | } 121 | } 122 | async function stopMonitoring() { 123 | if (!isMonitoring) return; 124 | isMonitoring = false; 125 | try { 126 | await invoke('stop_timer'); 127 | if (countdownTimer) { 128 | clearInterval(countdownTimer); 129 | countdownTimer = null; 130 | } 131 | 132 | if (startBtn) startBtn.disabled = false; 133 | if (stopBtn) stopBtn.disabled = true; 134 | if (monitorStatusEl) monitorStatusEl.textContent = '已停止'; 135 | if (countdownEl) countdownEl.textContent = '--'; 136 | 137 | addLogEntry('停止进程监控'); 138 | } catch (error) { 139 | addLogEntry(`停止监控失败: ${error}`); 140 | } 141 | } 142 | 143 | 144 | async function manualExecute() { 145 | if (!isMonitoring) { 146 | addLogEntry('请先启动监控'); 147 | return; 148 | } 149 | addLogEntry('手动执行限制操作'); 150 | await executeProcessRestriction(); 151 | if (countdownTimer) { 152 | clearInterval(countdownTimer); 153 | startCountdown(); 154 | } 155 | } 156 | function initializeUI() { 157 | startBtn = document.querySelector('#start-btn'); 158 | stopBtn = document.querySelector('#stop-btn'); 159 | manualBtn = document.querySelector('#manual-btn'); 160 | monitorStatusEl = document.querySelector('#monitor-status'); 161 | countdownEl = document.querySelector('#countdown'); 162 | targetCoreEl = document.querySelector('#target-core'); 163 | sguard64StatusEl = document.querySelector('#sguard64-status'); 164 | sguardsvc64StatusEl = document.querySelector('#sguardsvc64-status'); 165 | logContainerEl = document.querySelector('#log-container'); 166 | 167 | if (startBtn) { 168 | startBtn.addEventListener('click', startMonitoring); 169 | } 170 | 171 | if (stopBtn) { 172 | stopBtn.addEventListener('click', stopMonitoring); 173 | } 174 | 175 | if (manualBtn) { 176 | manualBtn.addEventListener('click', manualExecute); 177 | } 178 | if (monitorStatusEl) monitorStatusEl.textContent = '等待启动'; 179 | if (countdownEl) countdownEl.textContent = '60秒'; 180 | if (targetCoreEl) targetCoreEl.textContent = '检测中...'; 181 | 182 | addLogEntry('UI初始化完成'); 183 | } 184 | 185 | window.addEventListener('DOMContentLoaded', initializeUI); 186 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | .logo.vite:hover { 2 | filter: drop-shadow(0 0 2em #747bff); 3 | } 4 | 5 | .logo.typescript:hover { 6 | filter: drop-shadow(0 0 2em #2d79c7); 7 | } 8 | :root { 9 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 10 | font-size: 16px; 11 | line-height: 24px; 12 | font-weight: 400; 13 | 14 | color: #0f0f0f; 15 | background-color: #f6f6f6; 16 | 17 | font-synthesis: none; 18 | text-rendering: optimizeLegibility; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | -webkit-text-size-adjust: 100%; 22 | } 23 | 24 | @media (prefers-color-scheme: dark) { 25 | :root { 26 | color: #ffffff; 27 | background-color: #121212; 28 | } 29 | 30 | .status-section, 31 | .process-section, 32 | .log-section { 33 | background: #1e1e1e; 34 | color: #ffffff; 35 | } 36 | 37 | .process-item { 38 | border-color: #333; 39 | } 40 | 41 | .log-container { 42 | background: #1a1a1a; 43 | border-color: #333; 44 | } 45 | 46 | .process-status:not([data-status="running"]) { 47 | background: #333; 48 | color: #ccc; 49 | } 50 | } 51 | 52 | .container { 53 | margin: 0; 54 | padding: 2rem; 55 | display: flex; 56 | flex-direction: column; 57 | max-width: 800px; 58 | margin: 0 auto; 59 | } 60 | 61 | .subtitle { 62 | text-align: center; 63 | color: #666; 64 | margin-bottom: 2rem; 65 | } 66 | 67 | .status-section { 68 | background: white; 69 | border-radius: 8px; 70 | padding: 1.5rem; 71 | margin-bottom: 1.5rem; 72 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 73 | } 74 | 75 | .status-item { 76 | display: flex; 77 | justify-content: space-between; 78 | margin-bottom: 0.5rem; 79 | } 80 | 81 | .status-label { 82 | font-weight: 600; 83 | } 84 | 85 | .status-value { 86 | color: #007acc; 87 | } 88 | 89 | .process-section { 90 | background: white; 91 | border-radius: 8px; 92 | padding: 1.5rem; 93 | margin-bottom: 1.5rem; 94 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 95 | } 96 | 97 | .process-list { 98 | display: flex; 99 | flex-direction: column; 100 | gap: 1rem; 101 | } 102 | 103 | .process-item { 104 | display: flex; 105 | justify-content: space-between; 106 | align-items: center; 107 | padding: 0.5rem; 108 | border: 1px solid #e0e0e0; 109 | border-radius: 4px; 110 | } 111 | 112 | .process-name { 113 | font-weight: 500; 114 | } 115 | 116 | .process-status { 117 | padding: 0.25rem 0.75rem; 118 | border-radius: 12px; 119 | font-size: 0.875rem; 120 | font-weight: 500; 121 | } 122 | 123 | .process-status:not([data-status="running"]) { 124 | background: #f0f0f0; 125 | color: #666; 126 | } 127 | 128 | .process-status[data-status="running"] { 129 | background: #e8f5e8; 130 | color: #2e7d32; 131 | } 132 | 133 | .process-status[data-status="restricted"] { 134 | background: #fff3e0; 135 | color: #f57c00; 136 | } 137 | 138 | .control-section { 139 | display: flex; 140 | gap: 1rem; 141 | justify-content: center; 142 | margin-bottom: 1.5rem; 143 | } 144 | 145 | .btn { 146 | border-radius: 8px; 147 | border: 1px solid transparent; 148 | padding: 0.75rem 1.5rem; 149 | font-size: 1rem; 150 | font-weight: 500; 151 | font-family: inherit; 152 | cursor: pointer; 153 | transition: all 0.25s; 154 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); 155 | } 156 | 157 | .btn-primary { 158 | background-color: #007acc; 159 | color: white; 160 | } 161 | 162 | .btn-primary:hover { 163 | background-color: #005a9e; 164 | } 165 | 166 | .btn-secondary { 167 | background-color: #666; 168 | color: white; 169 | } 170 | 171 | .btn-secondary:hover:not(:disabled) { 172 | background-color: #555; 173 | } 174 | 175 | .btn-info { 176 | background-color: #17a2b8; 177 | color: white; 178 | } 179 | 180 | .btn-info:hover { 181 | background-color: #138496; 182 | } 183 | 184 | .btn:disabled { 185 | opacity: 0.6; 186 | cursor: not-allowed; 187 | } 188 | 189 | .log-section { 190 | background: white; 191 | border-radius: 8px; 192 | padding: 1.5rem; 193 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 194 | } 195 | 196 | .log-container { 197 | max-height: 200px; 198 | overflow-y: auto; 199 | border: 1px solid #e0e0e0; 200 | border-radius: 4px; 201 | padding: 1rem; 202 | background: #fafafa; 203 | } 204 | 205 | .log-entry { 206 | padding: 0.25rem 0; 207 | border-bottom: 1px solid #eee; 208 | font-family: 'Courier New', monospace; 209 | font-size: 0.875rem; 210 | } 211 | 212 | .log-entry:last-child { 213 | border-bottom: none; 214 | } 215 | 216 | @media (max-width: 768px) { 217 | .container { 218 | padding: 1rem; 219 | } 220 | 221 | .control-section { 222 | flex-direction: column; 223 | } 224 | 225 | .btn { 226 | width: 100%; 227 | } 228 | } 229 | 230 | .logo { 231 | height: 6em; 232 | padding: 1.5em; 233 | will-change: filter; 234 | transition: 0.75s; 235 | } 236 | 237 | .logo.tauri:hover { 238 | filter: drop-shadow(0 0 2em #24c8db); 239 | } 240 | 241 | .row { 242 | display: flex; 243 | justify-content: center; 244 | } 245 | 246 | a { 247 | font-weight: 500; 248 | color: #646cff; 249 | text-decoration: inherit; 250 | } 251 | 252 | a:hover { 253 | color: #535bf2; 254 | } 255 | 256 | h1 { 257 | text-align: center; 258 | } 259 | 260 | input, 261 | button { 262 | border-radius: 8px; 263 | border: 1px solid transparent; 264 | padding: 0.6em 1.2em; 265 | font-size: 1em; 266 | font-weight: 500; 267 | font-family: inherit; 268 | color: #0f0f0f; 269 | background-color: #ffffff; 270 | transition: border-color 0.25s; 271 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); 272 | } 273 | 274 | button { 275 | cursor: pointer; 276 | } 277 | 278 | button:hover { 279 | border-color: #396cd8; 280 | } 281 | button:active { 282 | border-color: #396cd8; 283 | background-color: #e8e8e8; 284 | } 285 | 286 | input, 287 | button { 288 | outline: none; 289 | } 290 | 291 | #greet-input { 292 | margin-right: 5px; 293 | } 294 | 295 | @media (prefers-color-scheme: dark) { 296 | :root { 297 | color: #f6f6f6; 298 | background-color: #2f2f2f; 299 | } 300 | 301 | a:hover { 302 | color: #24c8db; 303 | } 304 | 305 | input, 306 | button { 307 | color: #ffffff; 308 | background-color: #0f0f0f98; 309 | } 310 | button:active { 311 | background-color: #0f0f0f69; 312 | } 313 | 314 | .status-section, 315 | .process-section, 316 | .log-section { 317 | background: #1e1e1e; 318 | color: #f6f6f6; 319 | } 320 | 321 | .process-item { 322 | border-color: #444; 323 | } 324 | 325 | .log-container { 326 | background: #2a2a2a; 327 | border-color: #444; 328 | } 329 | 330 | .log-entry { 331 | border-color: #444; 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback, useRef } from 'react'; 2 | import { useInitialData } from './hooks/useOptimizedSupabase'; 3 | import { invoke } from '@tauri-apps/api/core'; 4 | import { open } from '@tauri-apps/plugin-shell'; 5 | import { Window } from '@tauri-apps/api/window'; 6 | import { storage } from './utils/storage'; 7 | import { 8 | Container, 9 | Paper, 10 | Typography, 11 | Button, 12 | Box, 13 | Chip, 14 | LinearProgress, 15 | List, 16 | ListItem, 17 | ListItemText, 18 | Divider, 19 | ThemeProvider, 20 | createTheme, 21 | CssBaseline, 22 | Avatar, 23 | Switch, 24 | FormControlLabel, 25 | Alert, 26 | Dialog, 27 | DialogTitle, 28 | DialogContent, 29 | DialogActions, 30 | Badge 31 | } from '@mui/material'; 32 | import { 33 | PlayArrow as StartIcon, 34 | CheckCircle, 35 | DarkMode as DarkModeIcon, 36 | LightMode as LightModeIcon, 37 | SportsEsports as GameIcon, 38 | Extension as ModIcon, 39 | GitHub as GitHubIcon, 40 | Notifications as NotificationsIcon, 41 | SystemUpdate as UpdateIcon, 42 | Close as CloseIcon, 43 | Warning as WarningIcon 44 | } from '@mui/icons-material'; 45 | 46 | const darkTheme = createTheme({ 47 | palette: { 48 | mode: 'dark', 49 | primary: { 50 | main: '#90caf9', 51 | }, 52 | secondary: { 53 | main: '#f48fb1', 54 | }, 55 | success: { 56 | main: '#81c784', 57 | }, 58 | warning: { 59 | main: '#ffb74d', 60 | }, 61 | error: { 62 | main: '#f44336', 63 | }, 64 | background: { 65 | default: '#121212', 66 | paper: '#1e1e1e', 67 | }, 68 | text: { 69 | primary: '#ffffff', 70 | secondary: 'rgba(255, 255, 255, 0.7)', 71 | }, 72 | }, 73 | typography: { 74 | h3: { 75 | fontWeight: 600, 76 | }, 77 | h6: { 78 | fontWeight: 500, 79 | }, 80 | }, 81 | components: { 82 | MuiPaper: { 83 | styleOverrides: { 84 | root: { 85 | backgroundImage: 'none', 86 | }, 87 | }, 88 | }, 89 | MuiChip: { 90 | styleOverrides: { 91 | root: { 92 | fontWeight: 500, 93 | }, 94 | }, 95 | }, 96 | }, 97 | }); 98 | 99 | interface ProcessStatus { 100 | target_core: number; 101 | sguard64_found: boolean; 102 | sguard64_restricted: boolean; 103 | sguardsvc64_found: boolean; 104 | sguardsvc64_restricted: boolean; 105 | weixin_found: boolean; 106 | weixin_restricted: boolean; 107 | message: string; 108 | } 109 | 110 | interface LogEntry { 111 | id: number; 112 | timestamp: string; 113 | message: string; 114 | } 115 | 116 | interface SystemInfo { 117 | cpu_model: string; 118 | cpu_cores: number; 119 | cpu_logical_cores: number; 120 | os_name: string; 121 | os_version: string; 122 | is_admin: boolean; 123 | total_memory_gb: number; 124 | webview2_env: string; 125 | } 126 | 127 | interface ProcessPerformance { 128 | pid: number; 129 | name: string; 130 | cpu_usage: number; 131 | memory_mb: number; 132 | } 133 | 134 | function App() { 135 | const [isMonitoring, setIsMonitoring] = useState(false); 136 | const [targetCore, setTargetCore] = useState(null); 137 | const [processStatus, setProcessStatus] = useState(null); 138 | const [logs, setLogs] = useState([]); 139 | const [loading, setLoading] = useState(false); 140 | const [darkMode, setDarkMode] = useState(true); 141 | const [systemInfo, setSystemInfo] = useState(null); 142 | const logContainerRef = useRef(null); 143 | const [performance, setPerformance] = useState([]); 144 | const [enableCpuAffinity, setEnableCpuAffinity] = useState(true); 145 | const [enableProcessPriority, setEnableProcessPriority] = useState(true); 146 | const [enableEfficiencyMode, setEnableEfficiencyMode] = useState(false); 147 | const [enableIoPriority, setEnableIoPriority] = useState(false); 148 | const [enableMemoryPriority, setEnableMemoryPriority] = useState(false); 149 | const [autoStartEnabled, setAutoStartEnabled] = useState(false); 150 | const [rememberChoices, setRememberChoices] = useState(false); 151 | const [showAnnouncements, setShowAnnouncements] = useState(false); 152 | const [showUpdateDialog, setShowUpdateDialog] = useState(false); 153 | const [showCloseConfirm, setShowCloseConfirm] = useState(false); 154 | const [gameProcesses] = useState([]); 155 | 156 | const { announcements, latestVersion, hasUpdate } = useInitialData('0.5.2'); 157 | 158 | const addLog = useCallback((message: string) => { 159 | const newLog: LogEntry = { 160 | id: Date.now() + Math.random(), 161 | timestamp: new Date().toLocaleTimeString(), 162 | message, 163 | }; 164 | setLogs(prev => [...prev, newLog]); 165 | }, []); 166 | 167 | const executeProcessRestriction = useCallback(async () => { 168 | try { 169 | addLog('进程限制开始b( ̄▽ ̄)d '); 170 | setLoading(true); 171 | 172 | const result = await invoke('restrict_processes', { 173 | enableCpuAffinity, 174 | enableProcessPriority, 175 | enableEfficiencyMode, 176 | enableIoPriority, 177 | enableMemoryPriority 178 | }); 179 | setProcessStatus(result); 180 | setTargetCore(result.target_core); 181 | 182 | addLog(result.message); 183 | } catch (error) { 184 | addLog(`执行失败: ${error}`); 185 | console.error('执行进程限制失败/(ㄒoㄒ)/~~', error); 186 | } finally { 187 | setLoading(false); 188 | } 189 | }, [addLog, enableCpuAffinity, enableProcessPriority, enableEfficiencyMode, enableIoPriority, enableMemoryPriority]); 190 | 191 | const executeOnce = useCallback(async () => { 192 | try { 193 | setIsMonitoring(true); 194 | const modeStr = [ 195 | enableCpuAffinity && 'CPU亲和性', 196 | enableProcessPriority && '进程优先级', 197 | enableEfficiencyMode && '效率模式', 198 | enableIoPriority && 'I/O优先级', 199 | enableMemoryPriority && '内存优先级' 200 | ].filter(Boolean).join('+') || '标准模式'; 201 | addLog(`执行进程限制 (${modeStr})`); 202 | await executeProcessRestriction(); 203 | setIsMonitoring(false); 204 | } catch (error) { 205 | addLog(`执行失败: ${error}`); 206 | setIsMonitoring(false); 207 | } 208 | }, [addLog, executeProcessRestriction, enableCpuAffinity, enableProcessPriority, enableEfficiencyMode, enableIoPriority, enableMemoryPriority]); 209 | 210 | const fetchSystemInfo = useCallback(async () => { 211 | try { 212 | const info = await invoke('get_system_info'); 213 | setSystemInfo(info); 214 | const lastCore = info.cpu_logical_cores - 1; 215 | setTargetCore(lastCore); 216 | addLog(`系统信息已加载: ${info.os_name} ${info.os_version}`); 217 | addLog(`CPU: ${info.cpu_model}`); 218 | addLog(`核心: ${info.cpu_cores}物理/${info.cpu_logical_cores}逻辑`); 219 | addLog(`内存: ${info.total_memory_gb.toFixed(2)} GB`); 220 | addLog(`WebView2环境: ${info.webview2_env}`); 221 | 222 | if (!info.is_admin) { 223 | addLog('小春未以管理员权限运行,部分功能可能受限'); 224 | } else { 225 | addLog('小春已获取管理员权限,正在降低ACE占用'); 226 | } 227 | } catch (error) { 228 | addLog(`获取系统信息失败: ${error}`); 229 | } 230 | }, [addLog]); 231 | 232 | const fetchPerformance = useCallback(async () => { 233 | try { 234 | const perf = await invoke('get_process_performance'); 235 | setPerformance(perf); 236 | } catch (error) { 237 | console.error('获取性能数据失败:', error); 238 | } 239 | }, []); 240 | 241 | const checkAutoStart = useCallback(async () => { 242 | try { 243 | const enabled = await invoke('check_autostart'); 244 | setAutoStartEnabled(enabled); 245 | } catch (error) { 246 | console.error('检查自启动状态失败:', error); 247 | } 248 | }, []); 249 | 250 | const toggleAutoStartup = useCallback(async () => { 251 | try { 252 | if (autoStartEnabled) { 253 | await invoke('disable_autostart'); 254 | setAutoStartEnabled(false); 255 | addLog('已禁用开机自启动'); 256 | } else { 257 | await invoke('enable_autostart'); 258 | setAutoStartEnabled(true); 259 | addLog('已启用开机自启动'); 260 | } 261 | } catch (error) { 262 | addLog(`切换自启动失败: ${error}`); 263 | console.error('切换自启动失败:', error); 264 | } 265 | }, [autoStartEnabled, addLog]); 266 | 267 | 268 | 269 | const lowerAcePriority = useCallback(async () => { 270 | try { 271 | setLoading(true); 272 | addLog('开始降低ACE优先级...'); 273 | const result = await invoke('lower_ace_priority'); 274 | addLog('ACE优先级降低完成:'); 275 | result.split('\n').forEach(line => addLog(line)); 276 | } catch (error) { 277 | addLog(`降低ACE优先级失败: ${error}`); 278 | console.error('降低ACE优先级失败:', error); 279 | } finally { 280 | setLoading(false); 281 | } 282 | }, [addLog]); 283 | 284 | const raiseDeltaPriority = useCallback(async () => { 285 | try { 286 | setLoading(true); 287 | addLog('开始提高三角洲优先级...'); 288 | const result = await invoke('raise_delta_priority'); 289 | addLog('三角洲优先级提高完成:'); 290 | result.split('\n').forEach(line => addLog(line)); 291 | } catch (error) { 292 | addLog(`提高三角洲优先级失败: ${error}`); 293 | console.error('提高三角洲优先级失败:', error); 294 | } finally { 295 | setLoading(false); 296 | } 297 | }, [addLog]); 298 | 299 | const modifyValorantRegistryPriority = useCallback(async () => { 300 | try { 301 | setLoading(true); 302 | addLog('开始修改瓦罗兰特注册表优先级...'); 303 | const result = await invoke('modify_valorant_registry_priority'); 304 | addLog('瓦罗兰特注册表修改完成:'); 305 | result.split('\n').forEach(line => addLog(line)); 306 | } catch (error) { 307 | addLog(`修改瓦罗兰特注册表失败: ${error}`); 308 | console.error('修改瓦罗兰特注册表失败:', error); 309 | } finally { 310 | setLoading(false); 311 | } 312 | }, [addLog]); 313 | 314 | const raiseLeaguePriority = useCallback(async () => { 315 | try { 316 | setLoading(true); 317 | addLog('开始提高英雄联盟优先级...'); 318 | const result = await invoke('raise_league_priority'); 319 | addLog('英雄联盟优先级修改完成:'); 320 | result.split('\n').forEach(line => addLog(line)); 321 | } catch (error) { 322 | addLog(`提高英雄联盟优先级失败: ${error}`); 323 | console.error('提高英雄联盟优先级失败:', error); 324 | } finally { 325 | setLoading(false); 326 | } 327 | }, [addLog]); 328 | 329 | const raiseArenaPriority = useCallback(async () => { 330 | try { 331 | setLoading(true); 332 | addLog('开始提高暗区突围优先级...'); 333 | const result = await invoke('raise_arena_priority'); 334 | addLog('暗区突围优先级修改完成:'); 335 | result.split('\n').forEach(line => addLog(line)); 336 | } catch (error) { 337 | addLog(`提高暗区突围优先级失败: ${error}`); 338 | console.error('提高暗区突围优先级失败:', error); 339 | } finally { 340 | setLoading(false); 341 | } 342 | }, [addLog]); 343 | 344 | const checkRegistryPriority = useCallback(async () => { 345 | try { 346 | setLoading(true); 347 | addLog('正在检查注册表状态...'); 348 | const result = await invoke('check_registry_priority'); 349 | addLog('注册表状态:'); 350 | result.split('\n').forEach(line => addLog(line)); 351 | } catch (error) { 352 | addLog(`检查注册表失败: ${error}`); 353 | console.error('检查注册表失败:', error); 354 | } finally { 355 | setLoading(false); 356 | } 357 | }, [addLog]); 358 | 359 | const resetRegistryPriority = useCallback(async () => { 360 | try { 361 | setLoading(true); 362 | addLog('开始恢复注册表默认设置...'); 363 | const result = await invoke('reset_registry_priority'); 364 | addLog('注册表恢复完成:'); 365 | result.split('\n').forEach(line => addLog(line)); 366 | } catch (error) { 367 | addLog(`恢复注册表失败: ${error}`); 368 | console.error('恢复注册表失败:', error); 369 | } finally { 370 | setLoading(false); 371 | } 372 | }, [addLog]); 373 | 374 | useEffect(() => { 375 | addLog('FuckACE已启动,开始法克ACE'); 376 | fetchSystemInfo(); 377 | checkAutoStart(); 378 | 379 | const perfInterval = setInterval(fetchPerformance, 5000); 380 | 381 | return () => { 382 | clearInterval(perfInterval); 383 | }; 384 | }, [addLog, fetchSystemInfo, fetchPerformance, checkAutoStart]); 385 | 386 | useEffect(() => { 387 | const cached = storage.getChoices(); 388 | if (cached.rememberChoices) { 389 | if (cached.enableCpuAffinity !== undefined) setEnableCpuAffinity(cached.enableCpuAffinity); 390 | if (cached.enableProcessPriority !== undefined) setEnableProcessPriority(cached.enableProcessPriority); 391 | if (cached.enableEfficiencyMode !== undefined) setEnableEfficiencyMode(cached.enableEfficiencyMode); 392 | if (cached.enableIoPriority !== undefined) setEnableIoPriority(cached.enableIoPriority); 393 | if (cached.enableMemoryPriority !== undefined) setEnableMemoryPriority(cached.enableMemoryPriority); 394 | setRememberChoices(true); 395 | } 396 | }, []); 397 | 398 | useEffect(() => { 399 | const unlisten = Window.getCurrent().listen('show-close-confirm', () => { 400 | setShowCloseConfirm(true); 401 | }); 402 | 403 | return () => { 404 | unlisten.then((fn: () => void) => fn()); 405 | }; 406 | }, []); 407 | 408 | useEffect(() => { 409 | if (hasUpdate) { 410 | setShowUpdateDialog(true); 411 | } 412 | }, [hasUpdate]); 413 | 414 | const getProcessStatusColor = (found: boolean, restricted: boolean) => { 415 | if (!found) return 'default'; 416 | return restricted ? 'warning' : 'success'; 417 | }; 418 | 419 | const getProcessStatusText = (found: boolean, restricted: boolean) => { 420 | if (!found) return '未找到'; 421 | return restricted ? '已限制' : '运行中'; 422 | }; 423 | 424 | const toggleDarkMode = () => { 425 | setDarkMode(!darkMode); 426 | }; 427 | 428 | const openExternalLink = async (url: string) => { 429 | try { 430 | await open(url); 431 | } catch (error) { 432 | console.error('打开链接失败:', error); 433 | window.open(url, '_blank', 'noopener,noreferrer'); 434 | } 435 | }; 436 | 437 | return ( 438 | 439 | 440 | 441 | 442 | 443 | 444 | 449 | 450 | 451 | FuckACE v0.5.2 452 | 453 | 454 | 小春正在持续监控并限制ACE占用 455 | 456 | 457 | 458 | 459 | 0 ? announcements.length : 0} color="info"> 460 | 469 | 470 | 471 | 481 | 482 | 492 | 502 | 512 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 529 | 监控状态 530 | 531 | 532 | 533 | 534 | 目标核心: 535 | 541 | 542 | 543 | 544 | 游戏进程: 545 | 0 ? gameProcesses.join(', ') : '未检测到'} 547 | color={gameProcesses.length > 0 ? 'success' : 'default'} 548 | size="small" 549 | /> 550 | 551 | 552 | {loading && } 553 | 554 | 555 | 556 | 557 | 进程状态 558 | 559 | 565 | } 566 | sx={{ py: 0.3 }} 567 | > 568 | 569 | 570 | 571 | 577 | } 578 | sx={{ py: 0.3 }} 579 | > 580 | 581 | 582 | 583 | 589 | } 590 | sx={{ py: 0.3 }} 591 | > 592 | 593 | 594 | 595 | 596 | 597 | 598 | 系统信息 599 | {systemInfo ? ( 600 | 601 | 602 | CPU: 603 | 604 | {systemInfo.cpu_model.split(' ').slice(-2).join(' ')} 605 | 606 | 607 | 608 | 核心: 609 | {systemInfo.cpu_cores}P / {systemInfo.cpu_logical_cores}L 610 | 611 | 612 | 系统: 613 | {systemInfo.os_name} {systemInfo.os_version.split('.')[0]} 614 | 615 | 616 | 内存: 617 | {systemInfo.total_memory_gb.toFixed(1)} GB 618 | 619 | 620 | 权限: 621 | 622 | {systemInfo.is_admin ? '管理员' : '普通用户'} 623 | {systemInfo.is_admin && } 624 | 625 | 626 | 627 | ) : ( 628 | 加载中... 629 | )} 630 | 631 | 632 | 633 | 634 | 635 | 性能监控 636 | {performance.length > 0 ? ( 637 | 638 | {performance.map((proc) => ( 639 | 640 | 641 | 644 | 645 | {proc.name} (PID: {proc.pid}) 646 | 647 | 10 ? 'error' : proc.cpu_usage > 5 ? 'warning' : 'success'} 651 | /> 652 | 653 | } 654 | secondary={ 655 | 656 | 657 | 内存: {proc.memory_mb.toFixed(2)} MB 658 | 659 | 10 ? 'error' : proc.cpu_usage > 5 ? 'warning' : 'success'} 664 | /> 665 | 666 | } 667 | /> 668 | 669 | 670 | 671 | ))} 672 | 673 | ) : ( 674 | 675 | 未检测到目标进程 676 | 677 | )} 678 | 679 | 680 | 682 | 被动限制(不直接干涉ACE,较安全) 683 | 684 | 695 | 696 | 706 | 716 | 726 | 736 | 737 | 738 | 749 | 760 | 761 | 762 | 763 | 764 | 766 | 主动限制(小白不建议使用) 767 | 768 | setEnableCpuAffinity(e.target.checked)} 773 | disabled={isMonitoring} 774 | color="success" 775 | size="small" 776 | /> 777 | } 778 | label={ 779 | CPU亲和性 780 | } 781 | sx={{ m: 0 }} 782 | /> 783 | setEnableProcessPriority(e.target.checked)} 788 | disabled={isMonitoring} 789 | color="success" 790 | size="small" 791 | /> 792 | } 793 | label={ 794 | 进程优先级 795 | } 796 | sx={{ m: 0 }} 797 | /> 798 | setEnableEfficiencyMode(e.target.checked)} 803 | disabled={isMonitoring} 804 | color="warning" 805 | size="small" 806 | /> 807 | } 808 | label={ 809 | 效率模式 810 | } 811 | sx={{ m: 0 }} 812 | /> 813 | setEnableIoPriority(e.target.checked)} 818 | disabled={isMonitoring} 819 | color="error" 820 | size="small" 821 | /> 822 | } 823 | label={ 824 | I/O优先级 825 | } 826 | sx={{ m: 0 }} 827 | /> 828 | setEnableMemoryPriority(e.target.checked)} 833 | disabled={isMonitoring} 834 | color="error" 835 | size="small" 836 | /> 837 | } 838 | label={ 839 | 内存优先级 840 | } 841 | sx={{ m: 0 }} 842 | /> 843 | 851 | } 852 | label={ 853 | 开机自启动 854 | } 855 | sx={{ m: 0 }} 856 | /> 857 | 858 | 859 | 870 | 871 | 872 | 873 | 874 | 875 | 888 | {logs.map((log) => ( 889 | 899 | [{log.timestamp}] {log.message} 900 | 901 | ))} 902 | 903 | 904 | 905 | 906 | {/* 公告对话框 */} 907 | setShowAnnouncements(false)} 910 | maxWidth="sm" 911 | fullWidth 912 | > 913 | 914 | 915 | 公告 916 | 919 | 920 | 921 | 922 | {announcements.map((announcement) => ( 923 | 932 | 933 | {announcement.title} 934 | 935 | 936 | {announcement.content} 937 | 938 | 939 | 发布时间: {new Date(announcement.created_at).toLocaleDateString('zh-CN')} 940 | 941 | 942 | ))} 943 | 944 | 945 | 946 | 947 | 948 | 949 | setShowUpdateDialog(false)} 952 | maxWidth="sm" 953 | fullWidth 954 | > 955 | 956 | 957 | {hasUpdate ? '发现新版本' : '版本检查'} 958 | 961 | 962 | 963 | 964 | {hasUpdate && latestVersion ? ( 965 | 966 | 967 | 968 | 版本 {latestVersion.version} 969 | {latestVersion.is_critical && ' (重要更新)'} 970 | 971 | 972 | 973 | 974 | 更新内容: 975 | 976 | 977 | {latestVersion.changelog} 978 | 979 | 980 | 981 | 发布时间: {new Date(latestVersion.created_at).toLocaleDateString('zh-CN')} 982 | 983 | 984 | ) : ( 985 | 986 | 987 | 988 | 已是最新版本 989 | 990 | 991 | 992 | 当前版本: v0.5.2 993 | 994 | 995 | )} 996 | 997 | 998 | 999 | {hasUpdate && latestVersion && ( 1000 | 1009 | )} 1010 | 1011 | 1012 | 1013 | setShowCloseConfirm(false)} 1016 | maxWidth="xs" 1017 | fullWidth 1018 | > 1019 | 1020 | 1021 | 1022 | 确认关闭 1023 | 1024 | 1025 | 1026 | 1027 | 您确定要关闭应用程序吗? 1028 | 1029 | 1030 | 选择"最小化到托盘"将保持程序在后台运行,选择"彻底退出"将关闭程序。 1031 | 1032 | setRememberChoices(e.target.checked)} 1037 | color="primary" 1038 | size="small" 1039 | /> 1040 | } 1041 | label={ 1042 | 记住选择 1043 | } 1044 | sx={{ mt: 2 }} 1045 | /> 1046 | 1047 | 1048 | 1054 | 1074 | 1084 | 1085 | 1086 | 1087 | 1088 | ); 1089 | } 1090 | 1091 | export default App; -------------------------------------------------------------------------------- /src-tauri/src/lib.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use sysinfo::{System, Pid}; 3 | use tauri::{State, Manager, AppHandle, WindowEvent, Emitter}; 4 | use tauri::tray::{TrayIconBuilder, TrayIconEvent}; 5 | use tauri::menu::{Menu, MenuItem, PredefinedMenuItem}; 6 | use winreg::enums::*; 7 | use winreg::RegKey; 8 | 9 | #[derive(Debug, Serialize, Deserialize)] 10 | struct RestrictResult { 11 | target_core: u32, 12 | sguard64_found: bool, 13 | sguard64_restricted: bool, 14 | sguardsvc64_found: bool, 15 | sguardsvc64_restricted: bool, 16 | weixin_found: bool, 17 | weixin_restricted: bool, 18 | message: String, 19 | } 20 | 21 | #[derive(Debug, Serialize, Deserialize)] 22 | struct SystemInfo { 23 | cpu_model: String, 24 | cpu_cores: usize, 25 | cpu_logical_cores: usize, 26 | os_name: String, 27 | os_version: String, 28 | is_admin: bool, 29 | total_memory_gb: f64, 30 | webview2_env: String, 31 | } 32 | 33 | #[derive(Debug, Serialize, Deserialize)] 34 | struct ProcessPerformance { 35 | pid: u32, 36 | name: String, 37 | cpu_usage: f32, 38 | memory_mb: f64, 39 | } 40 | 41 | struct AppState; 42 | 43 | fn find_target_core() -> (u32, u64, bool) { 44 | let system = System::new_all(); 45 | let total_cores = system.cpus().len() as u32; 46 | let target_core = if total_cores > 0 { total_cores - 1 } else { 0 }; 47 | let core_mask = 1u64 << target_core; 48 | 49 | (target_core, core_mask, false) 50 | } 51 | 52 | fn set_process_affinity(pid: Pid, core_mask: u64) -> (bool, Option) { 53 | unsafe { 54 | use windows::Win32::System::Threading::{ 55 | OpenProcess, SetProcessAffinityMask, PROCESS_SET_INFORMATION, PROCESS_QUERY_INFORMATION 56 | }; 57 | use windows::Win32::Foundation::CloseHandle; 58 | 59 | let process_handle = OpenProcess( 60 | PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION, 61 | false, 62 | pid.as_u32() 63 | ); 64 | 65 | let handle = match process_handle { 66 | Ok(h) => h, 67 | Err(e) => { 68 | eprintln!("[进程亲和性] PID {} OpenProcess失败: {:?}", pid, e); 69 | return (false, Some(format!("打开进程失败: {:?}", e))); 70 | }, 71 | }; 72 | 73 | if handle.is_invalid() { 74 | eprintln!("进程亲和性PID {} 进程句柄无效", pid); 75 | return (false, Some("进程句柄无效".to_string())); 76 | } 77 | 78 | let result = SetProcessAffinityMask(handle, core_mask as usize); 79 | 80 | let _ = CloseHandle(handle); 81 | 82 | if let Err(e) = &result { 83 | eprintln!("进程亲和性PID {} SetProcessAffinityMask失败: {:?}", pid, e); 84 | return (false, Some(format!("设置亲和性失败: {:?}", e))); 85 | } 86 | 87 | (true, None) 88 | } 89 | } 90 | 91 | fn set_process_affinity_with_fallback(pid: Pid, primary_core_mask: u64, is_e_core: bool) -> (bool, Option, u32) { 92 | let (success, error) = set_process_affinity(pid, primary_core_mask); 93 | 94 | if success || !is_e_core { 95 | let core_id = primary_core_mask.trailing_zeros(); 96 | return (success, error, core_id); 97 | } 98 | 99 | eprintln!("进程亲和性PID {} E-Core绑定失败,尝试备用方案", pid); 100 | let system = System::new_all(); 101 | let total_cores = system.cpus().len() as u32; 102 | let fallback_core = if total_cores > 0 { total_cores - 1 } else { 0 }; 103 | let fallback_mask = 1u64 << fallback_core; 104 | 105 | let (fallback_success, fallback_error) = set_process_affinity(pid, fallback_mask); 106 | 107 | if fallback_success { 108 | eprintln!("[进程亲和性] PID {} 备用方案成功,已绑定到核心 {}", pid, fallback_core); 109 | (true, None, fallback_core) 110 | } else { 111 | (false, fallback_error, fallback_core) 112 | } 113 | } 114 | 115 | fn set_process_priority(pid: Pid) -> bool { 116 | unsafe { 117 | use windows::Win32::System::Threading::{ 118 | OpenProcess, SetPriorityClass, PROCESS_SET_INFORMATION, PROCESS_QUERY_INFORMATION, IDLE_PRIORITY_CLASS 119 | }; 120 | use windows::Win32::Foundation::CloseHandle; 121 | 122 | let process_handle = match OpenProcess( 123 | PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION, 124 | false, 125 | pid.as_u32() 126 | ) { 127 | Ok(handle) => handle, 128 | Err(e) => { 129 | eprintln!("[进程优先级] PID {} OpenProcess失败: {:?}", pid, e); 130 | return false; 131 | }, 132 | }; 133 | 134 | if process_handle.is_invalid() { 135 | eprintln!("[进程优先级] PID {} 进程句柄无效", pid); 136 | return false; 137 | } 138 | 139 | let result = SetPriorityClass(process_handle, IDLE_PRIORITY_CLASS); 140 | 141 | let _ = CloseHandle(process_handle); 142 | 143 | if let Err(e) = &result { 144 | eprintln!("[进程优先级] PID {} SetPriorityClass失败: {:?}", pid, e); 145 | } 146 | 147 | result.is_ok() 148 | } 149 | } 150 | 151 | fn set_process_efficiency_mode(pid: Pid) -> (bool, Option) { 152 | unsafe { 153 | use windows::Win32::System::Threading::{ 154 | OpenProcess, SetProcessInformation, PROCESS_SET_INFORMATION, PROCESS_QUERY_INFORMATION, 155 | PROCESS_POWER_THROTTLING_STATE, ProcessPowerThrottling, PROCESS_POWER_THROTTLING_EXECUTION_SPEED, 156 | PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 157 | }; 158 | use windows::Win32::Foundation::CloseHandle; 159 | 160 | let process_handle = match OpenProcess( 161 | PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION, 162 | false, 163 | pid.as_u32() 164 | ) { 165 | Ok(handle) => handle, 166 | Err(e) => { 167 | eprintln!("[效率模式] PID {} OpenProcess失败: {:?}", pid, e); 168 | return (false, Some(format!("打开进程失败: {:?}", e))); 169 | }, 170 | }; 171 | 172 | if process_handle.is_invalid() { 173 | eprintln!("[效率模式] PID {} 进程句柄无效", pid); 174 | return (false, Some("进程句柄无效".to_string())); 175 | } 176 | 177 | let mut throttling_state = PROCESS_POWER_THROTTLING_STATE { 178 | Version: 1, 179 | ControlMask: PROCESS_POWER_THROTTLING_EXECUTION_SPEED | PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION, 180 | StateMask: PROCESS_POWER_THROTTLING_EXECUTION_SPEED | PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION, 181 | }; 182 | 183 | let result = SetProcessInformation( 184 | process_handle, 185 | ProcessPowerThrottling, 186 | &mut throttling_state as *mut _ as *mut _, 187 | std::mem::size_of::() as u32 188 | ); 189 | 190 | let _ = CloseHandle(process_handle); 191 | 192 | if let Err(e) = &result { 193 | eprintln!("[效率模式] PID {} SetProcessInformation失败: {:?}", pid, e); 194 | return (false, Some(format!("设置效率模式失败: {:?}", e))); 195 | } 196 | 197 | (true, None) 198 | } 199 | } 200 | 201 | fn set_process_io_priority(pid: Pid) -> (bool, Option) { 202 | unsafe { 203 | use windows::Win32::System::Threading::{ 204 | OpenProcess, SetProcessInformation, PROCESS_SET_INFORMATION, PROCESS_QUERY_INFORMATION, PROCESS_INFORMATION_CLASS 205 | }; 206 | use windows::Win32::Foundation::CloseHandle; 207 | 208 | let process_handle = match OpenProcess( 209 | PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION, 210 | false, 211 | pid.as_u32() 212 | ) { 213 | Ok(handle) => handle, 214 | Err(e) => { 215 | eprintln!("[I/O优先级] PID {} OpenProcess失败: {:?}", pid, e); 216 | return (false, Some(format!("打开进程失败: {:?}", e))); 217 | }, 218 | }; 219 | 220 | if process_handle.is_invalid() { 221 | eprintln!("[I/O优先级] PID {} 进程句柄无效", pid); 222 | return (false, Some("进程句柄无效".to_string())); 223 | } 224 | 225 | let io_priority: u32 = 0; 226 | let result = SetProcessInformation( 227 | process_handle, 228 | PROCESS_INFORMATION_CLASS(33), 229 | &io_priority as *const _ as *const _, 230 | std::mem::size_of::() as u32 231 | ); 232 | 233 | let _ = CloseHandle(process_handle); 234 | 235 | if let Err(e) = &result { 236 | eprintln!("[I/O优先级] PID {} SetProcessInformation失败: {:?}", pid, e); 237 | return (false, Some(format!("设置I/O优先级失败: {:?}", e))); 238 | } 239 | 240 | (true, None) 241 | } 242 | } 243 | 244 | fn set_process_memory_priority(pid: Pid) -> (bool, Option) { 245 | unsafe { 246 | use windows::Win32::System::Threading::{ 247 | OpenProcess, SetProcessInformation, PROCESS_SET_INFORMATION, PROCESS_QUERY_INFORMATION, PROCESS_INFORMATION_CLASS 248 | }; 249 | use windows::Win32::Foundation::CloseHandle; 250 | 251 | let process_handle = match OpenProcess( 252 | PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION, 253 | false, 254 | pid.as_u32() 255 | ) { 256 | Ok(handle) => handle, 257 | Err(e) => { 258 | eprintln!("[内存优先级] PID {} OpenProcess失败: {:?}", pid, e); 259 | return (false, Some(format!("打开进程失败: {:?}", e))); 260 | }, 261 | }; 262 | 263 | if process_handle.is_invalid() { 264 | eprintln!("[内存优先级] PID {} 进程句柄无效", pid); 265 | return (false, Some("进程句柄无效".to_string())); 266 | } 267 | 268 | let memory_priority: u32 = 1; 269 | let result = SetProcessInformation( 270 | process_handle, 271 | PROCESS_INFORMATION_CLASS(39), 272 | &memory_priority as *const _ as *const _, 273 | std::mem::size_of::() as u32 274 | ); 275 | 276 | let _ = CloseHandle(process_handle); 277 | 278 | if let Err(e) = &result { 279 | eprintln!("[内存优先级] PID {} SetProcessInformation失败: {:?}", pid, e); 280 | return (false, Some(format!("设置内存优先级失败: {:?}", e))); 281 | } 282 | 283 | (true, None) 284 | } 285 | } 286 | 287 | fn restrict_target_processes(enable_cpu_affinity: bool, enable_process_priority: bool, enable_efficiency_mode: bool, enable_io_priority: bool, enable_memory_priority: bool) -> RestrictResult { 288 | enable_debug_privilege(); 289 | 290 | let mut system = System::new_all(); 291 | system.refresh_processes(); 292 | 293 | let (target_core, core_mask, is_e_core) = find_target_core(); 294 | 295 | let mut sguard64_found = false; 296 | let mut sguard64_restricted = false; 297 | let mut sguardsvc64_found = false; 298 | let mut sguardsvc64_restricted = false; 299 | let mut weixin_found = false; 300 | let mut weixin_restricted = false; 301 | 302 | let mut message = String::new(); 303 | 304 | let mode_parts: Vec<&str> = vec![ 305 | if enable_cpu_affinity { "CPU亲和性" } else { "" }, 306 | if enable_process_priority { "进程优先级" } else { "" }, 307 | if enable_efficiency_mode { "效率模式" } else { "" }, 308 | if enable_io_priority { "I/O优先级" } else { "" }, 309 | if enable_memory_priority { "内存优先级" } else { "" }, 310 | ].into_iter().filter(|s| !s.is_empty()).collect(); 311 | 312 | let mode_str = if mode_parts.is_empty() { "标准模式".to_string() } else { mode_parts.join("+") }; 313 | message.push_str(&format!("限制模式: {}\n", mode_str)); 314 | 315 | message.push_str(&format!("绑定到最后一个逻辑核心 {}\n", target_core)); 316 | 317 | for (pid, process) in system.processes() { 318 | let process_name = process.name().to_lowercase(); 319 | 320 | if process_name.contains("sguard64.exe") { 321 | sguard64_found = true; 322 | 323 | let (affinity_ok, affinity_err, actual_core) = if enable_cpu_affinity { 324 | set_process_affinity_with_fallback(*pid, core_mask, is_e_core) 325 | } else { 326 | (false, None, 0) 327 | }; 328 | let priority_ok = if enable_process_priority { 329 | set_process_priority(*pid) 330 | } else { 331 | false 332 | }; 333 | 334 | let (efficiency_ok, io_priority_ok, mem_priority_ok) = { 335 | let (eff_ok, _) = if enable_efficiency_mode { 336 | set_process_efficiency_mode(*pid) 337 | } else { 338 | (false, None) 339 | }; 340 | let (io_ok, _) = if enable_io_priority { 341 | set_process_io_priority(*pid) 342 | } else { 343 | (false, None) 344 | }; 345 | let (mem_ok, _) = if enable_memory_priority { 346 | set_process_memory_priority(*pid) 347 | } else { 348 | (false, None) 349 | }; 350 | (eff_ok, io_ok, mem_ok) 351 | }; 352 | 353 | let mut details = Vec::new(); 354 | if affinity_ok { 355 | details.push(format!("CPU亲和性→核心{}", actual_core)); 356 | } else if let Some(err) = &affinity_err { 357 | details.push(format!("CPU亲和性✗({})", err)); 358 | } else { 359 | details.push("CPU亲和性✗".to_string()); 360 | } 361 | 362 | if priority_ok { 363 | details.push("优先级→最低".to_string()); 364 | } else { 365 | details.push("优先级✗".to_string()); 366 | } 367 | 368 | if efficiency_ok { 369 | details.push("效率模式✓".to_string()); 370 | } 371 | if io_priority_ok { 372 | details.push("I/O优先级✓".to_string()); 373 | } 374 | if mem_priority_ok { 375 | details.push("内存优先级✓".to_string()); 376 | } 377 | 378 | if affinity_ok || priority_ok || efficiency_ok || io_priority_ok || mem_priority_ok { 379 | sguard64_restricted = true; 380 | message.push_str(&format!("SGuard64.exe (PID: {}) [{}]\n", pid, details.join(", "))); 381 | } else { 382 | message.push_str(&format!("SGuard64.exe (PID: {}) 所有限制均失败 [{}]\n", pid, details.join(", "))); 383 | } 384 | } 385 | 386 | if process_name.contains("sguardsvc64.exe") { 387 | sguardsvc64_found = true; 388 | 389 | let (affinity_ok, affinity_err, actual_core) = if enable_cpu_affinity { 390 | set_process_affinity_with_fallback(*pid, core_mask, is_e_core) 391 | } else { 392 | (false, None, 0) 393 | }; 394 | let priority_ok = if enable_process_priority { 395 | set_process_priority(*pid) 396 | } else { 397 | false 398 | }; 399 | 400 | let (efficiency_ok, io_priority_ok, mem_priority_ok) = { 401 | let (eff_ok, _) = if enable_efficiency_mode { 402 | set_process_efficiency_mode(*pid) 403 | } else { 404 | (false, None) 405 | }; 406 | let (io_ok, _) = if enable_io_priority { 407 | set_process_io_priority(*pid) 408 | } else { 409 | (false, None) 410 | }; 411 | let (mem_ok, _) = if enable_memory_priority { 412 | set_process_memory_priority(*pid) 413 | } else { 414 | (false, None) 415 | }; 416 | (eff_ok, io_ok, mem_ok) 417 | }; 418 | 419 | let mut details = Vec::new(); 420 | if affinity_ok { 421 | details.push(format!("CPU亲和性→核心{}", actual_core)); 422 | } else if let Some(err) = &affinity_err { 423 | details.push(format!("CPU亲和性✗({})", err)); 424 | } else { 425 | details.push("CPU亲和性✗".to_string()); 426 | } 427 | 428 | if priority_ok { 429 | details.push("优先级→最低".to_string()); 430 | } else { 431 | details.push("优先级✗".to_string()); 432 | } 433 | 434 | if efficiency_ok { 435 | details.push("效率模式✓".to_string()); 436 | } 437 | if io_priority_ok { 438 | details.push("I/O优先级✓".to_string()); 439 | } 440 | if mem_priority_ok { 441 | details.push("内存优先级✓".to_string()); 442 | } 443 | 444 | if affinity_ok || priority_ok || efficiency_ok || io_priority_ok || mem_priority_ok { 445 | sguardsvc64_restricted = true; 446 | message.push_str(&format!("SGuardSvc64.exe (PID: {}) [{}]\n", pid, details.join(", "))); 447 | } else { 448 | message.push_str(&format!("SGuardSvc64.exe (PID: {}) 所有限制均失败 [{}]\n", pid, details.join(", "))); 449 | } 450 | } 451 | 452 | if process_name.contains("weixin.exe") { 453 | weixin_found = true; 454 | 455 | let (affinity_ok, affinity_err, actual_core) = if enable_cpu_affinity { 456 | set_process_affinity_with_fallback(*pid, core_mask, is_e_core) 457 | } else { 458 | (false, None, 0) 459 | }; 460 | let priority_ok = if enable_process_priority { 461 | set_process_priority(*pid) 462 | } else { 463 | false 464 | }; 465 | 466 | let (efficiency_ok, io_priority_ok, mem_priority_ok) = { 467 | let (eff_ok, _) = if enable_efficiency_mode { 468 | set_process_efficiency_mode(*pid) 469 | } else { 470 | (false, None) 471 | }; 472 | let (io_ok, _) = if enable_io_priority { 473 | set_process_io_priority(*pid) 474 | } else { 475 | (false, None) 476 | }; 477 | let (mem_ok, _) = if enable_memory_priority { 478 | set_process_memory_priority(*pid) 479 | } else { 480 | (false, None) 481 | }; 482 | (eff_ok, io_ok, mem_ok) 483 | }; 484 | 485 | let mut details = Vec::new(); 486 | if affinity_ok { 487 | details.push(format!("CPU亲和性→核心{}", actual_core)); 488 | } else if let Some(err) = &affinity_err { 489 | details.push(format!("CPU亲和性✗({})", err)); 490 | } else { 491 | details.push("CPU亲和性✗".to_string()); 492 | } 493 | 494 | if priority_ok { 495 | details.push("优先级→最低".to_string()); 496 | } else { 497 | details.push("优先级✗".to_string()); 498 | } 499 | 500 | if efficiency_ok { 501 | details.push("效率模式✓".to_string()); 502 | } 503 | if io_priority_ok { 504 | details.push("I/O优先级✓".to_string()); 505 | } 506 | if mem_priority_ok { 507 | details.push("内存优先级✓".to_string()); 508 | } 509 | 510 | if affinity_ok || priority_ok || efficiency_ok || io_priority_ok || mem_priority_ok { 511 | weixin_restricted = true; 512 | message.push_str(&format!("Weixin.exe (PID: {}) [{}]\n", pid, details.join(", "))); 513 | } else { 514 | message.push_str(&format!("Weixin.exe (PID: {}) 所有限制均失败 [{}]\n", pid, details.join(", "))); 515 | } 516 | } 517 | 518 | } 519 | 520 | if !sguard64_found { 521 | message.push_str("未找到SGuard64.exe进程\n"); 522 | } 523 | 524 | if !sguardsvc64_found { 525 | message.push_str("未找到SGuardSvc64.exe进程\n"); 526 | } 527 | 528 | if !weixin_found { 529 | message.push_str("未找到Weixin.exe进程\n"); 530 | } 531 | 532 | 533 | RestrictResult { 534 | target_core, 535 | sguard64_found, 536 | sguard64_restricted, 537 | sguardsvc64_found, 538 | sguardsvc64_restricted, 539 | weixin_found, 540 | weixin_restricted, 541 | message, 542 | } 543 | } 544 | 545 | #[tauri::command] 546 | fn restrict_processes( 547 | _state: State, 548 | enable_cpu_affinity: bool, 549 | enable_process_priority: bool, 550 | enable_efficiency_mode: bool, 551 | enable_io_priority: bool, 552 | enable_memory_priority: bool, 553 | ) -> RestrictResult { 554 | let result = restrict_target_processes(enable_cpu_affinity, enable_process_priority, enable_efficiency_mode, enable_io_priority, enable_memory_priority); 555 | result 556 | } 557 | 558 | 559 | fn enable_debug_privilege() -> bool { 560 | unsafe { 561 | use windows::Win32::Foundation::{CloseHandle, LUID}; 562 | use windows::Win32::Security::{ 563 | AdjustTokenPrivileges, LookupPrivilegeValueW, 564 | SE_PRIVILEGE_ENABLED, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, LUID_AND_ATTRIBUTES 565 | }; 566 | use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; 567 | use windows::core::PCWSTR; 568 | 569 | let mut token_handle = windows::Win32::Foundation::HANDLE::default(); 570 | 571 | if OpenProcessToken( 572 | GetCurrentProcess(), 573 | TOKEN_ADJUST_PRIVILEGES, 574 | &mut token_handle 575 | ).is_err() { 576 | eprintln!("[权限提升] OpenProcessToken失败"); 577 | return false; 578 | } 579 | 580 | let mut luid = LUID::default(); 581 | let privilege_name: Vec = "SeDebugPrivilege\0".encode_utf16().collect(); 582 | 583 | if LookupPrivilegeValueW( 584 | PCWSTR::null(), 585 | PCWSTR(privilege_name.as_ptr()), 586 | &mut luid 587 | ).is_err() { 588 | eprintln!("[权限提升] LookupPrivilegeValueW失败"); 589 | let _ = CloseHandle(token_handle); 590 | return false; 591 | } 592 | 593 | let mut tp = TOKEN_PRIVILEGES { 594 | PrivilegeCount: 1, 595 | Privileges: [LUID_AND_ATTRIBUTES { 596 | Luid: luid, 597 | Attributes: SE_PRIVILEGE_ENABLED, 598 | }], 599 | }; 600 | 601 | let result = AdjustTokenPrivileges( 602 | token_handle, 603 | false, 604 | Some(&mut tp), 605 | 0, 606 | None, 607 | None 608 | ); 609 | 610 | let _ = CloseHandle(token_handle); 611 | 612 | if result.is_ok() { 613 | eprintln!("[权限提升] SeDebugPrivilege已启用"); 614 | true 615 | } else { 616 | eprintln!("[权限提升] AdjustTokenPrivileges失败"); 617 | false 618 | } 619 | } 620 | } 621 | 622 | fn is_elevated() -> bool { 623 | unsafe { 624 | use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY}; 625 | use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; 626 | use windows::Win32::Foundation::CloseHandle; 627 | 628 | let mut token_handle = windows::Win32::Foundation::HANDLE::default(); 629 | 630 | 631 | if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() { 632 | return false; 633 | } 634 | 635 | let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 }; 636 | let mut return_length: u32 = 0; 637 | 638 | 639 | let result = GetTokenInformation( 640 | token_handle, 641 | TokenElevation, 642 | Some(&mut elevation as *mut _ as *mut _), 643 | std::mem::size_of::() as u32, 644 | &mut return_length, 645 | ); 646 | 647 | let _ = CloseHandle(token_handle); 648 | 649 | result.is_ok() && elevation.TokenIsElevated != 0 650 | } 651 | } 652 | 653 | #[tauri::command] 654 | fn get_webview2_environment() -> String { 655 | #[cfg(target_os = "windows")] 656 | { 657 | use std::env; 658 | if let Ok(webview_path) = env::var("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER") { 659 | if !webview_path.is_empty() { 660 | return "便携环境".to_string(); 661 | } 662 | } 663 | if let Ok(exe_path) = env::current_exe() { 664 | if let Some(exe_dir) = exe_path.parent() { 665 | if exe_dir.join("webview2").exists() { 666 | return "便携环境".to_string(); 667 | } 668 | } 669 | } 670 | "本地环境".to_string() 671 | } 672 | 673 | #[cfg(not(target_os = "windows"))] 674 | { 675 | "非Windows平台".to_string() 676 | } 677 | } 678 | 679 | #[tauri::command] 680 | fn get_system_info() -> SystemInfo { 681 | let mut system = System::new_all(); 682 | system.refresh_all(); 683 | 684 | let cpu_model = if let Some(cpu) = system.cpus().first() { 685 | cpu.brand().to_string() 686 | } else { 687 | "Unknown".to_string() 688 | }; 689 | 690 | let cpu_cores = system.physical_core_count().unwrap_or(0); 691 | let cpu_logical_cores = system.cpus().len(); 692 | 693 | let os_name = System::name().unwrap_or_else(|| "Unknown".to_string()); 694 | let os_version = System::os_version().unwrap_or_else(|| "Unknown".to_string()); 695 | 696 | let is_admin = is_elevated(); 697 | 698 | let total_memory_gb = system.total_memory() as f64 / 1024.0 / 1024.0 / 1024.0; 699 | 700 | SystemInfo { 701 | cpu_model, 702 | cpu_cores, 703 | cpu_logical_cores, 704 | os_name, 705 | os_version, 706 | is_admin, 707 | total_memory_gb, 708 | webview2_env: get_webview2_environment(), 709 | } 710 | } 711 | 712 | 713 | #[tauri::command] 714 | fn get_process_performance() -> Vec { 715 | let mut system = System::new_all(); 716 | system.refresh_all(); 717 | 718 | 719 | std::thread::sleep(std::time::Duration::from_millis(200)); 720 | system.refresh_processes(); 721 | 722 | let target_names = vec!["sguard64.exe", "sguardsvc64.exe", "weixin.exe"]; 723 | let mut performances = Vec::new(); 724 | 725 | for (pid, process) in system.processes() { 726 | let process_name = process.name().to_lowercase(); 727 | 728 | for target in &target_names { 729 | if process_name.contains(target) { 730 | performances.push(ProcessPerformance { 731 | pid: pid.as_u32(), 732 | name: process.name().to_string(), 733 | cpu_usage: process.cpu_usage(), 734 | memory_mb: process.memory() as f64 / 1024.0 / 1024.0, 735 | }); 736 | break; 737 | } 738 | } 739 | } 740 | 741 | performances 742 | } 743 | 744 | #[tauri::command] 745 | fn check_game_processes() -> Vec { 746 | Vec::new() 747 | } 748 | 749 | #[tauri::command] 750 | fn set_game_process_priority() -> Result { 751 | Ok("游戏进程优先级设置功能已改为手动执行模式。请通过前端界面手动选择要设置的进程。".to_string()) 752 | } 753 | 754 | fn setup_tray(app: &AppHandle) -> tauri::Result<()> { 755 | let show = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?; 756 | let hide = MenuItem::with_id(app, "hide", "隐藏到托盘", true, None::<&str>)?; 757 | let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?; 758 | 759 | let menu = Menu::with_items(app, &[ 760 | &show, 761 | &PredefinedMenuItem::separator(app)?, 762 | &hide, 763 | &PredefinedMenuItem::separator(app)?, 764 | &quit, 765 | ])?; 766 | 767 | let _tray = TrayIconBuilder::with_id("main-tray") 768 | .tooltip("FuckACE") 769 | .icon(app.default_window_icon().unwrap().clone()) 770 | .menu(&menu) 771 | .show_menu_on_left_click(false) 772 | .on_tray_icon_event(|tray, event| { 773 | match event { 774 | TrayIconEvent::Click { button: tauri::tray::MouseButton::Left, .. } => { 775 | let app = tray.app_handle(); 776 | if let Some(window) = app.get_webview_window("main") { 777 | let _ = window.show(); 778 | let _ = window.set_focus(); 779 | } 780 | } 781 | TrayIconEvent::DoubleClick { button: tauri::tray::MouseButton::Left, .. } => { 782 | let app = tray.app_handle(); 783 | if let Some(window) = app.get_webview_window("main") { 784 | let _ = window.show(); 785 | let _ = window.set_focus(); 786 | } 787 | } 788 | _ => {} 789 | } 790 | }) 791 | .on_menu_event(|app, event| { 792 | match event.id().as_ref() { 793 | "quit" => { 794 | std::process::exit(0); 795 | } 796 | "show" => { 797 | if let Some(window) = app.get_webview_window("main") { 798 | let _ = window.show(); 799 | let _ = window.set_focus(); 800 | } 801 | } 802 | "hide" => { 803 | if let Some(window) = app.get_webview_window("main") { 804 | let _ = window.hide(); 805 | } 806 | } 807 | _ => {} 808 | } 809 | }) 810 | .build(app)?; 811 | 812 | Ok(()) 813 | } 814 | 815 | #[tauri::command] 816 | async fn show_close_dialog(app_handle: AppHandle) -> Result { 817 | //最小化到托盘>﹏< 818 | if let Some(window) = app_handle.get_webview_window("main") { 819 | window.hide().unwrap(); 820 | } 821 | Ok("已最小化到托盘".to_string()) 822 | } 823 | 824 | #[tauri::command] 825 | fn close_application(_app_handle: AppHandle) -> Result { 826 | //退出FuckACE/(ㄒoㄒ)/~~ 827 | std::process::exit(0); 828 | } 829 | 830 | fn get_exe_path() -> Result { 831 | std::env::current_exe() 832 | .map_err(|e| format!("获取程序路径失败: {}", e))? 833 | .to_str() 834 | .ok_or_else(|| "路径转换失败".to_string()) 835 | .map(|s| s.to_string()) 836 | } 837 | 838 | #[tauri::command] 839 | fn enable_autostart() -> Result { 840 | let hkcu = RegKey::predef(HKEY_CURRENT_USER); 841 | let path = r"Software\Microsoft\Windows\CurrentVersion\Run"; 842 | 843 | let (key, _) = hkcu 844 | .create_subkey(path) 845 | .map_err(|e| format!("打开注册表失败: {}", e))?; 846 | 847 | let exe_path = get_exe_path()?; 848 | 849 | key.set_value("FuckACE", &exe_path) 850 | .map_err(|e| format!("设置注册表值失败: {}", e))?; 851 | 852 | Ok("开机自启动已启用".to_string()) 853 | } 854 | 855 | #[tauri::command] 856 | fn disable_autostart() -> Result { 857 | let hkcu = RegKey::predef(HKEY_CURRENT_USER); 858 | let path = r"Software\Microsoft\Windows\CurrentVersion\Run"; 859 | 860 | let key = hkcu 861 | .open_subkey_with_flags(path, KEY_WRITE) 862 | .map_err(|e| format!("打开注册表失败: {}", e))?; 863 | 864 | key.delete_value("FuckACE") 865 | .map_err(|e| format!("删除注册表值失败: {}", e))?; 866 | 867 | Ok("开机自启动已禁用".to_string()) 868 | } 869 | 870 | #[tauri::command] 871 | fn check_autostart() -> Result { 872 | let hkcu = RegKey::predef(HKEY_CURRENT_USER); 873 | let path = r"Software\Microsoft\Windows\CurrentVersion\Run"; 874 | 875 | let key = hkcu 876 | .open_subkey(path) 877 | .map_err(|_| "打开注册表失败".to_string())?; 878 | 879 | match key.get_value::("FuckACE") { 880 | Ok(_) => Ok(true), 881 | Err(_) => Ok(false), 882 | } 883 | } 884 | 885 | #[tauri::command] 886 | fn lower_ace_priority() -> Result { 887 | if !is_elevated() { 888 | return Err("需要管理员权限才能修改注册表".to_string()); 889 | } 890 | 891 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 892 | let base_path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"; 893 | 894 | let mut results = Vec::new(); 895 | 896 | let configs = vec![ 897 | ("SGuard64.exe", 1u32, 1u32), 898 | ("SGuardSvc64.exe", 1u32, 1u32), 899 | ]; 900 | 901 | for (exe_name, cpu_priority, io_priority) in configs { 902 | let key_path = format!(r"{}\{}\PerfOptions", base_path, exe_name); 903 | 904 | match hklm.create_subkey(&key_path) { 905 | Ok((key, _)) => { 906 | let mut success = true; 907 | 908 | // 设置 CPU 优先级 909 | if let Err(e) = key.set_value("CpuPriorityClass", &cpu_priority) { 910 | results.push(format!("{}:设置CPU优先级失败:{}", exe_name, e)); 911 | success = false; 912 | } 913 | 914 | // 设置 I/O 优先级 915 | if let Err(e) = key.set_value("IoPriority", &io_priority) { 916 | results.push(format!("{}:设置I/O优先级失败:{}", exe_name, e)); 917 | success = false; 918 | } 919 | 920 | if success { 921 | results.push(format!("{}:设置成功(CPU:{},I/O:{})", exe_name, cpu_priority, io_priority)); 922 | } 923 | } 924 | Err(e) => { 925 | results.push(format!("{}:创建注册表项失败:{}", exe_name, e)); 926 | } 927 | } 928 | } 929 | 930 | Ok(results.join("\n")) 931 | } 932 | 933 | #[tauri::command] 934 | fn raise_delta_priority() -> Result { 935 | if !is_elevated() { 936 | return Err("需要管理员权限才能修改注册表".to_string()); 937 | } 938 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 939 | let base_path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"; 940 | let mut results = Vec::new(); 941 | let configs = vec![ 942 | ("DeltaForceClient-Win64-Shipping.exe", 3u32, 3u32), 943 | ]; 944 | 945 | for (exe_name, cpu_priority, io_priority) in configs { 946 | let key_path = format!(r"{}\{}\PerfOptions", base_path, exe_name); 947 | 948 | match hklm.create_subkey(&key_path) { 949 | Ok((key, _)) => { 950 | let mut success = true; 951 | if let Err(e) = key.set_value("CpuPriorityClass", &cpu_priority) { 952 | results.push(format!("{}:设置CPU优先级失败:{}", exe_name, e)); 953 | success = false; 954 | } 955 | if let Err(e) = key.set_value("IoPriority", &io_priority) { 956 | results.push(format!("{}:设置I/O优先级失败:{}", exe_name, e)); 957 | success = false; 958 | } 959 | if success { 960 | results.push(format!("{}:设置成功(CPU:{},I/O:{})", exe_name, cpu_priority, io_priority)); 961 | } 962 | } 963 | Err(e) => { 964 | results.push(format!("{}:创建注册表项失败:{}", exe_name, e)); 965 | } 966 | } 967 | } 968 | 969 | Ok(results.join("\n")) 970 | } 971 | 972 | #[tauri::command] 973 | fn modify_valorant_registry_priority() -> Result { 974 | if !is_elevated() { 975 | return Err("需要管理员权限才能修改注册表".to_string()); 976 | } 977 | 978 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 979 | let base_path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"; 980 | 981 | let mut results = Vec::new(); 982 | let configs = vec![ 983 | ("VALORANT-Win64-Shipping.exe", 3u32, 3u32), 984 | ]; 985 | 986 | for (exe_name, cpu_priority, io_priority) in configs { 987 | let key_path = format!(r"{}\{}\PerfOptions", base_path, exe_name); 988 | 989 | match hklm.create_subkey(&key_path) { 990 | Ok((key, _)) => { 991 | let mut success = true; 992 | 993 | if let Err(e) = key.set_value("CpuPriorityClass", &cpu_priority) { 994 | results.push(format!("{}:设置CPU优先级失败:{}", exe_name, e)); 995 | success = false; 996 | } 997 | 998 | if let Err(e) = key.set_value("IoPriority", &io_priority) { 999 | results.push(format!("{}:设置I/O优先级失败:{}", exe_name, e)); 1000 | success = false; 1001 | } 1002 | 1003 | if success { 1004 | results.push(format!("{}:设置成功(CPU:{},I/O:{})", exe_name, cpu_priority, io_priority)); 1005 | } 1006 | } 1007 | Err(e) => { 1008 | results.push(format!("{}:创建注册表项失败:{}", exe_name, e)); 1009 | } 1010 | } 1011 | } 1012 | 1013 | Ok(results.join("\n")) 1014 | } 1015 | 1016 | #[tauri::command] 1017 | fn raise_league_priority() -> Result { 1018 | if !is_elevated() { 1019 | return Err("需要管理员权限才能修改注册表".to_string()); 1020 | } 1021 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 1022 | let base_path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"; 1023 | let mut results = Vec::new(); 1024 | let configs = vec![ 1025 | ("League of Legends.exe", 3u32, 3u32), 1026 | ]; 1027 | 1028 | for (exe_name, cpu_priority, io_priority) in configs { 1029 | let key_path = format!(r"{}\{}\PerfOptions", base_path, exe_name); 1030 | 1031 | match hklm.create_subkey(&key_path) { 1032 | Ok((key, _)) => { 1033 | let mut success = true; 1034 | if let Err(e) = key.set_value("CpuPriorityClass", &cpu_priority) { 1035 | results.push(format!("{}:设置CPU优先级失败:{}", exe_name, e)); 1036 | success = false; 1037 | } 1038 | if let Err(e) = key.set_value("IoPriority", &io_priority) { 1039 | results.push(format!("{}:设置I/O优先级失败:{}", exe_name, e)); 1040 | success = false; 1041 | } 1042 | if success { 1043 | results.push(format!("{}:设置成功(CPU:{},I/O:{})", exe_name, cpu_priority, io_priority)); 1044 | } 1045 | } 1046 | Err(e) => { 1047 | results.push(format!("{}:创建注册表项失败:{}", exe_name, e)); 1048 | } 1049 | } 1050 | } 1051 | 1052 | Ok(results.join("\n")) 1053 | } 1054 | 1055 | #[tauri::command] 1056 | fn raise_arena_priority() -> Result { 1057 | if !is_elevated() { 1058 | return Err("需要管理员权限才能修改注册表".to_string()); 1059 | } 1060 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 1061 | let base_path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"; 1062 | let mut results = Vec::new(); 1063 | let configs = vec![ 1064 | ("Arena Breakout Infinite.exe", 3u32, 3u32), 1065 | ]; 1066 | 1067 | for (exe_name, cpu_priority, io_priority) in configs { 1068 | let key_path = format!(r"{}\{}\PerfOptions", base_path, exe_name); 1069 | 1070 | match hklm.create_subkey(&key_path) { 1071 | Ok((key, _)) => { 1072 | let mut success = true; 1073 | if let Err(e) = key.set_value("CpuPriorityClass", &cpu_priority) { 1074 | results.push(format!("{}:设置CPU优先级失败:{}", exe_name, e)); 1075 | success = false; 1076 | } 1077 | if let Err(e) = key.set_value("IoPriority", &io_priority) { 1078 | results.push(format!("{}:设置I/O优先级失败:{}", exe_name, e)); 1079 | success = false; 1080 | } 1081 | if success { 1082 | results.push(format!("{}:设置成功(CPU:{},I/O:{})", exe_name, cpu_priority, io_priority)); 1083 | } 1084 | } 1085 | Err(e) => { 1086 | results.push(format!("{}:创建注册表项失败:{}", exe_name, e)); 1087 | } 1088 | } 1089 | } 1090 | 1091 | Ok(results.join("\n")) 1092 | } 1093 | 1094 | #[tauri::command] 1095 | fn check_registry_priority() -> Result { 1096 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 1097 | let base_path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"; 1098 | 1099 | let mut results = Vec::new(); 1100 | 1101 | let exe_names = vec![ 1102 | "DeltaForceClient-Win64-Shipping.exe", 1103 | "SGuard64.exe", 1104 | "SGuardSvc64.exe", 1105 | "VALORANT-Win64-Shipping.exe", 1106 | "League of Legends.exe", 1107 | "Arena Breakout Infinite.exe", 1108 | ]; 1109 | 1110 | for exe_name in exe_names { 1111 | let key_path = format!(r"{}\{}\PerfOptions", base_path, exe_name); 1112 | 1113 | match hklm.open_subkey(&key_path) { 1114 | Ok(key) => { 1115 | let cpu_priority: Result = key.get_value("CpuPriorityClass"); 1116 | let io_priority: Result = key.get_value("IoPriority"); 1117 | 1118 | let cpu_str = match cpu_priority { 1119 | Ok(v) => format!("CPU:{}", v), 1120 | Err(_) => "CPU:未设置".to_string(), 1121 | }; 1122 | 1123 | let io_str = match io_priority { 1124 | Ok(v) => format!("I/O:{}", v), 1125 | Err(_) => "I/O:未设置".to_string(), 1126 | }; 1127 | 1128 | results.push(format!("{}:[{},{}]", exe_name, cpu_str, io_str)); 1129 | } 1130 | Err(_) => { 1131 | results.push(format!("{}:未配置", exe_name)); 1132 | } 1133 | } 1134 | } 1135 | 1136 | Ok(results.join("\n")) 1137 | } 1138 | 1139 | 1140 | #[tauri::command] 1141 | fn reset_registry_priority() -> Result { 1142 | if !is_elevated() { 1143 | return Err("需要管理员权限才能修改注册表".to_string()); 1144 | } 1145 | 1146 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 1147 | let base_path = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"; 1148 | 1149 | let mut results = Vec::new(); 1150 | 1151 | let exe_names = vec![ 1152 | "DeltaForceClient-Win64-Shipping.exe", 1153 | "SGuard64.exe", 1154 | "SGuardSvc64.exe", 1155 | "VALORANT-Win64-Shipping.exe", 1156 | "League of Legends.exe", 1157 | "Arena Breakout Infinite.exe", 1158 | ]; 1159 | 1160 | for exe_name in exe_names { 1161 | let exe_key_path = format!(r"{}\{}", base_path, exe_name); 1162 | 1163 | match hklm.open_subkey_with_flags(&exe_key_path, KEY_WRITE) { 1164 | Ok(exe_key) => { 1165 | match exe_key.delete_subkey("PerfOptions") { 1166 | Ok(_) => { 1167 | results.push(format!("{}:已恢复默认", exe_name)); 1168 | } 1169 | Err(e) => { 1170 | results.push(format!("{}:删除失败:{}", exe_name, e)); 1171 | } 1172 | } 1173 | } 1174 | Err(_) => { 1175 | results.push(format!("{}:未找到配置项", exe_name)); 1176 | } 1177 | } 1178 | } 1179 | 1180 | Ok(results.join("\n")) 1181 | } 1182 | 1183 | #[cfg_attr(mobile, tauri::mobile_entry_point)] 1184 | pub fn run() { 1185 | tauri::Builder::default() 1186 | .plugin(tauri_plugin_opener::init()) 1187 | .plugin(tauri_plugin_shell::init()) 1188 | .plugin(tauri_plugin_process::init()) 1189 | .manage(AppState) 1190 | .setup(|app| { 1191 | setup_tray(app.handle())?; 1192 | Ok(()) 1193 | }) 1194 | .on_window_event(|window, event| { 1195 | match event { 1196 | WindowEvent::CloseRequested { api, .. } => { 1197 | api.prevent_close(); 1198 | if let Err(e) = window.emit("show-close-confirm", ()) { 1199 | eprintln!("发送关闭确认事件失败: {}", e); 1200 | if let Some(window) = window.app_handle().get_webview_window("main") { 1201 | let _ = window.hide(); 1202 | } 1203 | } 1204 | } 1205 | _ => {} 1206 | } 1207 | }) 1208 | .invoke_handler(tauri::generate_handler![ 1209 | restrict_processes, 1210 | get_system_info, 1211 | get_process_performance, 1212 | show_close_dialog, 1213 | close_application, 1214 | enable_autostart, 1215 | disable_autostart, 1216 | check_autostart, 1217 | lower_ace_priority, 1218 | raise_delta_priority, 1219 | modify_valorant_registry_priority, 1220 | raise_league_priority, 1221 | raise_arena_priority, 1222 | check_registry_priority, 1223 | reset_registry_priority, 1224 | check_game_processes, 1225 | set_game_process_priority]) 1226 | .run(tauri::generate_context!()) 1227 | .expect("error while running tauri application"); 1228 | } --------------------------------------------------------------------------------