mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-08 08:48:11 +08:00
✨ 实现成就数据读取
This commit is contained in:
1402
pnpm-lock.yaml
generated
1402
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,20 +5,27 @@ pub mod inject;
|
|||||||
pub mod proto;
|
pub mod proto;
|
||||||
|
|
||||||
use inject::{call_yaemain, create_named_pipe, find_module_base, inject_dll, spawn_process};
|
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 serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::windows::io::{FromRawHandle, OwnedHandle, RawHandle};
|
use std::os::windows::io::{FromRawHandle, OwnedHandle, RawHandle};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Emitter, Manager};
|
||||||
use windows_sys::Win32::Storage::FileSystem::ReadFile;
|
use windows_sys::Win32::Storage::FileSystem::ReadFile;
|
||||||
use windows_sys::Win32::System::Pipes::ConnectNamedPipe;
|
use windows_sys::Win32::System::Pipes::ConnectNamedPipe;
|
||||||
|
|
||||||
|
// 读取配置值
|
||||||
fn read_rva(key: &str) -> i32 {
|
fn read_rva(key: &str) -> i32 {
|
||||||
let path = format!("nativeConfig.methodRva.chinese.{}", key);
|
let path = format!("nativeConfig.methodRva.chinese.{}", key);
|
||||||
read_conf(&path)
|
read_conf(&path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取配置文件
|
||||||
fn read_conf(path: &str) -> i32 {
|
fn read_conf(path: &str) -> i32 {
|
||||||
// 编译时嵌入 JSON 文件,值都是32位整数
|
// 编译时嵌入 JSON 文件,值都是32位整数
|
||||||
let data = include_str!("../../lib/conf.json");
|
let data = include_str!("../../lib/conf.json");
|
||||||
@@ -35,9 +42,6 @@ fn read_conf(path: &str) -> i32 {
|
|||||||
current.as_i64().unwrap_or(0) as i32
|
current.as_i64().unwrap_or(0) as i32
|
||||||
}
|
}
|
||||||
|
|
||||||
use prost::encoding::{decode_key, WireType};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub fn parse_achievement_data(bytes: &[u8]) -> Vec<HashMap<u32, u32>> {
|
pub fn parse_achievement_data(bytes: &[u8]) -> Vec<HashMap<u32, u32>> {
|
||||||
let mut cursor = std::io::Cursor::new(bytes);
|
let mut cursor = std::io::Cursor::new(bytes);
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
@@ -155,13 +159,25 @@ pub fn call_yae_dll(app_handle: AppHandle, game_path: String) -> () {
|
|||||||
println!("收到命令: {}", cmd[0]);
|
println!("收到命令: {}", cmd[0]);
|
||||||
match cmd[0] {
|
match cmd[0] {
|
||||||
0x01 => {
|
0x01 => {
|
||||||
println!("AchievementNotify");
|
|
||||||
// 读取剩余数据
|
|
||||||
match read_u32_le(&mut file) {
|
match read_u32_le(&mut file) {
|
||||||
Ok(len) => match read_exact_vec(&mut file, len as usize) {
|
Ok(len) => {
|
||||||
Ok(data) => println!("长度: {}", len),
|
// 再读数据
|
||||||
Err(e) => println!("读取数据失败: {:?}", e),
|
match read_exact_vec(&mut file, len as usize) {
|
||||||
},
|
Ok(data) => {
|
||||||
|
println!("长度: {}", len);
|
||||||
|
// 解码成 AchievementInfo
|
||||||
|
match parse_achi_list(&data) {
|
||||||
|
Ok(list) => {
|
||||||
|
println!("解码成功,成就列表长度: {}", list.len());
|
||||||
|
let json = serde_json::to_string_pretty(&list).unwrap();
|
||||||
|
app_handle.emit("yae_achi_list", json);
|
||||||
|
}
|
||||||
|
Err(e) => println!("解析失败: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => println!("读取数据失败: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) => println!("读取长度失败: {:?}", e),
|
Err(e) => println!("读取长度失败: {:?}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
//! Yae 成就信息的 Protobuf 定义
|
//! Yae 成就信息的 Protobuf 定义
|
||||||
//! @since Beta v0.9.0
|
//! @since Beta v0.9.0
|
||||||
|
|
||||||
|
use prost::encoding::{decode_key, WireType};
|
||||||
|
use prost::DecodeError;
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Message)]
|
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||||
pub struct AchievementProtoFieldInfo {
|
pub struct AchievementProtoFieldInfo {
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
@@ -22,7 +27,7 @@ pub struct AchievementProtoFieldInfo {
|
|||||||
pub finish_timestamp: u32,
|
pub finish_timestamp: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Message)]
|
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||||
pub struct AchievementItem {
|
pub struct AchievementItem {
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub pre: u32,
|
pub pre: u32,
|
||||||
@@ -37,7 +42,7 @@ pub struct AchievementItem {
|
|||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Message)]
|
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||||
pub struct MethodRvaConfig {
|
pub struct MethodRvaConfig {
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub do_cmd: u32,
|
pub do_cmd: u32,
|
||||||
@@ -70,7 +75,7 @@ pub struct MethodRvaConfig {
|
|||||||
pub decompress: u32,
|
pub decompress: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Message)]
|
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||||
pub struct NativeLibConfig {
|
pub struct NativeLibConfig {
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub store_cmd_id: u32,
|
pub store_cmd_id: u32,
|
||||||
@@ -82,7 +87,7 @@ pub struct NativeLibConfig {
|
|||||||
pub method_rva: HashMap<u32, MethodRvaConfig>,
|
pub method_rva: HashMap<u32, MethodRvaConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Message)]
|
#[derive(Clone, PartialEq, Message, Serialize)]
|
||||||
pub struct AchievementInfo {
|
pub struct AchievementInfo {
|
||||||
#[prost(string, tag = "1")]
|
#[prost(string, tag = "1")]
|
||||||
pub version: String,
|
pub version: String,
|
||||||
@@ -99,3 +104,56 @@ pub struct AchievementInfo {
|
|||||||
#[prost(message, tag = "5")]
|
#[prost(message, tag = "5")]
|
||||||
pub native_config: Option<NativeLibConfig>,
|
pub native_config: Option<NativeLibConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize)]
|
||||||
|
pub struct AchievementEntry {
|
||||||
|
pub id: u32,
|
||||||
|
pub total_progress: u32,
|
||||||
|
pub current_progress: u32,
|
||||||
|
pub finish_timestamp: u32,
|
||||||
|
pub status: u32, // 数值类型
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_achi_list(bytes: &[u8]) -> Result<Vec<AchievementEntry>, DecodeError> {
|
||||||
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
let mut dicts: Vec<HashMap<u32, u32>> = Vec::new();
|
||||||
|
|
||||||
|
while let Ok((_, wire_type)) = decode_key(&mut cursor) {
|
||||||
|
if wire_type == WireType::LengthDelimited {
|
||||||
|
let len = prost::encoding::decode_varint(&mut cursor)? as usize;
|
||||||
|
let mut buf = vec![0u8; len];
|
||||||
|
if cursor.read_exact(&mut buf).is_err() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inner = Cursor::new(&buf);
|
||||||
|
let mut dict = HashMap::new();
|
||||||
|
|
||||||
|
while let Ok((tag, wire_type)) = decode_key(&mut inner) {
|
||||||
|
if wire_type != WireType::Varint {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let value = prost::encoding::decode_varint(&mut inner)? as u32;
|
||||||
|
dict.insert(tag, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dict.is_empty() {
|
||||||
|
println!("Raw dict: {:?}", dict);
|
||||||
|
dicts.push(dict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let achievements = dicts
|
||||||
|
.into_iter()
|
||||||
|
.map(|d| AchievementEntry {
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(achievements)
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,51 +15,30 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="btn-list">
|
<div class="btn-list">
|
||||||
<v-btn @click="test()" class="test-btn">测试</v-btn>
|
<v-btn @click="test()" class="test-btn">测试</v-btn>
|
||||||
<v-btn @click="test2()" class="test-btn">测试2</v-btn>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import showSnackbar from "@comp/func/snackbar.js";
|
import { event } from "@tauri-apps/api";
|
||||||
import hk4eReq from "@req/hk4eReq.js";
|
|
||||||
import takumiReq from "@req/takumiReq.js";
|
|
||||||
import useUserStore from "@store/user.js";
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { storeToRefs } from "pinia";
|
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||||
import { ref } from "vue";
|
import { onMounted, onUnmounted } from "vue";
|
||||||
|
|
||||||
const { cookie, account } = storeToRefs(useUserStore());
|
let listener: UnlistenFn | null = null;
|
||||||
|
|
||||||
const authkey = ref<string>("");
|
onMounted(async () => {
|
||||||
|
listener = await event.listen<string>("yae_achi_list", (e: Event<string>) => {
|
||||||
|
console.log(e.payload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (listener !== null) {
|
||||||
|
listener();
|
||||||
|
listener = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function test(): Promise<void> {
|
async function test(): Promise<void> {
|
||||||
if (!cookie.value || !account.value) {
|
|
||||||
showSnackbar.warn("请先登录账号");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const authkeyRes = await takumiReq.bind.authKey(cookie.value, account.value);
|
|
||||||
if (typeof authkeyRes === "string") {
|
|
||||||
authkey.value = authkeyRes;
|
|
||||||
} else {
|
|
||||||
showSnackbar.error("获取authkey失败");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const list: Array<TGApp.Game.Gacha.GachaBItem> = [];
|
|
||||||
let endId = "0";
|
|
||||||
while (true) {
|
|
||||||
const res = await hk4eReq.gachaB(authkey.value, "1000", endId);
|
|
||||||
if (Array.isArray(res)) {
|
|
||||||
if (res.length === 0) break;
|
|
||||||
list.push(...res);
|
|
||||||
endId = res[res.length - 1].id;
|
|
||||||
} else {
|
|
||||||
showSnackbar.warn(`[${res.retcode}] 获取祈愿记录失败:${res.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function test2(): Promise<void> {
|
|
||||||
try {
|
try {
|
||||||
await invoke("call_yae_dll", {
|
await invoke("call_yae_dll", {
|
||||||
gamePath: "D:\\Games\\Genshin Impact bilibili\\games\\Genshin Impact Game\\YuanShen.exe",
|
gamePath: "D:\\Games\\Genshin Impact bilibili\\games\\Genshin Impact Game\\YuanShen.exe",
|
||||||
|
|||||||
Reference in New Issue
Block a user