feat(installer): 实现游戏安装与快捷方式创建功能- 添加禁用右键菜单和开发者工具的保护机制
- 引入GAME_CONFIG配置文件统一管理游戏信息 - 安装页面支持记录安装路径到localStorage - 成功页面增加创建桌面快捷方式选项 - 实现调用系统资源管理器打开游戏目录功能 - 集成Windows COM组件创建快捷方式的能力 - 更新UI界面展示多语言标题和版本信息 - 升级daisyui依赖至5.5.2版本- 调整窗口标题和产品名称为"游戏安装工具 - NoPJ"
This commit is contained in:
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@@ -104,6 +104,7 @@ dependencies = [
|
||||
"tauri-plugin-shell",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"windows",
|
||||
"zip",
|
||||
]
|
||||
|
||||
|
||||
@@ -32,3 +32,4 @@ async-runtime = "0.0.0"
|
||||
tokio = { version = "1.48.0", features = ["time"] }
|
||||
tauri-plugin-shell = "2.3.3"
|
||||
tauri-plugin-opener = "2.5.2"
|
||||
windows = "0.61.3"
|
||||
|
||||
@@ -4,7 +4,8 @@ use thiserror::Error;
|
||||
use zip::ZipArchive;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use serde::Serialize;
|
||||
use tauri::Emitter;
|
||||
|
||||
@@ -145,3 +146,20 @@ pub async fn install_game(window: tauri::Window, target_dir: String) -> Result<S
|
||||
Ok(format!("成功解压 {} 个文件到指定目录!", total_files as u32))
|
||||
}
|
||||
|
||||
|
||||
|
||||
//调用资源管理器打开游戏目录
|
||||
#[tauri::command]
|
||||
pub async fn open_game_dir(install_path:String) -> Result<String,String> {
|
||||
let game_path = Path::new(&install_path);
|
||||
if !game_path.exists() {
|
||||
return Err("游戏目录不存在".to_string());
|
||||
}
|
||||
let result = Command::new("explorer")
|
||||
.arg(game_path)
|
||||
.spawn()
|
||||
.map_err(|e| format!("打开游戏目录失败: {}", e))?;
|
||||
|
||||
println!("资源管理器进程ID: {}", result.id());
|
||||
Ok("已打开游戏目录".to_string())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod network;
|
||||
mod gameins;
|
||||
mod shortcut;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
@@ -19,7 +20,9 @@ pub fn run() {
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
network::get_network_interfaces,
|
||||
gameins::install_game
|
||||
gameins::install_game,
|
||||
gameins::open_game_dir,
|
||||
shortcut::create_shortcut,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
132
src-tauri/src/shortcut.rs
Normal file
132
src-tauri/src/shortcut.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use std::path::Path;
|
||||
use windows::{
|
||||
core::{Interface, PCWSTR, HRESULT},
|
||||
Win32::{
|
||||
System::{
|
||||
Com::{
|
||||
CoCreateInstance, CoInitializeEx, CoUninitialize,
|
||||
IPersistFile, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED
|
||||
},
|
||||
},
|
||||
UI::Shell::{IShellLinkW, ShellLink},
|
||||
},
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_shortcut(
|
||||
install_path: String,
|
||||
shortcut_name: String,
|
||||
exe_name: String
|
||||
) -> Result<String, String> {
|
||||
unsafe {
|
||||
// 初始化COM库
|
||||
let com_result = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
|
||||
let needs_uninit = com_result.is_ok() || com_result == HRESULT(0x80010106u32 as i32); // S_FALSE or RPC_E_CHANGED_MODE
|
||||
|
||||
let result = create_shortcut_internal(&install_path, &shortcut_name, &exe_name);
|
||||
|
||||
if needs_uninit {
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_shortcut_internal(
|
||||
install_path: &str,
|
||||
shortcut_name: &str,
|
||||
exe_name: &str
|
||||
) -> Result<String, String> {
|
||||
// 获取当前用户桌面路径
|
||||
let desktop_path = get_desktop_path()?;
|
||||
let shortcut_file = format!("{}.lnk", shortcut_name);
|
||||
let shortcut_full_path = Path::new(&desktop_path).join(&shortcut_file);
|
||||
|
||||
// 构建目标程序路径
|
||||
let target_path = Path::new(install_path).join(exe_name);
|
||||
if !target_path.exists() {
|
||||
return Err(format!("目标程序不存在: {}", target_path.display()));
|
||||
}
|
||||
|
||||
// 创建IShellLink对象
|
||||
let shell_link: IShellLinkW = CoCreateInstance(
|
||||
&ShellLink,
|
||||
None,
|
||||
CLSCTX_INPROC_SERVER
|
||||
).map_err(|e| format!("创建ShellLink失败: {:?}", e))?;
|
||||
|
||||
// 设置快捷方式属性
|
||||
set_shell_link_properties(&shell_link, &target_path, install_path)?;
|
||||
|
||||
// 保存快捷方式
|
||||
save_shortcut(&shell_link, &shortcut_full_path)?;
|
||||
|
||||
Ok(format!("快捷方式创建成功: {}", shortcut_full_path.display()))
|
||||
}
|
||||
|
||||
unsafe fn get_desktop_path() -> Result<String, String> {
|
||||
std::env::var("USERPROFILE")
|
||||
.map(|profile| format!("{}\\Desktop", profile))
|
||||
.or_else(|_| {
|
||||
std::env::var("USERNAME")
|
||||
.map(|username| format!("C:\\Users\\{}\\Desktop", username))
|
||||
})
|
||||
.map_err(|_| "无法获取用户桌面路径".to_string())
|
||||
}
|
||||
|
||||
unsafe fn set_shell_link_properties(
|
||||
shell_link: &IShellLinkW,
|
||||
target_path: &Path,
|
||||
working_dir: &str
|
||||
) -> Result<(), String> {
|
||||
// 设置目标路径
|
||||
let target_path_str = target_path.to_string_lossy();
|
||||
let wide_target: Vec<u16> = to_wide_string(&target_path_str);
|
||||
|
||||
// 用 map_err + ? 处理错误并转换为 String
|
||||
shell_link
|
||||
.SetPath(PCWSTR(wide_target.as_ptr()))
|
||||
.map_err(|e| format!("设置目标路径失败: {:?}", e))?;
|
||||
|
||||
// 设置工作目录
|
||||
let wide_working_dir: Vec<u16> = to_wide_string(working_dir);
|
||||
shell_link
|
||||
.SetWorkingDirectory(PCWSTR(wide_working_dir.as_ptr()))
|
||||
.map_err(|e| format!("设置工作目录失败: {:?}", e))?;
|
||||
|
||||
// 设置描述
|
||||
let description = "NoPJ创建的快捷方式 - nopj.cn";
|
||||
let wide_desc: Vec<u16> = to_wide_string(description);
|
||||
shell_link
|
||||
.SetDescription(PCWSTR(wide_desc.as_ptr()))
|
||||
.map_err(|e| format!("设置描述失败: {:?}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
unsafe fn save_shortcut(
|
||||
shell_link: &IShellLinkW,
|
||||
shortcut_path: &Path
|
||||
) -> Result<(), String> {
|
||||
// 转换为 IPersistFile 接口
|
||||
let persist_file: IPersistFile = shell_link
|
||||
.cast()
|
||||
.map_err(|e| format!("接口转换失败: {:?}", e))?;
|
||||
|
||||
// 保存文件
|
||||
let path_str = shortcut_path.to_string_lossy();
|
||||
let wide_path: Vec<u16> = to_wide_string(&path_str);
|
||||
|
||||
// Save 可能返回 windows::core::Result<()>,同样用 map_err ? 处理
|
||||
persist_file
|
||||
.Save(PCWSTR(wide_path.as_ptr()), true)
|
||||
.map_err(|e| format!("保存快捷方式失败: {:?}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_wide_string(s: &str) -> Vec<u16> {
|
||||
s.encode_utf16().chain(std::iter::once(0)).collect()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "NoPJ安装器",
|
||||
"productName": "游戏安装工具 - NoPJ",
|
||||
"version": "0.1.0",
|
||||
"identifier": "cn.nopj.gametools",
|
||||
"build": {
|
||||
@@ -12,13 +12,13 @@
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Game Install",
|
||||
"title": "游戏安装工具 - NoPJ",
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"resizable": false,
|
||||
"fullscreen": false,
|
||||
"decorations": false
|
||||
|
||||
"decorations": false,
|
||||
"devtools": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
|
||||
Reference in New Issue
Block a user