54 Commits
3.1.0 ... 5.2.2

Author SHA1 Message Date
HolographicHat
1b861712eb fix #119 #120 2024-11-21 21:35:41 +08:00
HolographicHat
fb4e3f8d00 fix 2024-11-21 08:11:26 +08:00
HolographicHat
b1135542c1 5.2 2024-11-21 07:33:07 +08:00
HolographicHat
7d3d0f5e14 auto identify field ids 2024-11-21 07:31:21 +08:00
HolographicHat
4268b04f3c ignore zydis 2024-10-15 20:41:04 +08:00
HolographicHat
9a9d1310a1 Merge pull request #115 from 34736384/master 2024-10-15 20:37:59 +08:00
REL
618a9189ad use sse 2024-10-15 07:32:14 -04:00
REL
f73dbdc4fe switch to amalgamated zydis 2024-10-14 20:24:32 -04:00
REL
298134c063 added dynamic resolving of CmdId and ToUInt16 2024-10-14 05:54:32 -04:00
REL
e9ace26d69 refactor 2024-10-10 05:06:57 -04:00
REL
2c15353f86 remove detours 2024-10-09 08:32:36 -04:00
REL
99fec63867 remove il2cpp bloat 2024-10-09 08:14:41 -04:00
HolographicHat
bf5525d2ea 5.1 2024-10-09 16:03:25 +08:00
HolographicHat
cf3749f887 fix #114 2024-09-12 23:47:01 +08:00
HolographicHat
21af4de1a6 5.0 2024-08-29 18:24:21 +08:00
HolographicHat
8e0fd2d27c fix #111 2024-08-05 18:11:51 +08:00
HolographicHat
0348cfa365 Merge pull request #107 from eltociear/add-japanese-readme-tutorial 2024-07-31 07:58:19 +08:00
Ikko Eltociear Ashimine
494eda32c2 docs: add Japanese README and tutorial 2024-07-23 00:47:25 +09:00
HolographicHat
975638c1ee Merge pull request #104 from canmengxian/master 2024-07-17 18:33:19 +08:00
HolographicHat
793ad075fe 4.8 2024-07-17 18:12:24 +08:00
残梦
c82a10353f Update Utils.cs 2024-06-23 23:16:13 +08:00
HolographicHat
f737122247 4.7 2024-06-06 02:07:56 +08:00
HolographicHat
520167ef85 fix #82 #101 2024-04-29 14:38:13 +08:00
HolographicHat
faee6f6121 Update README_EN.md 2024-04-25 21:50:15 +08:00
HolographicHat
06c5468118 update readme 2024-04-25 21:42:14 +08:00
HolographicHat
b7c2204f68 Update CI 2024-04-25 02:52:46 +08:00
HolographicHat
5dc5e646d6 fix #87 2024-04-25 02:45:30 +08:00
HolographicHat
9cab7e8702 4.6 2024-04-24 23:26:24 +08:00
HolographicHat
1f080fe084 Merge pull request #98 from Anong0u0/patch 2024-04-22 14:27:29 +08:00
Anong0u0
c8497243c0 Fix GamePathRegex match to case insensitive 2024-04-22 13:44:20 +08:00
HolographicHat
9abdd123ee #93
remove appcenter
2024-04-02 15:06:05 +08:00
HolographicHat
e1429289ad refactor appcenter 2024-03-15 00:08:34 +08:00
HolographicHat
1f311ed987 4.5 2024-03-14 23:55:36 +08:00
HolographicHat
cc346915e3 Merge pull request #85 from BTMuli/master
️ 延长刷新间隔,Y/YES均可使用旧数据(大小写不敏感)
2024-02-06 23:27:16 +08:00
HolographicHat
cd0f49d83d shorten code 2024-02-06 23:19:41 +08:00
目棃
d0b7d15894 Merge branch 'master' into master 2024-02-06 22:54:51 +08:00
目棃
504c8a2a9a 👔 默认不使用旧数据 2024-02-06 22:46:37 +08:00
HolographicHat
b3162052da refactor 2024-02-06 21:31:14 +08:00
目棃
034d999d25 ️ 延长刷新间隔,回车/Y也能使用旧数据了 2024-02-02 19:30:03 +08:00
HolographicHat
45d5620e83 4.4 2024-01-31 12:46:31 +08:00
HolographicHat
fa13f9c8e5 fix 2023-12-25 00:21:48 +08:00
HolographicHat
2210a97d61 4.3 2023-12-24 23:59:24 +08:00
HolographicHat
feb7ac44da 4.2 2023-11-09 21:39:05 +08:00
HolographicHat
3924129560 Merge pull request #74 from Lightczx/master
Disable marshalling
2023-10-30 14:04:24 +08:00
Lightczx
4f7f0cdfd2 disable marshalling 2023-10-30 13:51:01 +08:00
HolographicHat
cf0753c676 Merge pull request #73 from BTMuli/master
🐛 修复 TeyvatGuide 链接错误
2023-10-16 13:42:22 +08:00
BTMuli
0b895d47ca 🐛 修复 TeyvatGuide 链接错误 2023-10-16 12:39:32 +08:00
HolographicHat
78d2722e20 Merge pull request #72 from BTMuli/master
更新文档
2023-10-15 01:16:19 +08:00
BTMuli
385c673323 🚨 revert,删除多余空格 2023-10-15 00:31:18 +08:00
BTMuli
50beb2cce7 🚨 修复 CI 报错 2023-10-15 00:18:39 +08:00
BTMuli
324a4153e0 📝 删除混入其中的表格文件 2023-10-14 23:56:24 +08:00
BTMuli
3de459aceb 📝 更新部分信息 2023-10-14 23:54:49 +08:00
BTMuli
295bb89177 📝 更新导出说明 2023-10-14 23:50:31 +08:00
HolographicHat
baaf4e8227 rollback 2023-10-13 21:22:54 +08:00
67 changed files with 68434 additions and 2574 deletions

2
.gitattributes vendored
View File

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

View File

@@ -12,11 +12,11 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
@@ -24,7 +24,7 @@ jobs:
- name: 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

View File

@@ -1,29 +1,42 @@
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
# YaeAchievement
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/YaeAchievement?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/YaeAchievement?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/YaeAchievement/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
简体中文 | [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-7.0.3-windows-x64-installer)
1. Q: 原神启动时报错: 数据异常(31-4302)
A: 不要把软件和原神主程序放一起
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
# YaeAchievement
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/YaeAchievement?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/YaeAchievement?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/YaeAchievement/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
简体中文 | [English](README_EN.md) | [日本語](README_JP.md)
</div>
- 支持导出所有类别的成就
- 支持官服,渠道服与国际服
- 没有窗口大小、游戏语言等要求
## 导出支持
> 按照数字键选择导出方式,<kbd>0</kbd> 为默认导出方式
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群: 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-8.0.4-windows-x64-installer)
1. Q: 原神启动时报错: 数据异常(31-4302)
A: 不要把软件和原神主程序放一起

View File

@@ -4,18 +4,28 @@
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/YaeAchievement?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/YaeAchievement?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/YaeAchievement/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
[简体中文](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.
## Export support
> Select the export method according to the number keys, <kbd>0</kbd> is the default export method
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)
@@ -26,7 +36,7 @@
## Frequently asked questions
0. Q: Unable to start
A: Download and install [.NET Runtime 7](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-7.0.7-windows-x64-installer) or ` winget install Microsoft.DotNet.Runtime.7`
A: Download and install [.NET Runtime 7](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.4-windows-x64-installer) or ` winget install Microsoft.DotNet.Runtime.8`
1. Q: Error while Genshin started: Data Exception (31-4302)

44
README_JP.md Normal file
View File

@@ -0,0 +1,44 @@
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
# YaeAchievement
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/YaeAchievement?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/YaeAchievement?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/YaeAchievement/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
[简体中文](README.md) | [English](README_EN.md) | 日本語
</div>
- すべてのカテゴリの実績のエクスポートをサポート
- すべてのバージョンの原神をサポート
- ウィンドウサイズ、ゲーム言語などの要件はありません
## エクスポートサポート
> 数字キーに従ってエクスポート方法を選択します。<kbd>0</kbd> はデフォルトのエクスポート方法です
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群: 913777414](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
## よくある質問
0. Q: 起動できない
A: [.NET Runtime 7](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.4-windows-x64-installer) をダウンロードしてインストールするか、`winget install Microsoft.DotNet.Runtime.8` を実行してください。
1. Q: 原神を起動中にエラーが発生しました: データ例外 (31-4302)
A: ソフトウェアを原神のディレクトリに配置しないでください。

View File

@@ -10,7 +10,7 @@
2.安装启动软件所需文件(若已安装该运行时可忽略此步骤)
点击该网址https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-7.0.3-windows-x64-installer 。
点击该网址https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.4-windows-x64-installer 。
进入网页后浏览器会自动弹出下载,同样地,将文件保存在桌面或者其它易于寻找的文件夹内。
@@ -71,11 +71,11 @@
### 各种工具的介绍烦请移步至各工具的官方页面进行查看(下方序号对应导出序号)
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`~~
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 文件

View File

@@ -11,7 +11,7 @@ Click on the file named "YaeAchievement.exe" in the red box to automatically pop
2.Install .NET Runtime 7 (this step can be ignored if the runtime is already installed)
Click Herehttps://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-7.0.7-windows-x64-installer .
Click Herehttps://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.4-windows-x64-installer .
Or `winget install Microsoft.DotNet.Runtime.7` if you use Windows 11 or have Winget installed.
@@ -65,9 +65,12 @@ At this time, you can select the Achievements in the left column to view the imp
### For the introduction of different tools, please visit the official page of each tool to see:
1. [Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao)
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. [Teyvat Guide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON file

75
Tutorial_JP.md Normal file
View File

@@ -0,0 +1,75 @@
## 使用説明書
1. YaeAchievement最新バージョンをダウンロード
こちらをクリックhttps://github.com/HolographicHat/YaeAchievement/releases
赤枠で囲まれた「YaeAchievement.exe」という名前のファイルをクリックすると、自動的にポップアップしてダウンロードされます。このファイルをデスクトップや他の見やすいフォルダに保存することをお勧めします。
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide1.png)
2. .NET Runtime 7をインストールランタイムが既にインストールされている場合はこのステップを無視できます
こちらをクリックhttps://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.4-windows-x64-installer .
または、Windows 11を使用しているか、Wingetがインストールされている場合は、`winget install Microsoft.DotNet.Runtime.7`を実行します。
ウェブページにアクセスすると、ブラウザが自動的にポップアップしてダウンロードされます。ファイルをデスクトップや他の見やすいフォルダに保存します。
ダウンロードが完了したら、dotnet-runtime-x.x.x-win-x64.exeという名前のファイルを開くと、インストールウィンドウがポップアップします。以下の図のように表示されます。
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide2.png)
インストールをクリックするだけです。
3. メインプログラムを開くための操作と実績エクスポートのオプション
最初のステップでダウンロードした「YaeAchievement.exe」という名前のファイルをダブルクリックして開くと、原神が起動していることを示します。以下の図のように表示されます。
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide3.png)
原神の起動が完了したら、ゲームに入ります。
ゲームに入ると、ツールがどのツールにエクスポートするかを選択するように促します。以下の図のように表示されます。
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide4.png)
グローバルユーザーの場合、[3] Seelie.meまたは[4] csvファイルにエクスポートを選択する必要があります。
選択後、各ページは次のようにツールをエクスポートします:
#### Snap.Hutao
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide5.png)
#### Seelie.me
この時点で、Yae Achievementは実績データがエクスポートされたことを通知します。Yae Achievement保存ディレクトリにexport-20xxxxxxxxxxxx-seelie.jsonという名前のファイルを見つけてください。
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide6.png)
次に、URLhttps://seelie.me/settings をクリックし、ウェブサイトにアクセスしてインポートアカウントを選択します。以下の図のように表示されます。
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide7.png)
インポートをクリックした後、export-20xxxxxxxxxxxx-seelie.jsonという名前のファイルを選択します。以下の図のように表示されます。
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide8.png)
以下の図のようなプロンプトが表示されたら、インポートプロセスは成功です。
![image](https://github.com/prpjzz/yae-markdown-en/blob/main/image/Guide9.png)
この時点で、左側の列の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`~~
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ファイル

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
@@ -21,16 +21,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.22.1" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.188-beta">
<PackageReference Include="Google.Protobuf" Version="3.26.1" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Grpc.Tools" Version="2.53.0">
<PackageReference Include="Grpc.Tools" Version="2.62.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -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,34 +44,28 @@
</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>
</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>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<AdditionalIncludeDirectories>$(ProjectDir)lib\detours\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</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>
@@ -83,50 +76,36 @@
<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>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<DebugInformationFormat>None</DebugInformationFormat>
<PreprocessorDefinitions>_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>$(ProjectDir)lib\detours\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<SDLCheck>true</SDLCheck>
<LanguageStandard_C>stdc11</LanguageStandard_C>
</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\net7.0-windows\win-x64\YaeAchievementLib.dll /y</Command>
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net8.0-windows\win-x64\YaeAchievementLib.dll /y</Command>
</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\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

View File

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

View File

@@ -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();
}
};

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,101 +1,186 @@
#include "pch.h"
// ReSharper disable CppClangTidyCertErr33C
#include <Windows.h>
#include <string>
#include <future>
#include <TlHelp32.h>
#include "globals.h"
#include "util.h"
#include "il2cpp-init.h"
#include "il2cpp-types.h"
using Genshin::ByteArray, Genshin::ClientKcpEvent, Genshin::KcpPacket, Genshin::KcpEventType;
using std::to_string;
HWND unityWnd = nullptr;
HANDLE hPipe = nullptr;
// Allow Protocol: GetPlayerTokenRsp, PlayerLoginRsp, AchievementAllDataNotify, PingRsp
std::set<UINT16> PacketWhitelist = { 1347, 4424, 20342, 25731 };
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 (!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) == 20342) {
const auto headLength = ReadMapped<UINT16>(data->vector, 4);
const auto dataLength = ReadMapped<UINT32>(data->vector, 6);
const auto cStr = base64_encode(data->vector + 10 + headLength, dataLength) + "\n";
WriteFile(hPipe, cStr.c_str(), cStr.length(), nullptr, nullptr);
CloseHandle(hPipe);
auto manager = Genshin::GetSingletonInstance(Genshin::GetSingletonManager(), il2cpp_string_new("GameManager"));
Genshin::ForceQuit(manager);
}
delete[] data;
return true;
}
std::string checksum;
CRITICAL_SECTION CriticalSection;
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index = 0);
namespace Hook {
void SetVersion(void* obj, Il2CppString* value, void* method) {
const auto version = ToString(value);
value = string_new(version + " YaeAchievement");
CALL_ORIGIN(SetVersion, obj, value, method);
}
bool KcpRecv(void* client, ClientKcpEvent* evt, void* method) {
const auto result = CALL_ORIGIN(KcpRecv, client, evt, method);
if (result == 0 || evt->fields.type != KcpEventType::EventRecvMsg) {
return result;
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);
EnterCriticalSection(&CriticalSection);
SetBreakpoint((HANDLE)-2, 0, false);
const auto ret = ToUInt16(val, startIndex);
SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true);
LeaveCriticalSection(&CriticalSection);
const auto packet = reinterpret_cast<PacketMeta*>(val->data());
auto CheckPacket = [](const PacketMeta* packet) -> bool {
const auto cmdid = _byteswap_ushort(packet->CmdId);
const auto dataLength = _byteswap_ulong(packet->DataLength);
if (dataLength < 500) {
return false;
}
if (CmdId != 0) {
return cmdid == CmdId;
}
return DynamicCmdIds.contains(cmdid);
};
using namespace Globals;
if (ret == 0xAB89 && CheckPacket(packet))
{
const auto headLength = _byteswap_ushort(packet->HeaderLength);
const auto dataLength = _byteswap_ulong(packet->DataLength);
printf("CmdId: %d\n", _byteswap_ushort(packet->CmdId));
printf("DataLength: %d\n", dataLength);
const auto base64 = Util::Base64Encode(packet->Data + headLength, dataLength) + "\n";
printf("Base64: %s\n", base64.c_str());
#ifdef _DEBUG
system("pause");
#endif
WriteFile(MessagePipe, base64.c_str(), (DWORD)base64.length(), nullptr, nullptr);
CloseHandle(MessagePipe);
ExitProcess(0);
}
return OnPacket(evt->fields.packet) ? result : false;
}
ByteArray* UnityEngine_RecordUserData(INT type) {
return new ByteArray {};
return ret;
}
// 不再使用checksum(?
}
void Run(HMODULE* phModule) {
//AllocConsole();
//freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
while ((unityWnd = FindMainWindowByPID(GetCurrentProcessId())) == nullptr) {
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)) {
return EXCEPTION_CONTINUE_SEARCH;
}
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::BitConverter_ToUInt16);
contextRecord->EFlags &= ~0x100; // clear the trap flag
return EXCEPTION_CONTINUE_EXECUTION;
}
Sleep(5000);
DisableVMProtect();
InitIL2CPP();
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);
return EXCEPTION_CONTINUE_SEARCH;
}
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index)
{
using namespace Globals;
if (index > 3) {
return;
}
if (!BaseAddress || Offset.BitConverter_ToUInt16 <= BaseAddress) {
// not initialized yet
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);
#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();
}
initFuture.get();
MessagePipe = CreateFileA(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (MessagePipe == INVALID_HANDLE_VALUE)
{
#ifdef _DEBUG
printf("CreateFile failed: %d\n", 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);
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)
{
if (fdwReason == DLL_PROCESS_ATTACH)
{
if (const auto hThread = CreateThread(nullptr, 0, ThreadProc, hInstance, 0, nullptr)) {
CloseHandle(hThread);
}
}
return TRUE;
}

31
lib/src/globals.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <Windows.h>
#include <unordered_set>
#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 HANDLE MessagePipe = nullptr;
inline bool IsCNREL = true;
inline uintptr_t BaseAddress = 0;
// 5.1.0 - 24082
inline uint16_t CmdId = 0; // use non-zero to override dynamic search
inline std::unordered_set<uint16_t> DynamicCmdIds;
class Offsets
{
public:
PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0, 0);
//PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0x0F826CF0, 0x0F825F10); // use non-zero to override dynamic search
};
inline Offsets Offset;
}

View File

@@ -1 +0,0 @@
DO_API(0x9D1AC0, 0x9CADC0, Il2CppString*, il2cpp_string_new, (const char* str));

View File

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

View File

@@ -1,15 +0,0 @@
using namespace Genshin;
// DO_APP_FUNC(CN_OFFSET, OS_OFFSET, RETURN, FUNC_NAME, (ARGS...));
DO_APP_FUNC(0x258fd40, 0x2548e50, void, SetVersion, (void* obj, Il2CppString* value, void* method));
DO_APP_FUNC(0x3220f00, 0x31c1650, void, XorEncrypt, (ByteArray** data, int length, void* method));
DO_APP_FUNC(0x12f5df0, 0x12ddd80, bool, KcpRecv, (void* client, ClientKcpEvent* evt, void* method));
DO_APP_FUNC(0x264ee90, 0x2606720, VOID, ForceQuit, (LPVOID obj));
DO_APP_FUNC(0x624d630, 0x61bd630, LPVOID, GetSingletonManager, ());
DO_APP_FUNC(0x624d360, 0x61bd360, LPVOID, GetSingletonInstance, (LPVOID obj, Il2CppString* value));

View File

@@ -1,39 +1,386 @@
#include "pch.h"
#include <Windows.h>
#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;
};
uintptr_t GetSection(LPCSTR name, size_t* sectionSize = nullptr)
{
using namespace Globals;
if (BaseAddress == 0)
return 0;
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)
{
if (sectionSize != nullptr) {
*sectionSize = sectionHeader[i].Misc.VirtualSize;
}
return BaseAddress + sectionHeader[i].VirtualAddress;
}
}
return 0;
}
/// <summary>
/// decodes all instruction until next push, ignores branching
/// </summary>
/// <param name="address"></param>
/// <returns>std::vector DecodedInstruction</returns>
std::vector<DecodedInstruction> DecodeFunction(uintptr_t address)
{
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;
}
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.RVA + instr.Instruction.length + instr.Operands[0].imm.value.s;
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 ResolveCmdId()
{
size_t sectionSize;
const auto sectionAddress = GetSection("il2cpp", &sectionSize);
const auto sectionEnd = sectionAddress + sectionSize;
printf("Section Address: 0x%llX\n", sectionAddress);
printf("Section End: 0x%llX\n", sectionEnd);
if (sectionAddress == 0)
return; // message box?
const auto candidates = Util::PatternScanAll(sectionAddress, sectionEnd, "56 48 83 EC 20 48 89 D0 48 89 CE 80 3D ? ? ? ? 00");
printf("Candidates: %llu\n", candidates.size());
std::vector<std::vector<DecodedInstruction>> candidateInstructions;
std::ranges::transform(candidates, std::back_inserter(candidateInstructions), DecodeFunction);
std::vector<std::vector<DecodedInstruction>> filteredInstructions;
std::ranges::copy_if(candidateInstructions, 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)
{
printf("Filtered Instructions: %llu\n", filteredInstructions.size());
return;
}
const auto& instructions = filteredInstructions[0];
printf("RVA: 0x%08X\n", instructions.front().RVA);
// extract all the non-zero immediate values from the cmp instructions
std::decay_t<decltype(instructions)> cmpInstructions;
std::ranges::copy_if(instructions, std::back_inserter(cmpInstructions), [](const DecodedInstruction& instr) {
return instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP && instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && instr.Operands[1].imm.value.u;
});
std::vector<uint32_t> cmdIds;
std::ranges::transform(cmpInstructions, std::back_inserter(cmdIds), [](const DecodedInstruction& instr) {
return instr.Operands[1].imm.value.u;
});
for (const auto& cmdId : cmdIds)
{
printf("CmdId: %u\n", cmdId);
Globals::DynamicCmdIds.insert(cmdId);
}
}
int32_t GetCallCount(uint8_t* target)
{
size_t sectionSize;
const auto sectionAddress = GetSection("il2cpp", &sectionSize);
const auto sectionEnd = sectionAddress + sectionSize;
int32_t count = 0;
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) {
count++;
}
// clear the bit we just processed and continue with the next match
mask &= ~(1 << first_match_idx);
}
}
return count;
}
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;
}
uintptr_t Resolve_BitConverter_ToUInt16()
{
size_t sectionSize;
const auto sectionAddress = GetSection("il2cpp", &sectionSize);
const auto sectionEnd = sectionAddress + sectionSize;
printf("Section Address: 0x%llX\n", sectionAddress);
printf("Section End: 0x%llX\n", sectionEnd);
/*
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(sectionAddress, sectionEnd, "B9 0F 00 00 00 E8 ? ? ? ? B9 0E 00 00 00 BA 16 00 00 00 E8 ? ? ? ? B9 05 00 00 00 E8 ? ? ? ?");
printf("Candidates: %llu\n", 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)
{
printf("Entry: 0x%llX\n", entry);
}
printf("Looking for call counts...\n");
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 count = GetCallCount((uint8_t*)e);
std::lock_guard lock(mutex);
callCounts[e] = count;
}, entry);
});
for (auto& future : futures) {
future.get();
}
uintptr_t targetEntry = 0;
for (const auto& [entry, count] : callCounts)
{
printf("Entry: 0x%llX, RVA: 0x%08llX, Count: %d\n", entry, entry - Globals::BaseAddress, count);
if (count == 5) {
targetEntry = entry;
}
}
return targetEntry;
}
#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
void 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, [] {
if (Offset.BitConverter_ToUInt16 != 0) {
Offset.BitConverter_ToUInt16 += BaseAddress;
}
else {
Offset.BitConverter_ToUInt16 = Resolve_BitConverter_ToUInt16();
}
});
std::future<void> resolveCmdIdFuture = std::async(std::launch::async, [] {
if (CmdId == 0) {
ResolveCmdId();
}
});
resolveFuncFuture.get();
resolveCmdIdFuture.get();
printf("BaseAddress: 0x%llX\n", BaseAddress);
printf("IsCNREL: %d\n", IsCNREL);
printf("BitConverter_ToUInt16: 0x%llX\n", Offset.BitConverter_ToUInt16);
}

View File

@@ -1,65 +1,38 @@
#pragma once
#include <cstdint>
#pragma region IL2CPPInternalTypes
template <typename T>
class Array
{
public:
void* klass;
void* monitor;
void* bounds;
size_t max_length;
T vector[1];
typedef uint16_t Il2CppChar;
typedef uintptr_t il2cpp_array_size_t;
typedef int32_t il2cpp_array_lower_bound_t;
Array() = delete;
typedef struct Il2CppObject {
union {
void* klass;
void* vtable;
} Il2CppClass;
void* monitor;
} Il2CppObject;
T* data() {
return vector;
}
};
typedef struct Il2CppString {
Il2CppObject object;
int32_t length;
Il2CppChar chars[32];
} Il2CppString;
static_assert(alignof(Array<uint8_t>) == 8, "Array alignment is incorrect");
static_assert(offsetof(Array<uint8_t>, vector) == 32, "vector offset is incorrect");
typedef struct Il2CppArrayBounds {
il2cpp_array_size_t length;
il2cpp_array_lower_bound_t lower_bound;
} Il2CppArrayBounds;
#pragma pack(push, 1)
struct PacketMeta
{
uint16_t HeadMagic;
uint16_t CmdId;
uint16_t HeaderLength;
uint32_t DataLength;
uint8_t Data[1];
};
#pragma pack(pop)
#pragma endregion
namespace Genshin {
struct ByteArray {
void* klass;
void* monitor;
Il2CppArrayBounds* bounds;
il2cpp_array_size_t max_length;
uint8_t vector[32];
};
struct KcpPacket {
BYTE* data;
UINT32 length;
};
enum class KcpEventType : int {
EventNotSet = -1,
EventConnect = 0,
EventConnectFailed = 1,
EventDisconnect = 2,
EventRecvMsg = 3,
EventCount = 4,
};
struct KcpEvent_Fields {
KcpEventType type;
UINT32 token;
UINT32 data;
KcpPacket* packet;
};
struct ClientKcpEvent {
KcpEvent_Fields fields;
};
}
static_assert(offsetof(PacketMeta, CmdId) == 2, "CmdId offset is incorrect");
static_assert(offsetof(PacketMeta, HeaderLength) == 4, "HeadLength offset is incorrect");
static_assert(offsetof(PacketMeta, DataLength) == 6, "DataLength offset is incorrect");
static_assert(offsetof(PacketMeta, Data) == 10, "Data offset is incorrect");

View File

@@ -1,3 +0,0 @@
using namespace Genshin;
DO_UNI_FUNC(0x103420, 0x103420, ByteArray*, UnityEngine_RecordUserData, (int32_t nType));

View File

@@ -1 +0,0 @@
#include "pch.h"

View File

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

View File

@@ -1,105 +1,182 @@
#include "pch.h"
#include <string>
#include "util.h"
#include "globals.h"
VOID DisableVMProtect() {
DWORD oldProtect = 0;
auto ntdll = GetModuleHandleA("ntdll.dll");
auto pNtProtectVirtualMemory = GetProcAddress(ntdll, "NtProtectVirtualMemory");
auto pNtQuerySection = GetProcAddress(ntdll, "NtQuerySection");
DWORD old;
VirtualProtect(pNtProtectVirtualMemory, 1, PAGE_EXECUTE_READWRITE, &old);
*(uintptr_t*)pNtProtectVirtualMemory = *(uintptr_t*)pNtQuerySection & ~(0xFFui64 << 32) | (uintptr_t)(*(uint32_t*)((uintptr_t)pNtQuerySection + 4) - 1) << 32;
VirtualProtect(pNtProtectVirtualMemory, 1, old, &old);
}
#pragma region StringConvert
string ToString(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 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) == (HWND)0 && 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::vector<bool>> PatternToBytes(const char* pattern)
{
std::vector<uint8_t> bytes;
std::vector<bool> maskBytes;
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);
maskBytes.push_back(false);
}
else {
bytes.push_back(strtoul(current, &current, 16));
maskBytes.push_back(true);
}
}
return { bytes, maskBytes };
}
HWND FindMainWindowByPID(DWORD pid) {
HandleData data = { pid, 0 };
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
return data.hwnd;
}
#pragma endregion
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static constexpr LPCSTR base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string base64_encode(BYTE const* buf, unsigned int bufLen) {
std::string ret;
int i = 0;
BYTE char_array_3[3];
BYTE 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;
namespace Util
{
HWND FindMainWindowByPID(DWORD pid)
{
HandleData data = { pid, 0 };
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
return data.hwnd;
}
std::string Base64Encode(BYTE const* buf, unsigned int bufLen)
{
std::string ret;
int i = 0;
BYTE char_array_3[3];
BYTE 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());
}
uintptr_t PatternScan(uintptr_t start, uintptr_t end, const char* pattern)
{
const auto [patternBytes, patternMask] = PatternToBytes(pattern);
const auto scanBytes = reinterpret_cast<uint8_t*>(start);
const auto patternSize = patternBytes.size();
const auto pBytes = patternBytes.data();
for (auto i = 0ul; i < end - start - patternSize; ++i) {
bool found = true;
for (auto j = 0ul; j < patternSize; ++j) {
if (scanBytes[i + j] != pBytes[j] && patternMask[j]) {
found = false;
break;
}
}
if (found) {
return reinterpret_cast<uintptr_t>(&scanBytes[i]);
}
}
return 0;
}
std::vector<uintptr_t> PatternScanAll(uintptr_t start, uintptr_t end, const char* pattern)
{
std::vector<uintptr_t> results;
const auto [patternBytes, patternMask] = PatternToBytes(pattern);
const auto scanBytes = reinterpret_cast<uint8_t*>(start);
const auto patternSize = patternBytes.size();
const auto pBytes = patternBytes.data();
for (auto i = 0ul; i < end - start - patternSize; ++i) {
bool found = true;
for (auto j = 0ul; j < patternSize; ++j) {
if (scanBytes[i + j] != pBytes[j] && patternMask[j]) {
found = false;
break;
}
}
if (found) {
results.push_back(reinterpret_cast<uintptr_t>(&scanBytes[i]));
i += patternSize - 1;
}
}
return results;
}
}
#ifdef _DEBUG
#pragma runtime_checks("", restore)
#endif

View File

@@ -1,29 +1,18 @@
// ReSharper disable CppClangTidyClangDiagnosticLanguageExtensionToken
#pragma once
#include <Windows.h>
#include <type_traits>
#include <vector>
using std::string;
namespace Util
{
HWND FindMainWindowByPID(DWORD pid);
std::string Base64Encode(BYTE const* buf, unsigned int bufLen);
VOID DisableVMProtect();
bool IsLittleEndian();
HWND FindMainWindowByPID(DWORD pid);
string ToString(Il2CppString* str, UINT codePage = CP_ACP);
std::string base64_encode(BYTE const* buf, unsigned int bufLen);
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;
}
uintptr_t PatternScan(uintptr_t start, uintptr_t end, const char* pattern);
std::vector<uintptr_t> PatternScanAll(uintptr_t start, uintptr_t end, const char* pattern);
}

9
res/App.Designer.cs generated
View File

@@ -393,5 +393,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);
}
}
}
}

View File

@@ -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">
@@ -137,4 +137,7 @@ Input a number (0-8): </value>
<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>
</root>

View File

@@ -127,4 +127,7 @@
<data name="ExportToTauriSuccess" xml:space="preserve">
<value>在 Teyvat Guide 进行下一步操作</value>
</data>
<data name="WaitMetadataUpdate" xml:space="preserve">
<value>当前元数据版本不匹配,请更新原神至最新版本或等待元数据更新后重试。</value>
</data>
</root>

View File

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

View File

@@ -1,21 +0,0 @@
syntax = "proto3";
option csharp_namespace = "Proto";
message Achievement {
enum Status {
INVALID = 0;
UNFINISHED = 1;
FINISHED = 2;
REWARD_TAKEN = 3;
}
uint32 timestamp = 3;
uint32 current = 14;
uint32 total = 8;
uint32 id = 1;
Status status = 13;
}
message AchievementAllDataNotify {
repeated Achievement list = 2;
}

View File

@@ -2,6 +2,14 @@ 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;
@@ -13,4 +21,5 @@ message AchievementInfo {
string version = 1;
map<uint32, string> group = 2;
map<uint32, AchievementItem> items = 3;
AchievementProtoFieldInfo pb_info = 4;
}

View File

@@ -3,13 +3,13 @@ syntax = "proto3";
option csharp_namespace = "Proto";
message UpdateInfo {
uint32 versionCode = 1;
string versionName = 2;
uint32 version_code = 1;
string version_name = 2;
string description = 3;
string packageLink = 4;
bool forceUpdate = 5;
bool enableLibDownload = 6;
bool enableAutoDownload = 7;
string currentCNGameHash = 8;
string currentOSGameHash = 9;
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;
}

View File

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

View File

@@ -1,70 +0,0 @@
using Microsoft.Win32;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
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(HWND.Null);
var size = $"{Native.GetDeviceCaps(desktop, GET_DEVICE_CAPS_INDEX.DESKTOPHORZRES)}x{Native.GetDeviceCaps(desktop, GET_DEVICE_CAPS_INDEX.DESKTOPVERTRES)}";
_ = Native.ReleaseDC(HWND.Null, 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
[JsonObject(JsonIdentifier)]
public class StartSessionLog : Log {
public const string JsonIdentifier = "startSession";
}

View File

@@ -1,10 +1,10 @@
using System.Text.RegularExpressions;
using YaeAchievement.res;
namespace YaeAchievement;
namespace YaeAchievement;
public static partial class AppConfig {
public static class AppConfig {
public static string GamePath { get; private set; } = null!;
internal static void Load(string argumentPath) {
@@ -36,8 +36,8 @@ public static class AppConfig {
var osLastWriteTime = File.GetLastWriteTime(osLogPath);
finalLogPath = cnLastWriteTime > osLastWriteTime ? cnLogPath : osLogPath;
}
GamePath = GetGamePathFromLogFile(finalLogPath)
?? GetGamePathFromLogFile($"{finalLogPath}.last")
GamePath = GetGamePathFromLogFile(finalLogPath)
?? GetGamePathFromLogFile($"{finalLogPath}.last")
?? throw new ApplicationException(App.ConfigNeedStartGenshin);
pathCacheFile.Write(GamePath);
}
@@ -52,11 +52,15 @@ public static class AppConfig {
try {
File.Delete(copiedLogFilePath);
} catch (Exception) { /* ignore */}
var matchResult = Regex.Match(content, @"(?m).:/.+(GenshinImpact_Data|YuanShen_Data)");
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();
}

View File

@@ -2,18 +2,14 @@
using Google.Protobuf;
using Proto;
namespace YaeAchievement;
namespace YaeAchievement;
public class CacheFile {
public class CacheFile(string identifier) {
private readonly string _cacheName;
private readonly string _cacheName = Path.Combine(GlobalVars.CachePath, $"{identifier.MD5Hash()[..16]}.miko");
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 DateTime LastWriteTime => Exists() ? File.GetLastWriteTimeUtc(_cacheName) : DateTime.UnixEpoch;
public bool Exists() => File.Exists(_cacheName);
@@ -29,7 +25,7 @@ public class CacheFile {
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);

View File

@@ -5,17 +5,19 @@ using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Win32;
using Proto;
using YaeAchievement.AppCenterSDK;
using YaeAchievement.Parsers;
using YaeAchievement.res;
using static Proto.Achievement.Types;
namespace YaeAchievement;
namespace YaeAchievement;
public static class Export {
public static uint ExportTo { get; set; } = uint.MaxValue;
private static readonly JsonSerializerOptions JsonOpts = new () {
WriteIndented = true
};
public static void Choose(AchievementAllDataNotify data) {
if (ExportTo == uint.MaxValue) {
Console.Write(App.ExportChoose);
@@ -40,16 +42,15 @@ public static class Export {
}
private class CocogoatResponse {
[JsonPropertyName("key")] public string Code { get; set; } = null!;
[JsonPropertyName("key")] public string Code { get; init; } = 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 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) {
Console.WriteLine(App.ExportToCocogoatFail);
@@ -61,18 +62,17 @@ public static class Export {
? 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 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);
Console.WriteLine(App.ExportToWxApp1Success, id);
}
@@ -87,7 +87,7 @@ public static class Export {
Utils.ShellOpen("ms-windows-store://pdp/?productid=9PH4NXJ2JN52");
}
}
private static void ToXunkong(AchievementAllDataNotify data) {
if (CheckWinUIAppScheme("xunkong")) {
Utils.CopyToClipboard(JsonSerializer.Serialize(ExportToUIAFApp(data)));
@@ -100,7 +100,7 @@ public static class Export {
}
private static void ToTeyvatGuide(AchievementAllDataNotify data) {
if (Process.GetProcessesByName("TeyvatGuide").Any()) {
if (Process.GetProcessesByName("TeyvatGuide").Length != 0) {
Utils.CopyToClipboard(JsonSerializer.Serialize(ExportToUIAFApp(data)));
Utils.ShellOpen("teyvatguide://import_uigf?app=YaeAchievement");
Console.WriteLine(App.ExportToTauriSuccess);
@@ -119,30 +119,25 @@ public static class Export {
}
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 info = GlobalVars.AchievementInfo.Items.ToDictionary(pair => pair.Key, pair => pair.Value.Group);
var final = new Dictionary<string, Dictionary<uint, Dictionary<uint, bool>>> {
["achievement"] = output.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value)
["achievement"] = data.AchievementList
.Where(achievement => achievement.Status is AchievementStatus.Finished or AchievementStatus.RewardTaken)
.Where(achievement => info.ContainsKey(achievement.Id))
.GroupBy(achievement => info[achievement.Id], achievement => achievement.Id)
.OrderBy(group => group.Key)
.ToDictionary(group => group.Key, group => group.ToDictionary(id => id, _ => true))
};
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json");
if (TryWriteToFile(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> {
foreach (var ach in data.AchievementList.Where(a => a.Status is AchievementStatus.Finished or AchievementStatus.RewardTaken)) {
output[ach.Id] = new Dictionary<string, bool> {
["done"] = true
};
}
@@ -157,24 +152,26 @@ public static class Export {
// ReSharper disable once InconsistentNaming
private static void ToCSV(AchievementAllDataNotify data) {
var info = LoadAchievementInfo();
var info = GlobalVars.AchievementInfo;
var outList = new List<List<object>>();
foreach (var ach in data.List.OrderBy(a => a.Id)) {
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) {
Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
continue;
}
var finishAt = "";
if (ach.Timestamp != 0) {
var ts = Convert.ToInt64(ach.Timestamp);
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 != 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 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 => {
@@ -190,9 +187,7 @@ public static class Export {
private static void ToRawJson(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
var text = JsonSerializer.Serialize(data, new JsonSerializerOptions {
WriteIndented = true
});
var text = JsonSerializer.Serialize(data, JsonOpts);
if (TryWriteToFile(path, text)) {
Console.WriteLine(App.ExportToFileSuccess, path);
}
@@ -200,13 +195,13 @@ public static class Export {
// 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)
var output = data.AchievementList
.Where(a => (uint)a.Status > 1 || a.CurrentProgress > 0)
.Select(ach => new Dictionary<string, uint> {
["id"] = ach.Id,
["status"] = (uint) ach.Status,
["current"] = ach.Current,
["timestamp"] = ach.Timestamp
["current"] = ach.CurrentProgress,
["timestamp"] = ach.FinishTimestamp
})
.ToList();
return new Dictionary<string, object> {
@@ -220,6 +215,7 @@ public static class Export {
};
}
// ReSharper disable once InconsistentNaming
private static bool CheckWinUIAppScheme(string protocol) {
return (string?)Registry.ClassesRoot.OpenSubKey(protocol)?.GetValue("") == $"URL:{protocol}";
}
@@ -227,28 +223,22 @@ public static class Export {
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) {
private static readonly List<uint> UnusedAchievement = [ 84517 ];
private static string ToDesc(this AchievementStatus status) {
return status switch {
Status.Invalid => App.StatusInvalid,
Status.Finished => App.StatusFinished,
Status.Unfinished => App.StatusUnfinished,
Status.RewardTaken => App.StatusRewardTaken,
AchievementStatus.Invalid => App.StatusInvalid,
AchievementStatus.Finished => App.StatusFinished,
AchievementStatus.Unfinished => App.StatusUnfinished,
AchievementStatus.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);
}
public static int PrintMsgAndReturnErrCode(this Win32Exception ex, string msg) {
// ReSharper disable once LocalizableElement
Console.WriteLine($"{msg}: {ex.Message}");
AppCenter.TrackCrash(ex, false);
return ex.NativeErrorCode;
}

View File

@@ -10,21 +10,21 @@ public static class Extensions {
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());
@@ -34,7 +34,7 @@ public static class Extensions {
public static string ToHex(this byte[] bytes) {
return Convert.ToHexString(bytes);
}
public static string ToBase64(this byte[] bytes) {
return Convert.ToBase64String(bytes);
}

View File

@@ -1,6 +1,7 @@
using System.Reflection;
using Proto;
namespace YaeAchievement;
namespace YaeAchievement;
// ReSharper disable InconsistentNaming
// ReSharper disable ConvertToConstant.Global
@@ -8,26 +9,29 @@ namespace YaeAchievement;
// ReSharper disable once MemberCanBePrivate.Global
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 = 41;
public const string AppVersionName = "3.1";
public const uint AppVersionCode = 234;
public const string AppVersionName = "5.2";
public const string PipeName = "YaeAchievementPipe";
public const string BucketHost = "https://cn-cd-1259389942.file.myqcloud.com";
public static AchievementInfo AchievementInfo { get; }
static GlobalVars() {
Directory.CreateDirectory(DataPath);
Directory.CreateDirectory(CachePath);
AchievementInfo = AchievementInfo.Parser.ParseFrom(Utils.GetBucketFileAsByteArray("schicksal/metadata"));
}
}

View File

@@ -7,7 +7,7 @@ using Windows.Win32.System.Threading;
namespace YaeAchievement;
public static class Injector {
public static unsafe bool CreateProcess(string path, out HANDLE hProc, out HANDLE hThread, out uint pid) {
Span<char> cmdLines = stackalloc char[1]; // "\0"
var si = new STARTUPINFOW {
@@ -45,7 +45,7 @@ public static class Injector {
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
}
}
var lpStartAddress = pLoadLibrary.CreateDelegate<LPTHREAD_START_ROUTINE>();
var lpStartAddress = (delegate* unmanaged[Stdcall]<void*, uint>)pLoadLibrary.Value; //THREAD_START_ROUTINE
var hThread = Native.CreateRemoteThread(hProc, default, 0, lpStartAddress, pBase, 0);
if (hThread.IsNull) {
var e = new Win32Exception();

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"className": "Native",
"allowMarshaling": true,
"allowMarshaling": false,
"public": true
}

View File

@@ -1,21 +1,21 @@
CreateProcess
CloseClipboard
CreateProcess
CreateRemoteThread
EmptyClipboard
GetConsoleMode
GetDC
GetDeviceCaps
GetModuleHandle
GetProcAddress
VirtualAllocEx
WriteProcessMemory
CreateRemoteThread
VirtualFreeEx
WaitForSingleObject
OpenClipboard
EmptyClipboard
GlobalLock
SetClipboardData
GlobalUnlock
CloseClipboard
GetStdHandle
GetConsoleMode
GlobalLock
GlobalUnlock
OpenClipboard
ResumeThread
SetClipboardData
SetConsoleMode
TerminateProcess
ResumeThread
GetDeviceCaps
GetDC
VirtualAllocEx
VirtualFreeEx
WaitForSingleObject
WriteProcessMemory

0
src/Outputs/.gitkeep Normal file
View File

View File

@@ -0,0 +1,111 @@
using System.Runtime.CompilerServices;
using Google.Protobuf;
using YaeAchievement.res;
namespace YaeAchievement.Parsers;
public enum AchievementStatus {
Invalid,
Unfinished,
Finished,
RewardTaken,
}
public 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 class AchievementAllDataNotify {
public List<AchievementItem> AchievementList { get; private init; } = [];
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 = new CodedInputStream(ReadRawBytes(stream, stream.ReadLength()));
try {
while ((tag = eStream.ReadTag()) != 0) {
if ((tag & 7) != 0) { // not VarInt
dict = null;
break;
}
dict[tag >> 3] = eStream.ReadUInt32();
}
if (dict != null) {
data.Add(dict);
}
} catch (InvalidProtocolBufferException) {
if (errTimes++ > 0) { // allows 1 fail on 'reward_taken_goal_id_list'
throw;
}
}
}
}
} catch (InvalidProtocolBufferException) {
// ReSharper disable once LocalizableElement
Console.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.Count > 20) { /* uwu */
(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
Console.WriteLine($"Id={iId}, Status={sId}, Total={totalId}, Current={currentId}, Timestamp={tId}");
#endif
} else {
var info = GlobalVars.AchievementInfo.PbInfo; // ...
iId = info.Id;
tId = info.FinishTimestamp;
sId = info.Status;
totalId = info.TotalProgress;
currentId = info.CurrentProgress;
if (data.Any(dict => !dict.ContainsKey(iId) || !dict.ContainsKey(sId) || !dict.ContainsKey(totalId))) {
Console.WriteLine(App.WaitMetadataUpdate);
Environment.Exit(0);
}
}
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()
};
}
[UnsafeAccessor(UnsafeAccessorKind.Method)]
private static extern byte[] ReadRawBytes(CodedInputStream stream, int size);
}

View File

@@ -1,10 +1,12 @@
using Proto;
using System.Text;
using YaeAchievement;
using YaeAchievement.AppCenterSDK;
using YaeAchievement.AppCenterSDK.Models;
using YaeAchievement.Parsers;
using YaeAchievement.res;
using static YaeAchievement.Utils;
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
TryDisableQuickEdit();
InstallExitHook();
InstallExceptionHook();
@@ -22,36 +24,27 @@ 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) {
AchievementAllDataNotify? data = null;
try {
data = AchievementAllDataNotify.ParseFrom(historyCache.Read().Content.ToByteArray());
} catch (Exception) { /* ignored */ }
if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null) {
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;
if (Console.ReadLine()?.ToUpper() is "Y" or "YES") {
Export.Choose(data);
return;
}
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;
});
}
}
StartAndWaitResult(AppConfig.GamePath, str => {
GlobalVars.UnexpectedExit = false;
var bytes = Convert.FromBase64String(str);
var list = AchievementAllDataNotify.ParseFrom(bytes);
historyCache.Write(bytes);
Export.Choose(list);
return true;
});

View File

@@ -0,0 +1,16 @@
// ReSharper disable once CheckNamespace
namespace System.Collections.Generic;
public static class Collection {
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;
}
}

View File

@@ -0,0 +1,14 @@
// ReSharper disable once CheckNamespace
namespace System.Linq;
public static class Enumerable {
public static IEnumerable<IGrouping<TKey, TKey>> GroupKeys<TKey, TValue>(
this IEnumerable<Dictionary<TKey, TValue>> source,
Func<TValue, bool> condition
) where TKey : notnull {
return source.SelectMany(dict => dict.Where(pair => condition(pair.Value)).Select(pair => pair.Key)).GroupBy(x => x);
}
}

View File

@@ -11,7 +11,6 @@ using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Console;
using Proto;
using YaeAchievement.AppCenterSDK;
using YaeAchievement.res;
namespace YaeAchievement;
@@ -31,10 +30,9 @@ public static class Utils {
public static byte[] GetBucketFileAsByteArray(string path, bool cache = true) {
try {
using var msg = new HttpRequestMessage {
Method = HttpMethod.Get,
RequestUri = new Uri($"{GlobalVars.BucketHost}/{path}")
};
using var msg = new HttpRequestMessage();
msg.Method = HttpMethod.Get;
msg.RequestUri = new Uri($"{GlobalVars.BucketHost}/{path}");
var cacheFile = new CacheFile(path);
if (cache && cacheFile.Exists()) {
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cacheFile.Read().Etag}");
@@ -64,7 +62,7 @@ public static class Utils {
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;
}
@@ -73,11 +71,11 @@ public static class Utils {
if (Native.OpenClipboard(HWND.Null))
{
Native.EmptyClipboard();
var hGlobal = (HANDLE) Marshal.AllocHGlobal((text.Length + 1) * 2);
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(hPtr);
Native.SetClipboardData(13, hGlobal);
Native.GlobalUnlock((HGLOBAL) hPtr);
Native.SetClipboardData(13, new HANDLE(hPtr));
Marshal.FreeHGlobal(hGlobal);
Native.CloseClipboard();
}
@@ -89,13 +87,13 @@ public static class Utils {
// 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) {
if (info.EnableAutoUpdate) {
Console.WriteLine(App.UpdateDownloading);
var tmpPath = Path.GetTempFileName();
File.WriteAllBytes(tmpPath, GetBucketFileAsByteArray(info.PackageLink));
@@ -162,8 +160,8 @@ public static class Utils {
public static void CheckGenshinIsRunning() {
Process.EnterDebugMode();
foreach (var process in Process.GetProcesses()) {
if (process.ProcessName is "GenshinImpact" or "YuanShen"
&& !process.HasExited
if (process.ProcessName is "GenshinImpact" or "YuanShen"
&& !process.HasExited
&& process.MainWindowHandle != nint.Zero
) {
Console.WriteLine(App.GenshinIsRunning, process.Id);
@@ -172,10 +170,10 @@ public static class Utils {
}
Process.LeaveDebugMode();
}
// ReSharper disable once InconsistentNaming
private static Process? proc;
public static void InstallExitHook() {
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
proc?.Kill();
@@ -201,33 +199,16 @@ public static class Utils {
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
if (!File.Exists(path)) return false;
var hash = File.ReadAllBytes(path).MD5Hash();
return 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 {
try {
File.Delete(GlobalVars.LibFilePath);
} catch (Exception) { /* ignored */ }
};
@@ -298,7 +279,7 @@ public static class Utils {
}
})
.Select(item => item?.GetValue("DisplayName") as string ?? string.Empty)
.Any(name => name.Contains("Microsoft Visual C++ 2022 X64 "));
.Any(name => name.Contains("Microsoft Visual C++ 2022 X64 ") || name.Contains("Microsoft Visual C++ 2015-2022 Redistributable (x64)"));
if (!installed) {
Console.WriteLine(App.VcRuntimeDownload);
var pkgPath = Path.Combine(GlobalVars.DataPath, "vc_redist.x64.exe");
@@ -312,12 +293,9 @@ public static class Utils {
}
await File.WriteAllBytesAsync(pkgPath, bytes);
Console.WriteLine(App.VcRuntimeInstalling);
using var process = new Process {
StartInfo = {
FileName = pkgPath,
Arguments = "/install /passive /norestart"
}
};
using var process = new Process();
process.StartInfo.FileName = pkgPath;
process.StartInfo.Arguments = "/install /passive /norestart";
process.Start();
await process.WaitForExitAsync();
File.Delete(pkgPath);