diff --git a/package-lock.json b/package-lock.json index a71b137..b6f2010 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@tauri-apps/plugin-opener": "^2.5.2", "@tauri-apps/plugin-shell": "^2.3.3", "@tauri-apps/plugin-store": "^2.4.1", - "daisyui": "^5.4.7" + "daisyui": "^5.5.2" }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.10", @@ -1204,7 +1204,6 @@ "integrity": "sha512-TGFX1pZUt9qqY20Cv5NyYvy0iLWHf2jXi8s+eCGsig7jQMdwZWKUFMR6TbvFNhfDSUpc1sH/Y5EHv20g3HHA3g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -1244,7 +1243,6 @@ "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", @@ -1842,7 +1840,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1935,9 +1932,9 @@ } }, "node_modules/daisyui": { - "version": "5.4.7", - "resolved": "https://registry.npmmirror.com/daisyui/-/daisyui-5.4.7.tgz", - "integrity": "sha512-2wYO61vTPCXk7xEBgnzLZAYoE0xS5IRLu/GSq0vORpB+cTrtubdx69NnA0loc0exvCY1s2fYL4lGZtFHe2ohNQ==", + "version": "5.5.2", + "resolved": "https://registry.npmmirror.com/daisyui/-/daisyui-5.5.2.tgz", + "integrity": "sha512-uhEz8X1urrEhHhK51/vIQAL1fWPEJP8mTqCTEhfHfC8Dgur9XAz9SOWwl41LZgNs/fjWGvWzFcGM5WCw5ovp8g==", "license": "MIT", "funding": { "url": "https://github.com/saadeghi/daisyui?sponsor=1" @@ -2572,7 +2569,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2615,7 +2611,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -2632,7 +2627,6 @@ "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" @@ -2824,7 +2818,6 @@ "integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", @@ -3222,7 +3215,6 @@ "integrity": "sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3365,7 +3357,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3387,7 +3378,6 @@ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/package.json b/package.json index 81dd54a..380828f 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,6 @@ "@tauri-apps/plugin-opener": "^2.5.2", "@tauri-apps/plugin-shell": "^2.3.3", "@tauri-apps/plugin-store": "^2.4.1", - "daisyui": "^5.4.7" + "daisyui": "^5.5.2" } } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 10fe022..a529cc3 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -104,6 +104,7 @@ dependencies = [ "tauri-plugin-shell", "thiserror 2.0.17", "tokio", + "windows", "zip", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1e1c974..dc8f0f5 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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" diff --git a/src-tauri/src/gameins.rs b/src-tauri/src/gameins.rs index 923a91c..208b654 100644 --- a/src-tauri/src/gameins.rs +++ b/src-tauri/src/gameins.rs @@ -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 Result { + 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()) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2d0adc8..307bed2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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"); diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs new file mode 100644 index 0000000..b4ccaf7 --- /dev/null +++ b/src-tauri/src/shortcut.rs @@ -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 { + 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 { + // 获取当前用户桌面路径 + 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 { + 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 = 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 = 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 = 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 = 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 { + s.encode_utf16().chain(std::iter::once(0)).collect() +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9da7149..94ce59f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -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": { diff --git a/src/app.html b/src/app.html index 8d3deba..9c69cc9 100644 --- a/src/app.html +++ b/src/app.html @@ -4,6 +4,7 @@ %sveltekit.head% +
%sveltekit.body%
diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..466ddca --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,10 @@ +export const GAME_CONFIG = { + title: '逃离鸭科夫', + title_rus: 'Побег из Дакова', + title_en: 'Escape From Duckov', + version: '1.1.6', + description: "「逃离鸭科夫」是一款鸭子题材PVE俯视角撤离射击游戏。你要在鸭科夫的世界里搜寻物资,建造藏身处,升级装备,从一无所有到做大做强;面对虎视眈眈的敌鸭,想方设法生存下去——或者逃离。", + pathname: '\\Duckov', + launch: 'Duckov.exe', + shortcut_name: "逃离鸭科夫", +}; \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 03fc52d..919912e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -12,6 +12,20 @@ + +
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a8a8af5..203b16d 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,61 +1,40 @@ - - - - - +
-
-
-

{isHover = true}} - on:mouseout={()=>{isHover = false}} - on:blur={handleBlur} - on:focus={handleFocus} - >逃离鸭科夫

-

{isHover = true}} - on:mouseout={()=>{isHover = false}} - on:blur={handleBlur} - on:focus={handleFocus} - > - 《逃离鸭科夫》是一款鸭子题材PVE俯视角撤离射击游戏。你要在鸭科夫的世界里搜寻物资,建造藏身处,升级装备,从一无所有到做大做强;面对虎视眈眈的敌鸭,想方设法生存下去——或者逃离。 +

+
+

+ + + {GAME_CONFIG.title} + {GAME_CONFIG.title_en} + {GAME_CONFIG.title_rus} + +

- - +

versin {GAME_CONFIG.version}

+

+ {GAME_CONFIG.description} +

+ +
+ +
+
+
\ No newline at end of file diff --git a/src/routes/install/+page.svelte b/src/routes/install/+page.svelte index f4340ad..ed230ff 100644 --- a/src/routes/install/+page.svelte +++ b/src/routes/install/+page.svelte @@ -5,6 +5,7 @@ import { onMount } from 'svelte'; import { listen } from '@tauri-apps/api/event'; import { goto } from '$app/navigation'; + import { GAME_CONFIG } from '$lib/config'; let installPath = ''; let isInstalling = false; @@ -30,10 +31,6 @@ progressPercent = event.payload.percentage; }); - // 组件销毁时停止监听 - return () => { - unlisten(); - }; }); @@ -60,7 +57,7 @@ installPath = ''; return; } - installPath = selected + '\\DK'; // 您的逻辑 + installPath = selected +GAME_CONFIG.pathname; // 您的逻辑 log('已选择安装目录:'+ installPath +",点击开始安装即可开始自动安装"); }else if (selected === null) { log("未选择安装目录"); @@ -82,7 +79,7 @@ progressPercent = 0; // 重置进度条 log("安装程序启动..."); - + localStorage.setItem('installPath', installPath) try { // 我们仍然调用 invoke,但现在它只负责启动 // 真正的日志将通过 'install_progress' 事件传来 diff --git a/src/routes/success/+page.svelte b/src/routes/success/+page.svelte index 55f06d6..bf3a837 100644 --- a/src/routes/success/+page.svelte +++ b/src/routes/success/+page.svelte @@ -2,10 +2,39 @@ import tl from '$lib/assets/images/tl.png' import { onMount } from 'svelte'; import { openUrl } from '@tauri-apps/plugin-opener'; + import { getCurrentWindow } from '@tauri-apps/api/window'; + import { invoke } from '@tauri-apps/api/core'; + import { GAME_CONFIG } from '$lib/config'; - let data:[{attributes:{title:string, slug:string, id:string}}] = []; + + const win = getCurrentWindow(); + + + const closeWindow = async () => { + if (openGameDir) { + try { + const result = await invoke('open_game_dir', { + installPath: installPath + }); + console.log(result); + } catch (error) { + console.error('启动游戏失败:', error); + } + } + await create_shortcut() + await win.close() + + }; + + let data:[{attributes:{title:string, slug:string, id:string}}]; let loading = true; let error; + //是否创建快捷方式变量 + let createShortcut = true; + //是否退出后启动变量 + let openGameDir = true; + + let installPath = localStorage.getItem('installPath'); const url = "https://nopj.cn/api/discussions?include=tags.parent&filter[tag]=resources&sort=-createdAt&page[offset]=0&page[limit]=5" @@ -44,11 +73,68 @@ } } + const create_shortcut = async () => { + if (createShortcut){ + + try{ + const result = await invoke('create_shortcut', { + installPath: localStorage.getItem('installPath'), + shortcutName: GAME_CONFIG.shortcut_name, + exeName: GAME_CONFIG.launch, + }) + console.log(result); + }catch (e){ + console.error("创建快捷方式失败:", e); + alert(e) + } + + } + } + -
-
- 123 +
+
+
+
+ + + + + + + + + + + + + + + + + +
+ 安装完成 +
+
+
+ 启动设置 + + +
+
+ +
+
@@ -56,12 +142,19 @@

NoPJ

-

最新资源

+

+ + + 最新资源 + Latest Resources + Последний ресурс + + +

+
+
\ No newline at end of file