🌱 尝试检测管理员&以管理员模式重启

This commit is contained in:
BTMuli
2025-12-01 23:09:06 +08:00
committed by 目棃
parent 93be279cbb
commit 14c47369e7
8 changed files with 223 additions and 32 deletions

View File

@@ -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 插件

View File

@@ -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<u16> = OsStr::new(exe_path.to_string_lossy().as_ref())
.encode_wide()
.chain(std::iter::once(0))
.collect();
let verb: Vec<u16> = 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())
}
}

View File

@@ -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");

View File

@@ -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<HashMap<u32, u32>> {
data
}
use windows_sys::Win32::Foundation::{GetLastError, ERROR_MORE_DATA};
fn read_u32_le<R: Read>(r: &mut R) -> io::Result<u32> {
let mut buf = [0u8; 4];
match r.read_exact(&mut buf) {

View File

@@ -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<Vec<AchievementEntry>, DecodeError> {
pub fn parse_achi_list(bytes: &[u8]) -> Result<Vec<AchiItemRes>, DecodeError> {
let mut cursor = Cursor::new(bytes);
let mut dicts: Vec<HashMap<u32, u32>> = Vec::new();
@@ -146,12 +146,12 @@ pub fn parse_achi_list(bytes: &[u8]) -> Result<Vec<AchievementEntry>, 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();

19
src/enum/yae.ts Normal file
View File

@@ -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,
};

View File

@@ -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<string>("");
const isSearch = ref<boolean>(false);
@@ -124,6 +131,21 @@ onMounted(async () => {
await handleImportOuter(route.query.app);
}
achiListener = await listen<void>("updateAchi", async () => await refreshOverview());
yaeListener = await listen<TGApp.Plugins.Yae.AchiListRes>(
"yae_achi_list",
(e: Event<TGApp.Plugins.Yae.AchiListRes>) => 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<void> {
}
async function toYae(): Promise<void> {
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<boolean>("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<void> {
console.log(payload);
showSnackbar.success(`已收到来自Yae的成就数据${payload.list.length}`);
}
});
</script>
<style lang="scss" scoped>
.achi-prepend {

51
src/types/Plugins/Yae.d.ts vendored Normal file
View File

@@ -0,0 +1,51 @@
/**
* Yae 插件类型定义
* @since Beta v0.7.8
*/
declare namespace TGApp.Plugins.Yae {
/**
* 后端返的成就列表数据
* @since Beta v0.7.8
*/
type AchiListRes = Array<AchiItemRes>;
/**
* 成就完成状态
* @since Beta v0.7.8
*/
const AchiItemStat = <const>{
/* 无效状态 */
Invalid: 0,
/* 未完成 */
Unfinished: 1,
/* 已完成未领取奖励 */
Finished: 2,
/* 已领取奖励 */
RewardTaken: 3,
};
/**
* 成就完成状态m枚举
* @since Beta v0.7.8
*/
type AchiItemStatEnum = (typeof AchiItemStat)[keyof typeof AchiItemStat];
/**
* 后端返的单个成就数据
* @since Beta v0.7.8
* @see src-tauri/yae/proto.rs AchiItemRes
*/
type AchiItemRes = {
/* 成就 ID */
id: number;
/* 成就总进度 */
total: number;
/* 成就当前进度 */
cur: number;
/* 完成时间戳,单位秒 */
ts: number;
/* 成就完成状态 */
stat: AchiItemStatEnum;
};
}