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 |
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 | }
463 | onClick={() => setShowAnnouncements(true)}
464 | sx={{ minWidth: 'auto', px: 0.8 }}
465 | size="small"
466 | >
467 | 公告
468 |
469 |
470 |
471 | }
474 | onClick={() => setShowUpdateDialog(true)}
475 | sx={{ minWidth: 'auto', px: 0.8 }}
476 | size="small"
477 | color={hasUpdate ? "error" : "success"}
478 | >
479 | 更新
480 |
481 |
482 | }
485 | onClick={async () => await openExternalLink('https://www.mikugame.icu/')}
486 | sx={{ minWidth: 'auto', px: 0.8 }}
487 | size="small"
488 | title="MikuGame - 初音游戏库"
489 | >
490 | 游戏资源
491 |
492 | }
495 | onClick={async () => await openExternalLink('https://www.mikumod.com/')}
496 | sx={{ minWidth: 'auto', px: 0.8 }}
497 | size="small"
498 | title="MikuMod - 游戏模组社区"
499 | >
500 | 模组社区
501 |
502 | }
505 | onClick={async () => await openExternalLink('https://afdian.com/a/shshouse')}
506 | sx={{ minWidth: 'auto', px: 0.8 }}
507 | size="small"
508 | title="作者: shshouse"
509 | >
510 | 投喂作者
511 |
512 | : }
515 | onClick={toggleDarkMode}
516 | sx={{ minWidth: 'auto', px: 0.8 }}
517 | size="small"
518 | >
519 | {darkMode ? '浅色' : '暗色'}
520 |
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 | }
862 | onClick={executeOnce}
863 | disabled={loading || isMonitoring}
864 | color="primary"
865 | size="small"
866 | fullWidth
867 | >
868 | 执行限制
869 |
870 |
871 |
872 |
873 |
874 |
875 |
888 | {logs.map((log) => (
889 |
899 | [{log.timestamp}] {log.message}
900 |
901 | ))}
902 |
903 |
904 |
905 |
906 | {/* 公告对话框 */}
907 |
948 |
949 |
1012 |
1013 |
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 | }
--------------------------------------------------------------------------------