初始化仓库
This commit is contained in:
1 parent
2bedb35f58
commit
75bf59adeb
21 files changed
+12064
-1
No files matched your search
@@ -1,2 +1,4 @@
|
||||
# UPX-GUI
|
||||
UPX套壳GUI
|
||||
GUI版UPX加壳/脱壳工具
|
||||
|
||||
[upx官方仓库](https://github.com/upx/upx)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,29 @@
|
||||
# 图标文件
|
||||
|
||||
请在此目录放置以下图标文件:
|
||||
|
||||
- `32x32.png` - 32x32 像素的 PNG 图标
|
||||
- `128x128.png` - 128x128 像素的 PNG 图标
|
||||
- `128x128@2x.png` - 256x256 像素的 PNG 图标(高分辨率)
|
||||
- `icon.ico` - Windows ICO 格式图标
|
||||
|
||||
## 图标要求
|
||||
|
||||
- 图标应该是正方形
|
||||
- 建议使用透明背景
|
||||
- PNG 图标建议 32 位色深
|
||||
- ICO 文件可以包含多个尺寸(16x16, 32x32, 48x48, 256x256)
|
||||
|
||||
## 图标生成工具
|
||||
|
||||
可以使用以下工具生成图标:
|
||||
- [IconKitchen](https://icon.kitchen/)
|
||||
- [Favicon.io](https://favicon.io/)
|
||||
- [RealFaviconGenerator](https://realfavicongenerator.net/)
|
||||
|
||||
## 临时方案
|
||||
|
||||
如果暂时没有图标,可以从 timer 项目复制图标文件:
|
||||
```
|
||||
copy e:\Code_Repository\timer\icons\*.* f:\Code\tools\upx-gui\icons\
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 340 KiB |
Generated
+5364
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "upx-gui"
|
||||
version = "1.0.0"
|
||||
description = "UPX 可视化加壳脱壳工具"
|
||||
authors = ["ASLant"]
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
encoding_rs = "0.8"
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
[profile.release]
|
||||
strip = true # 移除调试符号
|
||||
opt-level = "z" # 优化大小
|
||||
lto = true # 链接时优化
|
||||
codegen-units = 1 # 更好的优化
|
||||
panic = "abort" # 减小panic处理代码
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,303 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::process::Command;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use encoding_rs::GBK;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct UpxOptions {
|
||||
mode: String,
|
||||
input_file: String,
|
||||
output_file: String,
|
||||
compression_level: String,
|
||||
backup: bool,
|
||||
ultra_brute: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ScanFolderOptions {
|
||||
folder_path: String,
|
||||
include_subfolders: bool,
|
||||
}
|
||||
|
||||
// 获取UPX可执行文件路径
|
||||
fn get_upx_path() -> PathBuf {
|
||||
// 尝试1: 使用打包后的upx (在exe同目录下)
|
||||
if let Ok(exe_path) = std::env::current_exe() {
|
||||
if let Some(exe_dir) = exe_path.parent() {
|
||||
let bundled_upx = exe_dir.join("upx.exe");
|
||||
if bundled_upx.exists() {
|
||||
return bundled_upx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试2: 使用开发环境的upx (src-tauri/../upx/upx.exe)
|
||||
let dev_upx = PathBuf::from("../../upx/upx.exe");
|
||||
if dev_upx.exists() {
|
||||
return dev_upx;
|
||||
}
|
||||
|
||||
// 尝试3: 另一个开发环境路径
|
||||
let dev_upx2 = PathBuf::from("../upx/upx.exe");
|
||||
if dev_upx2.exists() {
|
||||
return dev_upx2;
|
||||
}
|
||||
|
||||
// 尝试4: 使用系统PATH中的upx (降级方案)
|
||||
PathBuf::from("upx")
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn process_upx(options: UpxOptions) -> Result<String, String> {
|
||||
// 获取UPX可执行文件路径
|
||||
let upx_path = get_upx_path();
|
||||
|
||||
// 检查 UPX 是否可用
|
||||
let mut upx_check = Command::new(&upx_path);
|
||||
|
||||
// 在 Windows 上隐藏控制台窗口
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
upx_check.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
let upx_check = upx_check.arg("--version").output();
|
||||
|
||||
if upx_check.is_err() {
|
||||
return Err("未找到 UPX 工具!".to_string());
|
||||
}
|
||||
|
||||
// 检查输入文件是否存在
|
||||
if !Path::new(&options.input_file).exists() {
|
||||
return Err(format!("输入文件不存在: {}", options.input_file));
|
||||
}
|
||||
|
||||
// 检查文件是否可写(如果需要覆盖)
|
||||
if options.input_file == options.output_file {
|
||||
let metadata = fs::metadata(&options.input_file)
|
||||
.map_err(|e| format!("无法读取文件属性: {}", e))?;
|
||||
|
||||
if metadata.permissions().readonly() {
|
||||
return Err(format!("文件为只读,请先修改文件属性"));
|
||||
}
|
||||
}
|
||||
|
||||
// 备份原文件
|
||||
if options.backup {
|
||||
let backup_path = format!("{}.bak", options.input_file);
|
||||
if let Err(e) = fs::copy(&options.input_file, &backup_path) {
|
||||
return Err(format!("备份文件失败: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
// 构建 UPX 命令
|
||||
let mut cmd = Command::new(&upx_path);
|
||||
|
||||
// 在 Windows 上隐藏控制台窗口
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
// 判断是否覆盖原文件
|
||||
let is_overwrite = options.input_file == options.output_file;
|
||||
|
||||
match options.mode.as_str() {
|
||||
"compress" => {
|
||||
// 加壳模式
|
||||
if options.ultra_brute {
|
||||
cmd.arg("--ultra-brute");
|
||||
cmd.arg("--no-lzma"); // 禁用LZMA以避免解压速度过慢
|
||||
} else if options.compression_level == "best" {
|
||||
cmd.arg("--best");
|
||||
} else {
|
||||
cmd.arg(format!("-{}", options.compression_level));
|
||||
}
|
||||
|
||||
if is_overwrite {
|
||||
// 直接覆盖原文件,不使用 -o 参数
|
||||
cmd.arg(&options.input_file);
|
||||
} else {
|
||||
// 输出到指定文件
|
||||
cmd.arg(&options.input_file);
|
||||
cmd.arg("-o");
|
||||
cmd.arg(&options.output_file);
|
||||
}
|
||||
cmd.arg("--force-overwrite");
|
||||
}
|
||||
"decompress" => {
|
||||
// 脱壳模式
|
||||
cmd.arg("-d");
|
||||
|
||||
if is_overwrite {
|
||||
// 直接覆盖原文件
|
||||
cmd.arg(&options.input_file);
|
||||
} else {
|
||||
// 输出到指定文件
|
||||
cmd.arg(&options.input_file);
|
||||
cmd.arg("-o");
|
||||
cmd.arg(&options.output_file);
|
||||
}
|
||||
cmd.arg("--force-overwrite");
|
||||
}
|
||||
_ => {
|
||||
return Err("未知的操作模式".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// 在处理前记录原始文件大小
|
||||
let original_size = fs::metadata(&options.input_file)
|
||||
.map(|m| m.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
// 在后台线程执行命令,避免阻塞 UI
|
||||
let output_file_clone = options.output_file.clone();
|
||||
let original_size_clone = original_size;
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
match cmd.output() {
|
||||
Ok(output) => {
|
||||
// 在 Windows 上 UPX 输出是 GBK 编码,需要转换为 UTF-8
|
||||
let (stdout, _, _) = GBK.decode(&output.stdout);
|
||||
let (stderr, _, _) = GBK.decode(&output.stderr);
|
||||
|
||||
if output.status.success() {
|
||||
// 获取处理后的文件大小
|
||||
let output_size = fs::metadata(&output_file_clone)
|
||||
.map(|m| m.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
let ratio = if original_size_clone > 0 {
|
||||
(output_size as f64 / original_size_clone as f64 * 100.0) as i32
|
||||
} else {
|
||||
100
|
||||
};
|
||||
|
||||
let result = format!(
|
||||
"操作成功!\n输出: {}\n原始大小: {}\n处理后大小: {}\n压缩率: {}%\n\nUPX 输出:\n{}{}",
|
||||
output_file_clone,
|
||||
format_bytes(original_size_clone),
|
||||
format_bytes(output_size),
|
||||
ratio,
|
||||
stdout,
|
||||
stderr
|
||||
);
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(format!("UPX 执行失败:\n{}{}", stdout, stderr))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
Err(format!("执行 UPX 命令失败: {}", e))
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("任务执行错误: {}", e))?
|
||||
}
|
||||
|
||||
// 格式化字节大小
|
||||
fn format_bytes(bytes: u64) -> String {
|
||||
const KB: u64 = 1024;
|
||||
const MB: u64 = KB * 1024;
|
||||
const GB: u64 = MB * 1024;
|
||||
|
||||
if bytes >= GB {
|
||||
format!("{:.2} GB", bytes as f64 / GB as f64)
|
||||
} else if bytes >= MB {
|
||||
format!("{:.2} MB", bytes as f64 / MB as f64)
|
||||
} else if bytes >= KB {
|
||||
format!("{:.2} KB", bytes as f64 / KB as f64)
|
||||
} else {
|
||||
format!("{} bytes", bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// 扫描文件夹获取所有exe和dll文件
|
||||
fn scan_folder_recursive(folder_path: &Path, include_subfolders: bool) -> Vec<String> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
if let Ok(entries) = fs::read_dir(folder_path) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() && include_subfolders {
|
||||
// 递归扫描子文件夹
|
||||
let sub_files = scan_folder_recursive(&path, include_subfolders);
|
||||
files.extend(sub_files);
|
||||
} else if path.is_file() {
|
||||
if let Some(extension) = path.extension() {
|
||||
let ext = extension.to_string_lossy().to_lowercase();
|
||||
if ext == "exe" || ext == "dll" {
|
||||
if let Some(path_str) = path.to_str() {
|
||||
files.push(path_str.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn scan_folder(options: ScanFolderOptions) -> Result<Vec<String>, String> {
|
||||
let path = Path::new(&options.folder_path);
|
||||
|
||||
if !path.exists() {
|
||||
return Err(format!("路径不存在: {}", options.folder_path));
|
||||
}
|
||||
|
||||
if !path.is_dir() {
|
||||
return Err(format!("不是文件夹: {}", options.folder_path));
|
||||
}
|
||||
|
||||
Ok(scan_folder_recursive(path, options.include_subfolders))
|
||||
}
|
||||
|
||||
// 获取UPX版本
|
||||
#[tauri::command]
|
||||
fn get_upx_version() -> Result<String, String> {
|
||||
let upx_path = get_upx_path();
|
||||
|
||||
let mut cmd = Command::new(&upx_path);
|
||||
|
||||
// 在 Windows 上隐藏控制台窗口
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
match cmd.arg("--version").output() {
|
||||
Ok(output) => {
|
||||
let (version_str, _, _) = GBK.decode(&output.stdout);
|
||||
let lines: Vec<&str> = version_str.lines().collect();
|
||||
if !lines.is_empty() {
|
||||
Ok(lines[0].to_string())
|
||||
} else {
|
||||
Err("无法获取UPX版本".to_string())
|
||||
}
|
||||
}
|
||||
Err(_) => Err("UPX未找到".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.invoke_handler(tauri::generate_handler![process_upx, scan_folder, get_upx_version])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "UPX GUI",
|
||||
"version": "1.0.0",
|
||||
"identifier": "top.aslant.upxgui",
|
||||
"build": {
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": "",
|
||||
"frontendDist": "../ui"
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"windows": [
|
||||
{
|
||||
"label": "main",
|
||||
"title": "UPX GUI",
|
||||
"width": 1000,
|
||||
"height": 800,
|
||||
"minWidth": 900,
|
||||
"minHeight": 700,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"center": true,
|
||||
"decorations": false,
|
||||
"transparent": false,
|
||||
"visible": true,
|
||||
"dragDropEnabled": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null,
|
||||
"capabilities": [
|
||||
{
|
||||
"identifier": "main-capability",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:default",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-show",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save",
|
||||
"shell:allow-execute"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["msi", "nsis"],
|
||||
"icon": [
|
||||
"../icons/32x32.png",
|
||||
"../icons/128x128.png",
|
||||
"../icons/128x128@2x.png",
|
||||
"../icons/icon.ico"
|
||||
],
|
||||
"resources": [
|
||||
"../upx/upx.exe"
|
||||
],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "",
|
||||
"wix": {
|
||||
"language": ["zh-CN"],
|
||||
"template": null
|
||||
},
|
||||
"nsis": {
|
||||
"installMode": "currentUser",
|
||||
"languages": ["SimpChinese"],
|
||||
"displayLanguageSelector": true,
|
||||
"compression": "lzma",
|
||||
"installerIcon": "../icons/icon.ico",
|
||||
"headerImage": null
|
||||
}
|
||||
},
|
||||
"shortDescription": "UPX 可视化工具",
|
||||
"longDescription": "一个基于 Tauri 的 UPX 可视化加壳脱壳工具,支持文件压缩和解压",
|
||||
"copyright": "Copyright © 2025",
|
||||
"license": "MIT",
|
||||
"category": "Utility"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/* 字体定义 */
|
||||
@font-face {
|
||||
font-family: 'HarmonyOS Sans SC';
|
||||
src: url('../font/HarmonyOS_Sans_SC_Regular.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* 全局样式 */
|
||||
:root {
|
||||
--border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'HarmonyOS Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
|
||||
'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* 呼吸灯效果 */
|
||||
@keyframes breathing {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.breathing-light {
|
||||
animation: breathing 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #4a4a4a;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #5a5a5a;
|
||||
}
|
||||
|
||||
/* 日志输出区域 */
|
||||
#log-output {
|
||||
font-family: 'HarmonyOS Sans SC', 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
overflow: overlay; /* 使用overlay避免内容被滚动条遮挡 */
|
||||
}
|
||||
|
||||
/* 日志输出区域滚动条 */
|
||||
#log-output::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
#log-output::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
#log-output::-webkit-scrollbar-thumb {
|
||||
background: rgba(74, 74, 74, 0.6);
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
#log-output::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(90, 90, 90, 0.8);
|
||||
}
|
||||
|
||||
/* 日志高亮样式 */
|
||||
#log-output .log-highlight {
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 日志行样式 */
|
||||
.log-line {
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.log-info {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.log-error {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.log-warning {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.log-success {
|
||||
color: #10b981;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 按钮动画 */
|
||||
button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 模式选择按钮激活状态 */
|
||||
.mode-active {
|
||||
border-color: #3b82f6 !important;
|
||||
background-color: #eff6ff !important;
|
||||
color: #1d4ed8 !important;
|
||||
}
|
||||
|
||||
.mode-inactive {
|
||||
border-color: #d1d5db !important;
|
||||
background-color: white !important;
|
||||
color: #6b7280 !important;
|
||||
}
|
||||
|
||||
/* 禁用选择 */
|
||||
.select-none {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
/* 输入框聚焦效果 */
|
||||
input:focus, select:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 自定义复选框 */
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* 滑动条样式 */
|
||||
input[type="range"].slider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: linear-gradient(to right, #93c5fd 0%, #3b82f6 50%, #1d4ed8 100%);
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
transition: opacity 0.2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
input[type="range"].slider:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 滑块样式 - Webkit浏览器 */
|
||||
input[type="range"].slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: white;
|
||||
border: 3px solid #3b82f6;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
input[type="range"].slider::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
input[type="range"].slider::-webkit-slider-thumb:active {
|
||||
transform: scale(1.05);
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
/* 滑块样式 - Firefox */
|
||||
input[type="range"].slider::-moz-range-thumb {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: white;
|
||||
border: 3px solid #3b82f6;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
input[type="range"].slider::-moz-range-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
input[type="range"].slider::-moz-range-thumb:active {
|
||||
transform: scale(1.05);
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* 渐入动画 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
.progress-bar {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 文件拖放区域 */
|
||||
.drop-zone {
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.drop-zone.drag-over {
|
||||
transform: scale(1.02) !important;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3) !important;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
#compress-btn.drag-over {
|
||||
background: linear-gradient(to bottom right, #dbeafe, #bfdbfe) !important;
|
||||
border-color: #2563eb !important;
|
||||
}
|
||||
|
||||
#decompress-btn.drag-over {
|
||||
background: linear-gradient(to bottom right, #d1fae5, #a7f3d0) !important;
|
||||
border-color: #059669 !important;
|
||||
}
|
||||
Binary file not shown.
+154
@@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UPX GUI</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body class="h-screen overflow-hidden bg-gray-50" oncontextmenu="return false;">
|
||||
<!-- 标题栏 -->
|
||||
<div id="titlebar" class="h-10 bg-white border-b border-gray-200 flex items-center justify-between px-4">
|
||||
<div data-tauri-drag-region class="flex items-center space-x-3 flex-1 h-full">
|
||||
<div class="w-2 h-2 rounded-full bg-blue-500 breathing-light"></div>
|
||||
<span class="text-gray-700 text-xs font-medium select-none">UPX 可视化工具</span>
|
||||
<span id="upx-version" class="text-gray-400 text-xs select-none"></span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<button id="settings-btn" class="w-9 h-9 hover:bg-gray-100 flex items-center justify-center transition-all duration-200" style="border-radius: 0.3rem;" title="设置">
|
||||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="titlebar-minimize" class="w-9 h-9 hover:bg-gray-100 flex items-center justify-center transition-all duration-200" style="border-radius: 0.3rem;">
|
||||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="titlebar-maximize" class="w-9 h-9 hover:bg-gray-100 flex items-center justify-center transition-all duration-200" style="border-radius: 0.3rem;">
|
||||
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="titlebar-close" class="w-9 h-9 hover:bg-red-50 flex items-center justify-center transition-all duration-200 group" style="border-radius: 0.3rem;">
|
||||
<svg class="w-4 h-4 text-gray-600 group-hover:text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="h-[calc(100vh-2.5rem)] flex flex-col p-6">
|
||||
<!-- 操作选择 -->
|
||||
<div class="bg-white shadow-lg p-6 border border-gray-200 mb-6" style="border-radius: 0.3rem;">
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<button id="compress-btn" class="drop-zone group relative py-8 px-6 border-2 border-blue-500 bg-gradient-to-br from-blue-50 to-blue-100 hover:from-blue-100 hover:to-blue-200 transition-all shadow-md hover:shadow-xl transform hover:-translate-y-1" style="border-radius: 0.3rem;">
|
||||
<svg class="w-16 h-16 mx-auto mb-3 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
<div class="text-xl text-blue-700 mb-2">加壳压缩</div>
|
||||
<div class="text-sm text-blue-600">点击选择或拖放文件/文件夹到此处</div>
|
||||
</button>
|
||||
<button id="decompress-btn" class="drop-zone group relative py-8 px-6 border-2 border-green-500 bg-gradient-to-br from-green-50 to-green-100 hover:from-green-100 hover:to-green-200 transition-all shadow-md hover:shadow-xl transform hover:-translate-y-1" style="border-radius: 0.3rem;">
|
||||
<svg class="w-16 h-16 mx-auto mb-3 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"/>
|
||||
</svg>
|
||||
<div class="text-xl text-green-700 mb-2">脱壳解压</div>
|
||||
<div class="text-sm text-green-600">点击选择或拖放文件/文件夹到此处</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 日志输出 -->
|
||||
<div class="bg-white shadow-lg p-6 border border-gray-200 flex-1 flex flex-col min-h-0" style="border-radius: 0.3rem;">
|
||||
<h2 class="text-lg font-bold text-gray-800 mb-4">执行日志</h2>
|
||||
<div id="log-output" class="flex-1 bg-gray-900 text-green-400 font-mono text-sm p-4 overflow-y-auto overflow-x-hidden" style="border-radius: 0.3rem;">
|
||||
<div class="text-gray-500">等待操作...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 全局设置弹窗 -->
|
||||
<div id="settings-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white shadow-2xl p-6 max-w-md w-full mx-4 transform transition-all" style="border-radius: 0.3rem;">
|
||||
<h2 class="text-xl font-medium text-gray-800 mb-5 pb-3 border-b border-gray-200">全局设置</h2>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<label class="text-sm font-medium text-gray-700">压缩级别</label>
|
||||
<span id="level-display" class="text-base font-semibold text-blue-600">级别 9</span>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="range" id="compression-level" min="1" max="10" value="9"
|
||||
class="w-full h-2 bg-gray-200 appearance-none cursor-pointer slider" style="border-radius: 0.3rem;">
|
||||
|
||||
<!-- 级别标记 -->
|
||||
<div class="flex justify-between mt-2 px-1">
|
||||
<span class="text-xs text-gray-400">1</span>
|
||||
<span class="text-xs text-gray-400">2</span>
|
||||
<span class="text-xs text-gray-400">3</span>
|
||||
<span class="text-xs text-gray-400">4</span>
|
||||
<span class="text-xs text-gray-400">5</span>
|
||||
<span class="text-xs text-gray-400">6</span>
|
||||
<span class="text-xs text-gray-400">7</span>
|
||||
<span class="text-xs text-gray-400">8</span>
|
||||
<span class="text-xs text-gray-400">9</span>
|
||||
<span class="text-xs text-gray-500 font-medium">best</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p id="level-description" class="mt-2 text-xs text-gray-500 text-center">推荐级别,平衡速度和压缩率</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-start space-x-3 cursor-pointer p-2.5 hover:bg-gray-50 transition-colors" style="border-radius: 0.3rem;">
|
||||
<input type="checkbox" id="overwrite" checked class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer">
|
||||
<div class="flex-1">
|
||||
<span class="text-sm font-medium text-gray-800">覆盖原文件</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">直接替换原文件,不创建新文件</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start space-x-3 cursor-pointer p-2.5 hover:bg-gray-50 transition-colors" style="border-radius: 0.3rem;">
|
||||
<input type="checkbox" id="backup" class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer">
|
||||
<div class="flex-1">
|
||||
<span class="text-sm font-medium text-gray-800">备份原文件</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">保留 .bak 备份文件</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start space-x-3 cursor-pointer p-2.5 hover:bg-gray-50 transition-colors" style="border-radius: 0.3rem;">
|
||||
<input type="checkbox" id="ultra-brute" class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer">
|
||||
<div class="flex-1">
|
||||
<span class="text-sm font-medium text-gray-800">极限压缩</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">尝试所有压缩方法,速度较慢但压缩率最高</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start space-x-3 cursor-pointer p-2.5 hover:bg-gray-50 transition-colors" style="border-radius: 0.3rem;">
|
||||
<input type="checkbox" id="include-subfolders" class="w-4 h-4 mt-0.5 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 cursor-pointer">
|
||||
<div class="flex-1">
|
||||
<span class="text-sm font-medium text-gray-800">包含子文件夹</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">处理文件夹时,递归处理所有子目录</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-6 pt-4 border-t border-gray-200">
|
||||
<button id="close-settings" class="w-full py-2.5 px-6 bg-blue-500 text-white text-sm font-medium hover:bg-blue-600 transition-colors shadow-sm" style="border-radius: 0.3rem;">
|
||||
保存设置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+542
@@ -0,0 +1,542 @@
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
const { open, save } = window.__TAURI__.dialog;
|
||||
const { getCurrentWindow } = window.__TAURI__.window;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
|
||||
// DOM 元素
|
||||
const compressBtn = document.getElementById('compress-btn');
|
||||
const decompressBtn = document.getElementById('decompress-btn');
|
||||
const compressionLevel = document.getElementById('compression-level');
|
||||
const levelDisplay = document.getElementById('level-display');
|
||||
const levelDescription = document.getElementById('level-description');
|
||||
const overwriteCheckbox = document.getElementById('overwrite');
|
||||
const backupCheckbox = document.getElementById('backup');
|
||||
const ultraBruteCheckbox = document.getElementById('ultra-brute');
|
||||
const includeSubfoldersCheckbox = document.getElementById('include-subfolders');
|
||||
const logOutput = document.getElementById('log-output');
|
||||
const settingsModal = document.getElementById('settings-modal');
|
||||
const settingsBtn = document.getElementById('settings-btn');
|
||||
const closeSettingsBtn = document.getElementById('close-settings');
|
||||
|
||||
// 窗口控制按钮
|
||||
const minimizeBtn = document.getElementById('titlebar-minimize');
|
||||
const maximizeBtn = document.getElementById('titlebar-maximize');
|
||||
const closeBtn = document.getElementById('titlebar-close');
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
initWindowControls();
|
||||
initOperationButtons();
|
||||
initCompressionLevelSlider();
|
||||
preventRefresh();
|
||||
await setupDragAndDrop();
|
||||
|
||||
// 获取并显示UPX版本
|
||||
try {
|
||||
const version = await invoke('get_upx_version');
|
||||
// 在标题栏显示版本
|
||||
const versionElement = document.getElementById('upx-version');
|
||||
if (versionElement) {
|
||||
versionElement.textContent = `- ${version}`;
|
||||
}
|
||||
addLog(`UPX GUI 已就绪 - ${version}`, 'info');
|
||||
} catch (error) {
|
||||
addLog('UPX GUI 已就绪 - 请选择操作', 'info');
|
||||
}
|
||||
});
|
||||
|
||||
// 屏蔽刷新快捷键
|
||||
function preventRefresh() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// F5 或 Ctrl+R 或 Ctrl+Shift+R 刷新
|
||||
if (e.key === 'F5' ||
|
||||
(e.ctrlKey && e.key === 'r') ||
|
||||
(e.ctrlKey && e.shiftKey && e.key === 'R')) {
|
||||
e.preventDefault();
|
||||
addLog('刷新功能已禁用', 'warning');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ctrl+W 关闭窗口
|
||||
if (e.ctrlKey && e.key === 'w') {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 窗口控制
|
||||
function initWindowControls() {
|
||||
const appWindow = getCurrentWindow();
|
||||
|
||||
minimizeBtn.addEventListener('click', () => {
|
||||
appWindow.minimize();
|
||||
});
|
||||
|
||||
maximizeBtn.addEventListener('click', () => {
|
||||
appWindow.toggleMaximize();
|
||||
});
|
||||
|
||||
closeBtn.addEventListener('click', () => {
|
||||
appWindow.close();
|
||||
});
|
||||
}
|
||||
|
||||
// 操作按钮初始化
|
||||
function initOperationButtons() {
|
||||
// 设置按钮 - 显示全局设置
|
||||
settingsBtn.addEventListener('click', () => {
|
||||
showSettingsModal();
|
||||
});
|
||||
|
||||
// 关闭设置按钮
|
||||
closeSettingsBtn.addEventListener('click', () => {
|
||||
hideSettingsModal();
|
||||
addLog('设置已保存', 'success');
|
||||
});
|
||||
|
||||
// 点击背景关闭弹窗
|
||||
settingsModal.addEventListener('click', (e) => {
|
||||
if (e.target === settingsModal) {
|
||||
hideSettingsModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 加壳压缩按钮 - 直接执行
|
||||
compressBtn.addEventListener('click', async () => {
|
||||
// 检查是否有多个文件
|
||||
if (window.droppedFiles && window.droppedFiles.length > 0) {
|
||||
const files = window.droppedFiles;
|
||||
window.droppedFiles = null;
|
||||
addLog('开始批量加壳压缩...', 'info');
|
||||
await processBatchFiles(files, 'compress');
|
||||
}
|
||||
// 检查是否有单个文件
|
||||
else if (window.droppedFile) {
|
||||
const filePath = window.droppedFile;
|
||||
window.droppedFile = null;
|
||||
addLog('开始加壳压缩...', 'info');
|
||||
await handleCompressWithFile(filePath);
|
||||
}
|
||||
// 没有拖放文件,弹出选择
|
||||
else {
|
||||
addLog('选择文件进行加壳...', 'info');
|
||||
await handleCompress();
|
||||
}
|
||||
});
|
||||
|
||||
// 脱壳解压按钮 - 直接执行
|
||||
decompressBtn.addEventListener('click', async () => {
|
||||
// 检查是否有多个文件
|
||||
if (window.droppedFiles && window.droppedFiles.length > 0) {
|
||||
const files = window.droppedFiles;
|
||||
window.droppedFiles = null;
|
||||
addLog('开始批量脱壳解压...', 'info');
|
||||
await processBatchFiles(files, 'decompress');
|
||||
}
|
||||
// 检查是否有单个文件
|
||||
else if (window.droppedFile) {
|
||||
const filePath = window.droppedFile;
|
||||
window.droppedFile = null;
|
||||
addLog('开始脱壳解压...', 'info');
|
||||
await handleDecompressWithFile(filePath);
|
||||
}
|
||||
// 没有拖放文件,弹出选择
|
||||
else {
|
||||
addLog('选择文件进行脱壳...', 'info');
|
||||
await handleDecompress();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化压缩级别滑动条
|
||||
function initCompressionLevelSlider() {
|
||||
// 级别描述映射
|
||||
const levelDescriptions = {
|
||||
1: '最快速度,压缩率最低',
|
||||
2: '较快速度,较低压缩率',
|
||||
3: '快速压缩',
|
||||
4: '平衡模式',
|
||||
5: '标准压缩',
|
||||
6: '良好压缩',
|
||||
7: '较高压缩率',
|
||||
8: '高压缩率',
|
||||
9: '推荐级别,平衡速度和压缩率',
|
||||
10: '极致压缩,速度最慢'
|
||||
};
|
||||
|
||||
// 更新显示
|
||||
function updateLevelDisplay(value) {
|
||||
const level = parseInt(value);
|
||||
if (level === 10) {
|
||||
levelDisplay.textContent = '级别 best';
|
||||
} else {
|
||||
levelDisplay.textContent = `级别 ${level}`;
|
||||
}
|
||||
levelDescription.textContent = levelDescriptions[level] || '';
|
||||
}
|
||||
|
||||
// 初始显示
|
||||
updateLevelDisplay(compressionLevel.value);
|
||||
|
||||
// 监听滑动条变化
|
||||
compressionLevel.addEventListener('input', (e) => {
|
||||
updateLevelDisplay(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
// 获取当前压缩级别值
|
||||
function getCompressionLevel() {
|
||||
const value = parseInt(compressionLevel.value);
|
||||
return value === 10 ? 'best' : value.toString();
|
||||
}
|
||||
|
||||
// 显示设置弹窗
|
||||
function showSettingsModal() {
|
||||
settingsModal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 隐藏设置弹窗
|
||||
function hideSettingsModal() {
|
||||
settingsModal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 扫描文件夹获取所有exe和dll文件
|
||||
async function scanFolder(folderPath, includeSubfolders) {
|
||||
try {
|
||||
const files = await invoke('scan_folder', {
|
||||
options: {
|
||||
folder_path: folderPath,
|
||||
include_subfolders: includeSubfolders
|
||||
}
|
||||
});
|
||||
return files;
|
||||
} catch (error) {
|
||||
addLog(`扫描文件夹失败: ${error}`, 'error');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 批量处理文件
|
||||
async function processBatchFiles(files, mode) {
|
||||
if (files.length === 0) {
|
||||
addLog('没有找到可处理的文件', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
addLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'info');
|
||||
addLog(`批量处理模式`, 'info');
|
||||
addLog(`找到 ${files.length} 个文件`, 'info');
|
||||
addLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'info');
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
// 并行处理,每次最多5个文件
|
||||
const batchSize = 5;
|
||||
for (let i = 0; i < files.length; i += batchSize) {
|
||||
const batch = files.slice(i, Math.min(i + batchSize, files.length));
|
||||
|
||||
await Promise.all(batch.map(async (file) => {
|
||||
try {
|
||||
if (mode === 'compress') {
|
||||
await handleCompressWithFile(file);
|
||||
} else {
|
||||
await handleDecompressWithFile(file);
|
||||
}
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
addLog(`处理失败: ${file}`, 'error');
|
||||
failCount++;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
addLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'info', true);
|
||||
addLog(`批量处理完成!`, 'success', true);
|
||||
addLog(`成功: ${successCount} 个,失败: ${failCount} 个`, 'info', true);
|
||||
addLog(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'info', true);
|
||||
}
|
||||
|
||||
// 判断路径是否为文件夹并扫描
|
||||
async function checkAndScanPath(path) {
|
||||
// 尝试扫描文件夹
|
||||
const files = await scanFolder(path, includeSubfoldersCheckbox.checked);
|
||||
|
||||
if (files.length > 0) {
|
||||
// 是文件夹,返回扫描到的文件
|
||||
addLog(`扫描文件夹: ${path} (找到 ${files.length} 个文件)`, 'info');
|
||||
return files;
|
||||
} else {
|
||||
// 不是文件夹或没有找到文件,检查是否是exe/dll文件
|
||||
if (path.toLowerCase().endsWith('.exe') || path.toLowerCase().endsWith('.dll')) {
|
||||
return [path];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 设置文件拖放功能
|
||||
async function setupDragAndDrop() {
|
||||
// 监听文件拖放释放事件
|
||||
await listen('tauri://drag-drop', async (event) => {
|
||||
const paths = event.payload.paths;
|
||||
const position = event.payload.position;
|
||||
|
||||
if (paths && paths.length > 0) {
|
||||
// 收集所有文件
|
||||
const allFiles = [];
|
||||
|
||||
for (const path of paths) {
|
||||
const files = await checkAndScanPath(path);
|
||||
allFiles.push(...files);
|
||||
}
|
||||
|
||||
if (allFiles.length === 0) {
|
||||
addLog('未找到 .exe 或 .dll 文件', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断拖放位置,自动触发对应操作
|
||||
const dropTarget = getDropTarget(position);
|
||||
|
||||
if (dropTarget === 'compress') {
|
||||
addLog('检测到拖放至加壳区域', 'info');
|
||||
await processBatchFiles(allFiles, 'compress');
|
||||
} else if (dropTarget === 'decompress') {
|
||||
addLog('检测到拖放至脱壳区域', 'info');
|
||||
await processBatchFiles(allFiles, 'decompress');
|
||||
} else {
|
||||
// 拖放到其他位置,存储文件等待用户选择
|
||||
if (allFiles.length === 1) {
|
||||
addLog('请点击"加壳压缩"或"脱壳解压"按钮', 'info');
|
||||
window.droppedFile = allFiles[0];
|
||||
} else {
|
||||
addLog(`已选择 ${allFiles.length} 个文件,请点击操作按钮`, 'info');
|
||||
window.droppedFiles = allFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 为按钮添加视觉反馈
|
||||
setupVisualFeedback(compressBtn);
|
||||
setupVisualFeedback(decompressBtn);
|
||||
}
|
||||
|
||||
// 判断拖放位置对应的目标
|
||||
function getDropTarget(position) {
|
||||
if (!position) return null;
|
||||
|
||||
const compressRect = compressBtn.getBoundingClientRect();
|
||||
const decompressRect = decompressBtn.getBoundingClientRect();
|
||||
|
||||
const x = position.x;
|
||||
const y = position.y;
|
||||
|
||||
// 检查是否在加壳压缩按钮范围内
|
||||
if (x >= compressRect.left && x <= compressRect.right &&
|
||||
y >= compressRect.top && y <= compressRect.bottom) {
|
||||
return 'compress';
|
||||
}
|
||||
|
||||
// 检查是否在脱壳解压按钮范围内
|
||||
if (x >= decompressRect.left && x <= decompressRect.right &&
|
||||
y >= decompressRect.top && y <= decompressRect.bottom) {
|
||||
return 'decompress';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 为元素设置视觉反馈
|
||||
function setupVisualFeedback(element) {
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
element.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
element.classList.add('drag-over');
|
||||
}, false);
|
||||
});
|
||||
|
||||
['dragleave', 'drop'].forEach(eventName => {
|
||||
element.addEventListener(eventName, () => {
|
||||
element.classList.remove('drag-over');
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
// 处理加壳压缩
|
||||
async function handleCompress() {
|
||||
try {
|
||||
// 选择文件(支持多选)
|
||||
const selected = await open({
|
||||
multiple: true,
|
||||
filters: [{
|
||||
name: '可执行文件',
|
||||
extensions: ['exe', 'dll']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!selected || (Array.isArray(selected) && selected.length === 0)) {
|
||||
addLog('未选择文件', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理选择结果(可能是单个路径或路径数组)
|
||||
const files = Array.isArray(selected) ? selected : [selected];
|
||||
|
||||
// 批量处理或单文件处理
|
||||
if (files.length === 1) {
|
||||
addLog(`选择文件: ${files[0]}`, 'info');
|
||||
await handleCompressWithFile(files[0]);
|
||||
} else {
|
||||
addLog(`选择了 ${files.length} 个文件`, 'info');
|
||||
await processBatchFiles(files, 'compress');
|
||||
}
|
||||
} catch (error) {
|
||||
addLog(`操作失败: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 使用指定文件处理加壳压缩
|
||||
async function handleCompressWithFile(inputFile) {
|
||||
try {
|
||||
|
||||
let outputFile;
|
||||
|
||||
// 根据覆盖选项决定输出文件
|
||||
if (overwriteCheckbox.checked) {
|
||||
outputFile = inputFile;
|
||||
addLog('将覆盖原文件', 'info');
|
||||
} else {
|
||||
// 自动生成输出文件名
|
||||
const ext = inputFile.substring(inputFile.lastIndexOf('.'));
|
||||
const baseName = inputFile.substring(0, inputFile.lastIndexOf('.'));
|
||||
const defaultOutput = `${baseName}_packed${ext}`;
|
||||
|
||||
// 选择输出位置
|
||||
outputFile = await save({
|
||||
filters: [{
|
||||
name: '可执行文件',
|
||||
extensions: ['exe', 'dll']
|
||||
}],
|
||||
defaultPath: defaultOutput
|
||||
});
|
||||
|
||||
if (!outputFile) {
|
||||
addLog('未选择输出位置', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
addLog(`输出文件: ${outputFile}`, 'info');
|
||||
}
|
||||
|
||||
// 执行压缩
|
||||
await processUpx('compress', inputFile, outputFile);
|
||||
|
||||
} catch (error) {
|
||||
addLog(`操作失败: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理脱壳解压
|
||||
async function handleDecompress() {
|
||||
try {
|
||||
// 选择文件(支持多选)
|
||||
const selected = await open({
|
||||
multiple: true,
|
||||
filters: [{
|
||||
name: '可执行文件',
|
||||
extensions: ['exe', 'dll']
|
||||
}]
|
||||
});
|
||||
|
||||
if (!selected || (Array.isArray(selected) && selected.length === 0)) {
|
||||
addLog('未选择文件', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理选择结果(可能是单个路径或路径数组)
|
||||
const files = Array.isArray(selected) ? selected : [selected];
|
||||
|
||||
// 批量处理或单文件处理
|
||||
if (files.length === 1) {
|
||||
addLog(`选择文件: ${files[0]}`, 'info');
|
||||
await handleDecompressWithFile(files[0]);
|
||||
} else {
|
||||
addLog(`选择了 ${files.length} 个文件`, 'info');
|
||||
await processBatchFiles(files, 'decompress');
|
||||
}
|
||||
} catch (error) {
|
||||
addLog(`操作失败: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 使用指定文件处理脱壳解压
|
||||
async function handleDecompressWithFile(inputFile) {
|
||||
try {
|
||||
// 脱壳默认覆盖原文件(直接恢复原状)
|
||||
const outputFile = inputFile;
|
||||
addLog('将覆盖原文件', 'info');
|
||||
|
||||
// 执行解压
|
||||
await processUpx('decompress', inputFile, outputFile);
|
||||
|
||||
} catch (error) {
|
||||
addLog(`操作失败: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 执行 UPX 处理
|
||||
async function processUpx(mode, inputFile, outputFile) {
|
||||
try {
|
||||
const options = {
|
||||
mode: mode,
|
||||
input_file: inputFile,
|
||||
output_file: outputFile,
|
||||
compression_level: getCompressionLevel(),
|
||||
backup: backupCheckbox.checked,
|
||||
ultra_brute: ultraBruteCheckbox.checked
|
||||
};
|
||||
|
||||
if (ultraBruteCheckbox.checked) {
|
||||
addLog('已启用极限压缩模式', 'info');
|
||||
}
|
||||
|
||||
addLog(`开始${mode === 'compress' ? '加壳压缩' : '脱壳解压'}...`, 'info');
|
||||
|
||||
const result = await invoke('process_upx', { options });
|
||||
|
||||
addLog(result, 'success');
|
||||
addLog('操作完成!', 'success', true);
|
||||
|
||||
} catch (error) {
|
||||
addLog(`处理失败: ${error}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 添加日志
|
||||
function addLog(message, type = 'info', highlight = false) {
|
||||
const logLine = document.createElement('div');
|
||||
logLine.className = `log-line log-${type} fade-in${highlight ? ' log-highlight' : ''}`;
|
||||
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
|
||||
logLine.textContent = `[${timestamp}] ${message}`;
|
||||
|
||||
// 清除初始提示
|
||||
if (logOutput.querySelector('.text-gray-500')) {
|
||||
logOutput.innerHTML = '';
|
||||
}
|
||||
|
||||
logOutput.appendChild(logLine);
|
||||
|
||||
// 自动滚动到底部
|
||||
logOutput.scrollTop = logOutput.scrollHeight;
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
Reference in new issue
Block a user