27 Commits
3.6.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
44 changed files with 68326 additions and 1830 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 # Auto detect text files and perform LF normalization
* text=auto * text=auto

View File

@@ -12,9 +12,9 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v4
with: with:
dotnet-version: 8.0.x dotnet-version: 8.0.x
- name: Restore dependencies - name: Restore dependencies
@@ -24,7 +24,7 @@ jobs:
- name: Publish - name: Publish
run: dotnet publish --property:OutputPath=.\publish\ run: dotnet publish --property:OutputPath=.\publish\
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3.1.0 uses: actions/upload-artifact@v4
with: with:
name: Artifacts name: Artifacts
path: publish path: publish

View File

@@ -1,10 +1,10 @@
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico"> <div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
# YaeAchievement # 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) ![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) 简体中文 | [English](README_EN.md) | [日本語](README_JP.md)
</div> </div>
- 支持导出所有类别的成就 - 支持导出所有类别的成就
@@ -36,7 +36,7 @@
## 常见问题 ## 常见问题
0. Q: 打不开 0. Q: 打不开
A: 安装 [.NET Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-7.0.3-windows-x64-installer) A: 安装 [.NET Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.4-windows-x64-installer)
1. Q: 原神启动时报错: 数据异常(31-4302) 1. Q: 原神启动时报错: 数据异常(31-4302)
A: 不要把软件和原神主程序放一起 A: 不要把软件和原神主程序放一起

View File

@@ -4,10 +4,7 @@
![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) ![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.md) | English | [日本語](README_JP.md)
**I18n support currently in [snapshot version](https://github.com/HolographicHat/YaeAchievement/actions/)**
**Next release: 2022/09/28 (Genshin 3.1)**
</div> </div>
@@ -39,7 +36,7 @@
## Frequently asked questions ## Frequently asked questions
0. Q: Unable to start 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) 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.安装启动软件所需文件(若已安装该运行时可忽略此步骤) 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 。
进入网页后浏览器会自动弹出下载,同样地,将文件保存在桌面或者其它易于寻找的文件夹内。 进入网页后浏览器会自动弹出下载,同样地,将文件保存在桌面或者其它易于寻找的文件夹内。

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) 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. Or `winget install Microsoft.DotNet.Runtime.7` if you use Windows 11 or have Winget installed.

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

@@ -20,9 +20,9 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType> <ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset> <PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet> <CharacterSet>MultiByte</CharacterSet>
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType> <ConfigurationType>DynamicLibrary</ConfigurationType>
@@ -30,7 +30,6 @@
<PlatformToolset>v143</PlatformToolset> <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet> <CharacterSet>MultiByte</CharacterSet>
<EnableASAN>false</EnableASAN>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings"> <ImportGroup Label="ExtensionSettings">
@@ -45,34 +44,28 @@
</ImportGroup> </ImportGroup>
<PropertyGroup Label="UserMacros" /> <PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir> <OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir>
<IntDir>build\$(Platform)\$(Configuration)\</IntDir> <IntDir>build\$(Platform)\$(Configuration)\</IntDir>
<TargetName>YaeLib</TargetName> <TargetName>YaeLib</TargetName>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir> <OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir>
<IntDir>build\$(Platform)\$(Configuration)\</IntDir> <IntDir>build\$(Platform)\$(Configuration)\</IntDir>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>false</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader> <LanguageStandard>stdcpp20</LanguageStandard>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<AdditionalIncludeDirectories>$(ProjectDir)lib\detours\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Windows</SubSystem> <SubSystem>NotSet</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>$(ProjectDir)lib\detours\;$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>detours-x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
<PostBuildEvent> <PostBuildEvent>
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0</Command> <Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0</Command>
@@ -83,49 +76,36 @@
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck> <PreprocessorDefinitions>_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>false</ConformanceMode>
<ConformanceMode>true</ConformanceMode> <LanguageStandard>stdcpp20</LanguageStandard>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<DebugInformationFormat>None</DebugInformationFormat>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>$(ProjectDir)lib\detours\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <SDLCheck>true</SDLCheck>
<LanguageStandard_C>stdc11</LanguageStandard_C>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Windows</SubSystem> <SubSystem>NotSet</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>$(ProjectDir)lib\detours\;$(OutDir)</AdditionalLibraryDirectories>
<AdditionalDependencies>detours-x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
<PostBuildEvent> <PostBuildEvent>
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net8.0-windows\win-x64\YaeAchievementLib.dll /y</Command> <Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net8.0-windows\win-x64\YaeAchievementLib.dll /y</Command>
</PostBuildEvent> </PostBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="src\HookManager.h" /> <ClInclude Include="src\globals.h" />
<ClInclude Include="src\il2cpp-appdata.h" />
<ClInclude Include="src\il2cpp-functions.h" />
<ClInclude Include="src\il2cpp-types-ptr.h" />
<ClInclude Include="src\il2cpp-types.h" /> <ClInclude Include="src\il2cpp-types.h" />
<ClInclude Include="src\il2cpp-init.h" /> <ClInclude Include="src\il2cpp-init.h" />
<ClInclude Include="src\pch.h" />
<ClInclude Include="src\util.h" /> <ClInclude Include="src\util.h" />
<ClInclude Include="src\Zydis.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="src\dllmain.cpp" /> <ClCompile Include="src\dllmain.cpp" />
<ClCompile Include="src\il2cpp-init.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\util.cpp" />
<ClCompile Include="src\Zydis.c" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <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,81 +1,186 @@
// ReSharper disable CppCStyleCast // ReSharper disable CppClangTidyCertErr33C
// ReSharper disable CppInconsistentNaming #include <Windows.h>
// ReSharper disable CppClangTidyModernizeUseStdPrint #include <string>
// ReSharper disable CppClangTidyClangDiagnosticCastAlign #include <future>
// ReSharper disable CppClangTidyHicppMultiwayPathsCovered #include <TlHelp32.h>
// ReSharper disable CppDefaultCaseNotHandledInSwitchStatement
// ReSharper disable CppClangTidyClangDiagnosticCastFunctionTypeStrict
#include "pch.h" #include "globals.h"
#include "util.h" #include "util.h"
#include "il2cpp-init.h" #include "il2cpp-init.h"
#include "il2cpp-types.h"
using Genshin::ByteArray; CRITICAL_SECTION CriticalSection;
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index = 0);
HWND unityWnd = nullptr;
HANDLE hPipe = nullptr;
std::string checksum;
namespace Hook { namespace Hook {
ByteArray* UnityEngine_RecordUserData(const INT type) {
if (type == 0) {
const auto arr = new ByteArray {};
const auto len = checksum.length();
arr->max_length = len;
memcpy(&arr->vector[0], checksum.data(), len);
return arr;
}
return new ByteArray {};
}
uint16_t BitConverter_ToUInt16(ByteArray* val, const int startIndex) { uint16_t __fastcall BitConverter_ToUInt16(Array<uint8_t>* val, const int startIndex)
const auto ret = CALL_ORIGIN(BitConverter_ToUInt16, val, startIndex); {
if (ret == 0xAB89 && ReadMapped<UINT16>(val->vector, 2) == 1655) { using namespace Globals;
const auto headLength = ReadMapped<UINT16>(val->vector, 4); const auto ToUInt16 = reinterpret_cast<decltype(&BitConverter_ToUInt16)>(Offset.BitConverter_ToUInt16);
const auto dataLength = ReadMapped<UINT32>(val->vector, 6);
const auto cStr = base64_encode(val->vector + 10 + headLength, dataLength) + "\n"; EnterCriticalSection(&CriticalSection);
WriteFile(hPipe, cStr.c_str(), (DWORD) cStr.length(), nullptr, nullptr); SetBreakpoint((HANDLE)-2, 0, false);
CloseHandle(hPipe); 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); ExitProcess(0);
} }
return ret; return ret;
} }
} }
void Run(HMODULE* phModule) { LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep)
//AllocConsole(); {
//freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); using namespace Globals;
while ((unityWnd = FindMainWindowByPID(GetCurrentProcessId())) == nullptr) { const auto exceptionRecord = ep->ExceptionRecord;
Sleep(1000); 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(); return EXCEPTION_CONTINUE_SEARCH;
InitIL2CPP(); }
for (int i = 0; i < 3; i++) {
const auto result = Genshin::RecordUserData(i); void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index)
checksum += string(reinterpret_cast<char*>(&result->vector[0]), result->max_length); {
using namespace Globals;
if (index > 3) {
return;
} }
HookManager::install(Genshin::BitConverter_ToUInt16, Hook::BitConverter_ToUInt16);
HookManager::install(Genshin::UnityEngine_RecordUserData, Hook::UnityEngine_RecordUserData); if (!BaseAddress || Offset.BitConverter_ToUInt16 <= BaseAddress) {
hPipe = CreateFile(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); // not initialized yet
if (hPipe == INVALID_HANDLE_VALUE) { return;
Win32ErrorDialog(1001); }
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); 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 // DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReasonForCall, LPVOID lpReserved) { BOOL __stdcall DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved)
switch (ulReasonForCall) { {
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Run, new HMODULE(hModule), 0, NULL); if (fdwReason == DLL_PROCESS_ATTACH)
break; {
case DLL_THREAD_ATTACH: if (const auto hThread = CreateThread(nullptr, 0, ThreadProc, hInstance, 0, nullptr)) {
case DLL_THREAD_DETACH: CloseHandle(hThread);
case DLL_PROCESS_DETACH: }
break;
} }
return TRUE; 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,19 +0,0 @@
// ReSharper disable CppClangTidyBugproneMacroParentheses
#pragma once
#include "il2cpp-types.h"
// Application-specific functions
#define DO_APP_FUNC(ca, oa, r, n, p) extern r (*n) p
#define DO_UNI_FUNC(ca, oa, r, n, p) extern r (*n) p
namespace Genshin {
#include "il2cpp-functions.h"
}
#undef DO_UNI_FUNC
#undef DO_APP_FUNC
#define DO_TYPEDEF(ca, oa, n) extern n##__Class **n##__TypeInfo
namespace Genshin {
#include "il2cpp-types-ptr.h"
}
#undef DO_TYPEDEF

View File

@@ -1,11 +0,0 @@
using namespace Genshin;
// DO_APP_FUNC(CN_OFFSET, OS_OFFSET, RETURN, FUNC_NAME, (ARGS...));
DO_APP_FUNC(0x57A390, 0x579D00, LPVOID, il2cpp_object_new, (LPVOID t));
DO_APP_FUNC(0x07315A30, 0x0710F580, ByteArray*, RecordUserData, (int32_t nType));
DO_APP_FUNC(0x0D257150, 0x0D244830, uint16_t, BitConverter_ToUInt16, (ByteArray* val, int startIndex));
DO_UNI_FUNC(0x10CF80, 0x10CF80, ByteArray*, UnityEngine_RecordUserData, (int32_t nType));

View File

@@ -1,38 +1,386 @@
// ReSharper disable CppClangTidyBugproneMacroParentheses #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 "pch.h" #include "globals.h"
#include "Zydis.h"
#include "util.h"
#include "il2cpp-init.h" 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
#define DO_UNI_FUNC(ca, oa, r, n, p) r (*n) p
namespace Genshin {
#include "il2cpp-functions.h"
} }
#undef DO_UNI_FUNC
#undef DO_APP_FUNC
#define DO_TYPEDEF(ca, oa, n) n##__Class **n##__TypeInfo void InitIL2CPP()
namespace Genshin { {
#include "il2cpp-types-ptr.h" std::string buffer;
} buffer.resize(MAX_PATH);
#undef DO_TYPEDEF ZeroMemory(buffer.data(), MAX_PATH);
const auto pathLength = GetModuleFileNameA(nullptr, buffer.data(), MAX_PATH);
using std::string; if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
void InitIL2CPP() { buffer.resize(pathLength);
TCHAR szFileName[MAX_PATH]; ZeroMemory(buffer.data(), pathLength);
GetModuleFileName(NULL, szFileName, MAX_PATH); GetModuleFileNameA(nullptr, buffer.data(), pathLength);
auto isCN = strstr(szFileName, "YuanShen.exe");//string(szFileName).contains(); }
auto hBase = GetModuleHandle("UserAssembly.dll"); buffer.shrink_to_fit();
auto bAddr = (UINT64)hBase;
auto cAddr = (UINT64)GetModuleHandle("UnityPlayer.dll"); using namespace Globals;
#define DO_APP_FUNC(ca, oa, r, n, p) n = (r (*) p)(bAddr + (isCN ? ca : oa)) IsCNREL = buffer.find("YuanShen.exe") != std::string::npos;
#define DO_UNI_FUNC(ca, oa, r, n, p) n = (r (*) p)(cAddr + (isCN ? ca : oa)) BaseAddress = (uintptr_t)GetModuleHandleA(nullptr);
#include "il2cpp-functions.h"
#undef DO_UNI_FUNC std::future<void> resolveFuncFuture = std::async(std::launch::async, [] {
#undef DO_APP_FUNC if (Offset.BitConverter_ToUInt16 != 0) {
#define DO_TYPEDEF(ca, oa, n) n##__TypeInfo = (n##__Class **)(bAddr + (isCN ? ca : oa)) Offset.BitConverter_ToUInt16 += BaseAddress;
#include "il2cpp-types-ptr.h" }
#undef DO_TYPEDEF 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,45 +1,38 @@
// ReSharper disable CppClangTidyClangDiagnosticReservedIdentifier
// ReSharper disable CppClangTidyBugproneReservedIdentifier
#pragma once #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; Array() = delete;
typedef uintptr_t il2cpp_array_size_t;
typedef int32_t il2cpp_array_lower_bound_t;
typedef struct Il2CppObject { T* data() {
union { return vector;
void* klass; }
void* vtable; };
} Il2CppClass;
void* monitor;
} Il2CppObject;
typedef struct Il2CppString { static_assert(alignof(Array<uint8_t>) == 8, "Array alignment is incorrect");
Il2CppObject object; static_assert(offsetof(Array<uint8_t>, vector) == 32, "vector offset is incorrect");
int32_t length;
Il2CppChar chars[32];
} Il2CppString;
typedef struct Il2CppArrayBounds { #pragma pack(push, 1)
il2cpp_array_size_t length; struct PacketMeta
il2cpp_array_lower_bound_t lower_bound; {
} Il2CppArrayBounds; uint16_t HeadMagic;
uint16_t CmdId;
uint16_t HeaderLength;
uint32_t DataLength;
uint8_t Data[1];
};
#pragma pack(pop)
#pragma endregion static_assert(offsetof(PacketMeta, CmdId) == 2, "CmdId offset is incorrect");
static_assert(offsetof(PacketMeta, HeaderLength) == 4, "HeadLength offset is incorrect");
namespace Genshin { static_assert(offsetof(PacketMeta, DataLength) == 6, "DataLength offset is incorrect");
static_assert(offsetof(PacketMeta, Data) == 10, "Data offset is incorrect");
struct ByteArray {
void* klass;
void* monitor;
Il2CppArrayBounds* bounds;
il2cpp_array_size_t max_length;
uint8_t vector[32];
};
struct CodedOutputStream__Class {
};
}

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 "util.h"
#include "globals.h"
VOID DisableVMProtect() { #ifdef _DEBUG
DWORD oldProtect = 0; #pragma runtime_checks("", off)
auto ntdll = GetModuleHandleA("ntdll.dll"); #endif
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
#pragma region FindMainWindowByPID #pragma region FindMainWindowByPID
struct HandleData { namespace
DWORD pid; {
HWND hwnd; struct HandleData {
}; DWORD pid;
HWND hwnd;
};
BOOL IsMainWindow(HWND handle) { bool IsMainWindow(HWND handle) {
return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle) == TRUE; return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle) == TRUE;
} }
BOOL IsUnityWindow(HWND handle) { bool IsUnityWindow(HWND handle) {
TCHAR name[256]; char szName[256]{};
GetClassName(handle, name, 256); GetClassNameA(handle, szName, 256);
return _strcmpi(name, "UnityWndClass") == 0; return _stricmp(szName, "UnityWndClass") == 0;
} }
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) { BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) {
HandleData& data = *(HandleData*)lParam; HandleData& data = *(HandleData*)lParam;
DWORD pid = 0; DWORD pid = 0;
GetWindowThreadProcessId(handle, &pid); GetWindowThreadProcessId(handle, &pid);
if (data.pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle)) if (data.pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle))
return TRUE; return TRUE;
data.hwnd = handle; data.hwnd = handle;
return FALSE; 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 #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) { namespace Util
std::string ret; {
int i = 0; HWND FindMainWindowByPID(DWORD pid)
BYTE char_array_3[3]; {
BYTE char_array_4[4]; HandleData data = { pid, 0 };
while (bufLen--) { EnumWindows(EnumWindowsCallback, (LPARAM)&data);
char_array_3[i++] = *buf++; return data.hwnd;
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); std::string Base64Encode(BYTE const* buf, unsigned int bufLen)
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; std::string ret;
for (i = 0; (i < 4); i++) int i = 0;
ret += base64_chars[char_array_4[i]]; BYTE char_array_3[3];
i = 0; BYTE char_array_4[4];
} while (bufLen--) {
} char_array_3[i++] = *buf++;
if (i) { if (i == 3) {
int j; char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
for (j = i; j < 3; j++) char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_3[j] = '\0'; char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[3] = char_array_3[2] & 0x3f;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); for (i = 0; (i < 4); i++)
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); ret += base64_chars[char_array_4[i]];
char_array_4[3] = char_array_3[2] & 0x3f; i = 0;
for (j = 0; j < i + 1; j++) }
ret += base64_chars[char_array_4[j]]; }
while (i++ < 3) if (i) {
ret += '='; int j;
} for (j = i; j < 3; j++)
return ret; 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,26 +1,18 @@
// ReSharper disable CppClangTidyClangDiagnosticLanguageExtensionToken
#pragma once #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(); void ErrorDialog(LPCSTR title, LPCSTR msg);
bool IsLittleEndian(); void ErrorDialog(LPCSTR msg);
HWND FindMainWindowByPID(DWORD pid); void Win32ErrorDialog(DWORD code, DWORD winerrcode);
string ToString(Il2CppString* str, UINT codePage = CP_ACP);
std::string base64_encode(BYTE const* buf, unsigned int bufLen);
#define ErrorDialogT(title, msg) MessageBox(unityWnd, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL); uintptr_t PatternScan(uintptr_t start, uintptr_t end, const char* pattern);
#define ErrorDialog(msg) ErrorDialogT("YaeAchievement", msg) std::vector<uintptr_t> PatternScanAll(uintptr_t start, uintptr_t end, const char* pattern);
#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;
} }

9
res/App.Designer.cs generated
View File

@@ -393,5 +393,14 @@ namespace YaeAchievement.res {
return ResourceManager.GetString("VcRuntimeInstalling", resourceCulture); 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

@@ -137,4 +137,7 @@ Input a number (0-8): </value>
<data name="ExportToTauriFail" xml:space="preserve"> <data name="ExportToTauriFail" xml:space="preserve">
<value>Please launch/update Teyvat Guide and retry.</value> <value>Please launch/update Teyvat Guide and retry.</value>
</data> </data>
<data name="WaitMetadataUpdate" xml:space="preserve">
<value>Please update game and retry.</value>
</data>
</root> </root>

View File

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

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 = 13;
uint32 current = 6;
uint32 total = 9;
uint32 id = 1;
Status status = 2;
}
message AchievementAllDataNotify {
repeated Achievement list = 11;
}

View File

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

View File

@@ -3,13 +3,13 @@ syntax = "proto3";
option csharp_namespace = "Proto"; option csharp_namespace = "Proto";
message UpdateInfo { message UpdateInfo {
uint32 versionCode = 1; uint32 version_code = 1;
string versionName = 2; string version_name = 2;
string description = 3; string description = 3;
string packageLink = 4; string package_link = 4;
bool forceUpdate = 5; bool force_update = 5;
bool enableLibDownload = 6; bool enable_lib_download = 6;
bool enableAutoDownload = 7; bool enable_auto_update = 7;
string currentCNGameHash = 8; string current_cn_hash = 8;
string currentOSGameHash = 9; string current_os_hash = 9;
} }

View File

@@ -5,9 +5,8 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.Win32; using Microsoft.Win32;
using Proto; using YaeAchievement.Parsers;
using YaeAchievement.res; using YaeAchievement.res;
using static Proto.Achievement.Types;
namespace YaeAchievement; namespace YaeAchievement;
@@ -120,19 +119,14 @@ public static class Export {
} }
private static void ToPaimon(AchievementAllDataNotify data) { private static void ToPaimon(AchievementAllDataNotify data) {
var info = LoadAchievementInfo(); var info = GlobalVars.AchievementInfo.Items.ToDictionary(pair => pair.Key, pair => pair.Value.Group);
var output = new Dictionary<uint, Dictionary<uint, bool>>();
foreach (var ach in data.List.Where(a => a.Status is Status.Finished or Status.RewardTaken)) {
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
continue;
}
var map = output.GetValueOrDefault(achInfo.Group, new Dictionary<uint, bool>());
map[ach.Id == 81222 ? 81219 : ach.Id] = true;
output[achInfo.Group] = map;
}
var final = new Dictionary<string, Dictionary<uint, Dictionary<uint, bool>>> { 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"); var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json");
if (TryWriteToFile(path, JsonSerializer.Serialize(final))) { if (TryWriteToFile(path, JsonSerializer.Serialize(final))) {
@@ -142,8 +136,8 @@ public static class Export {
private static void ToSeelie(AchievementAllDataNotify data) { private static void ToSeelie(AchievementAllDataNotify data) {
var output = new Dictionary<uint, Dictionary<string, bool>>(); var output = new Dictionary<uint, Dictionary<string, bool>>();
foreach (var ach in data.List.Where(a => a.Status is Status.Finished or Status.RewardTaken)) { foreach (var ach in data.AchievementList.Where(a => a.Status is AchievementStatus.Finished or AchievementStatus.RewardTaken)) {
output[ach.Id == 81222 ? 81219 : ach.Id] = new Dictionary<string, bool> { output[ach.Id] = new Dictionary<string, bool> {
["done"] = true ["done"] = true
}; };
} }
@@ -158,23 +152,25 @@ public static class Export {
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
private static void ToCSV(AchievementAllDataNotify data) { private static void ToCSV(AchievementAllDataNotify data) {
var info = LoadAchievementInfo(); var info = GlobalVars.AchievementInfo;
var outList = new List<List<object>>(); 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 (UnusedAchievement.Contains(ach.Id)) continue;
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) { if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
Console.WriteLine($@"Unable to find {ach.Id} in metadata."); Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
continue; continue;
} }
var finishAt = ""; var finishAt = "";
if (ach.Timestamp != 0) { if (ach.FinishTimestamp != 0) {
var ts = Convert.ToInt64(ach.Timestamp); var ts = Convert.ToInt64(ach.FinishTimestamp);
finishAt = DateTimeOffset.FromUnixTimeSeconds(ts).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"); 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; var current = ach.Status != AchievementStatus.Unfinished
? ach.CurrentProgress == 0 ? ach.TotalProgress : ach.CurrentProgress
: ach.CurrentProgress;
outList.Add([ outList.Add([
ach.Id, ach.Status.ToDesc(), achInfo.Group, achInfo.Name, ach.Id, ach.Status.ToDesc(), achInfo.Group, achInfo.Name,
achInfo.Description, current, ach.Total, finishAt achInfo.Description, current, ach.TotalProgress, finishAt
]); ]);
} }
var output = new List<string> { "ID,状态,特辑,名称,描述,当前进度,目标进度,完成时间" }; var output = new List<string> { "ID,状态,特辑,名称,描述,当前进度,目标进度,完成时间" };
@@ -199,13 +195,13 @@ public static class Export {
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
private static Dictionary<string, object> ExportToUIAFApp(AchievementAllDataNotify data) { private static Dictionary<string, object> ExportToUIAFApp(AchievementAllDataNotify data) {
var output = data.List var output = data.AchievementList
.Where(a => (uint)a.Status > 1 || a.Current > 0) .Where(a => (uint)a.Status > 1 || a.CurrentProgress > 0)
.Select(ach => new Dictionary<string, uint> { .Select(ach => new Dictionary<string, uint> {
["id"] = ach.Id, ["id"] = ach.Id,
["status"] = (uint) ach.Status, ["status"] = (uint) ach.Status,
["current"] = ach.Current, ["current"] = ach.CurrentProgress,
["timestamp"] = ach.Timestamp ["timestamp"] = ach.FinishTimestamp
}) })
.ToList(); .ToList();
return new Dictionary<string, object> { return new Dictionary<string, object> {
@@ -230,21 +226,16 @@ public static class Export {
private static readonly List<uint> UnusedAchievement = [ 84517 ]; private static readonly List<uint> UnusedAchievement = [ 84517 ];
private static string ToDesc(this Status status) { private static string ToDesc(this AchievementStatus status) {
return status switch { return status switch {
Status.Invalid => App.StatusInvalid, AchievementStatus.Invalid => App.StatusInvalid,
Status.Finished => App.StatusFinished, AchievementStatus.Finished => App.StatusFinished,
Status.Unfinished => App.StatusUnfinished, AchievementStatus.Unfinished => App.StatusUnfinished,
Status.RewardTaken => App.StatusRewardTaken, AchievementStatus.RewardTaken => App.StatusRewardTaken,
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null) _ => 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) { public static int PrintMsgAndReturnErrCode(this Win32Exception ex, string msg) {
// ReSharper disable once LocalizableElement // ReSharper disable once LocalizableElement
Console.WriteLine($"{msg}: {ex.Message}"); Console.WriteLine($"{msg}: {ex.Message}");

View File

@@ -1,4 +1,5 @@
using System.Reflection; using System.Reflection;
using Proto;
namespace YaeAchievement; namespace YaeAchievement;
@@ -20,14 +21,17 @@ public static class GlobalVars {
public static readonly string CachePath = Path.Combine(DataPath, "cache"); public static readonly string CachePath = Path.Combine(DataPath, "cache");
public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll"); public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll");
public const uint AppVersionCode = 46; public const uint AppVersionCode = 234;
public const string AppVersionName = "3.6"; public const string AppVersionName = "5.2";
public const string PipeName = "YaeAchievementPipe"; public const string PipeName = "YaeAchievementPipe";
public const string BucketHost = "https://cn-cd-1259389942.file.myqcloud.com"; public const string BucketHost = "https://cn-cd-1259389942.file.myqcloud.com";
public static AchievementInfo AchievementInfo { get; }
static GlobalVars() { static GlobalVars() {
Directory.CreateDirectory(DataPath); Directory.CreateDirectory(DataPath);
Directory.CreateDirectory(CachePath); Directory.CreateDirectory(CachePath);
AchievementInfo = AchievementInfo.Parser.ParseFrom(Utils.GetBucketFileAsByteArray("schicksal/metadata"));
} }
} }

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,8 +1,12 @@
using Proto; using System.Text;
using YaeAchievement; using YaeAchievement;
using YaeAchievement.Parsers;
using YaeAchievement.res; using YaeAchievement.res;
using static YaeAchievement.Utils; using static YaeAchievement.Utils;
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
TryDisableQuickEdit(); TryDisableQuickEdit();
InstallExitHook(); InstallExitHook();
InstallExceptionHook(); InstallExceptionHook();
@@ -25,7 +29,7 @@ var historyCache = new CacheFile("ExportData");
AchievementAllDataNotify? data = null; AchievementAllDataNotify? data = null;
try { try {
data = AchievementAllDataNotify.Parser.ParseFrom(historyCache.Read().Content); data = AchievementAllDataNotify.ParseFrom(historyCache.Read().Content.ToByteArray());
} catch (Exception) { /* ignored */ } } catch (Exception) { /* ignored */ }
if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null) { if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null) {
@@ -39,7 +43,7 @@ if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null)
StartAndWaitResult(AppConfig.GamePath, str => { StartAndWaitResult(AppConfig.GamePath, str => {
GlobalVars.UnexpectedExit = false; GlobalVars.UnexpectedExit = false;
var bytes = Convert.FromBase64String(str); var bytes = Convert.FromBase64String(str);
var list = AchievementAllDataNotify.Parser.ParseFrom(bytes); var list = AchievementAllDataNotify.ParseFrom(bytes);
historyCache.Write(bytes); historyCache.Write(bytes);
Export.Choose(list); Export.Choose(list);
return true; 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

@@ -93,7 +93,7 @@ public static class Utils {
if (GlobalVars.AppVersionCode < info.VersionCode) { if (GlobalVars.AppVersionCode < info.VersionCode) {
Console.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, info.VersionName); Console.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, info.VersionName);
Console.WriteLine(App.UpdateDescription, info.Description); Console.WriteLine(App.UpdateDescription, info.Description);
if (info.EnableAutoDownload) { if (info.EnableAutoUpdate) {
Console.WriteLine(App.UpdateDownloading); Console.WriteLine(App.UpdateDownloading);
var tmpPath = Path.GetTempFileName(); var tmpPath = Path.GetTempFileName();
File.WriteAllBytes(tmpPath, GetBucketFileAsByteArray(info.PackageLink)); File.WriteAllBytes(tmpPath, GetBucketFileAsByteArray(info.PackageLink));
@@ -205,22 +205,8 @@ public static class Utils {
}; };
} }
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 // ReSharper disable once UnusedMethodReturnValue.Global
public static Thread StartAndWaitResult(string exePath, Func<string, bool> onReceive) { public static Thread StartAndWaitResult(string exePath, Func<string, bool> onReceive) {
if (!CheckGenshinIsLatestVersion(exePath)) {
Console.WriteLine(App.GenshinHashError);
Environment.Exit(0);
}
AppDomain.CurrentDomain.ProcessExit += (_, _) => { AppDomain.CurrentDomain.ProcessExit += (_, _) => {
try { try {
File.Delete(GlobalVars.LibFilePath); File.Delete(GlobalVars.LibFilePath);
@@ -293,7 +279,7 @@ public static class Utils {
} }
}) })
.Select(item => item?.GetValue("DisplayName") as string ?? string.Empty) .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) { if (!installed) {
Console.WriteLine(App.VcRuntimeDownload); Console.WriteLine(App.VcRuntimeDownload);
var pkgPath = Path.Combine(GlobalVars.DataPath, "vc_redist.x64.exe"); var pkgPath = Path.Combine(GlobalVars.DataPath, "vc_redist.x64.exe");