mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-08 07:32:47 +08:00
Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a1da61904 | ||
|
|
9eb8955fda | ||
|
|
62c08f54ab | ||
|
|
645fe38c65 | ||
|
|
8f9a26a237 | ||
|
|
8648b3a308 | ||
|
|
829553b3a6 | ||
|
|
87898eedfa | ||
|
|
a10b491886 | ||
|
|
e3e7107b14 | ||
|
|
3231746aa5 | ||
|
|
5c9cdd46d2 | ||
|
|
881a4bc725 | ||
|
|
d08ac17d10 | ||
|
|
980a47bf43 | ||
|
|
0e7be25b23 | ||
|
|
4b052cf6c7 | ||
|
|
4ff2b454f3 | ||
|
|
f718687b3f | ||
|
|
9915b9246e | ||
|
|
e25bc9aeba | ||
|
|
08dd6eca76 | ||
|
|
acd2ccd803 | ||
|
|
13fda3ba12 | ||
|
|
1c821620bf | ||
|
|
8a39ad0a77 | ||
|
|
9d42141258 | ||
|
|
2a91656b2e | ||
|
|
602cf06a8b | ||
|
|
c87b8c976d | ||
|
|
099c22e9e7 | ||
|
|
f1fe6e1f9e | ||
|
|
fb0a46480f | ||
|
|
7b413384c3 | ||
|
|
e251497edc | ||
|
|
b1f5d9a2b2 | ||
|
|
36a5b9a0e7 | ||
|
|
b0b70d585a | ||
|
|
b9ab326d72 | ||
|
|
5f210d4236 | ||
|
|
826063da60 | ||
|
|
08697941d3 | ||
|
|
e7d21865c7 | ||
|
|
35773f49f4 | ||
|
|
c4856c821f | ||
|
|
6f1168e61e | ||
|
|
d1bd0c7d7b | ||
|
|
95ad187015 | ||
|
|
6c0264ce5a | ||
|
|
957c8b98e4 | ||
|
|
01a3f41323 | ||
|
|
32ceae074e | ||
|
|
2e2be07161 | ||
|
|
832c82f44e | ||
|
|
43b38df986 | ||
|
|
6e1c8f275f | ||
|
|
e65f046520 | ||
|
|
4025729677 | ||
|
|
07050c1c3d | ||
|
|
e56a6228aa | ||
|
|
66b29b1374 | ||
|
|
247c401a5b | ||
|
|
cf9d601b27 | ||
|
|
5abb9e2934 | ||
|
|
1b861712eb | ||
|
|
d62377ad96 | ||
|
|
fb4e3f8d00 | ||
|
|
b1135542c1 | ||
|
|
7d3d0f5e14 | ||
|
|
4268b04f3c | ||
|
|
9a9d1310a1 | ||
|
|
618a9189ad | ||
|
|
f73dbdc4fe | ||
|
|
298134c063 | ||
|
|
e9ace26d69 | ||
|
|
2c15353f86 | ||
|
|
99fec63867 | ||
|
|
bf5525d2ea | ||
|
|
cf3749f887 | ||
|
|
21af4de1a6 | ||
|
|
8e0fd2d27c | ||
|
|
0348cfa365 | ||
|
|
494eda32c2 | ||
|
|
975638c1ee | ||
|
|
793ad075fe | ||
|
|
c82a10353f | ||
|
|
f737122247 | ||
|
|
520167ef85 | ||
|
|
faee6f6121 | ||
|
|
06c5468118 | ||
|
|
b7c2204f68 | ||
|
|
5dc5e646d6 | ||
|
|
9cab7e8702 | ||
|
|
1f080fe084 | ||
|
|
c8497243c0 | ||
|
|
9abdd123ee | ||
|
|
e1429289ad | ||
|
|
1f311ed987 | ||
|
|
cc346915e3 | ||
|
|
cd0f49d83d | ||
|
|
d0b7d15894 | ||
|
|
504c8a2a9a | ||
|
|
b3162052da | ||
|
|
034d999d25 | ||
|
|
45d5620e83 | ||
|
|
fa13f9c8e5 | ||
|
|
2210a97d61 | ||
|
|
feb7ac44da | ||
|
|
3924129560 | ||
|
|
4f7f0cdfd2 | ||
|
|
cf0753c676 | ||
|
|
0b895d47ca | ||
|
|
78d2722e20 | ||
|
|
385c673323 | ||
|
|
50beb2cce7 | ||
|
|
324a4153e0 | ||
|
|
3de459aceb | ||
|
|
295bb89177 | ||
|
|
baaf4e8227 | ||
|
|
f41fe6fb12 | ||
|
|
78bda3f49c | ||
|
|
a10dc22461 | ||
|
|
74dda750ef | ||
|
|
099270ad29 | ||
|
|
fe5b2c0c12 | ||
|
|
ed5d99745c | ||
|
|
7175cd7427 | ||
|
|
73747bcce5 | ||
|
|
b12c3209d7 | ||
|
|
5805070627 | ||
|
|
811daba164 | ||
|
|
849f379ca5 | ||
|
|
29c57cbc1e | ||
|
|
8fdb10898a | ||
|
|
f4f1f14651 | ||
|
|
390af6cd85 | ||
|
|
a78f36dfa8 | ||
|
|
e3d7152da2 | ||
|
|
60a4dd04c4 | ||
|
|
69c402417b | ||
|
|
d9034ec635 | ||
|
|
f742cf37d5 | ||
|
|
4136a1b0c4 | ||
|
|
e039d2f944 | ||
|
|
0e23a05a78 | ||
|
|
5f5a0614a6 | ||
|
|
c60d3a3b82 | ||
|
|
58dcd5b228 | ||
|
|
a72007ffa6 | ||
|
|
d3b9d10d01 | ||
|
|
69184fa59d | ||
|
|
8473336b37 | ||
|
|
ffc0854291 | ||
|
|
3b2b1fba49 | ||
|
|
c325b5f754 | ||
|
|
8e2e438c96 | ||
|
|
9bc2cdb443 | ||
|
|
826ed661cd | ||
|
|
5f8ff734f2 | ||
|
|
50007c2c53 | ||
|
|
7512c6fca2 | ||
|
|
2feae6ddb9 | ||
|
|
52ae44f467 | ||
|
|
d93f6f92c0 | ||
|
|
20b59eab7e |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,4 +1,4 @@
|
||||
lib/src/il2cpp-types.h linguist-generated=true
|
||||
lib/src/Zydis.* linguist-generated=true
|
||||
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
14
.github/workflows/dotnet.yml
vendored
14
.github/workflows/dotnet.yml
vendored
@@ -12,19 +12,19 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
dotnet-version: 9.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
run: dotnet build -c Release --no-restore
|
||||
- name: Publish
|
||||
run: dotnet publish -o .\publish\
|
||||
run: dotnet publish --property:OutputPath=.\publish\
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Artifacts
|
||||
path: publish
|
||||
path: YaeAchievement\publish\publish
|
||||
|
||||
30
.github/workflows/lib-nuget.yml
vendored
Normal file
30
.github/workflows/lib-nuget.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: YaeLib NuGet Publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: nuget restore lib\YaeAchievementLib.sln
|
||||
|
||||
- name: Build
|
||||
continue-on-error: true
|
||||
run: msbuild lib\YaeAchievementLib.sln /p:Configuration=Release
|
||||
|
||||
- name: Pack
|
||||
run: nuget pack lib\YaeAchievementLib.nuspec
|
||||
|
||||
- name: Publish to NuGet
|
||||
run: nuget push *.nupkg ${{ secrets.NUGET_API_KEY }} -src https://api.nuget.org/v3/index.json
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,13 +1,6 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
bin
|
||||
obj
|
||||
.idea
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
.vs/
|
||||
.vs
|
||||
publish
|
||||
sync
|
||||
|
||||
64
.gitlab-ci.yml
Normal file
64
.gitlab-ci.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- release
|
||||
|
||||
Test:
|
||||
stage: test
|
||||
image: mcr.microsoft.com/windows/server
|
||||
tags:
|
||||
- windows
|
||||
script:
|
||||
- dotnet restore
|
||||
- dotnet build -c Release --no-restore
|
||||
- dotnet publish --property:OutputPath=.\publish\
|
||||
- Move-Item -Path .\publish\publish\*.exe -Destination ..\ -Force
|
||||
|
||||
Build:
|
||||
stage: build
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- windows
|
||||
needs:
|
||||
- job: Test
|
||||
script:
|
||||
- echo "This is build stage."
|
||||
- Move-Item -Path ..\YaeAchievement.exe .\ -Force
|
||||
after_script:
|
||||
- echo "Current Job ID is $CI_JOB_ID"
|
||||
- echo "THIS_JOB_ID=$CI_JOB_ID" >> build.env
|
||||
artifacts:
|
||||
paths:
|
||||
- .\*.exe
|
||||
expire_in: 90 days
|
||||
reports:
|
||||
dotenv: build.env
|
||||
|
||||
|
||||
release:
|
||||
stage: release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
only:
|
||||
- tags
|
||||
needs:
|
||||
- job: Build
|
||||
artifacts: true
|
||||
variables:
|
||||
TAG: '$CI_COMMIT_TAG'
|
||||
script:
|
||||
- echo "Create Release $TAG"
|
||||
- echo "$THIS_JOB_ID"
|
||||
release:
|
||||
name: '$TAG'
|
||||
tag_name: '$TAG'
|
||||
ref: '$TAG'
|
||||
description: 'Release $TAG by CI'
|
||||
assets:
|
||||
links:
|
||||
- name: "YaeAchievement.exe"
|
||||
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/YaeAchievement.exe?inline=false"
|
||||
link_type: package
|
||||
- name: ".NET 7.0 Desktop Runtime"
|
||||
url: "https://dotnet.microsoft.com/zh-cn/download/dotnet/thank-you/runtime-desktop-7.0.11-windows-x64-installer"
|
||||
link_type: other
|
||||
66
README.md
66
README.md
@@ -1,29 +1,37 @@
|
||||
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
|
||||
|
||||
# YaeAchievement
|
||||
|
||||
    
|
||||
|
||||
简体中文 | [English](README_EN.md)
|
||||
</div>
|
||||
|
||||
- 支持导出所有类别的成就
|
||||
- 支持官服,渠道服与国际服
|
||||
- 支持导出至[椰羊](https://cocogoat.work/achievement)、[Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao)、[Paimon.moe](https://paimon.moe/achievement/)、[Seelie.me](https://seelie.me/achievements)、[寻空](https://github.com/xunkong/xunkong)和表格文件(csv)
|
||||
- 没有窗口大小、游戏语言等要求
|
||||
|
||||
## 使用说明
|
||||
→ [Tutorial.md](Tutorial.md)
|
||||
|
||||
## 下载地址
|
||||
[releases/latest](https://github.com/HolographicHat/YaeAchievement/releases/latest)
|
||||
|
||||
## 问题反馈
|
||||
[issues](https://github.com/HolographicHat/YaeAchievement/issues)或[QQ群: 913777414](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
|
||||
|
||||
## 常见问题
|
||||
0. Q: 打不开
|
||||
A: 安装 [.NET Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-6.0.7-windows-x64-installer)
|
||||
|
||||
1. Q: 原神启动时报错: 数据异常(31-4302)
|
||||
A: 不要把软件和原神主程序放一起
|
||||
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/YaeAchievement/res/icon.ico" alt="AppIcon">
|
||||
|
||||
# YaeAchievement
|
||||
|
||||
    
|
||||
|
||||
简体中文 | [English](README_EN.md) | [日本語](README_JP.md)
|
||||
</div>
|
||||
|
||||
- 支持导出所有类别的成就
|
||||
- 支持官服,渠道服与国际服
|
||||
- 没有窗口大小、游戏语言等要求
|
||||
|
||||
## 导出支持
|
||||
|
||||
0. [椰羊](https://cocogoat.work/achievement)
|
||||
1. [胡桃工具箱](https://github.com/DGP-Studio/Snap.HuTao)
|
||||
2. [Paimon.moe](https://paimon.moe/achievement/)
|
||||
3. [Seelie.me](https://seelie.me/achievements)
|
||||
4. 表格文件 `.csv`
|
||||
5. [寻空](https://github.com/xunkong/xunkong)
|
||||
6. [原魔工具箱](https://apps.apple.com/app/id1663989619)
|
||||
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
|
||||
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON 文件
|
||||
|
||||
## 使用说明
|
||||
→ [Tutorial.md](Tutorial.md)
|
||||
|
||||
## 下载地址
|
||||
[releases/latest](https://github.com/HolographicHat/YaeAchievement/releases/latest)
|
||||
|
||||
## 问题反馈
|
||||
[issues](https://github.com/HolographicHat/YaeAchievement/issues)或[QQ群: 598720036](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
|
||||
|
||||
## 常见问题
|
||||
1. Q: 原神启动时报错: 数据异常(31-4302)
|
||||
A: 不要把软件和原神主程序放一起
|
||||
|
||||
33
README_EN.md
33
README_EN.md
@@ -1,37 +1,38 @@
|
||||
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
|
||||
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/YaeAchievement/res/icon.ico" alt="AppIcon">
|
||||
|
||||
# YaeAchievement
|
||||
|
||||
    
|
||||
|
||||
[简体中文](README.md) | English
|
||||
|
||||
**I18n support currently in [snapshot version](https://github.com/HolographicHat/YaeAchievement/actions/)**
|
||||
**Next release: 2022/09/28 (Genshin 3.1)**
|
||||
[简体中文](README.md) | English | [日本語](README_JP.md)
|
||||
|
||||
</div>
|
||||
|
||||
- Support for exporting all categories of achievements
|
||||
- Supports all versions of Genshin Impact
|
||||
- Support for exporting to [Cocogoat](https://cocogoat.work/achievement), [Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao), [Paimon.moe](https://paimon.moe/achievement/), [Seelie.me](https://seelie.me/achievements)、[XunKong](https://github.com/xunkong/xunkong) and form files (csv)
|
||||
- There are no requirements for window size, game language, etc.
|
||||
|
||||
## How to use:
|
||||
The first time you open it, you need to set the path that contains the Genshin main program (YuanShen.exe / GenshinIimpact.exe)
|
||||

|
||||
After setting up, wait for the game to start.
|
||||
When you see the door, press in to enter the game, the game will automatically exit.
|
||||
After the game exits, enter a number inside the program to select the site/application to export to
|
||||
## Export support
|
||||
|
||||
0. [Cocogoat](https://cocogoat.work/achievement)
|
||||
1. [Snap HuTao](https://github.com/DGP-Studio/Snap.HuTao)
|
||||
2. [Paimon.moe](https://paimon.moe/achievement/)
|
||||
3. [Seelie.me](https://seelie.me/achievements)
|
||||
4. Form File `.csv`
|
||||
5. [XunKong](https://github.com/xunkong/xunkong)
|
||||
6. [YuanmoTools](https://apps.apple.com/app/id1663989619)
|
||||
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
|
||||
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON file
|
||||
|
||||
## Instructions for Use:
|
||||
→ [Tutorial_EN.md](Tutorial_EN.md)
|
||||
|
||||
## Download: [Here](https://github.com/HolographicHat/YaeAchievement/releases/latest)
|
||||
|
||||
## Feedback or Problem?
|
||||
[issues](https://github.com/HolographicHat/YaeAchievement/issues) or [QQ群: 913777414](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
|
||||
[issues](https://github.com/HolographicHat/YaeAchievement/issues) or [QQ群: 598720036](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
|
||||
|
||||
## Frequently asked questions
|
||||
0. Q: Unable to start
|
||||
A: Download and install [.NET Runtime 6](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-6.0.8-windows-x64-installer)
|
||||
|
||||
1. Q: Error while Genshin started: Data Exception (31-4302)
|
||||
A: Don't place software in the directory containing Genshin Impact.
|
||||
|
||||
|
||||
38
README_JP.md
Normal file
38
README_JP.md
Normal file
@@ -0,0 +1,38 @@
|
||||
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/YaeAchievement/res/icon.ico" alt="AppIcon">
|
||||
|
||||
# YaeAchievement
|
||||
|
||||
    
|
||||
|
||||
[简体中文](README.md) | [English](README_EN.md) | 日本語
|
||||
|
||||
</div>
|
||||
|
||||
- すべてのカテゴリの実績のエクスポートをサポート
|
||||
- すべてのバージョンの原神をサポート
|
||||
- ウィンドウサイズ、ゲーム言語などの要件はありません
|
||||
|
||||
## エクスポートサポート
|
||||
|
||||
0. [椰羊](https://cocogoat.work/achievement)
|
||||
1. [胡桃ツールボックス](https://github.com/DGP-Studio/Snap.HuTao)
|
||||
2. [Paimon.moe](https://paimon.moe/achievement/)
|
||||
3. [Seelie.me](https://seelie.me/achievements)
|
||||
4. フォームファイル `.csv`
|
||||
5. [尋空](https://github.com/xunkong/xunkong)
|
||||
6. [原魔ツールボックス](https://apps.apple.com/app/id1663989619)
|
||||
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
|
||||
8. [UIAF](https://uigf.org/standards/UIAF.html) JSONファイル
|
||||
|
||||
## 使用説明書:
|
||||
→ [Tutorial_JP.md](Tutorial_JP.md)
|
||||
|
||||
## ダウンロード: [こちら](https://github.com/HolographicHat/YaeAchievement/releases/latest)
|
||||
|
||||
## フィードバックや問題?
|
||||
[issues](https://github.com/HolographicHat/YaeAchievement/issues) または [QQ群: 598720036](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
|
||||
|
||||
## よくある質問
|
||||
1. Q: 原神を起動中にエラーが発生しました: データ例外 (31-4302)
|
||||
A: ソフトウェアを原神のディレクトリに配置しないでください。
|
||||
|
||||
48
Tutorial.md
48
Tutorial.md
@@ -6,31 +6,19 @@
|
||||
|
||||
点击图中红框圈中的名称为“YaeAchievement.exe”的文件,浏览器会自动弹出下载。建议将该文件保存在桌面或者其它易于寻找的文件夹内。
|
||||
|
||||

|
||||

|
||||
|
||||
2.安装启动软件所需文件(若已安装该运行时可忽略此步骤)
|
||||
|
||||
点击该网址:https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-6.0.9-windows-x64-installer。
|
||||
|
||||
进入网页后浏览器会自动弹出下载,同样地,将文件保存在桌面或者其它易于寻找的文件夹内。
|
||||
|
||||
下载完成后打开名称形如dotnet-runtime-x.x.x-win-x64.exe的文件,会弹出安装窗口,如下图所示。
|
||||
|
||||

|
||||
|
||||
直接点击安装即可。
|
||||
|
||||
3.打开主程序所需的操作以及成就导出的选择
|
||||
2.打开主程序所需的操作以及成就导出的选择
|
||||
|
||||
双击在第一步下载的名称为“YaeAchievement.exe”的文件,成功打开后会提示原神正在启动,如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
原神启动完成后,点击进入游戏即可。
|
||||
|
||||
点击进入游戏后原神闪退,工具会提示您选择导出至何种工具,如下图所示。
|
||||
点击进入游戏后原神自动退出,工具会提示您选择导出至何种工具,如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
此时可根据自己的需要进行选择,一般推荐导出至[0]椰羊以及[4]表格文件(.csv)。
|
||||
|
||||
@@ -38,44 +26,44 @@
|
||||
|
||||
#### 椰羊:
|
||||
|
||||

|
||||

|
||||
|
||||
#### Snap.Hutao:
|
||||
|
||||

|
||||

|
||||
|
||||
#### Seelie.me:
|
||||
|
||||
此时YaeAchievement会提示成就数据已导出。请在保存YaeAchievement的文件夹内找到名称形如export-20xxxxxxxxxxxx-seelie.json的文件。
|
||||
|
||||

|
||||

|
||||
|
||||
然后点击该网址https://seelie.me/settings, 进入网页后选择导入,如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
点击导入后选中名称形如export-20xxxxxxxxxxxx-seelie.json的文件,如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
当弹出如下图所示的提示时表示导入成功。
|
||||
|
||||

|
||||

|
||||
|
||||
此时可选择左栏成就,查看导入的成就数据。
|
||||
|
||||
#### 寻空:
|
||||
|
||||

|
||||

|
||||
|
||||
### 各种工具的介绍烦请移步至各工具的官方页面进行查看(下方序号对应导出序号)
|
||||
|
||||
0. [椰羊](https://cocogoat.work/achievement)
|
||||
|
||||
1. [Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao)
|
||||
|
||||
1. [胡桃工具箱](https://github.com/DGP-Studio/Snap.HuTao)
|
||||
2. [Paimon.moe](https://paimon.moe/achievement/)
|
||||
|
||||
3. [Seelie.me](https://seelie.me/achievements)
|
||||
|
||||
4. [寻空](https://github.com/xunkong/xunkong)
|
||||
4. [表格文件 `.csv`](https://en.wikipedia.org/wiki/Comma-separated_values)
|
||||
5. [寻空](https://github.com/xunkong/xunkong)
|
||||
6. [原魔工具箱](https://apps.apple.com/app/id1663989619)
|
||||
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
|
||||
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON 文件
|
||||
|
||||
62
Tutorial_EN.md
Normal file
62
Tutorial_EN.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Instructions for Use
|
||||
|
||||
1.Download YaeAchievement(Latest Version):
|
||||
|
||||
Click Here:<https://github.com/HolographicHat/YaeAchievement/releases>
|
||||
|
||||
Click on the file named "YaeAchievement.exe" in the red box to automatically pop up and download.It is recommended that
|
||||
you save this file in a desktop or other easy-to-see folder.
|
||||
|
||||

|
||||
|
||||
3.The actions required to open the main program and the options for the achievement export
|
||||
|
||||
Double-click the file named "YaeAchievement.exe" downloaded in the first step to open it successfully, indicating that the original god is starting, as shown below.
|
||||
|
||||

|
||||
|
||||
Once the primordial startup is complete, click to enter the game.
|
||||
|
||||
When you click into the game, the tool prompts you to choose which tool to export, as shown below.
|
||||
|
||||

|
||||
|
||||
For global user, you should select [3] Seelie.me or [4] Export to csv file。
|
||||
|
||||
After selecting, each page exports the tool as follows:
|
||||
|
||||
#### Snap.Hutao:
|
||||
|
||||

|
||||
|
||||
#### Seelie.me:
|
||||
|
||||
At this point, Yae Achievement will remind that performance data has been exported. Please find the file named export-20xxxxxxxxxxxx-seelie.json in the Yae Achievement save directory.
|
||||
|
||||

|
||||
|
||||
Then click on the URL: https://seelie.me/settings, enter the website and select Import Account, as illustrated in the figure below.
|
||||
|
||||

|
||||
|
||||
After clicking Import, select a file named export-20xxxxxxxxxxxx-seelie.json, as shown in the figure below.
|
||||
|
||||

|
||||
|
||||
When the prompt as shown in the image below pops up, the import process succeeds.
|
||||
|
||||

|
||||
|
||||
At this time, you can select the Achievements in the left column to view the imported performance data.
|
||||
|
||||
### For the introduction of different tools, please visit the official page of each tool to see:
|
||||
|
||||
0. [Cocogoat](https://cocogoat.work/achievement)
|
||||
1. [Snap HuTao](https://github.com/DGP-Studio/Snap.HuTao)
|
||||
2. [Paimon.moe](https://paimon.moe/achievement/)
|
||||
3. [Seelie.me](https://seelie.me/achievements)
|
||||
4. [Form File `.csv`](https://en.wikipedia.org/wiki/Comma-separated_values)
|
||||
5. [XunKong](https://github.com/xunkong/xunkong)
|
||||
6. [YuanmoTools](https://apps.apple.com/app/id1663989619)
|
||||
7. [Teyvat Guide](https://github.com/BTMuli/TeyvatGuide)
|
||||
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON file
|
||||
61
Tutorial_JP.md
Normal file
61
Tutorial_JP.md
Normal file
@@ -0,0 +1,61 @@
|
||||
## 使用説明書
|
||||
|
||||
1. YaeAchievement(最新バージョン)をダウンロード:
|
||||
|
||||
こちらをクリック:https://github.com/HolographicHat/YaeAchievement/releases
|
||||
|
||||
赤枠で囲まれた「YaeAchievement.exe」という名前のファイルをクリックすると、自動的にポップアップしてダウンロードされます。このファイルをデスクトップや他の見やすいフォルダに保存することをお勧めします。
|
||||
|
||||

|
||||
|
||||
3. メインプログラムを開くための操作と実績エクスポートのオプション
|
||||
|
||||
最初のステップでダウンロードした「YaeAchievement.exe」という名前のファイルをダブルクリックして開くと、原神が起動していることを示します。以下の図のように表示されます。
|
||||
|
||||

|
||||
|
||||
原神の起動が完了したら、ゲームに入ります。
|
||||
|
||||
ゲームに入ると、ツールがどのツールにエクスポートするかを選択するように促します。以下の図のように表示されます。
|
||||
|
||||

|
||||
|
||||
グローバルユーザーの場合、[3] Seelie.meまたは[4] csvファイルにエクスポートを選択する必要があります。
|
||||
|
||||
選択後、各ページは次のようにツールをエクスポートします:
|
||||
|
||||
#### Snap.Hutao:
|
||||
|
||||

|
||||
|
||||
#### Seelie.me:
|
||||
|
||||
この時点で、Yae Achievementは実績データがエクスポートされたことを通知します。Yae Achievement保存ディレクトリにexport-20xxxxxxxxxxxx-seelie.jsonという名前のファイルを見つけてください。
|
||||
|
||||

|
||||
|
||||
次に、URL:https://seelie.me/settings をクリックし、ウェブサイトにアクセスしてインポートアカウントを選択します。以下の図のように表示されます。
|
||||
|
||||

|
||||
|
||||
インポートをクリックした後、export-20xxxxxxxxxxxx-seelie.jsonという名前のファイルを選択します。以下の図のように表示されます。
|
||||
|
||||

|
||||
|
||||
以下の図のようなプロンプトが表示されたら、インポートプロセスは成功です。
|
||||
|
||||

|
||||
|
||||
この時点で、左側の列のAchievementsを選択して、インポートされた実績データを表示できます。
|
||||
|
||||
### 各ツールの紹介については、各ツールの公式ページをご覧ください:
|
||||
|
||||
0. [椰羊](https://cocogoat.work/achievement)
|
||||
1. [胡桃ツールボックス](https://github.com/DGP-Studio/Snap.HuTao)
|
||||
2. [Paimon.moe](https://paimon.moe/achievement/)
|
||||
3. [Seelie.me](https://seelie.me/achievements)
|
||||
4. [フォームファイル `.csv`](https://ja.wikipedia.org/wiki/Comma-separated_values)
|
||||
5. [尋空](https://github.com/xunkong/xunkong)
|
||||
6. [原魔ツールボックス](https://apps.apple.com/app/id1663989619)
|
||||
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
|
||||
8. [UIAF](https://uigf.org/standards/UIAF.html) JSONファイル
|
||||
@@ -1,47 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ApplicationManifest>res\app.manifest</ApplicationManifest>
|
||||
<AssemblyVersion>2.0.0</AssemblyVersion>
|
||||
<FileVersion>2.0.0</FileVersion>
|
||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||
|
||||
<DebugType>embedded</DebugType>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.21.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="res\App.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>App.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Remove="res\Updater.exe" />
|
||||
<EmbeddedResource Include="res\Updater.exe">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="res\App.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>App.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YaeAchievement", "YaeAchievement.csproj", "{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
3
YaeAchievement.slnx
Normal file
3
YaeAchievement.slnx
Normal file
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="YaeAchievement\YaeAchievement.csproj" Type="Classic C#" />
|
||||
</Solution>
|
||||
6
YaeAchievement/NativeMethods.json
Normal file
6
YaeAchievement/NativeMethods.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||
"className": "Native",
|
||||
"allowMarshaling": false,
|
||||
"public": true
|
||||
}
|
||||
26
YaeAchievement/NativeMethods.txt
Normal file
26
YaeAchievement/NativeMethods.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
CloseClipboard
|
||||
CreateProcess
|
||||
CreateRemoteThread
|
||||
EmptyClipboard
|
||||
GetConsoleMode
|
||||
GetDC
|
||||
GetDeviceCaps
|
||||
GetModuleHandle
|
||||
GetProcAddress
|
||||
GetStdHandle
|
||||
GlobalLock
|
||||
GlobalUnlock
|
||||
OpenClipboard
|
||||
ResumeThread
|
||||
SetClipboardData
|
||||
SetConsoleMode
|
||||
TerminateProcess
|
||||
VirtualAllocEx
|
||||
VirtualFreeEx
|
||||
WaitForSingleObject
|
||||
WriteProcessMemory
|
||||
|
||||
GetCurrentConsoleFontEx
|
||||
|
||||
OpenProcess
|
||||
GetModuleFileNameEx
|
||||
65
YaeAchievement/YaeAchievement.csproj
Normal file
65
YaeAchievement/YaeAchievement.csproj
Normal file
@@ -0,0 +1,65 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Exe</OutputType>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<FileVersion>5.3.0</FileVersion>
|
||||
<AssemblyVersion>5.3.0</AssemblyVersion>
|
||||
<ApplicationIcon>res\icon.ico</ApplicationIcon>
|
||||
<ApplicationManifest>res\app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PublishAot>true</PublishAot>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<OptimizationPreference>Size</OptimizationPreference>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.30.2" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.71.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Sentry" Version="5.6.0" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.50.1-preview.0.3" />
|
||||
<PackageReference Include="Spectre.Console.Analyzer" Version="1.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="res\App.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>App.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Remove="res\updater.exe"/>
|
||||
<EmbeddedResource Include="res\updater.exe" LogicalName="updater"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="res\App.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>App.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="res/proto/*.proto" ProtoRoot="res/proto" GrpcServices="None"/>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CETCompat>false</CETCompat>
|
||||
<!-- <TrimmerSingleWarn>false</TrimmerSingleWarn>-->
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
225
res/App.Designer.cs → YaeAchievement/res/App.Designer.cs
generated
225
res/App.Designer.cs → YaeAchievement/res/App.Designer.cs
generated
@@ -87,7 +87,25 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You need to login genshin impact before exporting..
|
||||
/// Looks up a localized string similar to No.
|
||||
/// </summary>
|
||||
internal static string CommonNo {
|
||||
get {
|
||||
return ResourceManager.GetString("CommonNo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Yes.
|
||||
/// </summary>
|
||||
internal static string CommonYes {
|
||||
get {
|
||||
return ResourceManager.GetString("CommonYes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please launch GenshinImpact to continue..
|
||||
/// </summary>
|
||||
internal static string ConfigNeedStartGenshin {
|
||||
get {
|
||||
@@ -114,14 +132,7 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Export to:
|
||||
///[0] Cocogoat (https://cocogoat.work/achievement, Default)
|
||||
///[1] Snap.HuTao
|
||||
///[2] Paimon.moe
|
||||
///[3] Seelie.me
|
||||
///[4] Csv file
|
||||
///[5] Xunkong
|
||||
///Input a number (0-5): .
|
||||
/// Looks up a localized string similar to Export to:.
|
||||
/// </summary>
|
||||
internal static string ExportChoose {
|
||||
get {
|
||||
@@ -129,6 +140,87 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cocogoat (https://cocogoat.work/achievement).
|
||||
/// </summary>
|
||||
internal static string ExportTargetCocogoat {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetCocogoat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Csv file.
|
||||
/// </summary>
|
||||
internal static string ExportTargetCsv {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetCsv", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Snap.HuTao.
|
||||
/// </summary>
|
||||
internal static string ExportTargetHuTao {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetHuTao", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Paimon.moe.
|
||||
/// </summary>
|
||||
internal static string ExportTargetPaimon {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetPaimon", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Seelie.me.
|
||||
/// </summary>
|
||||
internal static string ExportTargetSeelie {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetSeelie", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Teyvat Guide.
|
||||
/// </summary>
|
||||
internal static string ExportTargetTeyvatGuide {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetTeyvatGuide", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to UIAF JSON File.
|
||||
/// </summary>
|
||||
internal static string ExportTargetUIAFJson {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetUIAFJson", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string ExportTargetWxApp1 {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetWxApp1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Xunkong.
|
||||
/// </summary>
|
||||
internal static string ExportTargetXunkong {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetXunkong", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Fail, please contact developer to get help information.
|
||||
/// </summary>
|
||||
@@ -157,7 +249,7 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please update SnapGenshin and retry..
|
||||
/// Looks up a localized string similar to Please update Snap Hutao and retry..
|
||||
/// </summary>
|
||||
internal static string ExportToSnapGenshinNeedUpdate {
|
||||
get {
|
||||
@@ -166,7 +258,7 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Successfully exported to snap genshin..
|
||||
/// Looks up a localized string similar to Successfully exported to Snap Hutao..
|
||||
/// </summary>
|
||||
internal static string ExportToSnapGenshinSuccess {
|
||||
get {
|
||||
@@ -174,6 +266,24 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please launch/update Teyvat Guide and retry..
|
||||
/// </summary>
|
||||
internal static string ExportToTauriFail {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportToTauriFail", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Successfully exported to Teyvat Guide..
|
||||
/// </summary>
|
||||
internal static string ExportToTauriSuccess {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportToTauriSuccess", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}.
|
||||
/// </summary>
|
||||
@@ -246,6 +356,15 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No write permission on {0}..
|
||||
/// </summary>
|
||||
internal static string NoWritePermission {
|
||||
get {
|
||||
return ResourceManager.GetString("NoWritePermission", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Press any key to exit..
|
||||
/// </summary>
|
||||
@@ -255,6 +374,42 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use the keyboard arrow keys to move the cursor and the Enter key to select.
|
||||
/// </summary>
|
||||
internal static string SelectionPromptCompatAnsiTip {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectionPromptCompatAnsiTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose an option:.
|
||||
/// </summary>
|
||||
internal static string SelectionPromptCompatChooseOne {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectionPromptCompatChooseOne", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please enter a number between 0 and {0}.
|
||||
/// </summary>
|
||||
internal static string SelectionPromptCompatInvalidChoice {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectionPromptCompatInvalidChoice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Type a number and press Enter to select.
|
||||
/// </summary>
|
||||
internal static string SelectionPromptCompatNonAnsiTip {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectionPromptCompatNonAnsiTip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Reward not taken.
|
||||
/// </summary>
|
||||
@@ -291,6 +446,24 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An error occurred while reading the data. Please try again..
|
||||
/// </summary>
|
||||
internal static string StreamReadDataFail {
|
||||
get {
|
||||
return ResourceManager.GetString("StreamReadDataFail", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Checking update....
|
||||
/// </summary>
|
||||
internal static string UpdateChecking {
|
||||
get {
|
||||
return ResourceManager.GetString("UpdateChecking", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Description:
|
||||
///{0}.
|
||||
@@ -310,6 +483,15 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The process cannot access the file '{0}' because it is being used by another process. Please restart your computer and try again..
|
||||
/// </summary>
|
||||
internal static string UpdateFileShareViolation {
|
||||
get {
|
||||
return ResourceManager.GetString("UpdateFileShareViolation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Has update: {0} => {1}.
|
||||
/// </summary>
|
||||
@@ -319,16 +501,6 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] Updater {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Updater", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Upload error to appcenter....
|
||||
/// </summary>
|
||||
@@ -339,7 +511,7 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use previous fetched data? (yes|no).
|
||||
/// Looks up a localized string similar to Use previous fetched data?.
|
||||
/// </summary>
|
||||
internal static string UsePreviousData {
|
||||
get {
|
||||
@@ -364,5 +536,14 @@ namespace YaeAchievement.res {
|
||||
return ResourceManager.GetString("VcRuntimeInstalling", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please update game and retry..
|
||||
/// </summary>
|
||||
internal static string WaitMetadataUpdate {
|
||||
get {
|
||||
return ResourceManager.GetString("WaitMetadataUpdate", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
@@ -25,14 +25,7 @@
|
||||
<value>all achievement</value>
|
||||
</data>
|
||||
<data name="ExportChoose" xml:space="preserve">
|
||||
<value>Export to:
|
||||
[0] Cocogoat (https://cocogoat.work/achievement, Default)
|
||||
[1] Snap.HuTao
|
||||
[2] Paimon.moe
|
||||
[3] Seelie.me
|
||||
[4] Csv file
|
||||
[5] Xunkong
|
||||
Input a number (0-5): </value>
|
||||
<value>Export to:</value>
|
||||
</data>
|
||||
<data name="ExportToCocogoatSuccess" xml:space="preserve">
|
||||
<value>Successfully exported to cocogoat.</value>
|
||||
@@ -41,10 +34,10 @@ Input a number (0-5): </value>
|
||||
<value>{0}</value>
|
||||
</data>
|
||||
<data name="ExportToSnapGenshinSuccess" xml:space="preserve">
|
||||
<value>Successfully exported to snap genshin.</value>
|
||||
<value>Successfully exported to Snap Hutao.</value>
|
||||
</data>
|
||||
<data name="ExportToSnapGenshinNeedUpdate" xml:space="preserve">
|
||||
<value>Please update SnapGenshin and retry.</value>
|
||||
<value>Please update Snap Hutao and retry.</value>
|
||||
</data>
|
||||
<data name="ExportToFileSuccess" xml:space="preserve">
|
||||
<value>Successfully exported to {0}</value>
|
||||
@@ -68,7 +61,7 @@ Input a number (0-5): </value>
|
||||
<value>Reward not taken</value>
|
||||
</data>
|
||||
<data name="ConfigNeedStartGenshin" xml:space="preserve">
|
||||
<value>You need to login genshin impact before exporting.</value>
|
||||
<value>Please launch GenshinImpact to continue.</value>
|
||||
</data>
|
||||
<data name="DownloadLink" xml:space="preserve">
|
||||
<value>Download: {0}</value>
|
||||
@@ -105,7 +98,7 @@ Input a number (0-5): </value>
|
||||
<value>YaeAchievement ({0})</value>
|
||||
</data>
|
||||
<data name="UsePreviousData" xml:space="preserve">
|
||||
<value>Use previous fetched data? (yes|no)</value>
|
||||
<value>Use previous fetched data?</value>
|
||||
</data>
|
||||
<data name="NetworkError" xml:space="preserve">
|
||||
<value>Network error:</value>
|
||||
@@ -119,11 +112,73 @@ Input a number (0-5): </value>
|
||||
<data name="ExceptionNetwork" xml:space="preserve">
|
||||
<value>Network error ({0}: {1})</value>
|
||||
</data>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="Updater" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>Updater.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="GenshinHashError" xml:space="preserve">
|
||||
<value>Please update genshin and retry.</value>
|
||||
</data>
|
||||
<data name="NoWritePermission" xml:space="preserve">
|
||||
<value>No write permission on {0}.</value>
|
||||
</data>
|
||||
<data name="ExportToTauriSuccess" xml:space="preserve">
|
||||
<value>Successfully exported to Teyvat Guide.</value>
|
||||
</data>
|
||||
<data name="ExportToTauriFail" xml:space="preserve">
|
||||
<value>Please launch/update Teyvat Guide and retry.</value>
|
||||
</data>
|
||||
<data name="WaitMetadataUpdate" xml:space="preserve">
|
||||
<value>Please update game and retry.</value>
|
||||
</data>
|
||||
<data name="UpdateChecking" xml:space="preserve">
|
||||
<value>Checking update...</value>
|
||||
</data>
|
||||
<data name="CommonYes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="CommonNo" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="ExportTargetCocogoat" xml:space="preserve">
|
||||
<value>Cocogoat (https://cocogoat.work/achievement)</value>
|
||||
</data>
|
||||
<data name="ExportTargetHuTao" xml:space="preserve">
|
||||
<value>Snap.HuTao</value>
|
||||
</data>
|
||||
<data name="ExportTargetPaimon" xml:space="preserve">
|
||||
<value>Paimon.moe</value>
|
||||
</data>
|
||||
<data name="ExportTargetSeelie" xml:space="preserve">
|
||||
<value>Seelie.me</value>
|
||||
</data>
|
||||
<data name="ExportTargetCsv" xml:space="preserve">
|
||||
<value>Csv file</value>
|
||||
</data>
|
||||
<data name="ExportTargetXunkong" xml:space="preserve">
|
||||
<value>Xunkong</value>
|
||||
</data>
|
||||
<data name="ExportTargetTeyvatGuide" xml:space="preserve">
|
||||
<value>Teyvat Guide</value>
|
||||
</data>
|
||||
<data name="ExportTargetUIAFJson" xml:space="preserve">
|
||||
<value>UIAF JSON File</value>
|
||||
</data>
|
||||
<data name="ExportTargetWxApp1" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="SelectionPromptCompatChooseOne" xml:space="preserve">
|
||||
<value>Choose an option:</value>
|
||||
</data>
|
||||
<data name="SelectionPromptCompatAnsiTip" xml:space="preserve">
|
||||
<value>Use the keyboard arrow keys to move the cursor and the Enter key to select</value>
|
||||
</data>
|
||||
<data name="SelectionPromptCompatNonAnsiTip" xml:space="preserve">
|
||||
<value>Type a number and press Enter to select</value>
|
||||
</data>
|
||||
<data name="SelectionPromptCompatInvalidChoice" xml:space="preserve">
|
||||
<value>Please enter a number between 0 and {0}</value>
|
||||
</data>
|
||||
<data name="StreamReadDataFail" xml:space="preserve">
|
||||
<value>An error occurred while reading the data. Please try again.</value>
|
||||
</data>
|
||||
<data name="UpdateFileShareViolation" xml:space="preserve">
|
||||
<value>The process cannot access the file '{0}' because it is being used by another process. Please restart your computer and try again.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -18,15 +18,7 @@
|
||||
<value>全部成就</value>
|
||||
</data>
|
||||
<data name="ExportChoose" xml:space="preserve">
|
||||
<value>导出至:
|
||||
[0] 椰羊 (https://cocogoat.work/achievement, 默认)
|
||||
[1] Snap.HuTao
|
||||
[2] Paimon.moe
|
||||
[3] Seelie.me
|
||||
[4] 表格文件
|
||||
[5] 寻空
|
||||
[6] 原魔工具箱
|
||||
输入一个数字 (0-6): </value>
|
||||
<value>要导出到哪里?</value>
|
||||
</data>
|
||||
<data name="ExportToCocogoatSuccess" xml:space="preserve">
|
||||
<value>在浏览器内进行下一步操作</value>
|
||||
@@ -35,10 +27,10 @@
|
||||
<value>在小程序导入页面输入以下代码: {0}</value>
|
||||
</data>
|
||||
<data name="ExportToSnapGenshinSuccess" xml:space="preserve">
|
||||
<value>在 SnapHutao 进行下一步操作</value>
|
||||
<value>在 Snap Hutao 中进行下一步操作</value>
|
||||
</data>
|
||||
<data name="ExportToSnapGenshinNeedUpdate" xml:space="preserve">
|
||||
<value>更新 SnapHutao 至最新版本后重试</value>
|
||||
<value>更新/安装 Snap Hutao 最新版本后重试</value>
|
||||
</data>
|
||||
<data name="ExportToFileSuccess" xml:space="preserve">
|
||||
<value>成就数据已导出至 {0}</value>
|
||||
@@ -62,7 +54,7 @@
|
||||
<value>已完成</value>
|
||||
</data>
|
||||
<data name="ConfigNeedStartGenshin" xml:space="preserve">
|
||||
<value>在导出前你需要先完成一次登入流程.</value>
|
||||
<value>请打开 原神 后继续操作</value>
|
||||
</data>
|
||||
<data name="DownloadLink" xml:space="preserve">
|
||||
<value>下载地址: {0}</value>
|
||||
@@ -99,7 +91,7 @@
|
||||
<value>YaeAchievement - 原神成就导出工具 ({0})</value>
|
||||
</data>
|
||||
<data name="UsePreviousData" xml:space="preserve">
|
||||
<value>要使用上一次获取到的成就数据吗? (yes|no)</value>
|
||||
<value>要使用上一次获取到的成就数据吗?</value>
|
||||
</data>
|
||||
<data name="NetworkError" xml:space="preserve">
|
||||
<value>网络错误: {0}</value>
|
||||
@@ -114,6 +106,72 @@
|
||||
<value>网络错误,请检查网络后重试 ({0}: {1})</value>
|
||||
</data>
|
||||
<data name="GenshinHashError" xml:space="preserve">
|
||||
<value>请将原神更新至最新版本后重试</value>
|
||||
<value>当前适配版本不匹配,请更新原神至最新版本后重试或等待工具更新。</value>
|
||||
</data>
|
||||
<data name="NoWritePermission" xml:space="preserve">
|
||||
<value>无法写入文件,请更换软件所在目录后重试</value>
|
||||
</data>
|
||||
<data name="ExportToTauriFail" xml:space="preserve">
|
||||
<value>启动 Teyvat Guide 或更新 Teyvat Guide 至最新版本后重试</value>
|
||||
</data>
|
||||
<data name="ExportToTauriSuccess" xml:space="preserve">
|
||||
<value>在 Teyvat Guide 进行下一步操作</value>
|
||||
</data>
|
||||
<data name="WaitMetadataUpdate" xml:space="preserve">
|
||||
<value>当前元数据版本不匹配,请更新原神至最新版本或等待元数据更新后重试。</value>
|
||||
</data>
|
||||
<data name="UpdateChecking" xml:space="preserve">
|
||||
<value>正在检查更新...</value>
|
||||
</data>
|
||||
<data name="CommonYes" xml:space="preserve">
|
||||
<value>是</value>
|
||||
</data>
|
||||
<data name="CommonNo" xml:space="preserve">
|
||||
<value>否</value>
|
||||
</data>
|
||||
<data name="ExportTargetCocogoat" xml:space="preserve">
|
||||
<value>椰羊 (https://cocogoat.work/achievement)</value>
|
||||
</data>
|
||||
<data name="ExportTargetHuTao" xml:space="preserve">
|
||||
<value>Snap Hutao</value>
|
||||
</data>
|
||||
<data name="ExportTargetPaimon" xml:space="preserve">
|
||||
<value>Paimon.moe</value>
|
||||
</data>
|
||||
<data name="ExportTargetSeelie" xml:space="preserve">
|
||||
<value>Seelie.me</value>
|
||||
</data>
|
||||
<data name="ExportTargetCsv" xml:space="preserve">
|
||||
<value>表格文件</value>
|
||||
</data>
|
||||
<data name="ExportTargetXunkong" xml:space="preserve">
|
||||
<value>寻空</value>
|
||||
</data>
|
||||
<data name="ExportTargetUIAFJson" xml:space="preserve">
|
||||
<value>UIAF JSON 文件</value>
|
||||
</data>
|
||||
<data name="ExportTargetTeyvatGuide" xml:space="preserve">
|
||||
<value>Teyvat Guide</value>
|
||||
</data>
|
||||
<data name="ExportTargetWxApp1" xml:space="preserve">
|
||||
<value>原魔工具箱</value>
|
||||
</data>
|
||||
<data name="SelectionPromptCompatChooseOne" xml:space="preserve">
|
||||
<value>选择一个选项:</value>
|
||||
</data>
|
||||
<data name="SelectionPromptCompatAnsiTip" xml:space="preserve">
|
||||
<value>键盘上下键移动光标,键盘回车键选择</value>
|
||||
</data>
|
||||
<data name="SelectionPromptCompatNonAnsiTip" xml:space="preserve">
|
||||
<value>输入数字并回车以选择选项</value>
|
||||
</data>
|
||||
<data name="SelectionPromptCompatInvalidChoice" xml:space="preserve">
|
||||
<value>请输入 0 到 {0} 之间的数字</value>
|
||||
</data>
|
||||
<data name="StreamReadDataFail" xml:space="preserve">
|
||||
<value>读取数据时发生错误,请重试</value>
|
||||
</data>
|
||||
<data name="UpdateFileShareViolation" xml:space="preserve">
|
||||
<value>文件 {0} 被其它程序占用,请 重启电脑 或 解除文件占用 后重试。</value>
|
||||
</data>
|
||||
</root>
|
||||
BIN
YaeAchievement/res/Updater.exe
Normal file
BIN
YaeAchievement/res/Updater.exe
Normal file
Binary file not shown.
@@ -49,7 +49,7 @@
|
||||
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
|
||||
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
|
||||
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
|
||||
|
||||
|
||||
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
25
YaeAchievement/res/proto/AchievementInfo.proto
Normal file
25
YaeAchievement/res/proto/AchievementInfo.proto
Normal file
@@ -0,0 +1,25 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "Proto";
|
||||
|
||||
message AchievementProtoFieldInfo {
|
||||
uint32 id = 1;
|
||||
uint32 status = 2;
|
||||
uint32 total_progress = 3;
|
||||
uint32 current_progress = 4;
|
||||
uint32 finish_timestamp = 5;
|
||||
}
|
||||
|
||||
message AchievementItem {
|
||||
uint32 pre = 1;
|
||||
uint32 group = 2;
|
||||
string name = 3;
|
||||
string description = 4;
|
||||
}
|
||||
|
||||
message AchievementInfo {
|
||||
string version = 1;
|
||||
map<uint32, string> group = 2;
|
||||
map<uint32, AchievementItem> items = 3;
|
||||
AchievementProtoFieldInfo pb_info = 4;
|
||||
}
|
||||
10
YaeAchievement/res/proto/CacheItem.proto
Normal file
10
YaeAchievement/res/proto/CacheItem.proto
Normal file
@@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "Proto";
|
||||
|
||||
message CacheItem {
|
||||
uint32 version = 1;
|
||||
string checksum = 2;
|
||||
string etag = 3;
|
||||
bytes content = 4;
|
||||
}
|
||||
79
YaeAchievement/res/proto/StoreData.proto
Normal file
79
YaeAchievement/res/proto/StoreData.proto
Normal file
@@ -0,0 +1,79 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "Proto";
|
||||
|
||||
enum StoreType {
|
||||
STORE_TYPE_NONE = 0;
|
||||
STORE_TYPE_PACK = 1;
|
||||
STORE_TYPE_DEPOT = 2;
|
||||
}
|
||||
|
||||
message MaterialDeleteInfo {
|
||||
message CountDownDelete {
|
||||
map<uint32, uint32> delete_time_num_map = 1;
|
||||
uint32 config_count_down_time = 2;
|
||||
}
|
||||
message DateTimeDelete {
|
||||
uint32 delete_time = 1;
|
||||
}
|
||||
message DelayWeekCountDownDelete {
|
||||
map<uint32, uint32> delete_time_num_map = 1;
|
||||
uint32 config_delay_week = 2;
|
||||
uint32 config_count_down_time = 3;
|
||||
}
|
||||
bool has_delete_config = 1;
|
||||
oneof delete_info {
|
||||
CountDownDelete count_down_delete = 2;
|
||||
DateTimeDelete date_delete = 3;
|
||||
DelayWeekCountDownDelete delay_week_count_down_delete = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message Material {
|
||||
uint32 count = 1;
|
||||
MaterialDeleteInfo delete_info = 2;
|
||||
}
|
||||
|
||||
message Reliquary {
|
||||
uint32 level = 1;
|
||||
uint32 exp = 2;
|
||||
uint32 promote_level = 3;
|
||||
uint32 main_prop_id = 4;
|
||||
repeated uint32 append_prop_id_list = 5;
|
||||
bool is_marked = 6;
|
||||
}
|
||||
|
||||
message Weapon {
|
||||
uint32 level = 1;
|
||||
uint32 exp = 2;
|
||||
uint32 promote_level = 3;
|
||||
map<uint32, uint32> affix_map = 4;
|
||||
bool is_arkhe_ousia = 5;
|
||||
}
|
||||
|
||||
message Equip {
|
||||
oneof detail {
|
||||
Reliquary reliquary = 1;
|
||||
Weapon weapon = 2;
|
||||
}
|
||||
bool is_locked = 3;
|
||||
}
|
||||
|
||||
message Furniture {
|
||||
uint32 count = 1;
|
||||
}
|
||||
|
||||
message VirtualItem {
|
||||
int64 count = 1;
|
||||
}
|
||||
|
||||
message Item {
|
||||
uint32 item_id = 1;
|
||||
uint64 guid = 2;
|
||||
oneof detail {
|
||||
Material material = 5;
|
||||
Equip equip = 6;
|
||||
Furniture furniture = 7;
|
||||
VirtualItem virtual_item = 255;
|
||||
}
|
||||
}
|
||||
15
YaeAchievement/res/proto/UpdateInfo.proto
Normal file
15
YaeAchievement/res/proto/UpdateInfo.proto
Normal file
@@ -0,0 +1,15 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "Proto";
|
||||
|
||||
message UpdateInfo {
|
||||
uint32 version_code = 1;
|
||||
string version_name = 2;
|
||||
string description = 3;
|
||||
string package_link = 4;
|
||||
bool force_update = 5;
|
||||
bool enable_lib_download = 6;
|
||||
bool enable_auto_update = 7;
|
||||
string current_cn_hash = 8;
|
||||
string current_os_hash = 9;
|
||||
}
|
||||
97
YaeAchievement/src/AppConfig.cs
Normal file
97
YaeAchievement/src/AppConfig.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.Utilities;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static partial class AppConfig {
|
||||
|
||||
public static string GamePath { get; private set; } = null!;
|
||||
|
||||
private static readonly string[] ProductNames = [ "原神", "Genshin Impact" ];
|
||||
|
||||
internal static void Load(string argumentPath) {
|
||||
if (argumentPath != "auto" && File.Exists(argumentPath)) {
|
||||
GamePath = argumentPath;
|
||||
} else if (TryReadGamePathFromCache(out var cachedPath)) {
|
||||
GamePath = cachedPath;
|
||||
} else if (TryReadGamePathFromUnityLog(out var loggedPath)) {
|
||||
GamePath = loggedPath;
|
||||
} else {
|
||||
GamePath = ReadGamePathFromProcess();
|
||||
}
|
||||
Span<byte> buffer = stackalloc byte[0x10000];
|
||||
using var stream = File.OpenRead(GamePath);
|
||||
if (stream.Read(buffer) == buffer.Length) {
|
||||
var hash = Convert.ToHexString(MD5.HashData(buffer));
|
||||
CacheFile.Write("genshin_impact_game_path_v2", Encoding.UTF8.GetBytes($"{GamePath}\u1145{hash}"));
|
||||
}
|
||||
SentrySdk.AddBreadcrumb(GamePath.EndsWith("YuanShen.exe") ? "CN" : "OS", "GamePath");
|
||||
return;
|
||||
static bool TryReadGamePathFromCache([NotNullWhen(true)] out string? path) {
|
||||
path = null;
|
||||
try {
|
||||
if (!CacheFile.TryRead("genshin_impact_game_path_v2", out var cacheFile)) {
|
||||
return false;
|
||||
}
|
||||
var cacheData = cacheFile.Content.ToStringUtf8().Split("\u1145");
|
||||
Span<byte> buffer = stackalloc byte[0x10000];
|
||||
using var stream = File.OpenRead(cacheData[0]);
|
||||
if (stream.Read(buffer) != buffer.Length || Convert.ToHexString(MD5.HashData(buffer)) != cacheData[1]) {
|
||||
return false;
|
||||
}
|
||||
path = cacheData[0];
|
||||
return true;
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static bool TryReadGamePathFromUnityLog([NotNullWhen(true)] out string? path) {
|
||||
path = null;
|
||||
try {
|
||||
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var logPath = ProductNames
|
||||
.Select(name => $"{appDataPath}/../LocalLow/miHoYo/{name}/output_log.txt")
|
||||
.Where(File.Exists)
|
||||
.MaxBy(File.GetLastWriteTime);
|
||||
if (logPath == null) {
|
||||
return false;
|
||||
}
|
||||
return (path = GetGamePathFromLogFile(logPath) ?? GetGamePathFromLogFile($"{logPath}.last")) != null;
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static string ReadGamePathFromProcess() {
|
||||
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).Start(App.ConfigNeedStartGenshin, _ => {
|
||||
Process? proc;
|
||||
while ((proc = Utils.GetGameProcess()) == null) {
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
var fileName = proc.GetFileName()!;
|
||||
proc.Kill();
|
||||
return fileName;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetGamePathFromLogFile(string path) {
|
||||
if (!File.Exists(path)) {
|
||||
return null;
|
||||
}
|
||||
var content = File.ReadAllText(path);
|
||||
var matchResult = GamePathRegex().Match(content);
|
||||
if (!matchResult.Success) {
|
||||
return null;
|
||||
}
|
||||
var entryName = matchResult.Groups["1"].Value.Replace("_Data", ".exe");
|
||||
return Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"(?m).:(?:\\|/).+(GenshinImpact_Data|YuanShen_Data)", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex GamePathRegex();
|
||||
|
||||
}
|
||||
237
YaeAchievement/src/Export.cs
Normal file
237
YaeAchievement/src/Export.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System.ComponentModel;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Win32;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.Outputs;
|
||||
using YaeAchievement.Parsers;
|
||||
using YaeAchievement.Utilities;
|
||||
|
||||
// ReSharper disable UnusedMember.Local
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class Export {
|
||||
|
||||
public static int ExportTo { get; set; } = 114514;
|
||||
|
||||
public static void Choose(AchievementAllDataNotify data) {
|
||||
var targets = new Dictionary<string, Action<AchievementAllDataNotify>> {
|
||||
{ App.ExportTargetCocogoat, ToCocogoat },
|
||||
{ App.ExportTargetHuTao, ToHuTao },
|
||||
{ App.ExportTargetPaimon, ToPaimon },
|
||||
{ App.ExportTargetSeelie, ToSeelie },
|
||||
{ App.ExportTargetCsv, ToCSV },
|
||||
{ App.ExportTargetXunkong, ToXunkong },
|
||||
// { App.ExportTargetWxApp1, ToWxApp1 },
|
||||
{ App.ExportTargetTeyvatGuide, ToTeyvatGuide },
|
||||
{ App.ExportTargetUIAFJson, ToUIAFJson },
|
||||
// { "", ToRawJson }
|
||||
};
|
||||
Action<AchievementAllDataNotify> action;
|
||||
if (ExportTo == 114514) {
|
||||
var prompt = new SelectionPromptCompat<string>().Title(App.ExportChoose).AddChoices(targets.Keys);
|
||||
action = targets[prompt.Prompt()];
|
||||
} else {
|
||||
action = targets.ElementAtOrDefault(ExportTo).Value ?? ToCocogoat;
|
||||
}
|
||||
action(data);
|
||||
}
|
||||
|
||||
private static void ToCocogoat(AchievementAllDataNotify data) {
|
||||
var result = UIAFSerializer.Serialize(data);
|
||||
using var request = new HttpRequestMessage();
|
||||
request.Method = HttpMethod.Post;
|
||||
request.RequestUri = new Uri($"https://77.cocogoat.cn/v1/memo?source={App.AllAchievement}");
|
||||
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
|
||||
using var response = Utils.CHttpClient.Send(request);
|
||||
if (response.StatusCode != HttpStatusCode.Created) {
|
||||
AnsiConsole.WriteLine(App.ExportToCocogoatFail);
|
||||
return;
|
||||
}
|
||||
var responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
var responseJson = JsonSerializer.Deserialize(responseText, CocogoatResponseContext.Default.CocogoatResponse)!;
|
||||
var cocogoatUrl = $"https://cocogoat.work/achievement?memo={responseJson.Key}";
|
||||
Utils.SetQuickEditMode(true);
|
||||
AnsiConsole.MarkupLineInterpolated($"[link]{cocogoatUrl}[/]");
|
||||
if (Utils.ShellOpen(cocogoatUrl))
|
||||
{
|
||||
AnsiConsole.WriteLine(App.ExportToCocogoatSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToWxApp1(AchievementAllDataNotify data) {
|
||||
var id = Guid.NewGuid().ToString("N").Substring(20, 8);
|
||||
var result = WxApp1Serializer.Serialize(data, id);
|
||||
using var request = new HttpRequestMessage();
|
||||
request.Method = HttpMethod.Post;
|
||||
request.RequestUri = new Uri("https://api.qyinter.com/achievementRedis");
|
||||
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
|
||||
using var response = Utils.CHttpClient.Send(request);
|
||||
AnsiConsole.WriteLine(App.ExportToWxApp1Success, id);
|
||||
}
|
||||
|
||||
private static void ToHuTao(AchievementAllDataNotify data) {
|
||||
if (CheckWinUIAppScheme("hutao")) {
|
||||
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
|
||||
Utils.ShellOpen("hutao://achievement/import");
|
||||
AnsiConsole.WriteLine(App.ExportToSnapGenshinSuccess);
|
||||
} else {
|
||||
AnsiConsole.WriteLine(App.ExportToSnapGenshinNeedUpdate);
|
||||
Utils.ShellOpen("ms-windows-store://pdp/?productid=9PH4NXJ2JN52");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToXunkong(AchievementAllDataNotify data) {
|
||||
if (CheckWinUIAppScheme("xunkong")) {
|
||||
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
|
||||
Utils.ShellOpen("xunkong://import-achievement?caller=YaeAchievement&from=clipboard");
|
||||
AnsiConsole.WriteLine(App.ExportToXunkongSuccess);
|
||||
} else {
|
||||
AnsiConsole.WriteLine(App.ExportToXunkongNeedUpdate);
|
||||
Utils.ShellOpen("ms-windows-store://pdp/?productid=9N2SVG0JMT12");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToTeyvatGuide(AchievementAllDataNotify data) {
|
||||
if (Process.GetProcessesByName("TeyvatGuide").Length != 0) {
|
||||
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
|
||||
Utils.ShellOpen("teyvatguide://import_uiaf?app=Yae");
|
||||
AnsiConsole.WriteLine(App.ExportToTauriSuccess);
|
||||
} else {
|
||||
AnsiConsole.WriteLine(App.ExportToTauriFail);
|
||||
Utils.ShellOpen("ms-windows-store://pdp/?productid=9NLBNNNBNSJN");
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static void ToUIAFJson(AchievementAllDataNotify data) {
|
||||
var path = Path.GetFullPath($"uiaf-{DateTime.Now:yyyyMMddHHmmss}.json");
|
||||
if (TryWriteToFile(path, UIAFSerializer.Serialize(data))) {
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToPaimon(AchievementAllDataNotify data) {
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json");
|
||||
if (TryWriteToFile(path, PaimonSerializer.Serialize(data))) {
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToSeelie(AchievementAllDataNotify data) {
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-seelie.json");
|
||||
if (TryWriteToFile(path, SeelieSerializer.Serialize(data))) {
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static void ToCSV(AchievementAllDataNotify data) {
|
||||
var info = GlobalVars.AchievementInfo;
|
||||
var outList = new List<List<object>>();
|
||||
foreach (var ach in data.AchievementList.OrderBy(a => a.Id)) {
|
||||
if (UnusedAchievement.Contains(ach.Id)) continue;
|
||||
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
|
||||
AnsiConsole.WriteLine($@"Unable to find {ach.Id} in metadata.");
|
||||
continue;
|
||||
}
|
||||
var finishAt = "";
|
||||
if (ach.FinishTimestamp != 0) {
|
||||
var ts = Convert.ToInt64(ach.FinishTimestamp);
|
||||
finishAt = DateTimeOffset.FromUnixTimeSeconds(ts).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
|
||||
}
|
||||
var current = ach.Status != AchievementStatus.Unfinished
|
||||
? ach.CurrentProgress == 0 ? ach.TotalProgress : ach.CurrentProgress
|
||||
: ach.CurrentProgress;
|
||||
outList.Add([
|
||||
ach.Id, ach.Status.ToDesc(), achInfo.Group, achInfo.Name,
|
||||
achInfo.Description, current, ach.TotalProgress, finishAt
|
||||
]);
|
||||
}
|
||||
var output = new List<string> { "ID,状态,特辑,名称,描述,当前进度,目标进度,完成时间" };
|
||||
output.AddRange(outList.OrderBy(v => v[2]).Select(item => {
|
||||
item[2] = info.Group[(uint) item[2]];
|
||||
return item.JoinToString(",");
|
||||
}));
|
||||
var path = Path.GetFullPath($"achievement-{DateTime.Now:yyyyMMddHHmmss}.csv");
|
||||
if (TryWriteToFile(path, $"\uFEFF{string.Join("\n", output)}")) {
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
Process.Start("explorer.exe", $"{Path.GetDirectoryName(path)}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToRawJson(AchievementAllDataNotify data) {
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
|
||||
var text = AchievementRawDataSerializer.Serialize(data);
|
||||
if (TryWriteToFile(path, text)) {
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static bool CheckWinUIAppScheme(string protocol) {
|
||||
return (string?)Registry.ClassesRoot.OpenSubKey(protocol)?.GetValue("") == $"URL:{protocol}";
|
||||
}
|
||||
|
||||
private static string JoinToString(this IEnumerable<object> list, string separator) {
|
||||
return string.Join(separator, list);
|
||||
}
|
||||
|
||||
private static readonly List<uint> UnusedAchievement = [ 84517 ];
|
||||
|
||||
private static string ToDesc(this AchievementStatus status) {
|
||||
return status switch {
|
||||
AchievementStatus.Invalid => App.StatusInvalid,
|
||||
AchievementStatus.Finished => App.StatusFinished,
|
||||
AchievementStatus.Unfinished => App.StatusUnfinished,
|
||||
AchievementStatus.RewardTaken => App.StatusRewardTaken,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static int PrintMsgAndReturnErrCode(this Win32Exception ex, string msg) {
|
||||
// ReSharper disable once LocalizableElement
|
||||
AnsiConsole.WriteLine($"{msg}: {ex.Message}");
|
||||
return ex.NativeErrorCode;
|
||||
}
|
||||
|
||||
private static bool TryWriteToFile(string path, string contents) {
|
||||
try {
|
||||
File.WriteAllText(path, contents);
|
||||
return true;
|
||||
} catch (UnauthorizedAccessException) {
|
||||
AnsiConsole.WriteLine(App.NoWritePermission, path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WxApp1Root {
|
||||
|
||||
public string Key { get; init; } = null!;
|
||||
|
||||
public UIAFRoot Data { get; init; } = null!;
|
||||
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(WxApp1Root))]
|
||||
[JsonSourceGenerationOptions(
|
||||
GenerationMode = JsonSourceGenerationMode.Serialization,
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
|
||||
)]
|
||||
public sealed partial class WxApp1Serializer : JsonSerializerContext {
|
||||
|
||||
public static string Serialize(AchievementAllDataNotify ntf, string key) => JsonSerializer.Serialize(new WxApp1Root {
|
||||
Key = key,
|
||||
Data = Outputs.UIAFRoot.FromNotify(ntf)
|
||||
}, Default.WxApp1Root);
|
||||
}
|
||||
|
||||
public sealed record CocogoatResponse(string Key);
|
||||
|
||||
[JsonSerializable(typeof(CocogoatResponse))]
|
||||
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
||||
public sealed partial class CocogoatResponseContext : JsonSerializerContext;
|
||||
@@ -1,33 +1,35 @@
|
||||
using System.Reflection;
|
||||
global using System.Diagnostics;
|
||||
global using YaeAchievement.res;
|
||||
|
||||
namespace YaeAchievement;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Proto;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable ConvertToConstant.Global
|
||||
// ReSharper disable FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class GlobalVars {
|
||||
|
||||
public static bool DebugProxy => false;
|
||||
public static bool UnexpectedExit { get; set; } = true;
|
||||
|
||||
public static bool PauseOnExit { get; set; } = true;
|
||||
public static Version AppVersion { get; } = Assembly.GetEntryAssembly()!.GetName().Version!;
|
||||
|
||||
|
||||
public static readonly string AppPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string CommonData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
|
||||
public static readonly string DataPath = Path.Combine(CommonData, "Yae");
|
||||
public static readonly string CachePath = Path.Combine(DataPath, "cache");
|
||||
public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll");
|
||||
|
||||
public const uint AppVersionCode = 35;
|
||||
public const string AppVersionName = "2.5";
|
||||
|
||||
|
||||
public const uint AppVersionCode = 238;
|
||||
public const string AppVersionName = "5.6";
|
||||
|
||||
public const string PipeName = "YaeAchievementPipe";
|
||||
public const string BucketHost = "https://cn-cd-1259389942.file.myqcloud.com";
|
||||
|
||||
[field:MaybeNull]
|
||||
public static AchievementInfo AchievementInfo =>
|
||||
field ??= AchievementInfo.Parser.ParseFrom(Utils.GetBucketFile("schicksal/metadata").GetAwaiter().GetResult());
|
||||
|
||||
static GlobalVars() {
|
||||
Directory.CreateDirectory(DataPath);
|
||||
Directory.CreateDirectory(CachePath);
|
||||
}
|
||||
|
||||
}
|
||||
37
YaeAchievement/src/Outputs/Paimon.cs
Normal file
37
YaeAchievement/src/Outputs/Paimon.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using YaeAchievement.Parsers;
|
||||
|
||||
namespace YaeAchievement.Outputs;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable PropertyCanBeMadeInitOnly.Global
|
||||
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
|
||||
|
||||
public sealed class PaimonRoot {
|
||||
|
||||
public Dictionary<uint, Dictionary<uint, bool>> Achievement { get; set; } = null!;
|
||||
|
||||
public static PaimonRoot FromNotify(AchievementAllDataNotify ntf) {
|
||||
var info = GlobalVars.AchievementInfo.Items.ToDictionary(pair => pair.Key, pair => pair.Value.Group);
|
||||
return new PaimonRoot {
|
||||
Achievement = ntf.AchievementList
|
||||
.Where(a => a.Status >= AchievementStatus.Finished && info.ContainsKey(a.Id))
|
||||
.GroupBy(a => info[a.Id], a => a.Id)
|
||||
.OrderBy(g => g.Key)
|
||||
.ToDictionary(g => g.Key, g => g.ToDictionary(id => id, _ => true))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(PaimonRoot))]
|
||||
[JsonSourceGenerationOptions(
|
||||
GenerationMode = JsonSourceGenerationMode.Serialization,
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
|
||||
)]
|
||||
public sealed partial class PaimonSerializer : JsonSerializerContext {
|
||||
|
||||
public static string Serialize(AchievementAllDataNotify ntf) {
|
||||
return JsonSerializer.Serialize(Outputs.PaimonRoot.FromNotify(ntf), Default.PaimonRoot);
|
||||
}
|
||||
}
|
||||
39
YaeAchievement/src/Outputs/Seelie.cs
Normal file
39
YaeAchievement/src/Outputs/Seelie.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using YaeAchievement.Parsers;
|
||||
|
||||
namespace YaeAchievement.Outputs;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable PropertyCanBeMadeInitOnly.Global
|
||||
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
|
||||
|
||||
public sealed class SeelieRoot {
|
||||
|
||||
public sealed class AchievementFinishStatus {
|
||||
|
||||
public bool Done => true;
|
||||
|
||||
}
|
||||
|
||||
public Dictionary<uint, AchievementFinishStatus> Achievements { get; set; } = null!;
|
||||
|
||||
public static SeelieRoot FromNotify(AchievementAllDataNotify ntf) => new () {
|
||||
Achievements = ntf.AchievementList
|
||||
.Where(a => a.Status >= AchievementStatus.Finished)
|
||||
.OrderBy(a => a.Id)
|
||||
.ToDictionary(a => a.Id, _ => new AchievementFinishStatus())
|
||||
};
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(SeelieRoot))]
|
||||
[JsonSourceGenerationOptions(
|
||||
GenerationMode = JsonSourceGenerationMode.Serialization,
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
|
||||
)]
|
||||
public sealed partial class SeelieSerializer : JsonSerializerContext {
|
||||
|
||||
public static string Serialize(AchievementAllDataNotify ntf) {
|
||||
return JsonSerializer.Serialize(Outputs.SeelieRoot.FromNotify(ntf), Default.SeelieRoot);
|
||||
}
|
||||
}
|
||||
65
YaeAchievement/src/Outputs/UIAF.cs
Normal file
65
YaeAchievement/src/Outputs/UIAF.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using YaeAchievement.Parsers;
|
||||
|
||||
namespace YaeAchievement.Outputs;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable PropertyCanBeMadeInitOnly.Global
|
||||
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
|
||||
|
||||
public sealed class UApplicationInfo {
|
||||
|
||||
public string ExportApp => "YaeAchievement";
|
||||
|
||||
public string ExportAppVersion => GlobalVars.AppVersionName;
|
||||
|
||||
public long ExportTimestamp => DateTimeOffset.Now.ToUnixTimeSeconds();
|
||||
|
||||
public string UIAFVersion => "v1.1";
|
||||
|
||||
}
|
||||
|
||||
public sealed class UAchievementInfo {
|
||||
|
||||
public uint Id { get; set; }
|
||||
|
||||
public uint Status { get; set; }
|
||||
|
||||
public uint Current { get; set; }
|
||||
|
||||
public uint Timestamp { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public sealed class UIAFRoot {
|
||||
|
||||
public UApplicationInfo Info => new ();
|
||||
|
||||
public IEnumerable<UAchievementInfo> List { get; set; } = null!;
|
||||
|
||||
public static UIAFRoot FromNotify(AchievementAllDataNotify ntf) => new () {
|
||||
List = ntf.AchievementList
|
||||
.Where(a => a.Status >= AchievementStatus.Finished || a.CurrentProgress > 0)
|
||||
.Select(a => new UAchievementInfo {
|
||||
Id = a.Id,
|
||||
Status = (uint) a.Status,
|
||||
Current = a.CurrentProgress,
|
||||
Timestamp = a.FinishTimestamp
|
||||
})
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(UIAFRoot))]
|
||||
[JsonSourceGenerationOptions(
|
||||
GenerationMode = JsonSourceGenerationMode.Serialization,
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
|
||||
)]
|
||||
public sealed partial class UIAFSerializer : JsonSerializerContext {
|
||||
|
||||
public static string Serialize(AchievementAllDataNotify ntf) {
|
||||
return JsonSerializer.Serialize(Outputs.UIAFRoot.FromNotify(ntf), Default.UIAFRoot);
|
||||
}
|
||||
}
|
||||
151
YaeAchievement/src/Parsers/AchievementAllDataNotify.cs
Normal file
151
YaeAchievement/src/Parsers/AchievementAllDataNotify.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Google.Protobuf;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.Utilities;
|
||||
|
||||
namespace YaeAchievement.Parsers;
|
||||
|
||||
public enum AchievementStatus {
|
||||
Invalid,
|
||||
Unfinished,
|
||||
Finished,
|
||||
RewardTaken,
|
||||
}
|
||||
|
||||
public sealed class AchievementItem {
|
||||
|
||||
public uint Id { get; init; }
|
||||
public uint TotalProgress { get; init; }
|
||||
public uint CurrentProgress { get; init; }
|
||||
public uint FinishTimestamp { get; init; }
|
||||
public AchievementStatus Status { get; init; }
|
||||
|
||||
}
|
||||
|
||||
public sealed class AchievementAllDataNotify {
|
||||
|
||||
public List<AchievementItem> AchievementList { get; private init; } = [];
|
||||
|
||||
private static AchievementAllDataNotify? Instance { get; set; }
|
||||
|
||||
public static bool OnReceive(BinaryReader reader) {
|
||||
var bytes = reader.ReadBytes();
|
||||
CacheFile.Write("achievement_data", bytes);
|
||||
Instance = ParseFrom(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void OnFinish() {
|
||||
if (Instance == null) {
|
||||
throw new ApplicationException("No data received");
|
||||
}
|
||||
Export.Choose(Instance);
|
||||
}
|
||||
|
||||
public static AchievementAllDataNotify ParseFrom(byte[] bytes) {
|
||||
using var stream = new CodedInputStream(bytes);
|
||||
var data = new List<Dictionary<uint, uint>>();
|
||||
var errTimes = 0;
|
||||
try {
|
||||
uint tag;
|
||||
while ((tag = stream.ReadTag()) != 0) {
|
||||
if ((tag & 7) == 2) { // is LengthDelimited
|
||||
var dict = new Dictionary<uint, uint>();
|
||||
using var eStream = stream.ReadLengthDelimitedAsStream();
|
||||
try {
|
||||
while ((tag = eStream.ReadTag()) != 0) {
|
||||
if ((tag & 7) != 0) { // not VarInt
|
||||
dict = null;
|
||||
break;
|
||||
}
|
||||
dict[tag >> 3] = eStream.ReadUInt32();
|
||||
}
|
||||
if (dict is { Count: > 2 }) { // at least 3 fields
|
||||
data.Add(dict);
|
||||
}
|
||||
} catch (InvalidProtocolBufferException) {
|
||||
if (errTimes++ > 0) { // allows 1 fail on 'reward_taken_goal_id_list'
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InvalidProtocolBufferException) {
|
||||
// ReSharper disable once LocalizableElement
|
||||
AnsiConsole.WriteLine("Parse failed");
|
||||
File.WriteAllBytes("achievement_raw_data.bin", bytes);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
if (data.Count == 0) {
|
||||
return new AchievementAllDataNotify();
|
||||
}
|
||||
uint tId, sId, iId, currentId, totalId;
|
||||
if (data.All(CheckKnownFieldIdIsValid)) {
|
||||
var info = GlobalVars.AchievementInfo.PbInfo;
|
||||
iId = info.Id;
|
||||
tId = info.FinishTimestamp;
|
||||
sId = info.Status;
|
||||
totalId = info.TotalProgress;
|
||||
currentId = info.CurrentProgress;
|
||||
} else if (data.Count > 20) {
|
||||
(tId, var cnt) = data // ↓ 2020-09-15 04:15:14
|
||||
.GroupKeys(value => value > 1600114514).Select(g => (g.Key, g.Count())).MaxBy(p => p.Item2);
|
||||
sId = data // FINISHED ↓ ↓ REWARD_TAKEN
|
||||
.GroupKeys(value => value is 2 or 3).First(g => g.Count() == cnt).Key;
|
||||
iId = data // ↓ id: 8xxxx
|
||||
.GroupKeys(value => value / 10000 % 10 == 8).MaxBy(g => g.Count())!.Key;
|
||||
(currentId, totalId) = data
|
||||
.Where(d => d[sId] is 2 or 3)
|
||||
.Select(d => d.ToDictionary().RemoveValues(tId, sId, iId).ToArray())
|
||||
.Where(d => d.Length == 2 && d[0].Value != d[1].Value)
|
||||
.GroupBy(a => a[0].Value > a[1].Value ? (a[0].Key, a[1].Key) : (a[1].Key, a[0].Key))
|
||||
.Select(g => (FieldIds: g.Key, Count: g.Count()))
|
||||
.MaxBy(p => p.Count)
|
||||
.FieldIds;
|
||||
#if DEBUG
|
||||
// ReSharper disable once LocalizableElement
|
||||
AnsiConsole.WriteLine($"Id={iId}, Status={sId}, Total={totalId}, Current={currentId}, Timestamp={tId}");
|
||||
#endif
|
||||
} else {
|
||||
AnsiConsole.WriteLine(App.WaitMetadataUpdate);
|
||||
Environment.Exit(0);
|
||||
return null!;
|
||||
}
|
||||
return new AchievementAllDataNotify {
|
||||
AchievementList = data.Select(dict => new AchievementItem {
|
||||
Id = dict[iId],
|
||||
Status = (AchievementStatus) dict[sId],
|
||||
TotalProgress = dict[totalId],
|
||||
CurrentProgress = dict.GetValueOrDefault(currentId),
|
||||
FinishTimestamp = dict.GetValueOrDefault(tId),
|
||||
}).ToList()
|
||||
};
|
||||
// ReSharper disable once ConvertIfStatementToSwitchStatement
|
||||
static bool CheckKnownFieldIdIsValid(Dictionary<uint, uint> data) {
|
||||
var info = GlobalVars.AchievementInfo;
|
||||
var status = data.GetValueOrDefault(info.PbInfo.Status, 114514u);
|
||||
if (status is 0 or > 3) {
|
||||
return false;
|
||||
}
|
||||
if (status > 1 && data.GetValueOrDefault(info.PbInfo.FinishTimestamp) < 1600114514) { // 2020-09-15 04:15:14
|
||||
return false;
|
||||
}
|
||||
return info.Items.ContainsKey(data.GetValueOrDefault(info.PbInfo.Id));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(AchievementAllDataNotify))]
|
||||
[JsonSourceGenerationOptions(
|
||||
WriteIndented = true,
|
||||
GenerationMode = JsonSourceGenerationMode.Serialization,
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
|
||||
)]
|
||||
public sealed partial class AchievementRawDataSerializer : JsonSerializerContext {
|
||||
|
||||
public static string Serialize(AchievementAllDataNotify ntf) {
|
||||
return JsonSerializer.Serialize(ntf, Default.AchievementAllDataNotify);
|
||||
}
|
||||
}
|
||||
106
YaeAchievement/src/Parsers/PlayerPropNotify.cs
Normal file
106
YaeAchievement/src/Parsers/PlayerPropNotify.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Proto;
|
||||
|
||||
using static YaeAchievement.Parsers.PropType;
|
||||
|
||||
namespace YaeAchievement.Parsers;
|
||||
|
||||
public enum PropType {
|
||||
None = 0,
|
||||
Exp = 1001,
|
||||
BreakLevel = 1002,
|
||||
SatiationVal = 1003,
|
||||
SatiationPenaltyTime = 1004,
|
||||
GearStartVal = 2001,
|
||||
GearStopVal = 2002,
|
||||
Level = 4001,
|
||||
LastChangeAvatarTime = 10001,
|
||||
MaxSpringVolume = 10002,
|
||||
CurSpringVolume = 10003,
|
||||
IsSpringAutoUse = 10004,
|
||||
SpringAutoUsePercent = 10005,
|
||||
IsFlyable = 10006,
|
||||
IsWeatherLocked = 10007,
|
||||
IsGameTimeLocked = 10008,
|
||||
IsTransferable = 10009,
|
||||
MaxStamina = 10010,
|
||||
CurPersistStamina = 10011,
|
||||
CurTemporaryStamina = 10012,
|
||||
PlayerLevel = 10013,
|
||||
PlayerExp = 10014,
|
||||
PlayerHCoin = 10015,
|
||||
PlayerSCoin = 10016,
|
||||
PlayerMpSettingType = 10017,
|
||||
IsMpModeAvailable = 10018,
|
||||
PlayerWorldLevel = 10019,
|
||||
PlayerResin = 10020,
|
||||
PlayerWaitSubHCoin = 10022,
|
||||
PlayerWaitSubSCoin = 10023,
|
||||
IsOnlyMpWithPsPlayer = 10024,
|
||||
PlayerMCoin = 10025,
|
||||
PlayerWaitSubMCoin = 10026,
|
||||
PlayerLegendaryKey = 10027,
|
||||
IsHasFirstShare = 10028,
|
||||
PlayerForgePoint = 10029,
|
||||
CurClimateMeter = 10035,
|
||||
CurClimateType = 10036,
|
||||
CurClimateAreaId = 10037,
|
||||
CurClimateAreaClimateType = 10038,
|
||||
PlayerWorldLevelLimit = 10039,
|
||||
PlayerWorldLevelAdjustCd = 10040,
|
||||
PlayerLegendaryDailyTaskNum = 10041,
|
||||
PlayerHomeCoin = 10042,
|
||||
PlayerWaitSubHomeCoin = 10043,
|
||||
IsAutoUnlockSpecificEquip = 10044,
|
||||
PlayerGCGCoin = 10045,
|
||||
PlayerWaitSubGCGCoin = 10046,
|
||||
PlayerOnlineTime = 10047,
|
||||
IsDiveable = 10048,
|
||||
MaxDiveStamina = 10049,
|
||||
CurPersistDiveStamina = 10050,
|
||||
IsCanPutFiveStarReliquary = 10051,
|
||||
IsAutoLockFiveStarReliquary = 10052,
|
||||
PlayerRoleCombatCoin = 10053,
|
||||
CurPhlogiston = 10054,
|
||||
ReliquaryTemporaryExp = 10055,
|
||||
IsMpCrossPlatformEnabled = 10056,
|
||||
IsOnlyMpWithPlatformPlayer = 10057,
|
||||
PlayerMusicGameBookCoin = 10058,
|
||||
IsNotShowReliquaryRecommendProp = 10059,
|
||||
}
|
||||
|
||||
public static class PlayerPropNotify {
|
||||
|
||||
private static readonly Dictionary<PropType, double> PropMap = [];
|
||||
|
||||
public static bool OnReceive(BinaryReader reader) {
|
||||
var propType = (PropType) reader.ReadInt32();
|
||||
var propValue = reader.ReadDouble();
|
||||
PropMap.Add(propType, propValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void OnFinish() {
|
||||
PlayerStoreNotify.Instance.ItemList.AddRange([
|
||||
CreateVirtualItem(201, GetPropValue(PlayerHCoin) - GetPropValue(PlayerWaitSubHCoin)),
|
||||
CreateVirtualItem(202, GetPropValue(PlayerSCoin) - GetPropValue(PlayerWaitSubSCoin)),
|
||||
CreateVirtualItem(203, GetPropValue(PlayerMCoin) - GetPropValue(PlayerWaitSubMCoin)),
|
||||
CreateVirtualItem(204, GetPropValue(PlayerHomeCoin) - GetPropValue(PlayerWaitSubHomeCoin)),
|
||||
CreateVirtualItem(206, GetPropValue(PlayerRoleCombatCoin)),
|
||||
CreateVirtualItem(207, GetPropValue(PlayerMusicGameBookCoin)),
|
||||
]);
|
||||
}
|
||||
|
||||
private static Item CreateVirtualItem(uint id, double count) {
|
||||
return new Item {
|
||||
ItemId = id,
|
||||
VirtualItem = new VirtualItem {
|
||||
Count = (long) count
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static double GetPropValue(PropType propType) {
|
||||
return PropMap.GetValueOrDefault(propType);
|
||||
}
|
||||
|
||||
}
|
||||
61
YaeAchievement/src/Parsers/PlayerStoreNotify.cs
Normal file
61
YaeAchievement/src/Parsers/PlayerStoreNotify.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Google.Protobuf;
|
||||
using Proto;
|
||||
using Spectre.Console;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable CollectionNeverQueried.Global
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
|
||||
|
||||
namespace YaeAchievement.Parsers;
|
||||
|
||||
public sealed class PlayerStoreNotify {
|
||||
|
||||
public uint WeightLimit { get; set; }
|
||||
|
||||
public StoreType StoreType { get; set; }
|
||||
|
||||
public List<Item> ItemList { get; set; } = [];
|
||||
|
||||
public static PlayerStoreNotify Instance { get; } = new ();
|
||||
|
||||
public static bool OnReceive(BinaryReader reader) {
|
||||
var bytes = reader.ReadBytes();
|
||||
Instance.ParseFrom(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ParseFrom(byte[] bytes) {
|
||||
using var stream = new CodedInputStream(bytes);
|
||||
try {
|
||||
uint tag;
|
||||
while ((tag = stream.ReadTag()) != 0) {
|
||||
var wireType = tag & 7;
|
||||
switch (wireType) {
|
||||
case 0: { // is VarInt
|
||||
var value = stream.ReadUInt32();
|
||||
if (value < 10) {
|
||||
StoreType = (StoreType) value;
|
||||
} else {
|
||||
WeightLimit = value;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case 2: { // is LengthDelimited
|
||||
using var eStream = stream.ReadLengthDelimitedAsStream();
|
||||
while (eStream.PeekTag() != 0) {
|
||||
ItemList.Add(Item.Parser.ParseFrom(eStream));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InvalidProtocolBufferException) {
|
||||
// ReSharper disable once LocalizableElement
|
||||
AnsiConsole.WriteLine("Parse failed");
|
||||
File.WriteAllBytes("store_raw_data.bin", bytes);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
97
YaeAchievement/src/Program.cs
Normal file
97
YaeAchievement/src/Program.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.Parsers;
|
||||
using YaeAchievement.Utilities;
|
||||
using static YaeAchievement.Utils;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
// TODO: WndHook
|
||||
|
||||
internal static class Program {
|
||||
|
||||
public static async Task Main(string[] args) {
|
||||
|
||||
AnsiConsole.WriteLine(@"----------------------------------------------------");
|
||||
AnsiConsole.WriteLine(App.AppBanner, GlobalVars.AppVersionName);
|
||||
AnsiConsole.WriteLine(@"https://github.com/HolographicHat/YaeAchievement");
|
||||
AnsiConsole.WriteLine(@"----------------------------------------------------");
|
||||
|
||||
if (!new Mutex(true, @"Global\YaeMiku~uwu").WaitOne(0, false)) {
|
||||
AnsiConsole.WriteLine(App.AnotherInstance);
|
||||
Environment.Exit(302);
|
||||
}
|
||||
SentrySdk.Init(options => {
|
||||
options.Dsn = "https://92f11b64b0ef52cabc94f21df0428f5b@sentry.snapgenshin.com/9";
|
||||
#if DEBUG
|
||||
options.Debug = true;
|
||||
#endif
|
||||
options.TracesSampleRate = 1.0;
|
||||
options.AutoSessionTracking = true;
|
||||
options.SetBeforeSend(static e => {
|
||||
e.Release = GlobalVars.AppVersionName;
|
||||
return e;
|
||||
});
|
||||
options.SetBeforeSendTransaction(static e => {
|
||||
e.Release = GlobalVars.AppVersionName;
|
||||
return e;
|
||||
});
|
||||
options.CacheDirectoryPath = GlobalVars.DataPath;
|
||||
});
|
||||
InstallExitHook();
|
||||
InstallExceptionHook();
|
||||
|
||||
if (GetGameProcess() != null) {
|
||||
AnsiConsole.WriteLine(App.GenshinIsRunning, 0);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
await CheckUpdate(ToBooleanOrDefault(args.GetOrNull(2)));
|
||||
|
||||
AppConfig.Load(args.GetOrNull(0) ?? "auto");
|
||||
Export.ExportTo = ToIntOrDefault(args.GetOrNull(1), 114514);
|
||||
|
||||
AchievementAllDataNotify? data = null;
|
||||
try {
|
||||
if (CacheFile.TryRead("achievement_data", out var cache)) {
|
||||
data = AchievementAllDataNotify.ParseFrom(cache.Content.ToByteArray());
|
||||
}
|
||||
} catch (Exception) { /* ignored */ }
|
||||
|
||||
if (CacheFile.GetLastWriteTime("achievement_data").AddMinutes(60) > DateTime.UtcNow && data != null) {
|
||||
var prompt = new SelectionPromptCompat<string>()
|
||||
.Title(App.UsePreviousData)
|
||||
.AddChoices(App.CommonYes, App.CommonNo);
|
||||
if (prompt.Prompt() == App.CommonYes) {
|
||||
Export.Choose(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
StartAndWaitResult(AppConfig.GamePath, new Dictionary<int, Func<BinaryReader, bool>> {
|
||||
{ 1, AchievementAllDataNotify.OnReceive },
|
||||
{ 2, PlayerStoreNotify.OnReceive },
|
||||
{ 100, PlayerPropNotify.OnReceive },
|
||||
}, () => {
|
||||
#if DEBUG_EX
|
||||
PlayerPropNotify.OnFinish();
|
||||
File.WriteAllText("store_data.json", JsonSerializer.Serialize(PlayerStoreNotify.Instance, new JsonSerializerOptions {
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
}));
|
||||
#endif
|
||||
AchievementAllDataNotify.OnFinish();
|
||||
Environment.Exit(0);
|
||||
});
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
[ModuleInitializer]
|
||||
internal static void SetupConsole() {
|
||||
SetQuickEditMode(false);
|
||||
Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8;
|
||||
FixTerminalFont();
|
||||
}
|
||||
|
||||
}
|
||||
53
YaeAchievement/src/Utilities/CacheFile.cs
Normal file
53
YaeAchievement/src/Utilities/CacheFile.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Google.Protobuf;
|
||||
using Proto;
|
||||
|
||||
namespace YaeAchievement.Utilities;
|
||||
|
||||
public static class CacheFile {
|
||||
|
||||
static CacheFile() {
|
||||
// remove deprecated cache
|
||||
foreach (var file in Directory.EnumerateFiles(GlobalVars.CachePath, "*.miko")) {
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime GetLastWriteTime(string id) {
|
||||
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
|
||||
return File.Exists(fileName) ? File.GetLastWriteTimeUtc(fileName) : DateTime.UnixEpoch;
|
||||
}
|
||||
|
||||
public static bool TryRead(string id, [NotNullWhen(true)] out CacheItem? item) {
|
||||
item = null;
|
||||
try {
|
||||
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
|
||||
using var fileStream = File.OpenRead(fileName);
|
||||
using var zipStream = new GZipStream(fileStream, CompressionMode.Decompress);
|
||||
item = CacheItem.Parser.ParseFrom(zipStream);
|
||||
return true;
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(string id, byte[] data, string? etag = null) {
|
||||
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
|
||||
using var fileStream = File.Open(fileName, FileMode.Create);
|
||||
using var zipStream = new GZipStream(fileStream, CompressionLevel.SmallestSize);
|
||||
new CacheItem {
|
||||
Etag = etag ?? string.Empty,
|
||||
Version = 3,
|
||||
Checksum = GetBinHash(data),
|
||||
Content = ByteString.CopyFrom(data)
|
||||
}.WriteTo(zipStream);
|
||||
}
|
||||
|
||||
private static string GetStrHash(string value) => GetBinHash(Encoding.UTF8.GetBytes(value));
|
||||
|
||||
private static string GetBinHash(byte[] value) => Convert.ToHexStringLower(MD5.HashData(value));
|
||||
|
||||
}
|
||||
31
YaeAchievement/src/Utilities/Extensions/Enumerable.cs
Normal file
31
YaeAchievement/src/Utilities/Extensions/Enumerable.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace System.Collections.Generic {
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class CollectionExtensions {
|
||||
|
||||
public static IDictionary<TKey, TValue> RemoveValues<TKey, TValue>(
|
||||
this IDictionary<TKey, TValue> dictionary, params TKey[] keys
|
||||
) {
|
||||
foreach (var key in keys) {
|
||||
dictionary.Remove(key);
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Linq {
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class EnumerableExtensions {
|
||||
|
||||
public static IEnumerable<IGrouping<TKey, TKey>> GroupKeys<TKey, TValue>(
|
||||
this IEnumerable<Dictionary<TKey, TValue>> source,
|
||||
Func<TValue, bool> condition
|
||||
) where TKey : notnull => source
|
||||
.SelectMany(dict => dict.Where(pair => condition(pair.Value)).Select(pair => pair.Key))
|
||||
.GroupBy(x => x);
|
||||
}
|
||||
}
|
||||
24
YaeAchievement/src/Utilities/Extensions/Process.cs
Normal file
24
YaeAchievement/src/Utilities/Extensions/Process.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using static Windows.Win32.System.Threading.PROCESS_ACCESS_RIGHTS;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
|
||||
namespace System.Diagnostics;
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class ProcessExtensions {
|
||||
|
||||
public static unsafe string? GetFileName(this Process process) {
|
||||
using var hProc = Native.OpenProcess_SafeHandle(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (uint) process.Id);
|
||||
if (hProc.IsInvalid) {
|
||||
return null;
|
||||
}
|
||||
var sProcPath = stackalloc char[32767];
|
||||
return Native.GetModuleFileNameEx((HANDLE) hProc.DangerousGetHandle(), HMODULE.Null, sProcPath, 32767) == 0
|
||||
? null
|
||||
: new string(sProcPath);
|
||||
}
|
||||
|
||||
}
|
||||
38
YaeAchievement/src/Utilities/Extensions/Stream.cs
Normal file
38
YaeAchievement/src/Utilities/Extensions/Stream.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Spectre.Console;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
|
||||
namespace Google.Protobuf;
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class BinaryReaderExtensions {
|
||||
|
||||
public static byte[] ReadBytes(this BinaryReader reader) {
|
||||
try {
|
||||
var length = reader.ReadInt32();
|
||||
if (length is < 0 or > 114514 * 2) {
|
||||
throw new ArgumentException(nameof(length));
|
||||
}
|
||||
return reader.ReadBytes(length);
|
||||
} catch (Exception e) when (e is IOException or ArgumentException) {
|
||||
AnsiConsole.WriteLine(App.StreamReadDataFail);
|
||||
Environment.Exit(-1);
|
||||
throw new UnreachableException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class CodedInputStreamExtensions {
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Method)]
|
||||
private static extern byte[] ReadRawBytes(CodedInputStream stream, int size);
|
||||
|
||||
public static CodedInputStream ReadLengthDelimitedAsStream(this CodedInputStream stream) {
|
||||
return new CodedInputStream(ReadRawBytes(stream, stream.ReadLength()));
|
||||
}
|
||||
|
||||
}
|
||||
82
YaeAchievement/src/Utilities/GameProcess.cs
Normal file
82
YaeAchievement/src/Utilities/GameProcess.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Threading;
|
||||
|
||||
using static Windows.Win32.System.Memory.VIRTUAL_ALLOCATION_TYPE;
|
||||
using static Windows.Win32.System.Memory.PAGE_PROTECTION_FLAGS;
|
||||
using static Windows.Win32.System.Memory.VIRTUAL_FREE_TYPE;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
namespace YaeAchievement.Utilities;
|
||||
|
||||
public sealed unsafe class GameProcess {
|
||||
|
||||
public uint Id { get; }
|
||||
|
||||
public HANDLE Handle { get; }
|
||||
|
||||
public HANDLE MainThreadHandle { get; }
|
||||
|
||||
public event Action? OnExit;
|
||||
|
||||
public GameProcess(string path) {
|
||||
const PROCESS_CREATION_FLAGS flags = PROCESS_CREATION_FLAGS.CREATE_SUSPENDED;
|
||||
Span<char> cmdLines = stackalloc char[1]; // "\0"
|
||||
var si = new STARTUPINFOW {
|
||||
cb = (uint) sizeof(STARTUPINFOW)
|
||||
};
|
||||
var wd = Path.GetDirectoryName(path)!;
|
||||
if (!Native.CreateProcess(path, ref cmdLines, null, null, false, flags, null, wd, si, out var pi)) {
|
||||
var argumentData = new Dictionary<string, object> {
|
||||
{ "path", path },
|
||||
{ "workdir", wd },
|
||||
{ "file_exists", File.Exists(path) },
|
||||
};
|
||||
throw new ApplicationException($"CreateProcess fail: {Marshal.GetLastPInvokeErrorMessage()}") {
|
||||
Data = {
|
||||
{ "sentry:context:Arguments", argumentData }
|
||||
}
|
||||
};
|
||||
}
|
||||
Id = pi.dwProcessId;
|
||||
Handle = pi.hProcess;
|
||||
MainThreadHandle = pi.hThread;
|
||||
Task.Run(() => {
|
||||
Native.WaitForSingleObject(Handle, 0xFFFFFFFF); // INFINITE
|
||||
OnExit?.Invoke();
|
||||
}).ContinueWith(task => { if (task.IsFaulted) Utils.OnUnhandledException(task.Exception!); });
|
||||
}
|
||||
|
||||
public void LoadLibrary(string libPath) {
|
||||
var hKrnl32 = NativeLibrary.Load("kernel32");
|
||||
var mLoadLibraryW = NativeLibrary.GetExport(hKrnl32, "LoadLibraryW");
|
||||
var libPathLen = (uint) libPath.Length * sizeof(char);
|
||||
var lpLibPath = Native.VirtualAllocEx(Handle, null, libPathLen + 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
if (lpLibPath == null) {
|
||||
throw new ApplicationException($"VirtualAllocEx fail: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
fixed (void* lpBuffer = libPath) {
|
||||
if (!Native.WriteProcessMemory(Handle, lpLibPath, lpBuffer, libPathLen)) {
|
||||
throw new ApplicationException($"WriteProcessMemory fail: {Marshal.GetLastPInvokeErrorMessage()}");
|
||||
}
|
||||
}
|
||||
var lpStartAddress = (delegate*unmanaged[Stdcall]<void*, uint>) mLoadLibraryW; // THREAD_START_ROUTINE
|
||||
var hThread = Native.CreateRemoteThread(Handle, null, 0, lpStartAddress, lpLibPath, 0);
|
||||
if (hThread.IsNull) {
|
||||
var error = Marshal.GetLastPInvokeErrorMessage();
|
||||
Native.VirtualFreeEx(Handle, lpLibPath, 0, MEM_RELEASE);
|
||||
throw new ApplicationException($"CreateRemoteThread fail: {error}");
|
||||
}
|
||||
if (Native.WaitForSingleObject(hThread, 2000) == 0) {
|
||||
Native.VirtualFreeEx(Handle, lpLibPath, 0, MEM_RELEASE);
|
||||
}
|
||||
Native.CloseHandle(hThread);
|
||||
}
|
||||
|
||||
public bool ResumeMainThread() => Native.ResumeThread(MainThreadHandle) != 0xFFFFFFFF;
|
||||
|
||||
public bool Terminate(uint exitCode) => Native.TerminateProcess(Handle, exitCode);
|
||||
|
||||
}
|
||||
47
YaeAchievement/src/Utilities/SelectionPromptCompat.cs
Normal file
47
YaeAchievement/src/Utilities/SelectionPromptCompat.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace YaeAchievement.Utilities;
|
||||
|
||||
public sealed class SelectionPromptCompat<T> where T : notnull {
|
||||
|
||||
private readonly List<T> _choices = [];
|
||||
private readonly SelectionPrompt<T> _prompt = new ();
|
||||
|
||||
public SelectionPromptCompat<T> Title(string? title) {
|
||||
_prompt.Title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SelectionPromptCompat<T> AddChoices(params IEnumerable<T> choices) {
|
||||
foreach (var choice in choices) {
|
||||
_prompt.AddChoice(choice);
|
||||
_choices.Add(choice);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public T Prompt() {
|
||||
if (AnsiConsole.Profile.Capabilities.Ansi) {
|
||||
var title = _prompt.Title;
|
||||
_prompt.Title += $" ({App.SelectionPromptCompatAnsiTip})";
|
||||
var result = AnsiConsole.Prompt(_prompt);
|
||||
_prompt.Title = title;
|
||||
return result;
|
||||
}
|
||||
if (_prompt.Title != null) {
|
||||
AnsiConsole.WriteLine(_prompt.Title + $" ({App.SelectionPromptCompatNonAnsiTip})");
|
||||
}
|
||||
for (var i = 0; i < _choices.Count; i++) {
|
||||
var choice = _choices[i];
|
||||
AnsiConsole.WriteLine($"[{i}] {choice}");
|
||||
}
|
||||
var choosePrompt = new TextPrompt<int>(App.SelectionPromptCompatChooseOne).Validate(i => {
|
||||
if (i < 0 || i >= _choices.Count) {
|
||||
return ValidationResult.Error(string.Format(App.SelectionPromptCompatInvalidChoice, _choices.Count - 1));
|
||||
}
|
||||
return ValidationResult.Success();
|
||||
});
|
||||
var resultIndex = AnsiConsole.Prompt(choosePrompt);
|
||||
return _choices[resultIndex];
|
||||
}
|
||||
}
|
||||
263
YaeAchievement/src/Utils.cs
Normal file
263
YaeAchievement/src/Utils.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO.Pipes;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Console;
|
||||
using Proto;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.Utilities;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class Utils {
|
||||
|
||||
public static HttpClient CHttpClient { get; } = new (new SentryHttpMessageHandler(new HttpClientHandler {
|
||||
AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip
|
||||
})) {
|
||||
DefaultRequestHeaders = {
|
||||
UserAgent = {
|
||||
new ProductInfoHeaderValue("YaeAchievement", GlobalVars.AppVersion.ToString(2))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static async Task<byte[]> GetBucketFile(string path, bool useCache = true) {
|
||||
var transaction = SentrySdk.StartTransaction(path, "bucket.get");
|
||||
SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);
|
||||
try {
|
||||
var data = await GetFile("https://api.qhy04.com/hutaocdn/download?filename={0}", path, useCache);
|
||||
transaction.Finish();
|
||||
return data;
|
||||
} catch (Exception e) when (e is SocketException or TaskCanceledException or HttpRequestException) {
|
||||
}
|
||||
try {
|
||||
var data = await Task.WhenAny(
|
||||
GetFile("https://rin.holohat.work/{0}", path, useCache),
|
||||
GetFile("https://cn-cd-1259389942.file.myqcloud.com/{0}", path, useCache)
|
||||
).Unwrap();
|
||||
transaction.Finish();
|
||||
return data;
|
||||
} catch (Exception ex) when (ex is HttpRequestException or SocketException or TaskCanceledException) {
|
||||
transaction.Finish();
|
||||
AnsiConsole.WriteLine(App.NetworkError, ex.Message);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
throw new UnreachableException();
|
||||
static async Task<byte[]> GetFile(string baseUrl, string objectKey, bool useCache) {
|
||||
using var reqwest = new HttpRequestMessage(HttpMethod.Get, string.Format(baseUrl, objectKey));
|
||||
CacheItem? cache = null;
|
||||
if (useCache && CacheFile.TryRead(objectKey, out cache)) {
|
||||
reqwest.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
|
||||
}
|
||||
using var response = await CHttpClient.SendAsync(reqwest);
|
||||
if (cache != null && response.StatusCode == HttpStatusCode.NotModified) {
|
||||
return cache.Content.ToByteArray();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync();
|
||||
if (useCache) {
|
||||
var etag = response.Headers.ETag!.Tag;
|
||||
CacheFile.Write(objectKey, bytes, etag);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public static T? GetOrNull<T>(this T[] array, uint index) where T : class {
|
||||
return array.Length > index ? array[index] : null;
|
||||
}
|
||||
|
||||
public static int ToIntOrDefault(string? value, int defaultValue = 0) {
|
||||
return value != null && int.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
public static bool ToBooleanOrDefault(string? value, bool defaultValue = false) {
|
||||
return value != null && bool.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
public static unsafe void CopyToClipboard(string text) {
|
||||
if (Native.OpenClipboard(HWND.Null)) {
|
||||
Native.EmptyClipboard();
|
||||
var hGlobal = (HGLOBAL) Marshal.AllocHGlobal((text.Length + 1) * 2);
|
||||
var hPtr = (nint) Native.GlobalLock(hGlobal);
|
||||
Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length);
|
||||
Native.GlobalUnlock((HGLOBAL) hPtr);
|
||||
Native.SetClipboardData(13, new HANDLE(hPtr));
|
||||
Marshal.FreeHGlobal(hGlobal);
|
||||
Native.CloseClipboard();
|
||||
} else {
|
||||
throw new Win32Exception();
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private static UpdateInfo _updateInfo = null!;
|
||||
|
||||
public static Task StartSpinnerAsync(string status, Func<StatusContext, Task> func) {
|
||||
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
|
||||
}
|
||||
|
||||
public static Task<T> StartSpinnerAsync<T>(string status, Func<StatusContext, Task<T>> func) {
|
||||
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
|
||||
}
|
||||
|
||||
public static async Task CheckUpdate(bool useLocalLib) {
|
||||
try {
|
||||
var versionData = await StartSpinnerAsync(App.UpdateChecking, _ => GetBucketFile("schicksal/version"));
|
||||
var versionInfo = UpdateInfo.Parser.ParseFrom(versionData)!;
|
||||
if (GlobalVars.AppVersionCode < versionInfo.VersionCode) {
|
||||
AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName);
|
||||
AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description);
|
||||
if (versionInfo.EnableAutoUpdate) {
|
||||
var newBin = await StartSpinnerAsync(App.UpdateDownloading, _ => GetBucketFile(versionInfo.PackageLink));
|
||||
var tmpPath = Path.GetTempFileName();
|
||||
var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe");
|
||||
await using (var dstStream = File.Open($"{GlobalVars.DataPath}/update.exe", FileMode.Create)) {
|
||||
await using var srcStream = typeof(Program).Assembly.GetManifestResourceStream("updater")!;
|
||||
await srcStream.CopyToAsync(dstStream);
|
||||
}
|
||||
await File.WriteAllBytesAsync(tmpPath, newBin);
|
||||
ShellOpen(updaterPath, $"{Environment.ProcessId} \"{tmpPath}\"");
|
||||
await StartSpinnerAsync(App.UpdateChecking, _ => Task.Delay(1919810));
|
||||
GlobalVars.PauseOnExit = false;
|
||||
Environment.Exit(0);
|
||||
}
|
||||
AnsiConsole.MarkupLine($"[link]{App.DownloadLink}[/]", versionInfo.PackageLink);
|
||||
if (versionInfo.ForceUpdate) {
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
if (versionInfo.EnableLibDownload && !useLocalLib) {
|
||||
var data = await GetBucketFile("schicksal/lic.dll");
|
||||
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data); // 要求重启电脑
|
||||
}
|
||||
_updateInfo = versionInfo;
|
||||
} catch (IOException e) when ((uint) e.HResult == 0x80070020) { // ERROR_SHARING_VIOLATION
|
||||
// IO_SharingViolation_File
|
||||
// The process cannot access the file '{0}' because it is being used by another process.
|
||||
AnsiConsole.WriteLine("文件 {0} 被其它程序占用,请 重启电脑 或 解除文件占用 后重试。", e.Message[36..^46]);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||
public static bool ShellOpen(string path, string? args = null) {
|
||||
try {
|
||||
var startInfo = new ProcessStartInfo {
|
||||
FileName = path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
if (args != null) {
|
||||
startInfo.Arguments = args;
|
||||
}
|
||||
return new Process {
|
||||
StartInfo = startInfo
|
||||
}.Start();
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Process? GetGameProcess() => Process.GetProcessesByName("YuanShen")
|
||||
.Concat(Process.GetProcessesByName("GenshinImpact"))
|
||||
.FirstOrDefault(p => File.Exists($"{p.GetFileName()}/../HoYoKProtect.sys"));
|
||||
|
||||
private static GameProcess? _proc;
|
||||
|
||||
public static void InstallExitHook() {
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
|
||||
_proc?.Terminate(0);
|
||||
if (GlobalVars.PauseOnExit) {
|
||||
AnsiConsole.WriteLine(App.PressKeyToExit);
|
||||
Console.ReadKey();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void InstallExceptionHook() {
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, e) => OnUnhandledException((Exception) e.ExceptionObject);
|
||||
}
|
||||
|
||||
public static void OnUnhandledException(Exception ex) {
|
||||
SentrySdk.CaptureException(ex);
|
||||
switch (ex) {
|
||||
case ApplicationException ex1:
|
||||
AnsiConsole.WriteLine(ex1.Message);
|
||||
break;
|
||||
case SocketException ex2:
|
||||
AnsiConsole.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
|
||||
break;
|
||||
case HttpRequestException ex3:
|
||||
AnsiConsole.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
|
||||
break;
|
||||
default:
|
||||
AnsiConsole.WriteLine(ex.ToString());
|
||||
break;
|
||||
}
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
|
||||
private static bool _isUnexpectedExit = true;
|
||||
|
||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||
public static void StartAndWaitResult(string exePath, Dictionary<int, Func<BinaryReader, bool>> handlers, Action onFinish) {
|
||||
_proc = new GameProcess(exePath);
|
||||
_proc.OnExit += () => {
|
||||
if (_isUnexpectedExit) {
|
||||
_proc = null;
|
||||
AnsiConsole.WriteLine(App.GameProcessExit);
|
||||
Environment.Exit(114514);
|
||||
}
|
||||
};
|
||||
_proc.LoadLibrary(GlobalVars.LibFilePath);
|
||||
_proc.ResumeMainThread();
|
||||
AnsiConsole.WriteLine(App.GameLoading, _proc.Id);
|
||||
Task.Run(() => {
|
||||
using var stream = new NamedPipeServerStream(GlobalVars.PipeName);
|
||||
using var reader = new BinaryReader(stream);
|
||||
stream.WaitForConnection();
|
||||
int type;
|
||||
while ((type = stream.ReadByte()) != -1) {
|
||||
if (type == 0xFF) {
|
||||
_isUnexpectedExit = false;
|
||||
onFinish();
|
||||
break;
|
||||
}
|
||||
if (handlers.TryGetValue(type, out var handler)) {
|
||||
if (handler(reader)) {
|
||||
handlers.Remove(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).ContinueWith(task => { if (task.IsFaulted) OnUnhandledException(task.Exception!); });
|
||||
}
|
||||
|
||||
internal static unsafe void SetQuickEditMode(bool enable) {
|
||||
var handle = Native.GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
|
||||
CONSOLE_MODE mode = default;
|
||||
Native.GetConsoleMode(handle, &mode);
|
||||
mode = enable ? mode | CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE : mode &~CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE;
|
||||
Native.SetConsoleMode(handle, mode);
|
||||
}
|
||||
|
||||
internal static unsafe void FixTerminalFont() {
|
||||
if (!CultureInfo.CurrentCulture.Name.StartsWith("zh")) {
|
||||
return;
|
||||
}
|
||||
var handle = Native.GetStdHandle(STD_HANDLE.STD_OUTPUT_HANDLE);
|
||||
var fontInfo = new CONSOLE_FONT_INFOEX {
|
||||
cbSize = (uint) sizeof(CONSOLE_FONT_INFOEX)
|
||||
};
|
||||
if (!Native.GetCurrentConsoleFontEx(handle, false, &fontInfo)) {
|
||||
return;
|
||||
}
|
||||
if (fontInfo.FaceName.ToString() == "Terminal") { // 点阵字体
|
||||
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US"); // todo: use better way like auto set console font etc.
|
||||
}
|
||||
}
|
||||
}
|
||||
17
lib/YaeAchievementLib.nuspec
Normal file
17
lib/YaeAchievementLib.nuspec
Normal file
@@ -0,0 +1,17 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Yae.Lib</id>
|
||||
<version>5.3.4</version>
|
||||
<authors>HolographicHat</authors>
|
||||
<developmentDependency>true</developmentDependency>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">GPL-3.0-only</license>
|
||||
<licenseUrl>https://licenses.nuget.org/GPL-3.0-only</licenseUrl>
|
||||
<projectUrl>https://github.com/HolographicHat/Yae</projectUrl>
|
||||
<description>Yae Lib</description>
|
||||
<repository type="git" url="https://github.com/HolographicHat/Yae" commit="$commit$" />
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="build\x64\Release\YaeLib.dll" target="runtimes\win-x64\native" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -20,9 +20,9 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
@@ -30,7 +30,6 @@
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<EnableASAN>false</EnableASAN>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
@@ -45,88 +44,69 @@
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>build\$(Platform)\$(Configuration)\</IntDir>
|
||||
<TargetName>YaeLib</TargetName>
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>build\$(Platform)\$(Configuration)\</IntDir>
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<TargetName>YaeLib</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PreprocessorDefinitions>_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)lib\detours\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)lib\detours\;$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>detours-x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<SubSystem>NotSet</SubSystem>
|
||||
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0</Command>
|
||||
</PostBuildEvent>
|
||||
<PostBuildEvent />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PreprocessorDefinitions>_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)lib\detours\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<LanguageStandard_C>stdc11</LanguageStandard_C>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<SubSystem>NotSet</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)lib\detours\;$(OutDir)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>detours-x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0\win-x64\YaeAchievementLib.dll /y</Command>
|
||||
</PostBuildEvent>
|
||||
<PostBuildEvent />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\HookManager.h" />
|
||||
<ClInclude Include="src\il2cpp-api-functions.h" />
|
||||
<ClInclude Include="src\il2cpp-appdata.h" />
|
||||
<ClInclude Include="src\il2cpp-functions.h" />
|
||||
<ClInclude Include="src\globals.h" />
|
||||
<ClInclude Include="src\il2cpp-types.h" />
|
||||
<ClInclude Include="src\il2cpp-init.h" />
|
||||
<ClInclude Include="src\il2cpp-unity-functions.h" />
|
||||
<ClInclude Include="src\pch.h" />
|
||||
<ClInclude Include="src\NamedPipe.h" />
|
||||
<ClInclude Include="src\ntprivate.h" />
|
||||
<ClInclude Include="src\util.h" />
|
||||
<ClInclude Include="src\Zydis.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\dllmain.cpp" />
|
||||
<ClCompile Include="src\il2cpp-init.cpp" />
|
||||
<ClCompile Include="src\pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\util.cpp" />
|
||||
<ClCompile Include="src\Zydis.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Common version parameters.
|
||||
//
|
||||
// Microsoft Research Detours Package, Version 4.0.1
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
|
||||
#define _USING_V110_SDK71_ 1
|
||||
#include "winver.h"
|
||||
#if 0
|
||||
#include <windows.h>
|
||||
#include <detours.h>
|
||||
#else
|
||||
#ifndef DETOURS_STRINGIFY
|
||||
#define DETOURS_STRINGIFY_(x) #x
|
||||
#define DETOURS_STRINGIFY(x) DETOURS_STRINGIFY_(x)
|
||||
#endif
|
||||
|
||||
#define VER_FILEFLAGSMASK 0x3fL
|
||||
#define VER_FILEFLAGS 0x0L
|
||||
#define VER_FILEOS 0x00040004L
|
||||
#define VER_FILETYPE 0x00000002L
|
||||
#define VER_FILESUBTYPE 0x00000000L
|
||||
#endif
|
||||
#define VER_DETOURS_BITS DETOURS_STRINGIFY(DETOURS_BITS)
|
||||
@@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#define CALL_ORIGIN(function, ...) \
|
||||
HookManager::call(function, __func__, __VA_ARGS__)
|
||||
|
||||
class HookManager {
|
||||
public:
|
||||
template <typename Fn>
|
||||
static void install(Fn func, Fn handler) {
|
||||
enable(func, handler);
|
||||
holderMap[reinterpret_cast<void*>(handler)] = reinterpret_cast<void*>(func);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
[[nodiscard]] static Fn getOrigin(Fn handler, const char* callerName = nullptr) noexcept {
|
||||
if (holderMap.count(reinterpret_cast<void*>(handler)) == 0) {
|
||||
printf("Origin not found for handler: %s. Maybe racing bug.", callerName == nullptr ? "<Unknown>" : callerName);
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<Fn>(holderMap[reinterpret_cast<void*>(handler)]);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
[[nodiscard]] static void detach(Fn handler) noexcept {
|
||||
disable(handler);
|
||||
holderMap.erase(reinterpret_cast<void*>(handler));
|
||||
}
|
||||
|
||||
template <typename RType, typename... Params>
|
||||
[[nodiscard]] static RType call(RType(*handler)(Params...), const char* callerName = nullptr, Params... params) {
|
||||
auto origin = getOrigin(handler, callerName);
|
||||
if (origin != nullptr)
|
||||
return origin(params...);
|
||||
|
||||
return RType();
|
||||
}
|
||||
|
||||
static void detachAll() noexcept {
|
||||
for (const auto &[key, value] : holderMap) {
|
||||
disable(key);
|
||||
}
|
||||
holderMap.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
inline static std::map<void*, void*> holderMap{};
|
||||
|
||||
template <typename Fn>
|
||||
static void disable(Fn handler) {
|
||||
Fn origin = getOrigin(handler);
|
||||
DetourTransactionBegin();
|
||||
DetourUpdateThread(GetCurrentThread());
|
||||
DetourDetach(&(PVOID&)origin, handler);
|
||||
DetourTransactionCommit();
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
static void enable(Fn& func, Fn handler) {
|
||||
DetourTransactionBegin();
|
||||
DetourUpdateThread(GetCurrentThread());
|
||||
DetourAttach(&(PVOID&)func, handler);
|
||||
DetourTransactionCommit();
|
||||
}
|
||||
};
|
||||
46
lib/src/NamedPipe.h
Normal file
46
lib/src/NamedPipe.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <span>
|
||||
|
||||
template <typename T>
|
||||
concept IsSpan = requires(T t) {
|
||||
{ t.data() } -> std::convertible_to<const void*>;
|
||||
{ t.size() } -> std::convertible_to<std::size_t>;
|
||||
{ t.size_bytes() } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
|
||||
class NamedPipe
|
||||
{
|
||||
HANDLE m_hPipe = INVALID_HANDLE_VALUE;
|
||||
public:
|
||||
NamedPipe(HANDLE hPipe) : m_hPipe(hPipe) {}
|
||||
~NamedPipe() { if (m_hPipe != INVALID_HANDLE_VALUE) CloseHandle(m_hPipe); }
|
||||
|
||||
operator HANDLE() const { return m_hPipe; }
|
||||
operator bool() const { return m_hPipe != INVALID_HANDLE_VALUE && m_hPipe != nullptr; }
|
||||
NamedPipe& operator= (HANDLE hPipe) {
|
||||
m_hPipe = hPipe;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Write(const void* data, size_t size) const
|
||||
{
|
||||
DWORD bytesWritten;
|
||||
if (!WriteFile(m_hPipe, data, static_cast<DWORD>(size), &bytesWritten, nullptr) || bytesWritten != size)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <IsSpan T>
|
||||
bool Write(const T& data) const
|
||||
{
|
||||
return Write(data.data(), data.size_bytes());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Write(const T& data) const
|
||||
{
|
||||
return Write(&data, sizeof(T));
|
||||
}
|
||||
|
||||
};
|
||||
54990
lib/src/Zydis.c
Normal file
54990
lib/src/Zydis.c
Normal file
File diff suppressed because one or more lines are too long
12113
lib/src/Zydis.h
Normal file
12113
lib/src/Zydis.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,113 +1,275 @@
|
||||
#include "pch.h"
|
||||
// ReSharper disable CppClangTidyCertErr33C
|
||||
#include <Windows.h>
|
||||
#include <print>
|
||||
#include <string>
|
||||
#include <future>
|
||||
#include <TlHelp32.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "util.h"
|
||||
#include "il2cpp-init.h"
|
||||
#include "il2cpp-types.h"
|
||||
#include "ntprivate.h"
|
||||
|
||||
using Genshin::ByteArray, Genshin::ClientKcpEvent, Genshin::KcpPacket, Genshin::KcpEventType;
|
||||
using std::to_string;
|
||||
CRITICAL_SECTION CriticalSection;
|
||||
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index);
|
||||
|
||||
HWND unityWnd = 0;
|
||||
HANDLE hPipe = 0;
|
||||
namespace
|
||||
{
|
||||
PacketType GetPacketType(const PacketMeta* packet)
|
||||
{
|
||||
using namespace Globals;
|
||||
const auto cmdid = packet->CmdId;
|
||||
|
||||
if (AchievementId && cmdid == AchievementId)
|
||||
return PacketType::Achivement;
|
||||
|
||||
std::set<UINT16> PacketWhitelist = { 105, 155, 187, 198, 2688, 20, 74 }; // Allow Protocol: GetPlayerToken, PlayerLogin, AchievementAllDataNotify, Ping
|
||||
if (AchievementIdSet.contains(cmdid) && packet->DataLength > 500)
|
||||
return PacketType::Achivement;
|
||||
|
||||
bool OnPacket(KcpPacket* pkt) {
|
||||
if (pkt->data == nullptr) return true;
|
||||
auto len = pkt->length;
|
||||
auto data = (ByteArray*)new BYTE[len + 32];
|
||||
data->max_length = len;
|
||||
memcpy(data->vector, pkt->data, len);
|
||||
Genshin::XorEncrypt(&data, len, nullptr);
|
||||
if (ReadMapped<UINT16>(data->vector, 0) != 0x4567) {
|
||||
delete[] data;
|
||||
return true;
|
||||
if (PlayerStoreId && cmdid == PlayerStoreId)
|
||||
return PacketType::Inventory;
|
||||
|
||||
return PacketType::None;
|
||||
}
|
||||
if (!PacketWhitelist.contains(ReadMapped<UINT16>(data->vector, 2))) {
|
||||
//ifdef _DEBUG
|
||||
printf("Blocked cmdid: %d\n", ReadMapped<UINT16>(data->vector, 2));
|
||||
//endif
|
||||
delete[] data;
|
||||
return false;
|
||||
}
|
||||
printf("Passed cmdid: %d\n", ReadMapped<UINT16>(data->vector, 2));
|
||||
if (ReadMapped<UINT16>(data->vector, 2) == 2688) {
|
||||
auto headLength = ReadMapped<UINT16>(data->vector, 4);
|
||||
auto dataLength = ReadMapped<UINT32>(data->vector, 6);
|
||||
auto iStr = Genshin::ToBase64String(data, 10 + headLength, dataLength, nullptr);
|
||||
auto cStr = IlStringToString(iStr) + "\n";
|
||||
WriteFile(hPipe, cStr.c_str(), cStr.length(), nullptr, nullptr);
|
||||
CloseHandle(hPipe);
|
||||
ExitProcess(0);
|
||||
}
|
||||
delete[] data;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<INT, UINT> signatures;
|
||||
|
||||
namespace Hook {
|
||||
|
||||
int KcpSend(void* client, KcpPacket* pkt, void* method) {
|
||||
return OnPacket(pkt) ? CALL_ORIGIN(KcpSend, client, pkt, method) : 0;
|
||||
}
|
||||
|
||||
void SetVersion(void* obj, Il2CppString* value, void* method) {
|
||||
auto version = IlStringToString(value);
|
||||
value = string_new(version + " YaeAchievement");
|
||||
CALL_ORIGIN(SetVersion, obj, value, method);
|
||||
}
|
||||
uint16_t __fastcall BitConverter_ToUInt16(Array<uint8_t>* val, const int startIndex)
|
||||
{
|
||||
using namespace Globals;
|
||||
const auto ToUInt16 = reinterpret_cast<decltype(&BitConverter_ToUInt16)>(Offset.BitConverter_ToUInt16);
|
||||
|
||||
bool KcpRecv(void* client, ClientKcpEvent* evt, void* method) {
|
||||
auto result = CALL_ORIGIN(KcpRecv, client, evt, method);
|
||||
if (result == 0 || evt->fields.type != KcpEventType::EventRecvMsg) {
|
||||
return result;
|
||||
EnterCriticalSection(&CriticalSection);
|
||||
SetBreakpoint((HANDLE)-2, 0, false, 0);
|
||||
const auto ret = ToUInt16(val, startIndex);
|
||||
SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true, 0);
|
||||
LeaveCriticalSection(&CriticalSection);
|
||||
|
||||
if (ret != 0xAB89)
|
||||
return ret;
|
||||
|
||||
const auto packet = val->As<PacketMeta*>();
|
||||
const auto packetType = GetPacketType(packet);
|
||||
if (packetType == PacketType::None)
|
||||
return ret;
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::println("PacketType: {}", static_cast<uint8_t>(packetType));
|
||||
std::println("CmdId: {}", packet->CmdId);
|
||||
std::println("DataLength: {}", packet->DataLength);
|
||||
//std::println("Data: {}", Util::Base64Encode(packet->AsSpan()));
|
||||
#endif
|
||||
|
||||
if (!MessagePipe.Write(packetType))
|
||||
Util::Win32ErrorDialog(1002, GetLastError());
|
||||
|
||||
if (!MessagePipe.Write(packet->DataLength))
|
||||
Util::Win32ErrorDialog(1003, GetLastError());
|
||||
|
||||
if (!MessagePipe.Write(packet->AsSpan()))
|
||||
Util::Win32ErrorDialog(1004, GetLastError());
|
||||
|
||||
if (!AchievementsWritten)
|
||||
AchievementsWritten = packetType == PacketType::Achivement;
|
||||
|
||||
if (!PlayerStoreWritten)
|
||||
PlayerStoreWritten = packetType == PacketType::Inventory;
|
||||
|
||||
if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0)
|
||||
{
|
||||
if (!MessagePipe.Write(PacketType::End))
|
||||
Util::Win32ErrorDialog(9001, GetLastError());
|
||||
#ifdef _DEBUG
|
||||
system("pause");
|
||||
#endif
|
||||
ExitProcess(0);
|
||||
}
|
||||
return OnPacket(evt->fields.packet) ? result : false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ByteArray* UnityEngine_RecordUserData(INT type) {
|
||||
return GCHandle_GetObject<ByteArray>(signatures[type]);
|
||||
void __fastcall AccountDataItem_UpdateNormalProp(const void* __this, const int type, const double value, const double lastValue, const int state)
|
||||
{
|
||||
using namespace Globals;
|
||||
const auto UpdateNormalProp = reinterpret_cast<decltype(&AccountDataItem_UpdateNormalProp)>(Offset.AccountDataItem_UpdateNormalProp);
|
||||
|
||||
EnterCriticalSection(&CriticalSection);
|
||||
SetBreakpoint((HANDLE)-2, 0, false, 1);
|
||||
UpdateNormalProp(__this, type, value, lastValue, state);
|
||||
SetBreakpoint((HANDLE)-2, Offset.AccountDataItem_UpdateNormalProp, true, 1);
|
||||
LeaveCriticalSection(&CriticalSection);
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::println("PropType: {}", type);
|
||||
std::println("PropState: {}", state);
|
||||
std::println("PropValue: {}", value);
|
||||
std::println("PropLastValue: {}", lastValue);
|
||||
#endif
|
||||
if (RequiredPlayerProperties.erase(type) != 0)
|
||||
{
|
||||
if (!MessagePipe.Write(PacketType::PropData))
|
||||
Util::Win32ErrorDialog(2002, GetLastError());
|
||||
if (!MessagePipe.Write(type))
|
||||
Util::Win32ErrorDialog(2003, GetLastError());
|
||||
if (!MessagePipe.Write(value))
|
||||
Util::Win32ErrorDialog(2004, GetLastError());
|
||||
}
|
||||
|
||||
if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0)
|
||||
{
|
||||
if (!MessagePipe.Write(PacketType::End))
|
||||
Util::Win32ErrorDialog(9001, GetLastError());
|
||||
#ifdef _DEBUG
|
||||
system("pause");
|
||||
#endif
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Run(HMODULE* phModule) {
|
||||
//AllocConsole();
|
||||
//freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
|
||||
while (
|
||||
GetModuleHandle("UserAssembly.dll") == nullptr ||
|
||||
(unityWnd = FindMainWindowByPID(GetCurrentProcessId())) == 0
|
||||
) {
|
||||
Sleep(1000);
|
||||
LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep)
|
||||
{
|
||||
using namespace Globals;
|
||||
const auto exceptionRecord = ep->ExceptionRecord;
|
||||
const auto contextRecord = ep->ContextRecord;
|
||||
|
||||
if (exceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
|
||||
{
|
||||
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.BitConverter_ToUInt16)) {
|
||||
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::BitConverter_ToUInt16);
|
||||
contextRecord->EFlags &= ~0x100; // clear the trap flag
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.AccountDataItem_UpdateNormalProp)) {
|
||||
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::AccountDataItem_UpdateNormalProp);
|
||||
contextRecord->EFlags &= ~0x100; // clear the trap flag
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
Sleep(5000);
|
||||
DisableVMProtect();
|
||||
InitIL2CPP();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
auto result = Genshin::RecordUserData(i, nullptr);
|
||||
signatures[i] = GCHandle_New(result, true);
|
||||
}
|
||||
signatures[3] = signatures[2];
|
||||
HookManager::install(Genshin::KcpSend, Hook::KcpSend);
|
||||
HookManager::install(Genshin::KcpRecv, Hook::KcpRecv);
|
||||
HookManager::install(Genshin::SetVersion, Hook::SetVersion);
|
||||
HookManager::install(Genshin::UnityEngine_RecordUserData, Hook::UnityEngine_RecordUserData);
|
||||
hPipe = CreateFile(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (hPipe == INVALID_HANDLE_VALUE) {
|
||||
Win32ErrorDialog(1001);
|
||||
ExitProcess(0);
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index)
|
||||
{
|
||||
using namespace Globals;
|
||||
|
||||
if (index > 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
CONTEXT ctx{};
|
||||
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
GetThreadContext(thread, &ctx);
|
||||
|
||||
DWORD64* dr = &ctx.Dr0;
|
||||
dr[index] = enable ? address : 0;
|
||||
|
||||
const auto mask = 1ull << (index * 2);
|
||||
ctx.Dr7 |= mask;
|
||||
|
||||
SetThreadContext(thread, &ctx);
|
||||
}
|
||||
|
||||
DWORD __stdcall ThreadProc(LPVOID hInstance)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
AllocConsole();
|
||||
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
|
||||
system("pause");
|
||||
#endif
|
||||
InitializeCriticalSection(&CriticalSection);
|
||||
|
||||
auto initFuture = std::async(std::launch::async, InitIL2CPP);
|
||||
|
||||
using namespace Globals;
|
||||
const auto pid = GetCurrentProcessId();
|
||||
|
||||
while ((GameWindow = Util::FindMainWindowByPID(pid)) == nullptr) {
|
||||
SwitchToThread();
|
||||
}
|
||||
|
||||
if (!initFuture.get())
|
||||
ExitProcess(0);
|
||||
|
||||
MessagePipe = CreateFileA(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (!MessagePipe)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
std::println("CreateFile failed: {}", GetLastError());
|
||||
#else
|
||||
Util::Win32ErrorDialog(1001, GetLastError());
|
||||
ExitProcess(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
|
||||
while (true)
|
||||
{
|
||||
THREADENTRY32 te32{};
|
||||
te32.dwSize = sizeof(THREADENTRY32);
|
||||
const auto hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
for (Thread32First(hSnapshot, &te32); Thread32Next(hSnapshot, &te32);)
|
||||
{
|
||||
if (te32.th32OwnerProcessID != pid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID))
|
||||
{
|
||||
EnterCriticalSection(&CriticalSection);
|
||||
SetBreakpoint(hThread, Offset.BitConverter_ToUInt16, true, 0);
|
||||
SetBreakpoint(hThread, Offset.AccountDataItem_UpdateNormalProp, true, 1);
|
||||
CloseHandle(hThread);
|
||||
LeaveCriticalSection(&CriticalSection);
|
||||
}
|
||||
}
|
||||
CloseHandle(hSnapshot);
|
||||
Sleep(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// DLL entry point
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReasonForCall, LPVOID lpReserved) {
|
||||
switch (ulReasonForCall) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Run, new HMODULE(hModule), 0, NULL);
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
case DLL_PROCESS_DETACH:
|
||||
break;
|
||||
BOOL __stdcall DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved)
|
||||
{
|
||||
// Check injectee
|
||||
WCHAR szFileName[MAX_PATH]{};
|
||||
DWORD length = 0;
|
||||
GetModuleFileNameW(NULL, szFileName, MAX_PATH);
|
||||
CharUpperBuffW(szFileName, wcslen(szFileName));
|
||||
if (!(wcsstr(szFileName, L"YUANSHEN.EXE") || wcsstr(szFileName, L"GENSHINIMPACT.EXE")))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (fdwReason == DLL_PROCESS_ATTACH)
|
||||
{
|
||||
if (hInstance)
|
||||
{
|
||||
LdrAddRefDll(LDR_ADDREF_DLL_PIN, hInstance);
|
||||
}
|
||||
|
||||
if (const auto hThread = CreateThread(nullptr, 0, ThreadProc, hInstance, 0, nullptr)) {
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static LRESULT WINAPI YaeGetWindowHookImpl(int code, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
return CallNextHookEx(NULL, code, wParam, lParam);
|
||||
}
|
||||
|
||||
EXTERN_C __declspec(dllexport) HRESULT WINAPI YaeGetWindowHook(_Out_ HOOKPROC* pHookProc)
|
||||
{
|
||||
*pHookProc = YaeGetWindowHookImpl;
|
||||
return S_OK;
|
||||
}
|
||||
54
lib/src/globals.h
Normal file
54
lib/src/globals.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <unordered_set>
|
||||
#include "NamedPipe.h"
|
||||
|
||||
#define PROPERTY2(type, name, cn, os) \
|
||||
type name##_cn = cn; \
|
||||
type name##_os = os; \
|
||||
type get_##name() { return Globals::IsCNREL ? name##_cn : name##_os; } \
|
||||
void set_##name(type value) { if (Globals::IsCNREL) name##_cn = value; else name##_os = value; } \
|
||||
__declspec(property(get = get_##name, put = set_##name)) type name;
|
||||
|
||||
namespace Globals
|
||||
{
|
||||
inline HWND GameWindow = nullptr;
|
||||
inline NamedPipe MessagePipe = nullptr;
|
||||
inline bool IsCNREL = true;
|
||||
inline uintptr_t BaseAddress = 0;
|
||||
|
||||
// 5.1.0 - 24082
|
||||
inline uint16_t AchievementId = 0; // use non-zero to override dynamic search
|
||||
inline std::unordered_set<uint16_t> AchievementIdSet;
|
||||
|
||||
// 5.3.0 - 23233
|
||||
inline uint16_t PlayerStoreId = 0; // use non-zero to override dynamic search
|
||||
|
||||
inline bool AchievementsWritten = false;
|
||||
inline bool PlayerStoreWritten = false;
|
||||
|
||||
/*
|
||||
* PROP_PLAYER_HCOIN = 10015,
|
||||
* PROP_PLAYER_WAIT_SUB_HCOIN = 10022,
|
||||
* PROP_PLAYER_SCOIN = 10016,
|
||||
* PROP_PLAYER_WAIT_SUB_SCOIN = 10023,
|
||||
* PROP_PLAYER_MCOIN = 10025,
|
||||
* PROP_PLAYER_WAIT_SUB_MCOIN = 10026,
|
||||
* PROP_PLAYER_HOME_COIN = 10042,
|
||||
* PROP_PLAYER_WAIT_SUB_HOME_COIN = 10043,
|
||||
* PROP_PLAYER_ROLE_COMBAT_COIN = 10053,
|
||||
* PROP_PLAYER_MUSIC_GAME_BOOK_COIN = 10058,
|
||||
*/
|
||||
inline std::unordered_set<int> RequiredPlayerProperties = { 10015, 10022, 10016, 10023, 10025, 10026, 10042, 10043, 10053, 10058 };
|
||||
|
||||
class Offsets
|
||||
{
|
||||
public:
|
||||
PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0, 0);
|
||||
//PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0x0F826CF0, 0x0F825F10); // use non-zero to override dynamic search
|
||||
PROPERTY2(uintptr_t, AccountDataItem_UpdateNormalProp, 0, 0);
|
||||
//PROPERTY2(uintptr_t, AccountDataItem_UpdateNormalProp, 0x0D9FE060, 0x0D94D910); // use non-zero to override dynamic search
|
||||
};
|
||||
|
||||
inline Offsets Offset;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
DO_API(0x02D2B620, 0x02D290A0, uint32_t, il2cpp_gchandle_new, (Il2CppObject* obj, bool pinned)); // search GCHandle.cpp in strings 2974550
|
||||
DO_API(0x02D2B330, 0x02D28DB0, Il2CppObject*, il2cpp_gchandle_get_target, (uint32_t gchandle));
|
||||
DO_API(0x02D2E840, 0x02D2C0A0, Il2CppString*, il2cpp_string_new, (const char* str));
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
#include "il2cpp-types.h"
|
||||
|
||||
// IL2CPP APIs
|
||||
#define DO_API(ca, oa, r, n, p) extern r (*n) p
|
||||
#include "il2cpp-api-functions.h"
|
||||
#undef DO_API
|
||||
|
||||
// Application-specific functions
|
||||
#define DO_APP_FUNC(ca, oa, r, n, p) extern r (*n) p
|
||||
namespace Genshin {
|
||||
#include "il2cpp-functions.h"
|
||||
}
|
||||
#undef DO_APP_FUNC
|
||||
|
||||
#define DO_UNI_FUNC(ca, oa, r, n, p) extern r (*n) p
|
||||
namespace Genshin {
|
||||
#include "il2cpp-unity-functions.h"
|
||||
}
|
||||
#undef DO_UNI_FUNC
|
||||
@@ -1,31 +0,0 @@
|
||||
using namespace Genshin;
|
||||
|
||||
// DO_APP_FUNC(CN_OFFSET, OS_OFFSET, RETURN, FUNC_NAME, (ARGS...));
|
||||
|
||||
// N: System.Convert$ToBase64String
|
||||
// L: mscorlib
|
||||
DO_APP_FUNC(0x08F056A0, 0x08F1A0F0, Il2CppString*, ToBase64String, (ByteArray* value, int offset, int length, void* method));
|
||||
|
||||
// N: MoleMole.MonoLoginMainPage.version$set
|
||||
// L: Assembly-CSharp
|
||||
DO_APP_FUNC(0X05265A70, 0x052704C0, void, SetVersion, (void* obj, Il2CppString* value, void* method));
|
||||
|
||||
// N: UnityEngine.Application$RecordUserData
|
||||
// L: UnityEngine.CoreModule
|
||||
DO_APP_FUNC(0x09932F30, 0x09947590, ByteArray*, RecordUserData, (int32_t nType, void* method));
|
||||
|
||||
// N: MoleMole.Packet$XorEncrypt [Obfuscated]
|
||||
// L: Assembly-CSharp
|
||||
DO_APP_FUNC(0x054B3120, 0x054BD670, void, XorEncrypt, (ByteArray** data, int length, void* method)); // OHOBJBNAMJM
|
||||
|
||||
// N: Kcp.KcpNative$kcp_client_send_packet [Obfuscated]
|
||||
// L: Assembly-CSharp
|
||||
DO_APP_FUNC(0x050BB390, 0x050C5AC0, int, KcpSend, (void* client, KcpPacket* pkt, void* method)); // CKKPANHPFAP
|
||||
|
||||
// N: MoleMole.KcpClient$TryDequeueEvent [Obfuscated]
|
||||
// L: Assembly-CSharp
|
||||
DO_APP_FUNC(0x04496B50, 0x0449DBF0, bool, KcpRecv, (void* client, ClientKcpEvent* evt, void* method)); // GFFOOBHMCOJ
|
||||
|
||||
DO_APP_FUNC(0x09254010, 0x09269550, LPVOID, GetDefaultEncoding, (void* method));
|
||||
|
||||
DO_APP_FUNC(0x092538C0, 0x09268DE0, Il2CppString*, GetString, (LPVOID encoding, LPVOID bytes, void* method));
|
||||
@@ -1,39 +1,588 @@
|
||||
#include "pch.h"
|
||||
#include <Windows.h>
|
||||
#include <print>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <immintrin.h>
|
||||
|
||||
#include "il2cpp-init.h"
|
||||
#include "globals.h"
|
||||
#include "Zydis.h"
|
||||
#include "util.h"
|
||||
|
||||
#define DO_API(ca, oa, r, n, p) r (*n) p
|
||||
#include "il2cpp-api-functions.h"
|
||||
#undef DO_API
|
||||
namespace
|
||||
{
|
||||
class DecodedInstruction
|
||||
{
|
||||
public:
|
||||
DecodedInstruction() = default;
|
||||
~DecodedInstruction() = default;
|
||||
DecodedInstruction(const ZydisDecodedInstruction& instruction) : Instruction(instruction) {}
|
||||
DecodedInstruction(const ZydisDecodedInstruction& instruction, ZydisDecodedOperand* operands, uint8_t operandCount) : Instruction(instruction) {
|
||||
Operands = { operands, operands + operandCount };
|
||||
}
|
||||
DecodedInstruction(const uint32_t rva, const ZydisDecodedInstruction& instruction, ZydisDecodedOperand* operands, uint8_t operandCount) : RVA(rva), Instruction(instruction) {
|
||||
Operands = { operands, operands + operandCount };
|
||||
}
|
||||
|
||||
// copy constructor
|
||||
DecodedInstruction(const DecodedInstruction& other) = default;
|
||||
|
||||
// move constructor
|
||||
DecodedInstruction(DecodedInstruction&& other) noexcept : RVA(other.RVA), Instruction(other.Instruction), Operands(std::move(other.Operands)) {}
|
||||
|
||||
uint32_t RVA = 0;
|
||||
ZydisDecodedInstruction Instruction;
|
||||
std::vector<ZydisDecodedOperand> Operands;
|
||||
};
|
||||
|
||||
std::span<uint8_t> GetSection(LPCSTR name)
|
||||
{
|
||||
using namespace Globals;
|
||||
if (BaseAddress == 0)
|
||||
return {};
|
||||
|
||||
const auto dosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
|
||||
const auto ntHeader = (PIMAGE_NT_HEADERS)((uintptr_t)dosHeader + dosHeader->e_lfanew);
|
||||
const auto sectionHeader = IMAGE_FIRST_SECTION(ntHeader);
|
||||
|
||||
for (auto i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
|
||||
{
|
||||
if (strcmp((char*)sectionHeader[i].Name, name) == 0)
|
||||
{
|
||||
const auto sectionSize = sectionHeader[i].Misc.VirtualSize;
|
||||
const auto virtualAddress = BaseAddress + sectionHeader[i].VirtualAddress;
|
||||
return std::span(reinterpret_cast<uint8_t*>(virtualAddress), sectionSize);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// decodes all instruction until next push, ignores branching
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="maxInstructions"></param>
|
||||
/// <returns>std::vector DecodedInstruction</returns>
|
||||
std::vector<DecodedInstruction> DecodeFunction(uintptr_t address, int32_t maxInstructions = -1)
|
||||
{
|
||||
using namespace Globals;
|
||||
|
||||
std::vector<DecodedInstruction> instructions;
|
||||
|
||||
ZydisDecoder decoder{};
|
||||
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
|
||||
|
||||
ZydisDecodedInstruction instruction{};
|
||||
ZydisDecoderContext context{};
|
||||
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT_VISIBLE]{};
|
||||
|
||||
while (true)
|
||||
{
|
||||
const auto data = reinterpret_cast<uint8_t*>(address);
|
||||
auto status = ZydisDecoderDecodeInstruction(&decoder, &context, data, ZYDIS_MAX_INSTRUCTION_LENGTH, &instruction);
|
||||
if (!ZYAN_SUCCESS(status))
|
||||
{
|
||||
// for skipping jump tables
|
||||
address += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
status = ZydisDecoderDecodeOperands(&decoder, &context, &instruction, operands, instruction.operand_count_visible);
|
||||
if (!ZYAN_SUCCESS(status))
|
||||
{
|
||||
// for skipping jump tables
|
||||
address += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (instruction.mnemonic == ZYDIS_MNEMONIC_PUSH && !instructions.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto rva = static_cast<uint32_t>(address - BaseAddress);
|
||||
instructions.emplace_back(rva, instruction, operands, instruction.operand_count_visible);
|
||||
|
||||
address += instruction.length;
|
||||
|
||||
if (maxInstructions != -1 && instructions.size() >= maxInstructions)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get the count of data references in the instructions (only second oprand of mov)
|
||||
/// </summary>
|
||||
/// <param name="instructions"></param>
|
||||
/// <returns></returns>
|
||||
int32_t GetDataReferenceCount(const std::vector<DecodedInstruction>& instructions)
|
||||
{
|
||||
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
|
||||
if (instr.Instruction.mnemonic != ZYDIS_MNEMONIC_MOV)
|
||||
return false;
|
||||
|
||||
if (instr.Operands.size() != 2)
|
||||
return false;
|
||||
|
||||
const auto& op = instr.Operands[1];
|
||||
|
||||
// access to memory, based off of rip, 32-bit displacement
|
||||
return op.type == ZYDIS_OPERAND_TYPE_MEMORY && op.mem.base == ZYDIS_REGISTER_RIP && op.mem.disp.has_displacement;
|
||||
}));
|
||||
}
|
||||
|
||||
int32_t GetCallCount(const std::vector<DecodedInstruction>& instructions)
|
||||
{
|
||||
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
|
||||
return instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL;
|
||||
}));
|
||||
}
|
||||
|
||||
int32_t GetUniqueCallCount(const std::vector<DecodedInstruction>& instructions)
|
||||
{
|
||||
std::unordered_set<uint32_t> calls;
|
||||
for (const auto& instr : instructions)
|
||||
{
|
||||
if (instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) {
|
||||
uint32_t destination = instr.Operands[0].imm.value.s + instr.RVA + instr.Instruction.length;
|
||||
calls.insert(destination);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(calls.size());
|
||||
}
|
||||
|
||||
int32_t GetCmpImmCount(const std::vector<DecodedInstruction>& instructions)
|
||||
{
|
||||
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
|
||||
return instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP && instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && instr.Operands[1].imm.value.u;
|
||||
}));
|
||||
}
|
||||
|
||||
void ResolveAchivementCmdId()
|
||||
{
|
||||
if (Globals::AchievementId != 0)
|
||||
return;
|
||||
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
|
||||
std::println("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
|
||||
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
|
||||
|
||||
if (il2cppSection.empty())
|
||||
return; // message box?
|
||||
|
||||
const auto candidates = Util::PatternScanAll(il2cppSection, "56 48 83 EC 20 48 89 D0 48 89 CE 80 3D ? ? ? ? 00");
|
||||
std::println("Candidates: {}", candidates.size());
|
||||
|
||||
std::vector<std::vector<DecodedInstruction>> filteredInstructions;
|
||||
std::ranges::copy_if(
|
||||
candidates | std::views::transform([](auto va) { return DecodeFunction(va); }),
|
||||
std::back_inserter(filteredInstructions),
|
||||
[](const std::vector<DecodedInstruction>& instr) {
|
||||
return GetDataReferenceCount(instr) == 5 && GetCallCount(instr) == 10 &&
|
||||
GetUniqueCallCount(instr) == 6 && GetCmpImmCount(instr) == 5;
|
||||
});
|
||||
|
||||
// should have only one result
|
||||
if (filteredInstructions.size() != 1)
|
||||
{
|
||||
std::println("Filtered Instructions: {}", filteredInstructions.size());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& instructions = filteredInstructions[0];
|
||||
std::println("RVA: 0x{:08X}", instructions.front().RVA);
|
||||
|
||||
// extract all the non-zero immediate values from the cmp instructions
|
||||
std::vector<uint32_t> cmdIds;
|
||||
std::ranges::for_each(instructions, [&cmdIds](const DecodedInstruction& instr) {
|
||||
if (instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP &&
|
||||
instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||
instr.Operands[1].imm.value.u != 0) {
|
||||
cmdIds.push_back(static_cast<uint32_t>(instr.Operands[1].imm.value.u));
|
||||
}
|
||||
});
|
||||
|
||||
for (const auto& cmdId : cmdIds)
|
||||
{
|
||||
std::println("AchievementId: {}", cmdId);
|
||||
Globals::AchievementIdSet.insert(static_cast<uint16_t>(cmdId));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
std::vector<uintptr_t> GetCalls(uint8_t* target)
|
||||
{
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
const auto sectionAddress = reinterpret_cast<uintptr_t>(il2cppSection.data());
|
||||
const auto sectionSize = il2cppSection.size();
|
||||
|
||||
std::vector<uintptr_t> callSites;
|
||||
const __m128i callOpcode = _mm_set1_epi8(0xE8);
|
||||
const size_t simdEnd = sectionSize / 16 * 16;
|
||||
|
||||
for (size_t i = 0; i < simdEnd; i += 16) {
|
||||
// load 16 bytes from the current address
|
||||
const __m128i chunk = _mm_loadu_si128((__m128i*)(sectionAddress + i));
|
||||
|
||||
// compare the loaded chunk with 0xE8 in all 16 bytes
|
||||
const __m128i result = _mm_cmpeq_epi8(chunk, callOpcode);
|
||||
|
||||
// move the comparison results into a mask
|
||||
int mask = _mm_movemask_epi8(result);
|
||||
|
||||
while (mask != 0) {
|
||||
DWORD first_match_idx = 0;
|
||||
_BitScanForward(&first_match_idx, mask); // index of the first set bit (match)
|
||||
|
||||
// index of the instruction
|
||||
const size_t instruction_index = i + first_match_idx;
|
||||
|
||||
const int32_t delta = *(int32_t*)(sectionAddress + instruction_index + 1);
|
||||
const uintptr_t dest = sectionAddress + instruction_index + 5 + delta;
|
||||
|
||||
if (dest == (uintptr_t)target) {
|
||||
callSites.push_back(sectionAddress + instruction_index);
|
||||
}
|
||||
|
||||
// clear the bit we just processed and continue with the next match
|
||||
mask &= ~(1 << first_match_idx);
|
||||
}
|
||||
}
|
||||
|
||||
return callSites;
|
||||
}
|
||||
|
||||
uintptr_t FindFunctionEntry(uintptr_t address) // not a correct way to find function entry
|
||||
{
|
||||
__try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// go back to 'sub rsp' instruction
|
||||
uint32_t code = *(uint32_t*)address;
|
||||
code &= ~0xFF000000;
|
||||
|
||||
if (_byteswap_ulong(code) == 0x4883EC00) { // sub rsp, ??
|
||||
return address;
|
||||
}
|
||||
|
||||
address--;
|
||||
}
|
||||
|
||||
}
|
||||
__except (1) {}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
void Resolve_BitConverter_ToUInt16()
|
||||
{
|
||||
if (Globals::Offset.BitConverter_ToUInt16 != 0) {
|
||||
Globals::Offset.BitConverter_ToUInt16 += Globals::BaseAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
|
||||
std::print("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
|
||||
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
|
||||
|
||||
/*
|
||||
mov ecx, 0Fh
|
||||
call ThrowHelper.ThrowArgumentNullException
|
||||
mov ecx, 0Eh
|
||||
mov edx, 16h
|
||||
call ThrowHelper.ThrowArgumentOutOfRangeException
|
||||
mov ecx, 5
|
||||
call ThrowHelper.ThrowArgumentException
|
||||
*/
|
||||
auto candidates = Util::PatternScanAll(il2cppSection, "B9 0F 00 00 00 E8 ? ? ? ? B9 0E 00 00 00 BA 16 00 00 00 E8 ? ? ? ? B9 05 00 00 00 E8 ? ? ? ?");
|
||||
std::println("Candidates: {}", candidates.size());
|
||||
|
||||
std::vector<uintptr_t> filteredEntries;
|
||||
std::ranges::copy_if(candidates, std::back_inserter(filteredEntries), [](uintptr_t& entry) {
|
||||
entry = FindFunctionEntry(entry);
|
||||
return entry % 16 == 0;
|
||||
});
|
||||
|
||||
for (const auto& entry : filteredEntries)
|
||||
{
|
||||
std::println("Entry: 0x{:X}", entry);
|
||||
}
|
||||
|
||||
std::println("Looking for call counts...");
|
||||
std::mutex mutex;
|
||||
std::unordered_map<uintptr_t, int32_t> callCounts;
|
||||
// find the call counts to candidate functions
|
||||
std::vector<std::future<void>> futures;
|
||||
std::ranges::transform(filteredEntries, std::back_inserter(futures), [&](uintptr_t entry) {
|
||||
return std::async(std::launch::async, [&](uintptr_t e) {
|
||||
const auto callSites = GetCalls((uint8_t*)e);
|
||||
std::lock_guard lock(mutex);
|
||||
callCounts[e] = callSites.size();
|
||||
}, entry);
|
||||
});
|
||||
|
||||
for (auto& future : futures) {
|
||||
future.get();
|
||||
}
|
||||
|
||||
uintptr_t targetEntry = 0;
|
||||
for (const auto& [entry, count] : callCounts)
|
||||
{
|
||||
std::println("Entry: 0x{:X}, RVA: 0x{:08X}, Count: {}", entry, entry - Globals::BaseAddress, count);
|
||||
if (count == 3) {
|
||||
targetEntry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
Globals::Offset.BitConverter_ToUInt16 = targetEntry;
|
||||
}
|
||||
|
||||
void ResolveInventoryCmdId()
|
||||
{
|
||||
if (Globals::PlayerStoreId != 0)
|
||||
return;
|
||||
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
std::println("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
|
||||
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
|
||||
|
||||
/*
|
||||
cmp r8d, 2
|
||||
jz 0x3B
|
||||
cmd r8d, 1
|
||||
mov rax
|
||||
*/
|
||||
|
||||
// look for ItemModule.GetBagManagerByStoreType <- mf got inlined in 5.5
|
||||
// we just gon to look for OnPlayerStoreNotify
|
||||
const auto candidates = Util::PatternScanAll(il2cppSection, "41 83 F8 02 B8 ? ? ? ? B9 ? ? ? ? 48 0F 45 C1");
|
||||
std::println("Candidates: {}", candidates.size());
|
||||
if (candidates.empty())
|
||||
return;
|
||||
|
||||
uintptr_t pOnPlayerStoreNotify = 0;
|
||||
{
|
||||
// one of the candidates is OnPlayerStoreNotify
|
||||
// search after the pattern to find an arbirary branch
|
||||
auto decodedInstructions = candidates | std::views::transform([](auto va) { return DecodeFunction(va, 20); });
|
||||
|
||||
// find the call site with an arbitrary branch (JMP or CALL) after the call
|
||||
auto targetInstructions = std::ranges::find_if(decodedInstructions, [](const auto& instr) {
|
||||
return std::ranges::any_of(instr, [](const DecodedInstruction& i) {
|
||||
return (i.Instruction.mnemonic == ZYDIS_MNEMONIC_JMP || i.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) &&
|
||||
i.Operands.size() == 1 && i.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER;
|
||||
});
|
||||
});
|
||||
|
||||
if (targetInstructions == decodedInstructions.end()) {
|
||||
std::println("Failed to find target instruction");
|
||||
return;
|
||||
}
|
||||
|
||||
// ItemModule.OnPlayerStoreNotify
|
||||
const auto& instructions = *targetInstructions;
|
||||
pOnPlayerStoreNotify = Globals::BaseAddress + instructions.front().RVA;
|
||||
|
||||
const auto isFunctionEntry = [](uintptr_t va) -> bool {
|
||||
auto* code = reinterpret_cast<uint8_t*>(va);
|
||||
return (va % 16 == 0 &&
|
||||
code[0] == 0x56 && // push rsi
|
||||
(*reinterpret_cast<uint32_t*>(&code[1]) & ~0xFF000000) == _byteswap_ulong(0x4883EC00)); // sub rsp, ??
|
||||
};
|
||||
|
||||
auto range = std::views::iota(0, 126);
|
||||
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pOnPlayerStoreNotify - i); });
|
||||
it != range.end())
|
||||
{
|
||||
pOnPlayerStoreNotify -= *it;
|
||||
}
|
||||
else {
|
||||
std::println("Failed to find function entry");
|
||||
return;
|
||||
}
|
||||
|
||||
std::println("OnPlayerStoreNotify: 0x{:X}", pOnPlayerStoreNotify);
|
||||
}
|
||||
|
||||
uintptr_t pOnPacket = 0;
|
||||
{
|
||||
// get all calls to OnPlayerStoreNotify
|
||||
const auto calls = GetCalls(reinterpret_cast<uint8_t*>(pOnPlayerStoreNotify));
|
||||
if (calls.size() != 1) {
|
||||
std::println("Failed to find call site");
|
||||
return;
|
||||
}
|
||||
|
||||
// ItemModule.OnPacket - search backwards for function entry
|
||||
pOnPacket = calls.front();
|
||||
const auto isFunctionEntry = [](uintptr_t va) -> bool {
|
||||
auto* code = reinterpret_cast<uint8_t*>(va);
|
||||
return (va % 16 == 0 &&
|
||||
code[0] == 0x56 && // push rsi
|
||||
(*reinterpret_cast<uint32_t*>(&code[1]) & ~0xFF000000) == _byteswap_ulong(0x4883EC00)); // sub rsp, ??
|
||||
};
|
||||
|
||||
auto range = std::views::iota(0, 3044);
|
||||
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pOnPacket - i); });
|
||||
it != range.end())
|
||||
{
|
||||
pOnPacket -= *it;
|
||||
}
|
||||
else {
|
||||
std::println("Failed to find function entry");
|
||||
return;
|
||||
}
|
||||
|
||||
std::println("OnPacket: 0x{:X}", pOnPacket);
|
||||
}
|
||||
|
||||
const auto decodedInstructions = DecodeFunction(pOnPacket);
|
||||
uint32_t cmdid = 0;
|
||||
std::ranges::for_each(decodedInstructions, [&cmdid, pOnPlayerStoreNotify](const DecodedInstruction& i) {
|
||||
static uint32_t immValue = 0; // keep track of the last immediate value
|
||||
|
||||
if (i.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP &&
|
||||
i.Operands.size() == 2 &&
|
||||
i.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||
i.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE)
|
||||
{
|
||||
immValue = static_cast<uint32_t>(i.Operands[1].imm.value.u);
|
||||
}
|
||||
|
||||
if (i.Instruction.meta.branch_type == ZYDIS_BRANCH_TYPE_NEAR && i.Operands.size() == 1 &&
|
||||
(i.Instruction.mnemonic == ZYDIS_MNEMONIC_JZ || i.Instruction.mnemonic == ZYDIS_MNEMONIC_JNZ)) // jz for true branch, jnz for false branch
|
||||
{
|
||||
// assume the branching is jz
|
||||
uintptr_t branchAddr = Globals::BaseAddress + i.RVA + i.Instruction.length + i.Operands[0].imm.value.s;
|
||||
|
||||
// check if the branch is jnz and adjust the branch address
|
||||
if (i.Instruction.mnemonic == ZYDIS_MNEMONIC_JNZ) {
|
||||
branchAddr = Globals::BaseAddress + i.RVA + i.Instruction.length;
|
||||
}
|
||||
|
||||
// decode the branch address immediately
|
||||
const auto instructions = DecodeFunction(branchAddr, 10);
|
||||
const auto isMatch = std::ranges::any_of(instructions, [pOnPlayerStoreNotify](const DecodedInstruction& instr) {
|
||||
if (instr.Instruction.mnemonic != ZYDIS_MNEMONIC_CALL)
|
||||
return false;
|
||||
|
||||
uintptr_t destination = 0;
|
||||
ZydisCalcAbsoluteAddress(&instr.Instruction, instr.Operands.data(), Globals::BaseAddress + instr.RVA, &destination);
|
||||
return destination == pOnPlayerStoreNotify;
|
||||
});
|
||||
|
||||
if (isMatch) {
|
||||
cmdid = immValue;
|
||||
}
|
||||
|
||||
}
|
||||
return cmdid == 0; // stop processing if cmdid is found
|
||||
});
|
||||
|
||||
Globals::PlayerStoreId = static_cast<uint16_t>(cmdid);
|
||||
std::println("PlayerStoreId: {}", Globals::PlayerStoreId);
|
||||
}
|
||||
|
||||
void Resolve_AccountDataItem_UpdateNormalProp()
|
||||
{
|
||||
if (Globals::Offset.AccountDataItem_UpdateNormalProp != 0) {
|
||||
Globals::Offset.AccountDataItem_UpdateNormalProp += Globals::BaseAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto il2cppSection = GetSection("il2cpp");
|
||||
|
||||
/*
|
||||
add ??, 0FFFFD8EEh
|
||||
cmp ??, 30h
|
||||
*/
|
||||
auto candidates = Util::PatternScanAll(il2cppSection, "81 ? EE D8 FF FF ? 83 ? 30");
|
||||
// should have only one result
|
||||
if (candidates.size() != 1)
|
||||
{
|
||||
std::println("Filtered Instructions: {}", candidates.size());
|
||||
return;
|
||||
}
|
||||
auto fp = candidates[0];
|
||||
|
||||
const auto isFunctionEntry = [](uintptr_t va) -> bool {
|
||||
auto* code = reinterpret_cast<uint8_t*>(va);
|
||||
/* push rsi */
|
||||
/* push rdi */
|
||||
return (va % 16 == 0 && code[0] == 0x56 && code[1] == 0x57);
|
||||
};
|
||||
|
||||
auto range = std::views::iota(0, 213);
|
||||
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(fp - i); }); it != range.end()) {
|
||||
fp -= *it;
|
||||
} else {
|
||||
std::println("Failed to find function entry");
|
||||
return;
|
||||
}
|
||||
|
||||
Globals::Offset.AccountDataItem_UpdateNormalProp = fp;
|
||||
}
|
||||
|
||||
#define DO_APP_FUNC(ca, oa, r, n, p) r (*n) p
|
||||
namespace Genshin {
|
||||
#include "il2cpp-functions.h"
|
||||
}
|
||||
#undef DO_APP_FUNC
|
||||
|
||||
#define DO_UNI_FUNC(ca, oa, r, n, p) r (*n) p
|
||||
namespace Genshin {
|
||||
#include "il2cpp-unity-functions.h"
|
||||
}
|
||||
#undef DO_UNI_FUNC
|
||||
|
||||
using std::string;
|
||||
|
||||
void InitIL2CPP() {
|
||||
TCHAR szFileName[MAX_PATH];
|
||||
GetModuleFileName(NULL, szFileName, MAX_PATH);
|
||||
auto isCN = strstr(szFileName, "YuanShen.exe");//string(szFileName).contains();
|
||||
auto hBase = GetModuleHandle("UserAssembly.dll");
|
||||
auto bAddr = (UINT64)hBase;
|
||||
auto cAddr = (UINT64)GetModuleHandle("UnityPlayer.dll");
|
||||
#define DO_API(ca, oa, r, n, p) n = (r (*) p)(bAddr + (isCN ? ca : oa))
|
||||
#include "il2cpp-api-functions.h"
|
||||
#undef DO_API
|
||||
#define DO_APP_FUNC(ca, oa, r, n, p) n = (r (*) p)(bAddr + (isCN ? ca : oa))
|
||||
#include "il2cpp-functions.h"
|
||||
#undef DO_APP_FUNC
|
||||
#define DO_UNI_FUNC(ca, oa, r, n, p) n = (r (*) p)(cAddr + (isCN ? ca : oa))
|
||||
#include "il2cpp-unity-functions.h"
|
||||
#undef DO_UNI_FUNC
|
||||
bool InitIL2CPP()
|
||||
{
|
||||
std::string buffer;
|
||||
buffer.resize(MAX_PATH);
|
||||
ZeroMemory(buffer.data(), MAX_PATH);
|
||||
const auto pathLength = GetModuleFileNameA(nullptr, buffer.data(), MAX_PATH);
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
buffer.resize(pathLength);
|
||||
ZeroMemory(buffer.data(), pathLength);
|
||||
GetModuleFileNameA(nullptr, buffer.data(), pathLength);
|
||||
}
|
||||
buffer.shrink_to_fit();
|
||||
|
||||
using namespace Globals;
|
||||
IsCNREL = buffer.find("YuanShen.exe") != std::string::npos;
|
||||
BaseAddress = (uintptr_t)GetModuleHandleA(nullptr);
|
||||
|
||||
std::future<void> resolveFuncFuture = std::async(std::launch::async, Resolve_BitConverter_ToUInt16);
|
||||
std::future<void> resolveCmdIdFuture = std::async(std::launch::async, ResolveAchivementCmdId);
|
||||
std::future<void> resolveInventoryFuture = std::async(std::launch::async, ResolveInventoryCmdId);
|
||||
std::future<void> resolveUpdatePropFuture = std::async(std::launch::async, Resolve_AccountDataItem_UpdateNormalProp);
|
||||
|
||||
resolveFuncFuture.get();
|
||||
resolveCmdIdFuture.get();
|
||||
resolveInventoryFuture.get();
|
||||
resolveUpdatePropFuture.get();
|
||||
|
||||
std::println("BaseAddress: 0x{:X}", BaseAddress);
|
||||
std::println("IsCNREL: {:d}", IsCNREL);
|
||||
std::println("BitConverter_ToUInt16: 0x{:X}", Offset.BitConverter_ToUInt16);
|
||||
std::println("AccountDataItem_UpdateNormalProp: 0x{:X}", Offset.AccountDataItem_UpdateNormalProp);
|
||||
|
||||
if (!AchievementId && AchievementIdSet.empty())
|
||||
{
|
||||
Util::ErrorDialog("Failed to resolve achievement data");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PlayerStoreId)
|
||||
{
|
||||
Util::ErrorDialog("Failed to resolve inventory data");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
// IL2CPP application initializer
|
||||
void InitIL2CPP();
|
||||
bool InitIL2CPP();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
using namespace Genshin;
|
||||
|
||||
DO_UNI_FUNC(0x00100550, 0x00100550, ByteArray*, UnityEngine_RecordUserData, (int32_t nType));
|
||||
9
lib/src/ntprivate.h
Normal file
9
lib/src/ntprivate.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <bcrypt.h>
|
||||
|
||||
#pragma comment(lib, "ntdll.lib")
|
||||
|
||||
#define LDR_ADDREF_DLL_PIN 0x00000001
|
||||
|
||||
EXTERN_C NTSYSAPI NTSTATUS NTAPI LdrAddRefDll(_In_ ULONG Flags, _In_ PVOID DllHandle);
|
||||
@@ -1 +0,0 @@
|
||||
#include "pch.h"
|
||||
@@ -1,27 +0,0 @@
|
||||
// pch.h: 这是预编译标头文件。
|
||||
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
|
||||
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
|
||||
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
|
||||
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
|
||||
|
||||
#ifndef PCH_H
|
||||
#define PCH_H
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
|
||||
// Windows 头文件
|
||||
#include <windows.h>
|
||||
|
||||
// 添加要在此处预编译的标头
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <codecvt>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <detours.h>
|
||||
#include "HookManager.h"
|
||||
#include "il2cpp-appdata.h"
|
||||
|
||||
#endif //PCH_H
|
||||
262
lib/src/util.cpp
262
lib/src/util.cpp
@@ -1,78 +1,210 @@
|
||||
#include "pch.h"
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <ranges>
|
||||
#include <intrin.h>
|
||||
#include "util.h"
|
||||
|
||||
VOID DisableVMProtect() {
|
||||
DWORD oldProtect = 0;
|
||||
auto ntdll = GetModuleHandleA("ntdll.dll");
|
||||
BYTE callcode = ((BYTE*)GetProcAddress(ntdll, "NtQuerySection"))[4] - 1;
|
||||
BYTE restore[] = { 0x4C, 0x8B, 0xD1, 0xB8, callcode };
|
||||
auto nt_vp = (BYTE*)GetProcAddress(ntdll, "NtProtectVirtualMemory");
|
||||
VirtualProtect(nt_vp, sizeof(restore), PAGE_EXECUTE_READWRITE, &oldProtect);
|
||||
memcpy(nt_vp, restore, sizeof(restore));
|
||||
VirtualProtect(nt_vp, sizeof(restore), oldProtect, &oldProtect);
|
||||
}
|
||||
#include "globals.h"
|
||||
|
||||
#pragma region StringConvert
|
||||
|
||||
string IlStringToString(Il2CppString* str, UINT codePage) {
|
||||
auto chars = reinterpret_cast<const wchar_t*>(str->chars);
|
||||
auto len = WideCharToMultiByte(codePage, 0, chars, -1, nullptr, 0, nullptr, nullptr);
|
||||
auto buffer = new char[len];
|
||||
WideCharToMultiByte(codePage, 0, chars, -1, buffer, len, nullptr, nullptr);
|
||||
return string(buffer);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region GC
|
||||
|
||||
UINT32 GCHandle_New(void* object, bool pinned) {
|
||||
return il2cpp_gchandle_new((Il2CppObject*)object, pinned);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ByteUtils
|
||||
|
||||
bool IsLittleEndian() {
|
||||
UINT i = 1;
|
||||
char* c = (char*)&i;
|
||||
return (*c);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
#ifdef _DEBUG
|
||||
#pragma runtime_checks("", off)
|
||||
#endif
|
||||
|
||||
#pragma region FindMainWindowByPID
|
||||
|
||||
struct HandleData {
|
||||
DWORD pid;
|
||||
HWND hwnd;
|
||||
};
|
||||
namespace
|
||||
{
|
||||
struct HandleData {
|
||||
DWORD Pid;
|
||||
HWND Hwnd;
|
||||
};
|
||||
|
||||
BOOL IsMainWindow(HWND handle) {
|
||||
return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle) == TRUE;
|
||||
}
|
||||
bool IsMainWindow(HWND handle) {
|
||||
return GetWindow(handle, GW_OWNER) == nullptr && IsWindowVisible(handle) == TRUE;
|
||||
}
|
||||
|
||||
BOOL IsUnityWindow(HWND handle) {
|
||||
TCHAR name[256];
|
||||
GetClassName(handle, name, 256);
|
||||
return _strcmpi(name, "UnityWndClass") == 0;
|
||||
}
|
||||
bool IsUnityWindow(HWND handle) {
|
||||
char szName[256]{};
|
||||
GetClassNameA(handle, szName, 256);
|
||||
return _stricmp(szName, "UnityWndClass") == 0;
|
||||
}
|
||||
|
||||
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) {
|
||||
HandleData& data = *(HandleData*)lParam;
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(handle, &pid);
|
||||
if (data.pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle))
|
||||
return TRUE;
|
||||
data.hwnd = handle;
|
||||
return FALSE;
|
||||
}
|
||||
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) {
|
||||
HandleData& data = *(HandleData*)lParam;
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(handle, &pid);
|
||||
if (data.Pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle))
|
||||
return TRUE;
|
||||
data.Hwnd = handle;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::tuple<std::vector<uint8_t>, std::string> PatternToBytes(const char* pattern)
|
||||
{
|
||||
std::vector<uint8_t> bytes;
|
||||
std::string mask;
|
||||
|
||||
const auto start = const_cast<char*>(pattern);
|
||||
const auto end = const_cast<char*>(pattern) + strlen(pattern);
|
||||
|
||||
for (auto current = start; current < end; ++current) {
|
||||
if (*current == '?') {
|
||||
++current;
|
||||
if (*current == '?')
|
||||
++current;
|
||||
bytes.push_back(-1);
|
||||
mask.push_back('?');
|
||||
}
|
||||
else {
|
||||
bytes.push_back(strtoul(current, ¤t, 16));
|
||||
mask.push_back('x');
|
||||
}
|
||||
}
|
||||
return { bytes, mask };
|
||||
}
|
||||
|
||||
HWND FindMainWindowByPID(DWORD pid) {
|
||||
HandleData data = { pid, 0 };
|
||||
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
|
||||
return data.hwnd;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
static constexpr LPCSTR base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
namespace Util
|
||||
{
|
||||
HWND FindMainWindowByPID(DWORD pid)
|
||||
{
|
||||
HandleData data = {
|
||||
.Pid = pid,
|
||||
.Hwnd = nullptr
|
||||
};
|
||||
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
|
||||
return data.Hwnd;
|
||||
}
|
||||
|
||||
std::string Base64Encode(std::span<uint8_t> data)
|
||||
{
|
||||
return Base64Encode(data.data(), data.size());
|
||||
}
|
||||
|
||||
std::string Base64Encode(uint8_t const* buf, size_t bufLen)
|
||||
{
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
uint8_t char_array_3[3];
|
||||
uint8_t char_array_4[4];
|
||||
while (bufLen--) {
|
||||
char_array_3[i++] = *buf++;
|
||||
if (i == 3) {
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
for (i = 0; (i < 4); i++)
|
||||
ret += base64_chars[char_array_4[i]];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (i) {
|
||||
int j;
|
||||
for (j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
for (j = 0; j < i + 1; j++)
|
||||
ret += base64_chars[char_array_4[j]];
|
||||
while (i++ < 3)
|
||||
ret += '=';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ErrorDialog(LPCSTR title, LPCSTR msg)
|
||||
{
|
||||
MessageBoxA(Globals::GameWindow, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
|
||||
}
|
||||
|
||||
void ErrorDialog(LPCSTR msg)
|
||||
{
|
||||
ErrorDialog("YaeAchievement", msg);
|
||||
}
|
||||
|
||||
void Win32ErrorDialog(DWORD code, DWORD winerrcode)
|
||||
{
|
||||
const std::string msg = "CRITICAL ERROR!\nError code: " + std::to_string(winerrcode) + "-" + std::to_string(code) +
|
||||
"\n\nPlease take the screenshot and contact developer by GitHub Issue to solve this problem\nNOT MIHOYO/COGNOSPHERE CUSTOMER SERVICE!";
|
||||
|
||||
ErrorDialog("YaeAchievement", msg.c_str());
|
||||
}
|
||||
|
||||
std::vector<uintptr_t> PatternScanAll(std::span<uint8_t> bytes, const char* pattern)
|
||||
{
|
||||
std::vector<uintptr_t> results;
|
||||
const auto [patternBytes, patternMask] = PatternToBytes(pattern);
|
||||
constexpr std::size_t chunkSize = 16;
|
||||
|
||||
const auto maskCount = static_cast<std::size_t>(std::ceil(patternMask.size() / chunkSize));
|
||||
std::array<int32_t, 32> masks{};
|
||||
|
||||
auto chunks = patternMask | std::views::chunk(chunkSize);
|
||||
for (std::size_t i = 0; auto chunk : chunks) {
|
||||
int32_t mask = 0;
|
||||
for (std::size_t j = 0; j < chunk.size(); ++j) {
|
||||
if (chunk[j] == 'x') {
|
||||
mask |= 1 << j;
|
||||
}
|
||||
}
|
||||
masks[i++] = mask;
|
||||
}
|
||||
|
||||
__m128i xmm1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(patternBytes.data()));
|
||||
__m128i xmm2, xmm3, mask;
|
||||
|
||||
auto pData = bytes.data();
|
||||
const auto end = pData + bytes.size() - patternMask.size();
|
||||
|
||||
while (pData < end)
|
||||
{
|
||||
_mm_prefetch(reinterpret_cast<const char*>(pData + 64), _MM_HINT_NTA);
|
||||
|
||||
if (patternBytes[0] == pData[0])
|
||||
{
|
||||
xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pData));
|
||||
mask = _mm_cmpeq_epi8(xmm1, xmm2);
|
||||
|
||||
if ((_mm_movemask_epi8(mask) & masks[0]) == masks[0])
|
||||
{
|
||||
bool found = true;
|
||||
for (int i = 1; i < maskCount; ++i)
|
||||
{
|
||||
xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pData + i * chunkSize));
|
||||
xmm3 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(patternBytes.data() + i * chunkSize));
|
||||
mask = _mm_cmpeq_epi8(xmm2, xmm3);
|
||||
if ((_mm_movemask_epi8(mask) & masks[i]) != masks[i])
|
||||
{
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (found) {
|
||||
results.push_back(reinterpret_cast<uintptr_t>(pData));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
++pData;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
#pragma runtime_checks("", restore)
|
||||
#endif
|
||||
@@ -1,34 +1,19 @@
|
||||
// ReSharper disable CppClangTidyClangDiagnosticLanguageExtensionToken
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
|
||||
using std::string;
|
||||
namespace Util
|
||||
{
|
||||
HWND FindMainWindowByPID(DWORD pid);
|
||||
std::string Base64Encode(std::span<uint8_t> data);
|
||||
std::string Base64Encode(uint8_t const* buf, size_t bufLen);
|
||||
|
||||
VOID DisableVMProtect();
|
||||
bool IsLittleEndian();
|
||||
HWND FindMainWindowByPID(DWORD pid);
|
||||
UINT32 GCHandle_New(LPVOID object, bool pinned);
|
||||
string IlStringToString(Il2CppString* str, UINT codePage = CP_ACP);
|
||||
void ErrorDialog(LPCSTR title, LPCSTR msg);
|
||||
void ErrorDialog(LPCSTR msg);
|
||||
void Win32ErrorDialog(DWORD code, DWORD winerrcode);
|
||||
|
||||
#define cstring_new(str) il2cpp_string_new(str)
|
||||
#define string_new(str) cstring_new((str).c_str())
|
||||
|
||||
#define ErrorDialogT(title, msg) MessageBox(unityWnd, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
|
||||
#define ErrorDialog(msg) ErrorDialogT("YaeAchievement", msg)
|
||||
#define Win32ErrorDialog(code) ErrorDialogT("YaeAchievement", ("CRITICAL ERROR!\nError code: " + std::to_string(GetLastError()) + "-"#code"\n\nPlease take the screenshot and contact developer by GitHub Issue to solve this problem\nNOT MIHOYO/COGNOSPHERE CUSTOMER SERVICE!").c_str())
|
||||
|
||||
template<class T>
|
||||
static T ReadMapped(void* data, int offset, bool littleEndian = false) {
|
||||
char* cData = (char*)data;
|
||||
T result = {};
|
||||
if (IsLittleEndian() != littleEndian) {
|
||||
for (int i = 0; i < sizeof(T); i++)
|
||||
((char*)&result)[i] = cData[offset + sizeof(T) - i - 1];
|
||||
return result;
|
||||
}
|
||||
memcpy(&result, cData + offset, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static T* GCHandle_GetObject(UINT handle) {
|
||||
return (T*) il2cpp_gchandle_get_target(handle);
|
||||
}
|
||||
std::vector<uintptr_t> PatternScanAll(std::span<uint8_t> bytes, const char* pattern);
|
||||
}
|
||||
BIN
res/Updater.exe
BIN
res/Updater.exe
Binary file not shown.
@@ -1,88 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Net;
|
||||
using YaeAchievement.AppCenterSDK.Models;
|
||||
using YaeAchievement.AppCenterSDK.Models.Serialization;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK;
|
||||
|
||||
public static class AppCenter {
|
||||
|
||||
private const string AppSecret = "648b83bf-d439-49bd-97f4-e1e506bdfe39";
|
||||
private const string ApiUrl = "https://in.appcenter.ms/logs?api-version=1.0.0";
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
public static Guid? SessionID { get; private set; }
|
||||
public static readonly string DeviceID;
|
||||
public static readonly Device DeviceInfo;
|
||||
private static List<Log> Queue;
|
||||
private static readonly Lazy<HttpClient> httpClient = new(() => new HttpClient(new HttpClientHandler {
|
||||
Proxy = GlobalVars.DebugProxy ? new WebProxy("http://127.0.0.1:8888") : null
|
||||
}) {
|
||||
DefaultRequestHeaders = {
|
||||
{ "Install-ID", DeviceID },
|
||||
{ "App-Secret", AppSecret }
|
||||
}
|
||||
});
|
||||
|
||||
static AppCenter() {
|
||||
Queue = new List<Log>();
|
||||
DeviceID = DeviceHelper.GetDeviceID();
|
||||
DeviceInfo = new Device();
|
||||
var running = true;
|
||||
Task.Run(() => {
|
||||
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
|
||||
while (running) {
|
||||
Upload();
|
||||
Thread.Sleep(5 * 1000);
|
||||
}
|
||||
});
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
|
||||
running = false;
|
||||
};
|
||||
LogSerializer.AddLogType(PageLog.JsonIdentifier, typeof(PageLog));
|
||||
LogSerializer.AddLogType(EventLog.JsonIdentifier, typeof(EventLog));
|
||||
LogSerializer.AddLogType(HandledErrorLog.JsonIdentifier, typeof(HandledErrorLog));
|
||||
LogSerializer.AddLogType(ManagedErrorLog.JsonIdentifier, typeof(ManagedErrorLog));
|
||||
LogSerializer.AddLogType(StartServiceLog.JsonIdentifier, typeof(StartServiceLog));
|
||||
LogSerializer.AddLogType(StartSessionLog.JsonIdentifier, typeof(StartSessionLog));
|
||||
}
|
||||
|
||||
// ReSharper restore InconsistentNaming
|
||||
public static void Upload() {
|
||||
if (Queue.Count == 0) return;
|
||||
var uploadStatus = "";
|
||||
do {
|
||||
Queue = Queue.Select(log => {
|
||||
log.Status = LogStatus.Uploading;
|
||||
return log;
|
||||
}).ToList();
|
||||
using var uploadContent = new StringContent(Queue.ToJson());
|
||||
try {
|
||||
using var response = httpClient.Value.PostAsync(ApiUrl, uploadContent).Result;
|
||||
var result = response.Content.ReadAsStringAsync().Result;
|
||||
uploadStatus = JsonConvert.DeserializeObject<LogUploadResult>(result)!.Status;
|
||||
} catch (Exception) {
|
||||
// ignored
|
||||
}
|
||||
} while (uploadStatus != "Success");
|
||||
Queue.RemoveAll(log => log.Status == LogStatus.Uploading);
|
||||
}
|
||||
|
||||
public static void Init() {
|
||||
new StartServiceLog("Analytics", "Crashes").Enqueue();
|
||||
SessionID = Guid.NewGuid();
|
||||
new StartSessionLog().Enqueue();
|
||||
}
|
||||
|
||||
public static void TrackCrash(Exception exception, bool fatal = true) {
|
||||
new ManagedErrorLog(exception, fatal).Enqueue();
|
||||
}
|
||||
|
||||
public static void Enqueue(this Log log) {
|
||||
Queue.Add(log);
|
||||
}
|
||||
|
||||
private static string ToJson(this IEnumerable<Log> queue) {
|
||||
return LogSerializer.Serialize(new LogContainer(queue));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using Microsoft.Win32;
|
||||
using YaeAchievement.Win32;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK;
|
||||
|
||||
#pragma warning disable CA1416
|
||||
public static class DeviceHelper {
|
||||
|
||||
public static string? GetOem() {
|
||||
using var root = Registry.LocalMachine;
|
||||
using var sub = root.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\BIOS");
|
||||
var oem = sub?.GetValue("SystemManufacturer") as string;
|
||||
return oem == "System manufacturer" ? null : oem;
|
||||
}
|
||||
|
||||
public static string? GetModel() {
|
||||
using var root = Registry.LocalMachine;
|
||||
using var sub = root.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\BIOS");
|
||||
var model = sub?.GetValue("SystemProductName") as string;
|
||||
return model == "System Product Name" ? null : model;
|
||||
}
|
||||
|
||||
public static string GetScreenSize() {
|
||||
var desktop = Native.GetDC(IntPtr.Zero);
|
||||
var size = $"{Native.GetDeviceCaps(desktop, 118)}x{Native.GetDeviceCaps(desktop, 117)}";
|
||||
Native.ReleaseDC(IntPtr.Zero, desktop);
|
||||
return size;
|
||||
}
|
||||
|
||||
public static string? GetCountry() {
|
||||
using var root = Registry.CurrentUser;
|
||||
using var sub = root.OpenSubKey("Control Panel\\International\\Geo");
|
||||
return sub?.GetValue("Name") as string;
|
||||
}
|
||||
|
||||
public static string GetSystemVersion() {
|
||||
using var root = Registry.LocalMachine;
|
||||
using var sub = root.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
|
||||
var majorVersion = sub?.GetValue("CurrentMajorVersionNumber");
|
||||
if (majorVersion != null) {
|
||||
var minorVersion = sub?.GetValue("CurrentMinorVersionNumber", "0");
|
||||
var buildNumber = sub?.GetValue("CurrentBuildNumber", "0");
|
||||
return $"{majorVersion}.{minorVersion}.{buildNumber}";
|
||||
} else {
|
||||
var version = sub?.GetValue("CurrentVersion", "0.0");
|
||||
var buildNumber = sub?.GetValue("CurrentBuild", "0");
|
||||
return $"{version}.{buildNumber}";
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetSystemBuild() {
|
||||
using var root = Registry.LocalMachine;
|
||||
using var sub = root.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
|
||||
return (int) (sub?.GetValue("UBR") ?? 0);
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static string GetDeviceID() {
|
||||
using var sdk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\miHoYoSDK");
|
||||
if (sdk?.GetValue("MIHOYOSDK_DEVICE_ID") is not string id) {
|
||||
id = $"{Random.Shared.NextInt64().ToString().SHA1Hash().ToLower()}{DateTimeOffset.Now.ToUnixTimeMilliseconds()}";
|
||||
sdk?.SetValue("MIHOYOSDK_DEVICE_ID", id);
|
||||
}
|
||||
id = id.MD5Hash().ToLower();
|
||||
return $"{id[..8]}-{id[8..12]}-{id[12..16]}-{id[16..20]}-{id[20..]}";
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1416
|
||||
@@ -1,25 +0,0 @@
|
||||
using YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK;
|
||||
|
||||
public static class ErrorLogHelper {
|
||||
public static MException CreateModelExceptionAndBinaries(Exception exception) {
|
||||
var modelException = new MException {
|
||||
Type = exception.GetType().ToString(),
|
||||
Message = exception.Message,
|
||||
StackTrace = exception.StackTrace
|
||||
};
|
||||
if (exception is AggregateException aggregateException) {
|
||||
if (aggregateException.InnerExceptions.Count != 0) {
|
||||
modelException.InnerExceptions = new List<MException>();
|
||||
foreach (var innerException in aggregateException.InnerExceptions) {
|
||||
modelException.InnerExceptions.Add(CreateModelExceptionAndBinaries(innerException));
|
||||
}
|
||||
}
|
||||
} else if (exception.InnerException != null) {
|
||||
modelException.InnerExceptions ??= new List<MException>();
|
||||
modelException.InnerExceptions.Add(CreateModelExceptionAndBinaries(exception.InnerException));
|
||||
}
|
||||
return modelException;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
public class Device {
|
||||
|
||||
[JsonProperty(PropertyName = "sdkName")]
|
||||
public string SdkName { get; set; } = "appcenter.wpf.netcore";
|
||||
|
||||
[JsonProperty(PropertyName = "sdkVersion")]
|
||||
public string SdkVersion { get; set; } = "4.5.0";
|
||||
|
||||
[JsonProperty(PropertyName = "osName")]
|
||||
public string OsName { get; set; } = "WINDOWS";
|
||||
|
||||
[JsonProperty(PropertyName = "osVersion")]
|
||||
public string OsVersion { get; set; } = DeviceHelper.GetSystemVersion();
|
||||
|
||||
[JsonProperty(PropertyName = "osBuild")]
|
||||
public string OsBuild { get; set; } = $"{DeviceHelper.GetSystemVersion()}.{DeviceHelper.GetSystemBuild()}";
|
||||
|
||||
[JsonProperty(PropertyName = "model")]
|
||||
public string? Model { get; set; } = DeviceHelper.GetModel();
|
||||
|
||||
[JsonProperty(PropertyName = "oemName")]
|
||||
public string? OemName { get; set; } = DeviceHelper.GetOem();
|
||||
|
||||
[JsonProperty(PropertyName = "screenSize")]
|
||||
public string ScreenSize { get; set; } = DeviceHelper.GetScreenSize();
|
||||
|
||||
[JsonProperty(PropertyName = "carrierCountry")]
|
||||
public string Country { get; set; } = DeviceHelper.GetCountry() ?? "CN";
|
||||
|
||||
[JsonProperty(PropertyName = "locale")]
|
||||
public string Locale { get; set; } = CultureInfo.CurrentCulture.Name;
|
||||
|
||||
[JsonProperty(PropertyName = "timeZoneOffset")]
|
||||
public int TimeZoneOffset { get; set; } = (int) TimeZoneInfo.Local.BaseUtcOffset.TotalMinutes;
|
||||
|
||||
[JsonProperty(PropertyName = "appVersion")]
|
||||
public string AppVersion { get; set; } = GlobalVars.AppVersionName;
|
||||
|
||||
[JsonProperty(PropertyName = "appBuild")]
|
||||
public string AppBuild { get; set; } = GlobalVars.AppVersionCode.ToString();
|
||||
|
||||
[JsonProperty(PropertyName = "appNamespace")]
|
||||
public string AppNamespace { get; set; } = Assembly.GetEntryAssembly()?.EntryPoint?.DeclaringType?.Namespace ?? string.Empty;
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
[JsonObject(JsonIdentifier)]
|
||||
public class EventLog : LogWithProperties {
|
||||
|
||||
public const string JsonIdentifier = "event";
|
||||
|
||||
public EventLog(string name) {
|
||||
Name = name;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
private Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
private string Name { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
[JsonObject(JsonIdentifier)]
|
||||
public class HandledErrorLog : LogWithProperties {
|
||||
|
||||
public const string JsonIdentifier = "handledError";
|
||||
|
||||
public HandledErrorLog(MException exception) {
|
||||
Id = Guid.NewGuid();
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public Guid? Id { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "exception")]
|
||||
public MException Exception { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
public abstract class Log {
|
||||
|
||||
[JsonProperty(PropertyName = "sid")]
|
||||
private Guid? Session { get; set; } = AppCenter.SessionID;
|
||||
|
||||
[JsonProperty(PropertyName = "timestamp")]
|
||||
private DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[JsonProperty(PropertyName = "device")]
|
||||
private Device Device { get; set; } = AppCenter.DeviceInfo;
|
||||
|
||||
[JsonIgnore]
|
||||
internal LogStatus Status = LogStatus.Pending;
|
||||
|
||||
}
|
||||
|
||||
public enum LogStatus {
|
||||
Pending, Uploading, Uploaded
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
public class LogContainer {
|
||||
|
||||
public LogContainer(IEnumerable<Log> logs) {
|
||||
Logs = logs;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "logs")]
|
||||
public IEnumerable<Log> Logs { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
public class LogUploadResult {
|
||||
|
||||
[JsonProperty(PropertyName = "status")]
|
||||
public string Status { get; set; } = null!;
|
||||
|
||||
[JsonProperty(PropertyName = "validDiagnosticsIds")]
|
||||
public Guid[] ValidDiagnosticsIds { get; set; } = Array.Empty<Guid>();
|
||||
|
||||
[JsonProperty(PropertyName = "throttledDiagnosticsIds")]
|
||||
public Guid[] ThrottledDiagnosticsIds { get; set; } = Array.Empty<Guid>();
|
||||
|
||||
[JsonProperty(PropertyName = "correlationId")]
|
||||
public Guid CorrelationId { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
public class LogWithProperties : Log {
|
||||
|
||||
[JsonProperty(PropertyName = "properties")]
|
||||
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
public class MException {
|
||||
|
||||
[JsonProperty(PropertyName = "type")]
|
||||
public string Type { get; set; } = "UnknownType";
|
||||
|
||||
[JsonProperty(PropertyName = "message")]
|
||||
public string? Message { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "stackTrace")]
|
||||
public string? StackTrace { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "frames")]
|
||||
public IList<StackFrame>? Frames { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "innerExceptions")]
|
||||
public IList<MException>? InnerExceptions { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
[JsonObject(JsonIdentifier)]
|
||||
public class ManagedErrorLog : Log {
|
||||
|
||||
public const string JsonIdentifier = "managedError";
|
||||
|
||||
public ManagedErrorLog(
|
||||
Exception exception,
|
||||
bool fatal = true
|
||||
) {
|
||||
var p = Process.GetCurrentProcess();
|
||||
Id = Guid.NewGuid();
|
||||
Fatal = fatal;
|
||||
UserId = AppCenter.DeviceID;
|
||||
ProcessId = p.Id;
|
||||
Exception = ErrorLogHelper.CreateModelExceptionAndBinaries(exception);
|
||||
ProcessName = p.ProcessName;
|
||||
Architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
|
||||
AppLaunchTimestamp = p.StartTime.ToUniversalTime();
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "id")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "userId")]
|
||||
public string? UserId { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "processId")]
|
||||
public int ProcessId { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "processName")]
|
||||
public string ProcessName { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "fatal")]
|
||||
public bool Fatal { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "appLaunchTimestamp")]
|
||||
public DateTime? AppLaunchTimestamp { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "architecture")]
|
||||
public string? Architecture { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "exception")]
|
||||
public MException Exception { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
[JsonObject(JsonIdentifier)]
|
||||
public class PageLog : LogWithProperties {
|
||||
|
||||
public const string JsonIdentifier = "page";
|
||||
|
||||
public PageLog(string name) {
|
||||
Name = name;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models.Serialization;
|
||||
|
||||
#pragma warning disable CS8604, CS8765
|
||||
public class LogJsonConverter : JsonConverter {
|
||||
|
||||
private readonly Dictionary<string, Type> _logTypes = new ();
|
||||
private readonly object _jsonConverterLock = new ();
|
||||
private static readonly JsonSerializerSettings SerializationSettings;
|
||||
|
||||
static LogJsonConverter() {
|
||||
SerializationSettings = new JsonSerializerSettings {
|
||||
Formatting = Formatting.None,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DateFormatHandling = DateFormatHandling.IsoDateFormat,
|
||||
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
|
||||
};
|
||||
}
|
||||
|
||||
public void AddLogType(string typeName, Type type) {
|
||||
lock (_jsonConverterLock) {
|
||||
_logTypes[typeName] = type;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType) {
|
||||
return typeof(Log).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override object? ReadJson(JsonReader reader, Type t, object o, JsonSerializer s) {
|
||||
Type logType;
|
||||
var jsonObject = JObject.Load(reader);
|
||||
var typeName = jsonObject.GetValue("type")?.ToString();
|
||||
lock (_jsonConverterLock) {
|
||||
if (typeName == null || !_logTypes.ContainsKey(typeName)) {
|
||||
throw new JsonReaderException("Could not identify type of log");
|
||||
}
|
||||
logType = _logTypes[typeName];
|
||||
jsonObject.Remove("type");
|
||||
}
|
||||
return jsonObject.ToObject(logType);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
||||
var info = value.GetType().GetTypeInfo();
|
||||
if (info.GetCustomAttribute(typeof(JsonObjectAttribute)) is not JsonObjectAttribute attribute) {
|
||||
throw new JsonWriterException("Log type is missing JsonObjectAttribute");
|
||||
}
|
||||
var jsonObject = JObject.FromObject(value, JsonSerializer.CreateDefault(SerializationSettings));
|
||||
jsonObject.Add("type", JToken.FromObject(attribute.Id));
|
||||
writer.WriteRawValue(jsonObject.ToString(Formatting.None));
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models.Serialization;
|
||||
|
||||
#pragma warning disable CS8604, CS8765, CS8603
|
||||
public static class LogSerializer {
|
||||
|
||||
private static readonly JsonSerializerSettings SerializationSettings;
|
||||
private static readonly LogJsonConverter Converter = new ();
|
||||
|
||||
static LogSerializer() {
|
||||
SerializationSettings = new JsonSerializerSettings {
|
||||
Formatting = Formatting.None,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
DateFormatHandling = DateFormatHandling.IsoDateFormat,
|
||||
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
|
||||
Converters = { Converter }
|
||||
};
|
||||
}
|
||||
|
||||
public static void AddLogType(string typeName, Type type) {
|
||||
Converter.AddLogType(typeName, type);
|
||||
}
|
||||
|
||||
public static string Serialize(LogContainer logContainer) {
|
||||
return JsonConvert.SerializeObject(logContainer, SerializationSettings);
|
||||
}
|
||||
|
||||
public static string Serialize(Log log) {
|
||||
return JsonConvert.SerializeObject(log, SerializationSettings);
|
||||
}
|
||||
|
||||
public static LogContainer? DeserializeLogs(string json) {
|
||||
return JsonConvert.DeserializeObject<LogContainer>(json, SerializationSettings);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
public class StackFrame {
|
||||
|
||||
public StackFrame(string address, string code, string className, string methodName, int? lineNumber, string fileName) {
|
||||
Address = address;
|
||||
Code = code;
|
||||
ClassName = className;
|
||||
MethodName = methodName;
|
||||
LineNumber = lineNumber;
|
||||
FileName = fileName;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "address")]
|
||||
public string Address { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "code")]
|
||||
public string Code { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "className")]
|
||||
public string ClassName { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "methodName")]
|
||||
public string MethodName { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "lineNumber")]
|
||||
public int? LineNumber { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "fileName")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
[JsonObject(JsonIdentifier)]
|
||||
public class StartServiceLog : Log {
|
||||
|
||||
public const string JsonIdentifier = "startService";
|
||||
|
||||
public StartServiceLog(params string[] services) {
|
||||
Services = services;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "services")]
|
||||
public string[] Services { get; set; }
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace YaeAchievement.AppCenterSDK.Models;
|
||||
|
||||
[JsonObject(JsonIdentifier)]
|
||||
public class StartSessionLog : Log {
|
||||
public const string JsonIdentifier = "startSession";
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using YaeAchievement.res;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class AppConfig {
|
||||
|
||||
public static string GamePath { get; private set; } = null!;
|
||||
|
||||
internal static void Load(string argumentPath) {
|
||||
if (argumentPath != "auto" && File.Exists(argumentPath)) {
|
||||
GamePath = argumentPath;
|
||||
return;
|
||||
}
|
||||
var pathCacheFile = new CacheFile("genshin_impact_game_path");
|
||||
if (pathCacheFile.Exists()) {
|
||||
var path = pathCacheFile.Read().Content.ToStringUtf8();
|
||||
if (path != null && File.Exists(path)) {
|
||||
GamePath = path;
|
||||
return;
|
||||
}
|
||||
}
|
||||
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var cnLogPath = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt");
|
||||
var osLogPath = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\Genshin Impact\output_log.txt");
|
||||
if (!File.Exists(cnLogPath) && !File.Exists(osLogPath)) {
|
||||
throw new ApplicationException(App.ConfigNeedStartGenshin);
|
||||
}
|
||||
string finalLogPath;
|
||||
if (!File.Exists(osLogPath)) {
|
||||
finalLogPath = cnLogPath;
|
||||
} else if (!File.Exists(cnLogPath)) {
|
||||
finalLogPath = osLogPath;
|
||||
} else {
|
||||
var cnLastWriteTime = File.GetLastWriteTime(cnLogPath);
|
||||
var osLastWriteTime = File.GetLastWriteTime(osLogPath);
|
||||
finalLogPath = cnLastWriteTime > osLastWriteTime ? cnLogPath : osLogPath;
|
||||
}
|
||||
GamePath = GetGamePathFromLogFile(finalLogPath)
|
||||
?? GetGamePathFromLogFile($"{finalLogPath}.last")
|
||||
?? throw new ApplicationException(App.ConfigNeedStartGenshin);
|
||||
pathCacheFile.Write(GamePath);
|
||||
}
|
||||
|
||||
private static string? GetGamePathFromLogFile(string path) {
|
||||
if (!File.Exists(path)) {
|
||||
return null;
|
||||
}
|
||||
var copiedLogFilePath = Path.GetTempFileName();
|
||||
File.Copy(path, copiedLogFilePath, true);
|
||||
var content = File.ReadAllText(copiedLogFilePath);
|
||||
try {
|
||||
File.Delete(copiedLogFilePath);
|
||||
} catch (Exception) { /* ignore */}
|
||||
var matchResult = Regex.Match(content, @"(?m).:/.+(GenshinImpact_Data|YuanShen_Data)");
|
||||
if (!matchResult.Success) {
|
||||
return null;
|
||||
}
|
||||
var entryName = matchResult.Groups["1"].Value.Replace("_Data", ".exe");
|
||||
return Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System.IO.Compression;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public class CacheFile {
|
||||
|
||||
private readonly string _cacheName;
|
||||
private CacheItem? _content;
|
||||
|
||||
public DateTime LastWriteTime => Exists() ? File.GetLastWriteTimeUtc(_cacheName) : DateTime.UnixEpoch;
|
||||
|
||||
public CacheFile(string identifier) {
|
||||
_cacheName = Path.Combine(GlobalVars.CachePath, $"{identifier.MD5Hash()[..16]}.miko");
|
||||
}
|
||||
|
||||
public bool Exists() => File.Exists(_cacheName);
|
||||
|
||||
public CacheItem Read() {
|
||||
if (_content == null) {
|
||||
using var fInput = File.OpenRead(_cacheName);
|
||||
using var dInput = new GZipStream(fInput, CompressionMode.Decompress);
|
||||
_content = CacheItem.Parser.ParseFrom(dInput);
|
||||
}
|
||||
return _content;
|
||||
}
|
||||
|
||||
public void Write(string data, string? etag = null) => Write(ByteString.CopyFromUtf8(data), data.MD5Hash(), etag);
|
||||
|
||||
public void Write(byte[] data, string? etag = null) => Write(ByteString.CopyFrom(data), data.MD5Hash(), etag);
|
||||
|
||||
private void Write(ByteString data, string hash, string? etag) {
|
||||
using var fOut = File.OpenWrite(_cacheName);
|
||||
using var cOut = new GZipStream(fOut, CompressionLevel.SmallestSize);
|
||||
new CacheItem {
|
||||
Etag = etag ?? string.Empty,
|
||||
Version = 3,
|
||||
Checksum = hash,
|
||||
Content = data
|
||||
}.WriteTo(cOut);
|
||||
}
|
||||
}
|
||||
213
src/Export.cs
213
src/Export.cs
@@ -1,213 +0,0 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Win32;
|
||||
using YaeAchievement.res;
|
||||
using static AchievementAllDataNotify.Types.Achievement.Types;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class Export {
|
||||
|
||||
public static uint ExportTo { get; set; } = uint.MaxValue;
|
||||
|
||||
public static void Choose(AchievementAllDataNotify data) {
|
||||
if (ExportTo == uint.MaxValue) {
|
||||
Console.Write(App.ExportChoose);
|
||||
while (Console.KeyAvailable) {
|
||||
Console.ReadKey(false);
|
||||
}
|
||||
if (!uint.TryParse(Console.ReadLine(), out var num)) num = 0;
|
||||
ExportTo = num;
|
||||
}
|
||||
((Action<AchievementAllDataNotify>) (ExportTo switch {
|
||||
1 => ToHuTao,
|
||||
2 => ToPaimon,
|
||||
3 => ToSeelie,
|
||||
4 => ToCSV,
|
||||
5 => ToXunkong,
|
||||
6 => ToWxApp1,
|
||||
7 => ToRawJson,
|
||||
_ => ToCocogoat
|
||||
})).Invoke(data);
|
||||
}
|
||||
|
||||
private class CocogoatResponse {
|
||||
[JsonPropertyName("key")] public string Code { get; set; } = null!;
|
||||
}
|
||||
|
||||
private static void ToCocogoat(AchievementAllDataNotify data) {
|
||||
var result = JsonSerializer.Serialize(ExportToUIAFApp(data));
|
||||
using var request = new HttpRequestMessage {
|
||||
Method = HttpMethod.Post,
|
||||
RequestUri = new Uri($"https://77.cocogoat.cn/v1/memo?source={App.AllAchievement}"),
|
||||
Content = new StringContent(result, Encoding.UTF8, "application/json")
|
||||
};
|
||||
using var response = Utils.CHttpClient.Send(request);
|
||||
if (response.StatusCode != HttpStatusCode.Created) {
|
||||
Console.WriteLine(App.ExportToCocogoatFail);
|
||||
return;
|
||||
}
|
||||
var responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
var responseJson = JsonSerializer.Deserialize<CocogoatResponse>(responseText)!;
|
||||
Console.WriteLine(Utils.ShellOpen($"https://cocogoat.work/achievement?memo={responseJson.Code}")
|
||||
? App.ExportToCocogoatSuccess
|
||||
: $"https://cocogoat.work/achievement?memo={responseJson.Code}");
|
||||
}
|
||||
|
||||
private static void ToWxApp1(AchievementAllDataNotify data) {
|
||||
var id = Guid.NewGuid().ToString("N").Substring(20, 8);
|
||||
var result = JsonSerializer.Serialize(new Dictionary<string, object> {
|
||||
{ "key", id },
|
||||
{ "data", ExportToUIAFApp(data) }
|
||||
});
|
||||
using var request = new HttpRequestMessage {
|
||||
Method = HttpMethod.Post,
|
||||
RequestUri = new Uri("https://api.qyinter.com/achievementRedis"),
|
||||
Content = new StringContent(result, Encoding.UTF8, "application/json")
|
||||
};
|
||||
using var response = Utils.CHttpClient.Send(request);
|
||||
Console.WriteLine(App.ExportToWxApp1Success, id);
|
||||
}
|
||||
|
||||
private static void ToHuTao(AchievementAllDataNotify data) {
|
||||
Utils.CopyToClipboard(JsonSerializer.Serialize(ExportToUIAFApp(data)));
|
||||
Utils.ShellOpen("hutao://achievement/import");
|
||||
Console.WriteLine(App.ExportToSnapGenshinSuccess);
|
||||
}
|
||||
|
||||
private static void ToPaimon(AchievementAllDataNotify data) {
|
||||
var info = LoadAchievementInfo();
|
||||
var output = new Dictionary<uint, Dictionary<uint, bool>>();
|
||||
foreach (var ach in data.List.Where(a => a.Status is Status.Finished or Status.RewardTaken)) {
|
||||
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
|
||||
Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
|
||||
continue;
|
||||
}
|
||||
var map = output.GetValueOrDefault(achInfo.Group, new Dictionary<uint, bool>());
|
||||
map[ach.Id == 81222 ? 81219 : ach.Id] = true;
|
||||
output[achInfo.Group] = map;
|
||||
}
|
||||
var final = new Dictionary<string, Dictionary<uint, Dictionary<uint, bool>>> {
|
||||
["achievement"] = output.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value)
|
||||
};
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json");
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(final));
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
|
||||
private static void ToSeelie(AchievementAllDataNotify data) {
|
||||
var output = new Dictionary<uint, Dictionary<string, bool>>();
|
||||
foreach (var ach in data.List.Where(a => a.Status is Status.Finished or Status.RewardTaken)) {
|
||||
output[ach.Id == 81222 ? 81219 : ach.Id] = new Dictionary<string, bool> {
|
||||
["done"] = true
|
||||
};
|
||||
}
|
||||
var final = new Dictionary<string, Dictionary<uint, Dictionary<string, bool>>> {
|
||||
["achievements"] = output.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value)
|
||||
};
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-seelie.json");
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(final));
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static void ToCSV(AchievementAllDataNotify data) {
|
||||
var info = LoadAchievementInfo();
|
||||
var outList = new List<List<object>>();
|
||||
foreach (var ach in data.List.OrderBy(a => a.Id)) {
|
||||
if (UnusedAchievement.Contains(ach.Id)) continue;
|
||||
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
|
||||
Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
|
||||
continue;
|
||||
}
|
||||
var finishAt = "";
|
||||
if (ach.Timestamp != 0) {
|
||||
var ts = Convert.ToInt64(ach.Timestamp);
|
||||
finishAt = DateTimeOffset.FromUnixTimeSeconds(ts).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
|
||||
}
|
||||
var current = ach.Status != Status.Unfinished ? ach.Current == 0 ? ach.Total : ach.Current : ach.Current;
|
||||
outList.Add(new List<object> {
|
||||
ach.Id, ach.Status.ToDesc(), achInfo.Group, achInfo.Name,
|
||||
achInfo.Description, current, ach.Total, finishAt
|
||||
});
|
||||
}
|
||||
var output = new List<string> { "ID,状态,特辑,名称,描述,当前进度,目标进度,完成时间" };
|
||||
output.AddRange(outList.OrderBy(v => v[2]).Select(item => {
|
||||
item[2] = info.Group[(uint) item[2]];
|
||||
return item.JoinToString(",");
|
||||
}));
|
||||
var path = Path.GetFullPath($"achievement-{DateTime.Now:yyyyMMddHHmmss}.csv");
|
||||
File.WriteAllText(path, $"\uFEFF{string.Join("\n", output)}");
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
|
||||
private static void ToXunkong(AchievementAllDataNotify data) {
|
||||
if (CheckXunkongScheme()) {
|
||||
Utils.CopyToClipboard(JsonSerializer.Serialize(ExportToUIAFApp(data)));
|
||||
Utils.ShellOpen("xunkong://import-achievement?caller=YaeAchievement&from=clipboard");
|
||||
Console.WriteLine(App.ExportToXunkongSuccess);
|
||||
} else {
|
||||
Console.WriteLine(App.ExportToXunkongNeedUpdate);
|
||||
Utils.ShellOpen("ms-windows-store://pdp/?productid=9N2SVG0JMT12");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToRawJson(AchievementAllDataNotify data) {
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(data, new JsonSerializerOptions {
|
||||
WriteIndented = true
|
||||
}));
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static Dictionary<string, object> ExportToUIAFApp(AchievementAllDataNotify data) {
|
||||
var output = data.List
|
||||
.Where(a => (uint)a.Status > 1 || a.Current > 0)
|
||||
.Select(ach => new Dictionary<string, uint> {
|
||||
["id"] = ach.Id,
|
||||
["status"] = (uint) ach.Status,
|
||||
["current"] = ach.Current,
|
||||
["timestamp"] = ach.Timestamp
|
||||
})
|
||||
.ToList();
|
||||
return new Dictionary<string, object> {
|
||||
["info"] = new Dictionary<string, object> {
|
||||
["export_app"] = "YaeAchievement",
|
||||
["export_timestamp"] = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
||||
["export_app_version"] = GlobalVars.AppVersionName,
|
||||
["uiaf_version"] = "v1.1"
|
||||
},
|
||||
["list"] = output
|
||||
};
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416
|
||||
private static bool CheckXunkongScheme() {
|
||||
return (string?)Registry.ClassesRoot.OpenSubKey("xunkong")?.GetValue("") == "URL:xunkong";
|
||||
}
|
||||
#pragma warning restore CA1416
|
||||
|
||||
private static string JoinToString(this IEnumerable<object> list, string separator) {
|
||||
return string.Join(separator, list);
|
||||
}
|
||||
|
||||
private static readonly List<uint> UnusedAchievement = new() { 84517 };
|
||||
|
||||
private static string ToDesc(this Status status) {
|
||||
return status switch {
|
||||
Status.Invalid => App.StatusInvalid,
|
||||
Status.Finished => App.StatusFinished,
|
||||
Status.Unfinished => App.StatusUnfinished,
|
||||
Status.RewardTaken => App.StatusRewardTaken,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
|
||||
};
|
||||
}
|
||||
|
||||
private static AchievementInfo LoadAchievementInfo() {
|
||||
var b = Utils.GetBucketFileAsByteArray("schicksal/metadata");
|
||||
return AchievementInfo.Parser.ParseFrom(b);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
public static class Extensions {
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly Lazy<MD5> md5 = new (MD5.Create);
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly Lazy<SHA1> sha1 = new (SHA1.Create);
|
||||
|
||||
public static byte[] ToBytes(this string text) {
|
||||
return Encoding.UTF8.GetBytes(text);
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static string MD5Hash(this string text) {
|
||||
return text.ToBytes().MD5Hash();
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static string MD5Hash(this byte[] data) {
|
||||
return md5.Value.ComputeHash(data).ToHex().ToLower();
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static string SHA1Hash(this string text, bool base64 = true) {
|
||||
var bytes = sha1.Value.ComputeHash(text.ToBytes());
|
||||
return base64 ? bytes.ToBase64() : bytes.ToHex();
|
||||
}
|
||||
|
||||
public static string ToHex(this byte[] bytes) {
|
||||
return Convert.ToHexString(bytes);
|
||||
}
|
||||
|
||||
public static string ToBase64(this byte[] bytes) {
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using YaeAchievement.Win32;
|
||||
using static YaeAchievement.Win32.Native;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class Injector {
|
||||
|
||||
public static unsafe bool CreateProcess(string path, out IntPtr hProc, out IntPtr hThread, out uint pid) {
|
||||
var si = new StartupInfo();
|
||||
SecurityAttributes* attr = null;
|
||||
var dir = Path.GetDirectoryName(path)!;
|
||||
var result = Native.CreateProcess(
|
||||
path, null, ref *attr, ref *attr, false,
|
||||
CreationFlags.CreateSuspended, IntPtr.Zero, dir, ref si, out var pi
|
||||
);
|
||||
pid = pi.dwProcessID;
|
||||
hProc = pi.hProcess;
|
||||
hThread = pi.hThread;
|
||||
return result;
|
||||
}
|
||||
|
||||
// todo: refactor
|
||||
public static int LoadLibraryAndInject(IntPtr hProc, string libPath) {
|
||||
var hKernel = GetModuleHandle("kernel32.dll");
|
||||
if (hKernel == IntPtr.Zero) {
|
||||
return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail");
|
||||
}
|
||||
var pLoadLibrary = GetProcAddress(hKernel, "LoadLibraryA");
|
||||
if (pLoadLibrary == IntPtr.Zero) {
|
||||
return new Win32Exception().PrintMsgAndReturnErrCode("GetProcAddress fail");
|
||||
}
|
||||
var pBase = VirtualAllocEx(hProc, IntPtr.Zero, libPath.Length + 1, AllocationType.Reserve | AllocationType.Commit, MemoryProtection.ReadWrite);
|
||||
if (pBase == IntPtr.Zero) {
|
||||
return new Win32Exception().PrintMsgAndReturnErrCode("VirtualAllocEx fail");
|
||||
}
|
||||
if (!WriteProcessMemory(hProc, pBase, libPath.ToCharArray(), libPath.Length, out _)) {
|
||||
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
|
||||
}
|
||||
var hThread = CreateRemoteThread(hProc, IntPtr.Zero, 0, pLoadLibrary, pBase, 0, out _);
|
||||
if (hThread == IntPtr.Zero) {
|
||||
var e = new Win32Exception();
|
||||
VirtualFreeEx(hProc, pBase, 0, AllocationType.Release);
|
||||
return e.PrintMsgAndReturnErrCode("CreateRemoteThread fail");
|
||||
}
|
||||
if (WaitForSingleObject(hThread, 2000) == 0) {
|
||||
VirtualFreeEx(hProc, pBase, 0, AllocationType.Release);
|
||||
}
|
||||
return !CloseHandle(hThread) ? new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail") : 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using YaeAchievement;
|
||||
using YaeAchievement.AppCenterSDK;
|
||||
using YaeAchievement.AppCenterSDK.Models;
|
||||
using YaeAchievement.res;
|
||||
using static YaeAchievement.Utils;
|
||||
|
||||
InstallExitHook();
|
||||
InstallExceptionHook();
|
||||
|
||||
await CheckVcRuntime();
|
||||
CheckSelfIsRunning();
|
||||
TryDisableQuickEdit();
|
||||
CheckGenshinIsRunning();
|
||||
|
||||
Console.WriteLine(@"----------------------------------------------------");
|
||||
Console.WriteLine(App.AppBanner, GlobalVars.AppVersionName);
|
||||
Console.WriteLine(@"https://github.com/HolographicHat/YaeAchievement");
|
||||
Console.WriteLine(@"----------------------------------------------------");
|
||||
|
||||
AppConfig.Load(args.GetOrNull(0) ?? "auto");
|
||||
Export.ExportTo = ToUIntOrNull(args.GetOrNull(1)) ?? uint.MaxValue;
|
||||
|
||||
CheckUpdate(ToBooleanOrFalse(args.GetOrNull(2)));
|
||||
AppCenter.Init();
|
||||
new EventLog("AppInit") {
|
||||
Properties = {
|
||||
{ "AppVersion", GlobalVars.AppVersionName },
|
||||
{ "SystemVersion", DeviceHelper.GetSystemVersion() }
|
||||
}
|
||||
}.Enqueue();
|
||||
var usePreviousData = false;
|
||||
var historyCache = new CacheFile("ExportData");
|
||||
if (historyCache.LastWriteTime.AddMinutes(10) > DateTime.UtcNow) {
|
||||
Console.WriteLine(App.UsePreviousData);
|
||||
usePreviousData = Console.ReadLine() == "yes";
|
||||
}
|
||||
Export:
|
||||
if(usePreviousData) {
|
||||
AchievementAllDataNotify data;
|
||||
try {
|
||||
data = AchievementAllDataNotify.Parser.ParseFrom(historyCache.Read().Content);
|
||||
} catch (Exception) {
|
||||
usePreviousData = false;
|
||||
goto Export;
|
||||
}
|
||||
Export.Choose(data);
|
||||
} else {
|
||||
StartAndWaitResult(AppConfig.GamePath, str => {
|
||||
GlobalVars.UnexpectedExit = false;
|
||||
var data = Convert.FromBase64String(str);
|
||||
var list = AchievementAllDataNotify.Parser.ParseFrom(data);
|
||||
historyCache.Write(data);
|
||||
Export.Choose(list);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -1,584 +0,0 @@
|
||||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: AchievementAllDataNotify.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021, 8981
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::Google.Protobuf;
|
||||
using pbc = global::Google.Protobuf.Collections;
|
||||
using pbr = global::Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
/// <summary>Holder for reflection information generated from AchievementAllDataNotify.proto</summary>
|
||||
public static partial class AchievementAllDataNotifyReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for AchievementAllDataNotify.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static AchievementAllDataNotifyReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"Ch5BY2hpZXZlbWVudEFsbERhdGFOb3RpZnkucHJvdG8iowIKGEFjaGlldmVt",
|
||||
"ZW50QWxsRGF0YU5vdGlmeRIzCgRsaXN0GAQgAygLMiUuQWNoaWV2ZW1lbnRB",
|
||||
"bGxEYXRhTm90aWZ5LkFjaGlldmVtZW50GtEBCgtBY2hpZXZlbWVudBIRCgl0",
|
||||
"aW1lc3RhbXAYBiABKA0SDwoHY3VycmVudBgKIAEoDRINCgV0b3RhbBgOIAEo",
|
||||
"DRIKCgJpZBgNIAEoDRI8CgZzdGF0dXMYBSABKA4yLC5BY2hpZXZlbWVudEFs",
|
||||
"bERhdGFOb3RpZnkuQWNoaWV2ZW1lbnQuU3RhdHVzIkUKBlN0YXR1cxILCgdJ",
|
||||
"TlZBTElEEAASDgoKVU5GSU5JU0hFRBABEgwKCEZJTklTSEVEEAISEAoMUkVX",
|
||||
"QVJEX1RBS0VOEANiBnByb3RvMw=="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::AchievementAllDataNotify), global::AchievementAllDataNotify.Parser, new[]{ "List" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::AchievementAllDataNotify.Types.Achievement), global::AchievementAllDataNotify.Types.Achievement.Parser, new[]{ "Timestamp", "Current", "Total", "Id", "Status" }, null, new[]{ typeof(global::AchievementAllDataNotify.Types.Achievement.Types.Status) }, null, null)})
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
public sealed partial class AchievementAllDataNotify : pb::IMessage<AchievementAllDataNotify>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<AchievementAllDataNotify> _parser = new pb::MessageParser<AchievementAllDataNotify>(() => new AchievementAllDataNotify());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pb::MessageParser<AchievementAllDataNotify> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::AchievementAllDataNotifyReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementAllDataNotify() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementAllDataNotify(AchievementAllDataNotify other) : this() {
|
||||
list_ = other.list_.Clone();
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementAllDataNotify Clone() {
|
||||
return new AchievementAllDataNotify(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "list" field.</summary>
|
||||
public const int ListFieldNumber = 4;
|
||||
private static readonly pb::FieldCodec<global::AchievementAllDataNotify.Types.Achievement> _repeated_list_codec
|
||||
= pb::FieldCodec.ForMessage(34, global::AchievementAllDataNotify.Types.Achievement.Parser);
|
||||
private readonly pbc::RepeatedField<global::AchievementAllDataNotify.Types.Achievement> list_ = new pbc::RepeatedField<global::AchievementAllDataNotify.Types.Achievement>();
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public pbc::RepeatedField<global::AchievementAllDataNotify.Types.Achievement> List {
|
||||
get { return list_; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as AchievementAllDataNotify);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Equals(AchievementAllDataNotify other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if(!list_.Equals(other.list_)) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
hash ^= list_.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
list_.WriteTo(output, _repeated_list_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
list_.WriteTo(ref output, _repeated_list_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
size += list_.CalculateSize(_repeated_list_codec);
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(AchievementAllDataNotify other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
list_.Add(other.list_);
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 34: {
|
||||
list_.AddEntriesFrom(input, _repeated_list_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 34: {
|
||||
list_.AddEntriesFrom(ref input, _repeated_list_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#region Nested types
|
||||
/// <summary>Container for nested types declared in the AchievementAllDataNotify message type.</summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static partial class Types {
|
||||
public sealed partial class Achievement : pb::IMessage<Achievement>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<Achievement> _parser = new pb::MessageParser<Achievement>(() => new Achievement());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pb::MessageParser<Achievement> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::AchievementAllDataNotify.Descriptor.NestedTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public Achievement() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public Achievement(Achievement other) : this() {
|
||||
timestamp_ = other.timestamp_;
|
||||
current_ = other.current_;
|
||||
total_ = other.total_;
|
||||
id_ = other.id_;
|
||||
status_ = other.status_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public Achievement Clone() {
|
||||
return new Achievement(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "timestamp" field.</summary>
|
||||
public const int TimestampFieldNumber = 6;
|
||||
private uint timestamp_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public uint Timestamp {
|
||||
get { return timestamp_; }
|
||||
set {
|
||||
timestamp_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "current" field.</summary>
|
||||
public const int CurrentFieldNumber = 10;
|
||||
private uint current_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public uint Current {
|
||||
get { return current_; }
|
||||
set {
|
||||
current_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "total" field.</summary>
|
||||
public const int TotalFieldNumber = 14;
|
||||
private uint total_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public uint Total {
|
||||
get { return total_; }
|
||||
set {
|
||||
total_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "id" field.</summary>
|
||||
public const int IdFieldNumber = 13;
|
||||
private uint id_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public uint Id {
|
||||
get { return id_; }
|
||||
set {
|
||||
id_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "status" field.</summary>
|
||||
public const int StatusFieldNumber = 5;
|
||||
private global::AchievementAllDataNotify.Types.Achievement.Types.Status status_ = global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public global::AchievementAllDataNotify.Types.Achievement.Types.Status Status {
|
||||
get { return status_; }
|
||||
set {
|
||||
status_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as Achievement);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Equals(Achievement other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (Timestamp != other.Timestamp) return false;
|
||||
if (Current != other.Current) return false;
|
||||
if (Total != other.Total) return false;
|
||||
if (Id != other.Id) return false;
|
||||
if (Status != other.Status) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (Timestamp != 0) hash ^= Timestamp.GetHashCode();
|
||||
if (Current != 0) hash ^= Current.GetHashCode();
|
||||
if (Total != 0) hash ^= Total.GetHashCode();
|
||||
if (Id != 0) hash ^= Id.GetHashCode();
|
||||
if (Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) hash ^= Status.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) {
|
||||
output.WriteRawTag(40);
|
||||
output.WriteEnum((int) Status);
|
||||
}
|
||||
if (Timestamp != 0) {
|
||||
output.WriteRawTag(48);
|
||||
output.WriteUInt32(Timestamp);
|
||||
}
|
||||
if (Current != 0) {
|
||||
output.WriteRawTag(80);
|
||||
output.WriteUInt32(Current);
|
||||
}
|
||||
if (Id != 0) {
|
||||
output.WriteRawTag(104);
|
||||
output.WriteUInt32(Id);
|
||||
}
|
||||
if (Total != 0) {
|
||||
output.WriteRawTag(112);
|
||||
output.WriteUInt32(Total);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) {
|
||||
output.WriteRawTag(40);
|
||||
output.WriteEnum((int) Status);
|
||||
}
|
||||
if (Timestamp != 0) {
|
||||
output.WriteRawTag(48);
|
||||
output.WriteUInt32(Timestamp);
|
||||
}
|
||||
if (Current != 0) {
|
||||
output.WriteRawTag(80);
|
||||
output.WriteUInt32(Current);
|
||||
}
|
||||
if (Id != 0) {
|
||||
output.WriteRawTag(104);
|
||||
output.WriteUInt32(Id);
|
||||
}
|
||||
if (Total != 0) {
|
||||
output.WriteRawTag(112);
|
||||
output.WriteUInt32(Total);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (Timestamp != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Timestamp);
|
||||
}
|
||||
if (Current != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Current);
|
||||
}
|
||||
if (Total != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Total);
|
||||
}
|
||||
if (Id != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id);
|
||||
}
|
||||
if (Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Status);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(Achievement other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.Timestamp != 0) {
|
||||
Timestamp = other.Timestamp;
|
||||
}
|
||||
if (other.Current != 0) {
|
||||
Current = other.Current;
|
||||
}
|
||||
if (other.Total != 0) {
|
||||
Total = other.Total;
|
||||
}
|
||||
if (other.Id != 0) {
|
||||
Id = other.Id;
|
||||
}
|
||||
if (other.Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) {
|
||||
Status = other.Status;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 40: {
|
||||
Status = (global::AchievementAllDataNotify.Types.Achievement.Types.Status) input.ReadEnum();
|
||||
break;
|
||||
}
|
||||
case 48: {
|
||||
Timestamp = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 80: {
|
||||
Current = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 104: {
|
||||
Id = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 112: {
|
||||
Total = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 40: {
|
||||
Status = (global::AchievementAllDataNotify.Types.Achievement.Types.Status) input.ReadEnum();
|
||||
break;
|
||||
}
|
||||
case 48: {
|
||||
Timestamp = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 80: {
|
||||
Current = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 104: {
|
||||
Id = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 112: {
|
||||
Total = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#region Nested types
|
||||
/// <summary>Container for nested types declared in the Achievement message type.</summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static partial class Types {
|
||||
public enum Status {
|
||||
[pbr::OriginalName("INVALID")] Invalid = 0,
|
||||
[pbr::OriginalName("UNFINISHED")] Unfinished = 1,
|
||||
[pbr::OriginalName("FINISHED")] Finished = 2,
|
||||
[pbr::OriginalName("REWARD_TAKEN")] RewardTaken = 3,
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#endregion Designer generated code
|
||||
@@ -1,588 +0,0 @@
|
||||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: AchievementInfo.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021, 8981
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::Google.Protobuf;
|
||||
using pbc = global::Google.Protobuf.Collections;
|
||||
using pbr = global::Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
/// <summary>Holder for reflection information generated from AchievementInfo.proto</summary>
|
||||
public static partial class AchievementInfoReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for AchievementInfo.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static AchievementInfoReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"ChVBY2hpZXZlbWVudEluZm8ucHJvdG8iUAoPQWNoaWV2ZW1lbnRJdGVtEgsK",
|
||||
"A3ByZRgBIAEoDRINCgVncm91cBgCIAEoDRIMCgRuYW1lGAMgASgJEhMKC2Rl",
|
||||
"c2NyaXB0aW9uGAQgASgJIugBCg9BY2hpZXZlbWVudEluZm8SDwoHdmVyc2lv",
|
||||
"bhgBIAEoCRIqCgVncm91cBgCIAMoCzIbLkFjaGlldmVtZW50SW5mby5Hcm91",
|
||||
"cEVudHJ5EioKBWl0ZW1zGAMgAygLMhsuQWNoaWV2ZW1lbnRJbmZvLkl0ZW1z",
|
||||
"RW50cnkaLAoKR3JvdXBFbnRyeRILCgNrZXkYASABKA0SDQoFdmFsdWUYAiAB",
|
||||
"KAk6AjgBGj4KCkl0ZW1zRW50cnkSCwoDa2V5GAEgASgNEh8KBXZhbHVlGAIg",
|
||||
"ASgLMhAuQWNoaWV2ZW1lbnRJdGVtOgI4AWIGcHJvdG8z"));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::AchievementItem), global::AchievementItem.Parser, new[]{ "Pre", "Group", "Name", "Description" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::AchievementInfo), global::AchievementInfo.Parser, new[]{ "Version", "Group", "Items" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { null, null, })
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
public sealed partial class AchievementItem : pb::IMessage<AchievementItem>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<AchievementItem> _parser = new pb::MessageParser<AchievementItem>(() => new AchievementItem());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pb::MessageParser<AchievementItem> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::AchievementInfoReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementItem() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementItem(AchievementItem other) : this() {
|
||||
pre_ = other.pre_;
|
||||
group_ = other.group_;
|
||||
name_ = other.name_;
|
||||
description_ = other.description_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementItem Clone() {
|
||||
return new AchievementItem(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "pre" field.</summary>
|
||||
public const int PreFieldNumber = 1;
|
||||
private uint pre_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public uint Pre {
|
||||
get { return pre_; }
|
||||
set {
|
||||
pre_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "group" field.</summary>
|
||||
public const int GroupFieldNumber = 2;
|
||||
private uint group_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public uint Group {
|
||||
get { return group_; }
|
||||
set {
|
||||
group_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "name" field.</summary>
|
||||
public const int NameFieldNumber = 3;
|
||||
private string name_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string Name {
|
||||
get { return name_; }
|
||||
set {
|
||||
name_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "description" field.</summary>
|
||||
public const int DescriptionFieldNumber = 4;
|
||||
private string description_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string Description {
|
||||
get { return description_; }
|
||||
set {
|
||||
description_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as AchievementItem);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Equals(AchievementItem other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (Pre != other.Pre) return false;
|
||||
if (Group != other.Group) return false;
|
||||
if (Name != other.Name) return false;
|
||||
if (Description != other.Description) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (Pre != 0) hash ^= Pre.GetHashCode();
|
||||
if (Group != 0) hash ^= Group.GetHashCode();
|
||||
if (Name.Length != 0) hash ^= Name.GetHashCode();
|
||||
if (Description.Length != 0) hash ^= Description.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (Pre != 0) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteUInt32(Pre);
|
||||
}
|
||||
if (Group != 0) {
|
||||
output.WriteRawTag(16);
|
||||
output.WriteUInt32(Group);
|
||||
}
|
||||
if (Name.Length != 0) {
|
||||
output.WriteRawTag(26);
|
||||
output.WriteString(Name);
|
||||
}
|
||||
if (Description.Length != 0) {
|
||||
output.WriteRawTag(34);
|
||||
output.WriteString(Description);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (Pre != 0) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteUInt32(Pre);
|
||||
}
|
||||
if (Group != 0) {
|
||||
output.WriteRawTag(16);
|
||||
output.WriteUInt32(Group);
|
||||
}
|
||||
if (Name.Length != 0) {
|
||||
output.WriteRawTag(26);
|
||||
output.WriteString(Name);
|
||||
}
|
||||
if (Description.Length != 0) {
|
||||
output.WriteRawTag(34);
|
||||
output.WriteString(Description);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (Pre != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Pre);
|
||||
}
|
||||
if (Group != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Group);
|
||||
}
|
||||
if (Name.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
|
||||
}
|
||||
if (Description.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(Description);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(AchievementItem other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.Pre != 0) {
|
||||
Pre = other.Pre;
|
||||
}
|
||||
if (other.Group != 0) {
|
||||
Group = other.Group;
|
||||
}
|
||||
if (other.Name.Length != 0) {
|
||||
Name = other.Name;
|
||||
}
|
||||
if (other.Description.Length != 0) {
|
||||
Description = other.Description;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 8: {
|
||||
Pre = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
Group = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
Name = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
Description = input.ReadString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 8: {
|
||||
Pre = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
Group = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
Name = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
Description = input.ReadString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public sealed partial class AchievementInfo : pb::IMessage<AchievementInfo>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<AchievementInfo> _parser = new pb::MessageParser<AchievementInfo>(() => new AchievementInfo());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pb::MessageParser<AchievementInfo> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::AchievementInfoReflection.Descriptor.MessageTypes[1]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementInfo() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementInfo(AchievementInfo other) : this() {
|
||||
version_ = other.version_;
|
||||
group_ = other.group_.Clone();
|
||||
items_ = other.items_.Clone();
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public AchievementInfo Clone() {
|
||||
return new AchievementInfo(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "version" field.</summary>
|
||||
public const int VersionFieldNumber = 1;
|
||||
private string version_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string Version {
|
||||
get { return version_; }
|
||||
set {
|
||||
version_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "group" field.</summary>
|
||||
public const int GroupFieldNumber = 2;
|
||||
private static readonly pbc::MapField<uint, string>.Codec _map_group_codec
|
||||
= new pbc::MapField<uint, string>.Codec(pb::FieldCodec.ForUInt32(8, 0), pb::FieldCodec.ForString(18, ""), 18);
|
||||
private readonly pbc::MapField<uint, string> group_ = new pbc::MapField<uint, string>();
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public pbc::MapField<uint, string> Group {
|
||||
get { return group_; }
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "items" field.</summary>
|
||||
public const int ItemsFieldNumber = 3;
|
||||
private static readonly pbc::MapField<uint, global::AchievementItem>.Codec _map_items_codec
|
||||
= new pbc::MapField<uint, global::AchievementItem>.Codec(pb::FieldCodec.ForUInt32(8, 0), pb::FieldCodec.ForMessage(18, global::AchievementItem.Parser), 26);
|
||||
private readonly pbc::MapField<uint, global::AchievementItem> items_ = new pbc::MapField<uint, global::AchievementItem>();
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public pbc::MapField<uint, global::AchievementItem> Items {
|
||||
get { return items_; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as AchievementInfo);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Equals(AchievementInfo other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (Version != other.Version) return false;
|
||||
if (!Group.Equals(other.Group)) return false;
|
||||
if (!Items.Equals(other.Items)) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (Version.Length != 0) hash ^= Version.GetHashCode();
|
||||
hash ^= Group.GetHashCode();
|
||||
hash ^= Items.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (Version.Length != 0) {
|
||||
output.WriteRawTag(10);
|
||||
output.WriteString(Version);
|
||||
}
|
||||
group_.WriteTo(output, _map_group_codec);
|
||||
items_.WriteTo(output, _map_items_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (Version.Length != 0) {
|
||||
output.WriteRawTag(10);
|
||||
output.WriteString(Version);
|
||||
}
|
||||
group_.WriteTo(ref output, _map_group_codec);
|
||||
items_.WriteTo(ref output, _map_items_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (Version.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(Version);
|
||||
}
|
||||
size += group_.CalculateSize(_map_group_codec);
|
||||
size += items_.CalculateSize(_map_items_codec);
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(AchievementInfo other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.Version.Length != 0) {
|
||||
Version = other.Version;
|
||||
}
|
||||
group_.Add(other.group_);
|
||||
items_.Add(other.items_);
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 10: {
|
||||
Version = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
group_.AddEntriesFrom(input, _map_group_codec);
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
items_.AddEntriesFrom(input, _map_items_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 10: {
|
||||
Version = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
group_.AddEntriesFrom(ref input, _map_group_codec);
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
items_.AddEntriesFrom(ref input, _map_items_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#endregion Designer generated code
|
||||
@@ -1,341 +0,0 @@
|
||||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: CacheItem.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021, 8981
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::Google.Protobuf;
|
||||
using pbc = global::Google.Protobuf.Collections;
|
||||
using pbr = global::Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
/// <summary>Holder for reflection information generated from CacheItem.proto</summary>
|
||||
public static partial class CacheItemReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for CacheItem.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static CacheItemReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"Cg9DYWNoZUl0ZW0ucHJvdG8iTQoJQ2FjaGVJdGVtEg8KB3ZlcnNpb24YASAB",
|
||||
"KAUSEAoIY2hlY2tzdW0YAiABKAkSDAoEZXRhZxgDIAEoCRIPCgdjb250ZW50",
|
||||
"GAQgASgMYgZwcm90bzM="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::CacheItem), global::CacheItem.Parser, new[]{ "Version", "Checksum", "Etag", "Content" }, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
public sealed partial class CacheItem : pb::IMessage<CacheItem>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<CacheItem> _parser = new pb::MessageParser<CacheItem>(() => new CacheItem());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pb::MessageParser<CacheItem> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::CacheItemReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public CacheItem() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public CacheItem(CacheItem other) : this() {
|
||||
version_ = other.version_;
|
||||
checksum_ = other.checksum_;
|
||||
etag_ = other.etag_;
|
||||
content_ = other.content_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public CacheItem Clone() {
|
||||
return new CacheItem(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "version" field.</summary>
|
||||
public const int VersionFieldNumber = 1;
|
||||
private int version_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int Version {
|
||||
get { return version_; }
|
||||
set {
|
||||
version_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "checksum" field.</summary>
|
||||
public const int ChecksumFieldNumber = 2;
|
||||
private string checksum_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string Checksum {
|
||||
get { return checksum_; }
|
||||
set {
|
||||
checksum_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "etag" field.</summary>
|
||||
public const int EtagFieldNumber = 3;
|
||||
private string etag_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string Etag {
|
||||
get { return etag_; }
|
||||
set {
|
||||
etag_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "content" field.</summary>
|
||||
public const int ContentFieldNumber = 4;
|
||||
private pb::ByteString content_ = pb::ByteString.Empty;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public pb::ByteString Content {
|
||||
get { return content_; }
|
||||
set {
|
||||
content_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as CacheItem);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Equals(CacheItem other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (Version != other.Version) return false;
|
||||
if (Checksum != other.Checksum) return false;
|
||||
if (Etag != other.Etag) return false;
|
||||
if (Content != other.Content) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (Version != 0) hash ^= Version.GetHashCode();
|
||||
if (Checksum.Length != 0) hash ^= Checksum.GetHashCode();
|
||||
if (Etag.Length != 0) hash ^= Etag.GetHashCode();
|
||||
if (Content.Length != 0) hash ^= Content.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (Version != 0) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteInt32(Version);
|
||||
}
|
||||
if (Checksum.Length != 0) {
|
||||
output.WriteRawTag(18);
|
||||
output.WriteString(Checksum);
|
||||
}
|
||||
if (Etag.Length != 0) {
|
||||
output.WriteRawTag(26);
|
||||
output.WriteString(Etag);
|
||||
}
|
||||
if (Content.Length != 0) {
|
||||
output.WriteRawTag(34);
|
||||
output.WriteBytes(Content);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (Version != 0) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteInt32(Version);
|
||||
}
|
||||
if (Checksum.Length != 0) {
|
||||
output.WriteRawTag(18);
|
||||
output.WriteString(Checksum);
|
||||
}
|
||||
if (Etag.Length != 0) {
|
||||
output.WriteRawTag(26);
|
||||
output.WriteString(Etag);
|
||||
}
|
||||
if (Content.Length != 0) {
|
||||
output.WriteRawTag(34);
|
||||
output.WriteBytes(Content);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (Version != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeInt32Size(Version);
|
||||
}
|
||||
if (Checksum.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(Checksum);
|
||||
}
|
||||
if (Etag.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(Etag);
|
||||
}
|
||||
if (Content.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeBytesSize(Content);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(CacheItem other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.Version != 0) {
|
||||
Version = other.Version;
|
||||
}
|
||||
if (other.Checksum.Length != 0) {
|
||||
Checksum = other.Checksum;
|
||||
}
|
||||
if (other.Etag.Length != 0) {
|
||||
Etag = other.Etag;
|
||||
}
|
||||
if (other.Content.Length != 0) {
|
||||
Content = other.Content;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 8: {
|
||||
Version = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
Checksum = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
Etag = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
Content = input.ReadBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 8: {
|
||||
Version = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
Checksum = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
Etag = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
Content = input.ReadBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#endregion Designer generated code
|
||||
@@ -1,529 +0,0 @@
|
||||
// <auto-generated>
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: UpdateInfo.proto
|
||||
// </auto-generated>
|
||||
#pragma warning disable 1591, 0612, 3021, 8981
|
||||
#region Designer generated code
|
||||
|
||||
using pb = global::Google.Protobuf;
|
||||
using pbc = global::Google.Protobuf.Collections;
|
||||
using pbr = global::Google.Protobuf.Reflection;
|
||||
using scg = global::System.Collections.Generic;
|
||||
/// <summary>Holder for reflection information generated from UpdateInfo.proto</summary>
|
||||
public static partial class UpdateInfoReflection {
|
||||
|
||||
#region Descriptor
|
||||
/// <summary>File descriptor for UpdateInfo.proto</summary>
|
||||
public static pbr::FileDescriptor Descriptor {
|
||||
get { return descriptor; }
|
||||
}
|
||||
private static pbr::FileDescriptor descriptor;
|
||||
|
||||
static UpdateInfoReflection() {
|
||||
byte[] descriptorData = global::System.Convert.FromBase64String(
|
||||
string.Concat(
|
||||
"ChBVcGRhdGVJbmZvLnByb3RvIuIBCgpVcGRhdGVJbmZvEhMKC3ZlcnNpb25D",
|
||||
"b2RlGAEgASgNEhMKC3ZlcnNpb25OYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9u",
|
||||
"GAMgASgJEhMKC3BhY2thZ2VMaW5rGAQgASgJEhMKC2ZvcmNlVXBkYXRlGAUg",
|
||||
"ASgIEhkKEWVuYWJsZUxpYkRvd25sb2FkGAYgASgIEhoKEmVuYWJsZUF1dG9E",
|
||||
"b3dubG9hZBgHIAEoCBIZChFjdXJyZW50Q05HYW1lSGFzaBgIIAEoCRIZChFj",
|
||||
"dXJyZW50T1NHYW1lSGFzaBgJIAEoCWIGcHJvdG8z"));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { },
|
||||
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::UpdateInfo), global::UpdateInfo.Parser, new[]{ "VersionCode", "VersionName", "Description", "PackageLink", "ForceUpdate", "EnableLibDownload", "EnableAutoDownload", "CurrentCNGameHash", "CurrentOSGameHash" }, null, null, null, null)
|
||||
}));
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
#region Messages
|
||||
public sealed partial class UpdateInfo : pb::IMessage<UpdateInfo>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<UpdateInfo> _parser = new pb::MessageParser<UpdateInfo>(() => new UpdateInfo());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pb::MessageParser<UpdateInfo> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::UpdateInfoReflection.Descriptor.MessageTypes[0]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public UpdateInfo() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public UpdateInfo(UpdateInfo other) : this() {
|
||||
versionCode_ = other.versionCode_;
|
||||
versionName_ = other.versionName_;
|
||||
description_ = other.description_;
|
||||
packageLink_ = other.packageLink_;
|
||||
forceUpdate_ = other.forceUpdate_;
|
||||
enableLibDownload_ = other.enableLibDownload_;
|
||||
enableAutoDownload_ = other.enableAutoDownload_;
|
||||
currentCNGameHash_ = other.currentCNGameHash_;
|
||||
currentOSGameHash_ = other.currentOSGameHash_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public UpdateInfo Clone() {
|
||||
return new UpdateInfo(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "versionCode" field.</summary>
|
||||
public const int VersionCodeFieldNumber = 1;
|
||||
private uint versionCode_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public uint VersionCode {
|
||||
get { return versionCode_; }
|
||||
set {
|
||||
versionCode_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "versionName" field.</summary>
|
||||
public const int VersionNameFieldNumber = 2;
|
||||
private string versionName_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string VersionName {
|
||||
get { return versionName_; }
|
||||
set {
|
||||
versionName_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "description" field.</summary>
|
||||
public const int DescriptionFieldNumber = 3;
|
||||
private string description_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string Description {
|
||||
get { return description_; }
|
||||
set {
|
||||
description_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "packageLink" field.</summary>
|
||||
public const int PackageLinkFieldNumber = 4;
|
||||
private string packageLink_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string PackageLink {
|
||||
get { return packageLink_; }
|
||||
set {
|
||||
packageLink_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "forceUpdate" field.</summary>
|
||||
public const int ForceUpdateFieldNumber = 5;
|
||||
private bool forceUpdate_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool ForceUpdate {
|
||||
get { return forceUpdate_; }
|
||||
set {
|
||||
forceUpdate_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "enableLibDownload" field.</summary>
|
||||
public const int EnableLibDownloadFieldNumber = 6;
|
||||
private bool enableLibDownload_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool EnableLibDownload {
|
||||
get { return enableLibDownload_; }
|
||||
set {
|
||||
enableLibDownload_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "enableAutoDownload" field.</summary>
|
||||
public const int EnableAutoDownloadFieldNumber = 7;
|
||||
private bool enableAutoDownload_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool EnableAutoDownload {
|
||||
get { return enableAutoDownload_; }
|
||||
set {
|
||||
enableAutoDownload_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "currentCNGameHash" field.</summary>
|
||||
public const int CurrentCNGameHashFieldNumber = 8;
|
||||
private string currentCNGameHash_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string CurrentCNGameHash {
|
||||
get { return currentCNGameHash_; }
|
||||
set {
|
||||
currentCNGameHash_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "currentOSGameHash" field.</summary>
|
||||
public const int CurrentOSGameHashFieldNumber = 9;
|
||||
private string currentOSGameHash_ = "";
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string CurrentOSGameHash {
|
||||
get { return currentOSGameHash_; }
|
||||
set {
|
||||
currentOSGameHash_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as UpdateInfo);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Equals(UpdateInfo other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if (VersionCode != other.VersionCode) return false;
|
||||
if (VersionName != other.VersionName) return false;
|
||||
if (Description != other.Description) return false;
|
||||
if (PackageLink != other.PackageLink) return false;
|
||||
if (ForceUpdate != other.ForceUpdate) return false;
|
||||
if (EnableLibDownload != other.EnableLibDownload) return false;
|
||||
if (EnableAutoDownload != other.EnableAutoDownload) return false;
|
||||
if (CurrentCNGameHash != other.CurrentCNGameHash) return false;
|
||||
if (CurrentOSGameHash != other.CurrentOSGameHash) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
if (VersionCode != 0) hash ^= VersionCode.GetHashCode();
|
||||
if (VersionName.Length != 0) hash ^= VersionName.GetHashCode();
|
||||
if (Description.Length != 0) hash ^= Description.GetHashCode();
|
||||
if (PackageLink.Length != 0) hash ^= PackageLink.GetHashCode();
|
||||
if (ForceUpdate != false) hash ^= ForceUpdate.GetHashCode();
|
||||
if (EnableLibDownload != false) hash ^= EnableLibDownload.GetHashCode();
|
||||
if (EnableAutoDownload != false) hash ^= EnableAutoDownload.GetHashCode();
|
||||
if (CurrentCNGameHash.Length != 0) hash ^= CurrentCNGameHash.GetHashCode();
|
||||
if (CurrentOSGameHash.Length != 0) hash ^= CurrentOSGameHash.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
if (VersionCode != 0) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteUInt32(VersionCode);
|
||||
}
|
||||
if (VersionName.Length != 0) {
|
||||
output.WriteRawTag(18);
|
||||
output.WriteString(VersionName);
|
||||
}
|
||||
if (Description.Length != 0) {
|
||||
output.WriteRawTag(26);
|
||||
output.WriteString(Description);
|
||||
}
|
||||
if (PackageLink.Length != 0) {
|
||||
output.WriteRawTag(34);
|
||||
output.WriteString(PackageLink);
|
||||
}
|
||||
if (ForceUpdate != false) {
|
||||
output.WriteRawTag(40);
|
||||
output.WriteBool(ForceUpdate);
|
||||
}
|
||||
if (EnableLibDownload != false) {
|
||||
output.WriteRawTag(48);
|
||||
output.WriteBool(EnableLibDownload);
|
||||
}
|
||||
if (EnableAutoDownload != false) {
|
||||
output.WriteRawTag(56);
|
||||
output.WriteBool(EnableAutoDownload);
|
||||
}
|
||||
if (CurrentCNGameHash.Length != 0) {
|
||||
output.WriteRawTag(66);
|
||||
output.WriteString(CurrentCNGameHash);
|
||||
}
|
||||
if (CurrentOSGameHash.Length != 0) {
|
||||
output.WriteRawTag(74);
|
||||
output.WriteString(CurrentOSGameHash);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
if (VersionCode != 0) {
|
||||
output.WriteRawTag(8);
|
||||
output.WriteUInt32(VersionCode);
|
||||
}
|
||||
if (VersionName.Length != 0) {
|
||||
output.WriteRawTag(18);
|
||||
output.WriteString(VersionName);
|
||||
}
|
||||
if (Description.Length != 0) {
|
||||
output.WriteRawTag(26);
|
||||
output.WriteString(Description);
|
||||
}
|
||||
if (PackageLink.Length != 0) {
|
||||
output.WriteRawTag(34);
|
||||
output.WriteString(PackageLink);
|
||||
}
|
||||
if (ForceUpdate != false) {
|
||||
output.WriteRawTag(40);
|
||||
output.WriteBool(ForceUpdate);
|
||||
}
|
||||
if (EnableLibDownload != false) {
|
||||
output.WriteRawTag(48);
|
||||
output.WriteBool(EnableLibDownload);
|
||||
}
|
||||
if (EnableAutoDownload != false) {
|
||||
output.WriteRawTag(56);
|
||||
output.WriteBool(EnableAutoDownload);
|
||||
}
|
||||
if (CurrentCNGameHash.Length != 0) {
|
||||
output.WriteRawTag(66);
|
||||
output.WriteString(CurrentCNGameHash);
|
||||
}
|
||||
if (CurrentOSGameHash.Length != 0) {
|
||||
output.WriteRawTag(74);
|
||||
output.WriteString(CurrentOSGameHash);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
if (VersionCode != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(VersionCode);
|
||||
}
|
||||
if (VersionName.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(VersionName);
|
||||
}
|
||||
if (Description.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(Description);
|
||||
}
|
||||
if (PackageLink.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(PackageLink);
|
||||
}
|
||||
if (ForceUpdate != false) {
|
||||
size += 1 + 1;
|
||||
}
|
||||
if (EnableLibDownload != false) {
|
||||
size += 1 + 1;
|
||||
}
|
||||
if (EnableAutoDownload != false) {
|
||||
size += 1 + 1;
|
||||
}
|
||||
if (CurrentCNGameHash.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(CurrentCNGameHash);
|
||||
}
|
||||
if (CurrentOSGameHash.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(CurrentOSGameHash);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(UpdateInfo other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.VersionCode != 0) {
|
||||
VersionCode = other.VersionCode;
|
||||
}
|
||||
if (other.VersionName.Length != 0) {
|
||||
VersionName = other.VersionName;
|
||||
}
|
||||
if (other.Description.Length != 0) {
|
||||
Description = other.Description;
|
||||
}
|
||||
if (other.PackageLink.Length != 0) {
|
||||
PackageLink = other.PackageLink;
|
||||
}
|
||||
if (other.ForceUpdate != false) {
|
||||
ForceUpdate = other.ForceUpdate;
|
||||
}
|
||||
if (other.EnableLibDownload != false) {
|
||||
EnableLibDownload = other.EnableLibDownload;
|
||||
}
|
||||
if (other.EnableAutoDownload != false) {
|
||||
EnableAutoDownload = other.EnableAutoDownload;
|
||||
}
|
||||
if (other.CurrentCNGameHash.Length != 0) {
|
||||
CurrentCNGameHash = other.CurrentCNGameHash;
|
||||
}
|
||||
if (other.CurrentOSGameHash.Length != 0) {
|
||||
CurrentOSGameHash = other.CurrentOSGameHash;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 8: {
|
||||
VersionCode = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
VersionName = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
Description = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
PackageLink = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
ForceUpdate = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
case 48: {
|
||||
EnableLibDownload = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
case 56: {
|
||||
EnableAutoDownload = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
case 66: {
|
||||
CurrentCNGameHash = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 74: {
|
||||
CurrentOSGameHash = input.ReadString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 8: {
|
||||
VersionCode = input.ReadUInt32();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
VersionName = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
Description = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
PackageLink = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
ForceUpdate = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
case 48: {
|
||||
EnableLibDownload = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
case 56: {
|
||||
EnableAutoDownload = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
case 66: {
|
||||
CurrentCNGameHash = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 74: {
|
||||
CurrentOSGameHash = input.ReadString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#endregion Designer generated code
|
||||
306
src/Utils.cs
306
src/Utils.cs
@@ -1,306 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipes;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
using YaeAchievement.AppCenterSDK;
|
||||
using YaeAchievement.res;
|
||||
using YaeAchievement.Win32;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class Utils {
|
||||
|
||||
public static readonly HttpClient CHttpClient = new (new HttpClientHandler {
|
||||
Proxy = GlobalVars.DebugProxy ? new WebProxy("http://127.0.0.1:8888") : null,
|
||||
AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip
|
||||
}) {
|
||||
DefaultRequestHeaders = {
|
||||
UserAgent = {
|
||||
new ProductInfoHeaderValue("YaeAchievement", GlobalVars.AppVersion.ToString(2))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static byte[] GetBucketFileAsByteArray(string path, bool cache = true) {
|
||||
try {
|
||||
using var msg = new HttpRequestMessage {
|
||||
Method = HttpMethod.Get,
|
||||
RequestUri = new Uri($"{GlobalVars.BucketHost}/{path}")
|
||||
};
|
||||
var cacheFile = new CacheFile(path);
|
||||
if (cache && cacheFile.Exists()) {
|
||||
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cacheFile.Read().Etag}");
|
||||
}
|
||||
using var response = CHttpClient.Send(msg);
|
||||
if (cache && response.StatusCode == HttpStatusCode.NotModified) {
|
||||
return cacheFile.Read().Content.ToByteArray();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseBytes = response.Content.ReadAsByteArrayAsync().Result;
|
||||
if (cache) {
|
||||
var etag = response.Headers.ETag!.Tag;
|
||||
cacheFile.Write(responseBytes, etag);
|
||||
}
|
||||
return responseBytes;
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine(App.NetworkError, e.Message);
|
||||
Environment.Exit(-1);
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
|
||||
public static T? GetOrNull<T>(this T[] array, uint index) where T : class {
|
||||
return array.Length > index ? array[index] : null;
|
||||
}
|
||||
|
||||
public static uint? ToUIntOrNull(string? value) {
|
||||
return value != null ? uint.TryParse(value, out var result) ? result : null : null;
|
||||
}
|
||||
|
||||
public static bool ToBooleanOrFalse(string? value) {
|
||||
return value != null && bool.TryParse(value, out var result) && result;
|
||||
}
|
||||
|
||||
public static void CopyToClipboard(string text) {
|
||||
if (Native.OpenClipboard(IntPtr.Zero)) {
|
||||
Native.EmptyClipboard();
|
||||
var hGlobal = Marshal.AllocHGlobal((text.Length + 1) * 2);
|
||||
var hPtr = Native.GlobalLock(hGlobal);
|
||||
Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length);
|
||||
Native.GlobalUnlock(hPtr);
|
||||
Native.SetClipboardData(13, hGlobal);
|
||||
Marshal.FreeHGlobal(hGlobal);
|
||||
Native.CloseClipboard();
|
||||
} else {
|
||||
throw new Win32Exception();
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private static UpdateInfo _updateInfo = null!;
|
||||
|
||||
public static void CheckUpdate(bool useLocalLib) {
|
||||
var info = UpdateInfo.Parser.ParseFrom(GetBucketFileAsByteArray("schicksal/version"))!;
|
||||
if (GlobalVars.AppVersionCode != info.VersionCode) {
|
||||
Console.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, info.VersionName);
|
||||
Console.WriteLine(App.UpdateDescription, info.Description);
|
||||
if (info.EnableAutoDownload) {
|
||||
Console.WriteLine(App.UpdateDownloading);
|
||||
var tmpPath = Path.GetTempFileName();
|
||||
File.WriteAllBytes(tmpPath, GetBucketFileAsByteArray(info.PackageLink));
|
||||
var updaterArgs = $"{Environment.ProcessId}|{Environment.ProcessPath}|{tmpPath}";
|
||||
var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe");
|
||||
var updaterHash = App.Updater.MD5Hash();
|
||||
if (!File.Exists(updaterPath) || File.ReadAllBytes(updaterPath).MD5Hash() != updaterHash) {
|
||||
File.WriteAllBytes(updaterPath, App.Updater);
|
||||
}
|
||||
ShellOpen(updaterPath, updaterArgs.ToBytes().ToBase64());
|
||||
GlobalVars.PauseOnExit = false;
|
||||
Environment.Exit(0);
|
||||
}
|
||||
Console.WriteLine(App.DownloadLink, info.PackageLink);
|
||||
if (info.ForceUpdate) {
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
if (useLocalLib) {
|
||||
Console.WriteLine(@"[DEBUG] Use local native lib.");
|
||||
File.Copy(Path.Combine(GlobalVars.AppPath, "YaeAchievementLib.dll"), GlobalVars.LibFilePath, true);
|
||||
} else if (info.EnableLibDownload) {
|
||||
File.WriteAllBytes(GlobalVars.LibFilePath, GetBucketFileAsByteArray("schicksal/lib.dll"));
|
||||
}
|
||||
_updateInfo = info;
|
||||
}
|
||||
|
||||
public static void CheckSelfIsRunning() {
|
||||
Process.EnterDebugMode();
|
||||
var cur = Process.GetCurrentProcess();
|
||||
foreach (var process in Process.GetProcesses().Where(process => process.Id != cur.Id)) {
|
||||
if (process.ProcessName == cur.ProcessName) {
|
||||
Console.WriteLine(App.AnotherInstance);
|
||||
Environment.Exit(302);
|
||||
}
|
||||
}
|
||||
Process.LeaveDebugMode();
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||
public static bool ShellOpen(string path, string? args = null) {
|
||||
try {
|
||||
var startInfo = new ProcessStartInfo {
|
||||
FileName = path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
if (args != null) {
|
||||
startInfo.Arguments = args;
|
||||
}
|
||||
return new Process {
|
||||
StartInfo = startInfo
|
||||
}.Start();
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||
public static bool TryDisableQuickEdit() {
|
||||
var handle = Native.GetStdHandle();
|
||||
return Native.GetConsoleMode(handle, out var mode) && Native.SetConsoleMode(handle, mode&~64);
|
||||
}
|
||||
|
||||
public static void CheckGenshinIsRunning() {
|
||||
Process.EnterDebugMode();
|
||||
foreach (var process in Process.GetProcesses()) {
|
||||
if (process.ProcessName is "GenshinImpact" or "YuanShen"
|
||||
&& !process.HasExited
|
||||
&& process.MainWindowHandle != IntPtr.Zero
|
||||
) {
|
||||
Console.WriteLine(App.GenshinIsRunning, process.Id);
|
||||
Environment.Exit(301);
|
||||
}
|
||||
}
|
||||
Process.LeaveDebugMode();
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static Process? proc;
|
||||
|
||||
public static void InstallExitHook() {
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
|
||||
proc?.Kill();
|
||||
if (GlobalVars.PauseOnExit) {
|
||||
Console.WriteLine(App.PressKeyToExit);
|
||||
Console.ReadKey();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void InstallExceptionHook() {
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, e) => {
|
||||
var ex = e.ExceptionObject;
|
||||
switch (ex) {
|
||||
case ApplicationException ex1:
|
||||
Console.WriteLine(ex1.Message);
|
||||
break;
|
||||
case SocketException ex2:
|
||||
Console.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
|
||||
break;
|
||||
case HttpRequestException ex3:
|
||||
Console.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine(ex.ToString());
|
||||
Console.WriteLine(App.UploadError);
|
||||
AppCenter.TrackCrash((Exception) e.ExceptionObject);
|
||||
AppCenter.Upload();
|
||||
break;
|
||||
}
|
||||
Environment.Exit(-1);
|
||||
};
|
||||
}
|
||||
|
||||
private static bool CheckGenshinIsLatestVersion(string path) {
|
||||
#if DEBUG
|
||||
return true;
|
||||
#else
|
||||
var hash = File.ReadAllBytes(path).MD5Hash();
|
||||
return File.Exists(path) && (hash == _updateInfo.CurrentCNGameHash || hash == _updateInfo.CurrentOSGameHash);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||
public static Thread StartAndWaitResult(string exePath, Func<string, bool> onReceive) {
|
||||
if (!CheckGenshinIsLatestVersion(exePath)) {
|
||||
Console.WriteLine(App.GenshinHashError);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
|
||||
try {
|
||||
File.Delete(GlobalVars.LibFilePath);
|
||||
} catch (Exception) { /* ignored */ }
|
||||
};
|
||||
if (!Injector.CreateProcess(exePath, out var hProcess, out var hThread, out var pid)) {
|
||||
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("ICreateProcess fail"));
|
||||
}
|
||||
if (Injector.LoadLibraryAndInject(hProcess, GlobalVars.LibFilePath) != 0) {
|
||||
if (!Native.TerminateProcess(hProcess, 0)) {
|
||||
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail"));
|
||||
}
|
||||
}
|
||||
Console.WriteLine(App.GameLoading, pid);
|
||||
proc = Process.GetProcessById(Convert.ToInt32(pid));
|
||||
proc.EnableRaisingEvents = true;
|
||||
proc.Exited += (_, _) => {
|
||||
if (GlobalVars.UnexpectedExit) {
|
||||
proc = null;
|
||||
Console.WriteLine(App.GameProcessExit);
|
||||
Environment.Exit(114514);
|
||||
}
|
||||
};
|
||||
if (Native.ResumeThread(hThread) == 0xFFFFFFFF) {
|
||||
var e = new Win32Exception();
|
||||
if (!Native.TerminateProcess(hProcess, 0)) {
|
||||
new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail");
|
||||
}
|
||||
Environment.Exit(e.PrintMsgAndReturnErrCode("ResumeThread fail"));
|
||||
}
|
||||
if (!Native.CloseHandle(hProcess)) {
|
||||
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail"));
|
||||
}
|
||||
var ts = new ThreadStart(() => {
|
||||
var server = new NamedPipeServerStream(GlobalVars.PipeName);
|
||||
server.WaitForConnection();
|
||||
using var reader = new StreamReader(server);
|
||||
while (!proc.HasExited) {
|
||||
var line = reader.ReadLine();
|
||||
if (line?.Length > 0) {
|
||||
if (onReceive(line)) {
|
||||
break;
|
||||
}
|
||||
server.Disconnect();
|
||||
server.WaitForConnection();
|
||||
}
|
||||
}
|
||||
});
|
||||
var th = new Thread(ts);
|
||||
th.Start();
|
||||
return th;
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416
|
||||
|
||||
public static async Task CheckVcRuntime() {
|
||||
using var root = Registry.LocalMachine;
|
||||
using var sub = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall")!;
|
||||
var installed = sub.GetSubKeyNames()
|
||||
.Select(subKeyName => {
|
||||
try {
|
||||
return sub.OpenSubKey(subKeyName);
|
||||
} catch (Exception) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Select(item => item?.GetValue("DisplayName") as string ?? string.Empty)
|
||||
.Any(name => name.Contains("Microsoft Visual C++ 2022 X64 "));
|
||||
if (!installed) {
|
||||
Console.WriteLine(App.VcRuntimeDownload);
|
||||
var pkgPath = Path.Combine(GlobalVars.DataPath, "vc_redist.x64.exe");
|
||||
var bytes = await CHttpClient.GetByteArrayAsync("https://aka.ms/vs/17/release/vc_redist.x64.exe");
|
||||
await File.WriteAllBytesAsync(pkgPath, bytes);
|
||||
Console.WriteLine(App.VcRuntimeInstalling);
|
||||
using var process = new Process {
|
||||
StartInfo = {
|
||||
FileName = pkgPath,
|
||||
Arguments = "/install /passive /norestart"
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
await process.WaitForExitAsync();
|
||||
File.Delete(pkgPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace YaeAchievement.Win32;
|
||||
|
||||
[Flags]
|
||||
public enum AllocationType : uint {
|
||||
Commit = 0x00001000,
|
||||
Reserve = 0x00002000,
|
||||
Reset = 0x00080000,
|
||||
TopDown = 0x00100000,
|
||||
WriteWatch = 0x00200000,
|
||||
Physical = 0x00400000,
|
||||
Rotate = 0x00800000,
|
||||
ResetUndo = 0x01000000,
|
||||
LargePages = 0x20000000,
|
||||
Decommit = 0x00004000,
|
||||
Release = 0x00008000
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace YaeAchievement.Win32;
|
||||
|
||||
[Flags]
|
||||
public enum CreationFlags : uint {
|
||||
CreateSuspended = 0x00000004,
|
||||
DetachedProcess = 0x00000008,
|
||||
CreateNoWindow = 0x08000000,
|
||||
ExtendedStartupInfoPresent = 0x00080000
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using YaeAchievement.AppCenterSDK;
|
||||
|
||||
namespace YaeAchievement.Win32;
|
||||
|
||||
public static class Extensions {
|
||||
|
||||
public static int PrintMsgAndReturnErrCode(this Win32Exception ex, string msg) {
|
||||
Console.WriteLine($"{msg}: {ex.Message}");
|
||||
AppCenter.TrackCrash(ex, false);
|
||||
return ex.NativeErrorCode;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user