完成成就导入

This commit is contained in:
BTMuli
2025-12-01 23:52:39 +08:00
committed by 目棃
parent 14c47369e7
commit 38f3301664
8 changed files with 133 additions and 115 deletions

View File

@@ -1,6 +1,5 @@
//! @file src/commands.rs
//! @desc 命令模块,负责处理命令
//! @since Beta v0.7.4
//! 命令模块,负责处理命令
//! @since Beta v0.7.8
use tauri::{AppHandle, Emitter, Manager, WebviewWindowBuilder};
use tauri_utils::config::{WebviewUrl, WindowConfig};
@@ -72,9 +71,13 @@ pub async fn get_dir_size(path: String) -> u64 {
}
// 判断是否是管理员权限
#[cfg(target_os = "windows")]
#[tauri::command]
pub fn is_in_admin() -> bool {
#[cfg(not(target_os = "windows"))]
{
Err("This function is only supported on Windows.".into())
}
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Security::{
AllocateAndInitializeSid, CheckTokenMembership, FreeSid, SID_IDENTIFIER_AUTHORITY, TOKEN_QUERY,
@@ -113,10 +116,14 @@ pub fn is_in_admin() -> bool {
}
}
// 在 Windows 上以管理员权限重启应用
#[cfg(target_os = "windows")]
// 以管理员权限重启应用
#[tauri::command]
pub fn run_with_admin() -> Result<(), String> {
#[cfg(not(target_os = "windows"))]
{
Err("This function is only supported on Windows.".into())
}
use std::ffi::OsStr;
use std::iter::once;
use std::os::windows::ffi::OsStrExt;

View File

@@ -104,6 +104,10 @@ fn read_exact_vec<R: Read>(r: &mut R, len: usize) -> io::Result<Vec<u8>> {
/// 调用 dll
#[tauri::command]
pub fn call_yae_dll(app_handle: AppHandle, game_path: String) -> () {
#[cfg(not(target_os = "windows"))]
{
Err("This function is only supported on Windows.".into())
}
let dll_path = app_handle.path().resource_dir().unwrap().join("resources/YaeAchievementLib.dll");
dbg!(&dll_path);
// 0. 创建 YaeAchievementPipe 的 命名管道,获取句柄

View File

@@ -1,13 +1,12 @@
//! Yae 成就信息的 Protobuf 定义
//! @since Beta v0.9.0
//! @since Beta v0.8.7
use prost::encoding::{decode_key, WireType};
use prost::DecodeError;
use prost::Message;
use serde::Serialize;
use std::collections::HashMap;
use std::io::Cursor;
use std::io::Read;
use std::io::{Cursor, Read};
#[derive(Clone, PartialEq, Message, Serialize)]
pub struct AchievementProtoFieldInfo {
@@ -106,15 +105,14 @@ pub struct AchievementInfo {
}
#[derive(Debug, Default, Serialize)]
pub struct AchiItemRes {
pub struct UiafAchiItem {
pub id: u32,
pub total: u32,
pub cur: u32,
pub ts: u32,
pub stat: u32,
pub current: u32,
pub timestamp: u32,
pub status: u32,
}
pub fn parse_achi_list(bytes: &[u8]) -> Result<Vec<AchiItemRes>, DecodeError> {
pub fn parse_achi_list(bytes: &[u8]) -> Result<Vec<UiafAchiItem>, DecodeError> {
let mut cursor = Cursor::new(bytes);
let mut dicts: Vec<HashMap<u32, u32>> = Vec::new();
@@ -146,13 +144,13 @@ pub fn parse_achi_list(bytes: &[u8]) -> Result<Vec<AchiItemRes>, DecodeError> {
let achievements = dicts
.into_iter()
.map(|d| AchiItemRes {
.map(|d| UiafAchiItem {
id: d.get(&15).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),
status: d.get(&11).copied().unwrap_or(0),
current: d.get(&13).copied().unwrap_or(0),
timestamp: d.get(&7).copied().unwrap_or(0),
})
.filter(|a| a.timestamp != 0)
.collect();
Ok(achievements)

View File

@@ -7,7 +7,7 @@
*
* @since Beta v0.7.8
*/
export const YaeAchiStatEnum: typeof TGApp.Plugins.Yae.AchiItemStat = {
export const UiafAchiStatEnum: typeof TGApp.Plugins.UIAF.AchiItemStat = {
/** 无效状态 */
Invalid: 0,
/** 未完成 */

View File

@@ -79,7 +79,7 @@ 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 { listen, type UnlistenFn, type Event } from "@tauri-apps/api/event";
import { open, save } from "@tauri-apps/plugin-dialog";
import { exists, writeTextFile } from "@tauri-apps/plugin-fs";
import { platform } from "@tauri-apps/plugin-os";
@@ -133,7 +133,14 @@ onMounted(async () => {
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),
async (e: Event<string>) => {
try {
await tryParseYaeAchi(JSON.parse(e.payload));
} catch (err) {
await TGLogger.Error(`[Achievements][yae_achi_list] 解析Yae成就数据失败: ${err}`);
showSnackbar.error(`解析Yae成就数据失败:${err}`);
}
},
);
});
@@ -327,8 +334,28 @@ async function toYae(): Promise<void> {
}
async function tryParseYaeAchi(payload: TGApp.Plugins.Yae.AchiListRes): Promise<void> {
console.log(payload);
showSnackbar.success(`已收到来自Yae的成就数据${payload.list.length}`);
console.log(typeof payload, payload);
if (payload.length === 0) {
showSnackbar.warn(`未从Yae获取到成就数据`);
return;
}
const input = await showDialog.input("请输入存档UID", "UID:", uidCur.value.toString());
if (!input) {
showSnackbar.cancel("已取消存档导入");
return;
}
if (input === "" || isNaN(Number(input))) {
showSnackbar.warn("请输入合法数字");
return;
}
await showLoading.start("正在导入成就数据", `UID:${input},数量:${payload.length}`);
await TSUserAchi.mergeUiaf(payload, input);
await showLoading.end();
showSnackbar.success("导入成功,即将刷新页面");
await TGLogger.Info("[Achievements][handleImportOuter] 导入成功");
await new Promise<void>((resolve) => setTimeout(resolve, 1500));
await router.push("/achievements");
window.location.reload();
}
</script>
<style lang="scss" scoped>

View File

@@ -1,9 +1,9 @@
/**
* @file plugins/Sqlite/modules/userAchi.ts
* @description 用户成就模块
* @since Beta v0.6.0
* 用户成就模块
* @since Beta v0.8.7
*/
import { UiafAchiStatEnum } from "@enum/uiaf.js";
import { path } from "@tauri-apps/api";
import { exists, mkdir, readDir, readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
import TGLogger from "@utils/TGLogger.js";
@@ -155,11 +155,16 @@ async function getAchievements(
}
/**
* @description 查找成就数据
* @since Beta v0.6.0
* 查找成就数据
* @since Beta v0.8.7
* @remarks
* 支持三种搜索方式:
* 1. 版本搜索:输入 vx.x 格式的关键词(如 v1.2),搜索对应版本的成就
* 2. ID搜索输入 ixxx 格式的关键词(如 i1001搜索对应ID的成就
* 3. 名称/描述搜索:输入任意关键词,搜索成就名称或描述中包含该关键词的成就
* @param {number} uid - 存档 UID
* @param {string} keyword - 关键词
* @returns {Promise<TGApp.Sqlite.Achievement.RenderAchi[]>} 成就数据
* @returns {Promise<Array<TGApp.Sqlite.Achievement.RenderAchi>>} 成就数据
*/
async function searchAchi(
uid: number,
@@ -167,11 +172,15 @@ async function searchAchi(
): Promise<TGApp.Sqlite.Achievement.RenderAchi[]> {
if (keyword === "") return await getAchievements(uid);
const versionReg = /^v\d+(\.\d+)?$/;
let rawData: TGApp.App.Achievement.Item[];
const res: TGApp.Sqlite.Achievement.RenderAchi[] = [];
const idReg = /^i\d+$/;
let rawData: Array<TGApp.App.Achievement.Item>;
const res: Array<TGApp.Sqlite.Achievement.RenderAchi> = [];
if (versionReg.test(keyword)) {
const version = keyword.replace("v", "");
rawData = AppAchievementsData.filter((i) => i.version.includes(version));
} else if (idReg.test(keyword)) {
const id = parseInt(keyword.replace("i", ""));
rawData = AppAchievementsData.filter((a) => a.id === id);
} else {
rawData = AppAchievementsData.filter((a) => {
if (a.name.includes(keyword)) return true;
@@ -312,16 +321,19 @@ async function restoreUiaf(dir: string): Promise<boolean> {
}
/**
* @description 导入Uiaf数据
* @since Beta v0.6.0
* @param {TGApp.Plugins.UIAF.Achievement[]} data - 成就数据
* 导入Uiaf数据
* @since Beta v0.7.8
* @param {Array<TGApp.Plugins.UIAF.Achievement>} data - 成就数据
* @param {number} uid - 存档UID
* @returns {Promise<void>}
*/
async function mergeUiaf(data: TGApp.Plugins.UIAF.Achievement[], uid: number): Promise<void> {
async function mergeUiaf(data: Array<TGApp.Plugins.UIAF.Achievement>, uid: number): Promise<void> {
const db = await TGSqlite.getDB();
for (const achi of data) {
const status = achi.status === 2 || achi.status === 3 ? 1 : 0;
const status =
achi.status === UiafAchiStatEnum.Finished || achi.status === UiafAchiStatEnum.RewardTaken
? 1
: 0;
const timeC = status === 1 ? timestampToDate(achi.timestamp * 1000) : "";
const timeN = timestampToDate(new Date().getTime());
await db.execute(

View File

@@ -1,60 +1,69 @@
/**
* @file types/Plugins/UIAF.d.ts
* @description UIAF 插件类型定义文件
* @since Beta v0.6.0
* UIAF 插件类型定义文件
* @since Beta v0.7.8
*/
/**
* @description UIAF 插件类型命名空间
* @namespace TGApp.Plugins.UIAF
* @merberof TGApp.Plugins
* @since Beta v0.6.0
*/
declare namespace TGApp.Plugins.UIAF {
/**
* @interface Data
* UIAF完整数据
* @since Alpha v0.1.5
* @description UIAF 成就数据
* @property {Export} info UIAF 头部信息
* @property {Achievement[]} list UIAF 成就列表
* @return Data
*/
interface Data {
type Data = {
/* UIAF 头部信息 */
info: Export;
list: Achievement[];
}
/* UIAF 成就列表 */
list: Array<Achievement>;
};
/**
* @interface Export
* UIAF 头部信息
* @since Alpha v0.1.5
* @description UIAF 头部信息
* @property {string} export_app 导出的应用名称
* @property {number} export_timestamp 导出时间戳,正确时间戳得乘以 1000
* @property {string} export_app_version 导出的应用版本
* @property {string} uiaf_version UIAF 版本
* @return Export
*/
interface Export {
type Export = {
/* 导出的应用名称 */
export_app: string;
/* 导出时间戳,秒级 */
export_timestamp: number;
/* 导出的应用版本 */
export_app_version: string;
/* UIAF 版本 */
uiaf_version: string;
}
};
/**
* @interface Achievement
* @since Alpha v0.1.5
* @description UIAF 单个成就数据
* @property {number} id 成就 ID
* @property {number} timestamp 成就记录时间戳,正确时间戳得乘以 1000
* @property {number} current 成就进度
* @property {number} status 成就状态0 为未完成1 为已完成
* @return Achievement
* 成就完成状态
* @since Beta v0.7.8
*/
interface Achievement {
const AchiItemStat = <const>{
/* 无效状态 */
Invalid: 0,
/* 未完成 */
Unfinished: 1,
/* 已完成未领取奖励 */
Finished: 2,
/* 已领取奖励 */
RewardTaken: 3,
};
/**
* 成就完成状态枚举
* @since Beta v0.7.8
*/
type AchiItemStatEnum = (typeof AchiItemStat)[keyof typeof AchiItemStat];
/**
* 成就信息
* @since Beta v0.7.8
* @description UIAF 单个成就数据
*/
type Achievement = {
/* 成就 ID */
id: number;
/* 成就记录时间戳,秒级 */
timestamp: number;
/* 成就进度 */
current: number;
status: number;
}
/* 成就状态 */
status: AchiItemStatEnum;
};
}

View File

@@ -8,44 +8,5 @@ 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;
};
type AchiListRes = Array<TGApp.Plugins.UIAF.Achievement>;
}