From 14c47369e76d865990d897163f6960a2b9a7206a Mon Sep 17 00:00:00 2001 From: BTMuli Date: Mon, 1 Dec 2025 23:09:06 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=B1=20=E5=B0=9D=E8=AF=95=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E7=AE=A1=E7=90=86=E5=91=98&=E4=BB=A5=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E6=A8=A1=E5=BC=8F=E9=87=8D=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.toml | 8 ++-- src-tauri/src/commands.rs | 78 +++++++++++++++++++++++++++++++++++ src-tauri/src/lib.rs | 8 +++- src-tauri/src/yae/mod.rs | 8 +--- src-tauri/src/yae/proto.rs | 22 +++++----- src/enum/yae.ts | 19 +++++++++ src/pages/common/PageAchi.vue | 61 +++++++++++++++++++++++---- src/types/Plugins/Yae.d.ts | 51 +++++++++++++++++++++++ 8 files changed, 223 insertions(+), 32 deletions(-) create mode 100644 src/enum/yae.ts create mode 100644 src/types/Plugins/Yae.d.ts diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 81b5c3a2..3df47989 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -36,14 +36,14 @@ walkdir = "2.5.0" [target.'cfg(windows)'.dependencies.windows-sys] version = "0.61.2" features = [ - "Win32_System_Diagnostics_ToolHelp", - "Win32_System_WindowsProgramming", - "Win32_System_Pipes", - "Win32_System_Memory", "Win32_System_Diagnostics", "Win32_System_Diagnostics_Debug", + "Win32_System_Diagnostics_ToolHelp", "Win32_System_LibraryLoader", + "Win32_System_Memory", + "Win32_System_Pipes", "Win32_System_Threading", + "Win32_System_WindowsProgramming", ] # deep link 插件 diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 47f6fd6a..27d0e726 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -70,3 +70,81 @@ pub async fn get_dir_size(path: String) -> u64 { } size } + +// 判断是否是管理员权限 +#[cfg(target_os = "windows")] +#[tauri::command] +pub fn is_in_admin() -> bool { + use windows_sys::Win32::Foundation::HANDLE; + use windows_sys::Win32::Security::{ + AllocateAndInitializeSid, CheckTokenMembership, FreeSid, SID_IDENTIFIER_AUTHORITY, TOKEN_QUERY, + }; + use windows_sys::Win32::System::SystemServices::{ + DOMAIN_ALIAS_RID_ADMINS, SECURITY_BUILTIN_DOMAIN_RID, + }; + use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; + + unsafe { + let mut token_handle: HANDLE = std::ptr::null_mut(); + if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle) != 0 { + let nt_authority = SID_IDENTIFIER_AUTHORITY { Value: [0, 0, 0, 0, 0, 5] }; + let mut admin_group = std::ptr::null_mut(); + if AllocateAndInitializeSid( + &nt_authority, + 2, + SECURITY_BUILTIN_DOMAIN_RID.try_into().unwrap(), + DOMAIN_ALIAS_RID_ADMINS.try_into().unwrap(), + 0, + 0, + 0, + 0, + 0, + 0, + &mut admin_group, + ) != 0 + { + let mut is_admin = 0i32; + CheckTokenMembership(std::ptr::null_mut(), admin_group, &mut is_admin); + FreeSid(admin_group); + return is_admin != 0; + } + } + false + } +} + +// 在 Windows 上以管理员权限重启应用 +#[cfg(target_os = "windows")] +#[tauri::command] +pub fn run_with_admin() -> Result<(), String> { + use std::ffi::OsStr; + use std::iter::once; + use std::os::windows::ffi::OsStrExt; + use std::process::exit; + use windows_sys::Win32::UI::Shell::ShellExecuteW; + use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL; + + let exe_path = std::env::current_exe().map_err(|e| e.to_string())?; + let exe_str: Vec = OsStr::new(exe_path.to_string_lossy().as_ref()) + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + let verb: Vec = OsStr::new("runas").encode_wide().chain(once(0)).collect(); + + let result = unsafe { + ShellExecuteW( + std::ptr::null_mut(), + verb.as_ptr(), + exe_str.as_ptr(), + std::ptr::null(), + std::ptr::null(), + SW_SHOWNORMAL, + ) + }; + + if result as usize > 32 { + exit(0); + } else { + Err("Failed to restart as administrator.".into()) + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ca1a41a7..a64c2913 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -8,7 +8,9 @@ mod utils; mod yae; use crate::client::create_mhy_client; -use crate::commands::{create_window, execute_js, get_dir_size, init_app}; +use crate::commands::{ + create_window, execute_js, get_dir_size, init_app, is_in_admin, run_with_admin, +}; use crate::plugins::{build_log_plugin, build_si_plugin}; use crate::yae::call_yae_dll; use tauri::{generate_context, generate_handler, Builder, Manager, Window, WindowEvent}; @@ -63,7 +65,9 @@ pub fn run() { execute_js, get_dir_size, create_mhy_client, - call_yae_dll + call_yae_dll, + is_in_admin, + run_with_admin ]) .run(generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/yae/mod.rs b/src-tauri/src/yae/mod.rs index 336cc964..a7397552 100644 --- a/src-tauri/src/yae/mod.rs +++ b/src-tauri/src/yae/mod.rs @@ -6,17 +6,15 @@ pub mod proto; use inject::{call_yaemain, create_named_pipe, find_module_base, inject_dll, spawn_process}; use prost::encoding::{decode_key, WireType}; -use prost::Message; -use proto::{parse_achi_list, AchievementInfo}; +use proto::parse_achi_list; use serde_json::Value; use std::collections::HashMap; use std::fs::File; use std::io; use std::io::{Read, Write}; -use std::os::windows::io::{FromRawHandle, OwnedHandle, RawHandle}; +use std::os::windows::io::{FromRawHandle, RawHandle}; use std::sync::Arc; use tauri::{AppHandle, Emitter, Manager}; -use windows_sys::Win32::Storage::FileSystem::ReadFile; use windows_sys::Win32::System::Pipes::ConnectNamedPipe; // 读取配置值 @@ -79,8 +77,6 @@ pub fn parse_achievement_data(bytes: &[u8]) -> Vec> { data } -use windows_sys::Win32::Foundation::{GetLastError, ERROR_MORE_DATA}; - fn read_u32_le(r: &mut R) -> io::Result { let mut buf = [0u8; 4]; match r.read_exact(&mut buf) { diff --git a/src-tauri/src/yae/proto.rs b/src-tauri/src/yae/proto.rs index 881ef303..245ab77d 100644 --- a/src-tauri/src/yae/proto.rs +++ b/src-tauri/src/yae/proto.rs @@ -106,15 +106,15 @@ pub struct AchievementInfo { } #[derive(Debug, Default, Serialize)] -pub struct AchievementEntry { +pub struct AchiItemRes { pub id: u32, - pub total_progress: u32, - pub current_progress: u32, - pub finish_timestamp: u32, - pub status: u32, // 数值类型 + pub total: u32, + pub cur: u32, + pub ts: u32, + pub stat: u32, } -pub fn parse_achi_list(bytes: &[u8]) -> Result, DecodeError> { +pub fn parse_achi_list(bytes: &[u8]) -> Result, DecodeError> { let mut cursor = Cursor::new(bytes); let mut dicts: Vec> = Vec::new(); @@ -146,12 +146,12 @@ pub fn parse_achi_list(bytes: &[u8]) -> Result, DecodeErro let achievements = dicts .into_iter() - .map(|d| AchievementEntry { + .map(|d| AchiItemRes { id: d.get(&15).copied().unwrap_or(0), - status: d.get(&11).copied().unwrap_or(0), - total_progress: d.get(&8).copied().unwrap_or(0), - current_progress: d.get(&13).copied().unwrap_or(0), - finish_timestamp: d.get(&7).copied().unwrap_or(0), + stat: d.get(&11).copied().unwrap_or(0), + total: d.get(&8).copied().unwrap_or(0), + cur: d.get(&13).copied().unwrap_or(0), + ts: d.get(&7).copied().unwrap_or(0), }) .collect(); diff --git a/src/enum/yae.ts b/src/enum/yae.ts new file mode 100644 index 00000000..cd04de8c --- /dev/null +++ b/src/enum/yae.ts @@ -0,0 +1,19 @@ +/** + * Yae 插件相关枚举类型 + * @since Beta v0.7.8 + */ + +/** + * 成就完成状态枚举 + * @since Beta v0.7.8 + */ +export const YaeAchiStatEnum: typeof TGApp.Plugins.Yae.AchiItemStat = { + /** 无效状态 */ + Invalid: 0, + /** 未完成 */ + Unfinished: 1, + /** 已完成未领取奖励 */ + Finished: 2, + /** 已领取奖励 */ + RewardTaken: 3, +}; diff --git a/src/pages/common/PageAchi.vue b/src/pages/common/PageAchi.vue index 0285eed8..c677c4b1 100644 --- a/src/pages/common/PageAchi.vue +++ b/src/pages/common/PageAchi.vue @@ -76,11 +76,13 @@ import showSnackbar from "@comp/func/snackbar.js"; import TuaAchiList from "@comp/userAchi/tua-achi-list.vue"; import TuaSeries from "@comp/userAchi/tua-series.vue"; import TSUserAchi from "@Sqlm/userAchi.js"; +import useAppStore from "@store/app.js"; import { path } from "@tauri-apps/api"; +import { invoke } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { open, save } from "@tauri-apps/plugin-dialog"; -import { writeTextFile } from "@tauri-apps/plugin-fs"; -import { openUrl } from "@tauri-apps/plugin-opener"; +import { exists, writeTextFile } from "@tauri-apps/plugin-fs"; +import { platform } from "@tauri-apps/plugin-os"; import TGLogger from "@utils/TGLogger.js"; import { getUiafHeader, @@ -88,15 +90,20 @@ import { verifyUiafData, verifyUiafDataClipboard, } from "@utils/UIAF.js"; +import { storeToRefs } from "pinia"; import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; import { AppAchievementSeriesData } from "@/data/index.js"; const seriesList = AppAchievementSeriesData.sort((a, b) => a.order - b.order).map((s) => s.id); + const route = useRoute(); const router = useRouter(); +const { gameDir } = storeToRefs(useAppStore()); + let achiListener: UnlistenFn | null = null; +let yaeListener: UnlistenFn | null = null; const search = ref(""); const isSearch = ref(false); @@ -124,6 +131,21 @@ onMounted(async () => { await handleImportOuter(route.query.app); } achiListener = await listen("updateAchi", async () => await refreshOverview()); + yaeListener = await listen( + "yae_achi_list", + (e: Event) => tryParseYaeAchi(e.payload), + ); +}); + +onUnmounted(async () => { + if (achiListener !== null) { + achiListener(); + achiListener = null; + } + if (yaeListener !== null) { + yaeListener(); + yaeListener = null; + } }); watch(() => uidCur.value, refreshOverview); @@ -278,15 +300,36 @@ async function deleteUid(): Promise { } async function toYae(): Promise { - await openUrl("https://github.com/HolographicHat/Yae"); + if (platform() !== "windows") { + showSnackbar.warn("MacOS暂不支持该功能"); + return; + } + if (gameDir.value === "未设置") { + showSnackbar.warn("请前往设置页面设置游戏安装目录"); + return; + } + const gamePath = `${gameDir.value}${path.sep()}YuanShen.exe`; + if (!(await exists(gamePath))) { + showSnackbar.warn("未检测到原神本体应用!"); + return; + } + // 判断是否是管理员权限 + const isAdmin = await invoke("is_in_admin"); + if (!isAdmin) { + const check = await showDialog.check("是否以管理员模式重启?", "该功能需要管理员权限才能使用"); + if (!check) { + showSnackbar.cancel("已取消以管理员模式重启"); + return; + } + await invoke("run_with_admin"); + } + await invoke("call_yae_dll", { gamePath: gamePath }); } -onUnmounted(async () => { - if (achiListener !== null) { - achiListener(); - achiListener = null; - } -}); +async function tryParseYaeAchi(payload: TGApp.Plugins.Yae.AchiListRes): Promise { + console.log(payload); + showSnackbar.success(`已收到来自Yae的成就数据,共${payload.list.length}条`); +}