mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-13 01:48:13 +08:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b861712eb | ||
|
|
fb4e3f8d00 | ||
|
|
b1135542c1 | ||
|
|
7d3d0f5e14 | ||
|
|
4268b04f3c | ||
|
|
9a9d1310a1 | ||
|
|
618a9189ad | ||
|
|
f73dbdc4fe | ||
|
|
298134c063 | ||
|
|
e9ace26d69 | ||
|
|
2c15353f86 | ||
|
|
99fec63867 | ||
|
|
bf5525d2ea | ||
|
|
cf3749f887 | ||
|
|
21af4de1a6 | ||
|
|
8e0fd2d27c | ||
|
|
0348cfa365 | ||
|
|
494eda32c2 | ||
|
|
975638c1ee | ||
|
|
793ad075fe | ||
|
|
c82a10353f | ||
|
|
f737122247 | ||
|
|
520167ef85 | ||
|
|
faee6f6121 | ||
|
|
06c5468118 | ||
|
|
b7c2204f68 | ||
|
|
5dc5e646d6 | ||
|
|
9cab7e8702 | ||
|
|
1f080fe084 | ||
|
|
c8497243c0 | ||
|
|
9abdd123ee | ||
|
|
e1429289ad | ||
|
|
1f311ed987 | ||
|
|
cc346915e3 | ||
|
|
cd0f49d83d | ||
|
|
d0b7d15894 | ||
|
|
504c8a2a9a | ||
|
|
b3162052da | ||
|
|
034d999d25 | ||
|
|
45d5620e83 | ||
|
|
fa13f9c8e5 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,4 +1,4 @@
|
|||||||
lib/src/il2cpp-types.h linguist-generated=true
|
lib/src/Zydis.* linguist-generated=true
|
||||||
|
|
||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto
|
* text=auto
|
||||||
|
|||||||
8
.github/workflows/dotnet.yml
vendored
8
.github/workflows/dotnet.yml
vendored
@@ -12,11 +12,11 @@ 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: 7.0.x
|
dotnet-version: 8.0.x
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
    
|
    
|
||||||
|
|
||||||
简体中文 | [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: 不要把软件和原神主程序放一起
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
|
|
||||||
    
|
    
|
||||||
|
|
||||||
[简体中文](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
44
README_JP.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
|
||||||
|
|
||||||
|
# YaeAchievement
|
||||||
|
|
||||||
|
    
|
||||||
|
|
||||||
|
[简体中文](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: ソフトウェアを原神のディレクトリに配置しないでください。
|
||||||
|
|
||||||
@@ -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 。
|
||||||
|
|
||||||
进入网页后浏览器会自动弹出下载,同样地,将文件保存在桌面或者其它易于寻找的文件夹内。
|
进入网页后浏览器会自动弹出下载,同样地,将文件保存在桌面或者其它易于寻找的文件夹内。
|
||||||
|
|
||||||
|
|||||||
@@ -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 Here:https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-7.0.7-windows-x64-installer .
|
Click Here:https://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
75
Tutorial_JP.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
## 使用説明書
|
||||||
|
|
||||||
|
1. YaeAchievement(最新バージョン)をダウンロード:
|
||||||
|
|
||||||
|
こちらをクリック:https://github.com/HolographicHat/YaeAchievement/releases
|
||||||
|
|
||||||
|
赤枠で囲まれた「YaeAchievement.exe」という名前のファイルをクリックすると、自動的にポップアップしてダウンロードされます。このファイルをデスクトップや他の見やすいフォルダに保存することをお勧めします。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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という名前のファイルを開くと、インストールウィンドウがポップアップします。以下の図のように表示されます。
|
||||||
|
|
||||||
|

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

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

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

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

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

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

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

|
||||||
|
|
||||||
|
この時点で、左側の列のAchievementsを選択して、インポートされた実績データを表示できます。
|
||||||
|
|
||||||
|
### 各ツールの紹介については、各ツールの公式ページをご覧ください:
|
||||||
|
|
||||||
|
0. [椰羊](https://cocogoat.work/achievement)
|
||||||
|
1. [胡桃ツールボックス](https://github.com/DGP-Studio/Snap.HuTao)
|
||||||
|
2. [Paimon.moe](https://paimon.moe/achievement/)
|
||||||
|
3. [Seelie.me](https://seelie.me/achievements)
|
||||||
|
4. ~~フォームファイル `.csv`~~
|
||||||
|
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ファイル
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net7.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
@@ -21,16 +21,14 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.22.1" />
|
<PackageReference Include="Google.Protobuf" Version="3.26.1" />
|
||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.188-beta">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.53.0">
|
<PackageReference Include="Grpc.Tools" Version="2.62.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -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\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>
|
</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
@@ -1,27 +0,0 @@
|
|||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Common version parameters.
|
|
||||||
//
|
|
||||||
// Microsoft Research Detours Package, Version 4.0.1
|
|
||||||
//
|
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#define _USING_V110_SDK71_ 1
|
|
||||||
#include "winver.h"
|
|
||||||
#if 0
|
|
||||||
#include <windows.h>
|
|
||||||
#include <detours.h>
|
|
||||||
#else
|
|
||||||
#ifndef DETOURS_STRINGIFY
|
|
||||||
#define DETOURS_STRINGIFY_(x) #x
|
|
||||||
#define DETOURS_STRINGIFY(x) DETOURS_STRINGIFY_(x)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define VER_FILEFLAGSMASK 0x3fL
|
|
||||||
#define VER_FILEFLAGS 0x0L
|
|
||||||
#define VER_FILEOS 0x00040004L
|
|
||||||
#define VER_FILETYPE 0x00000002L
|
|
||||||
#define VER_FILESUBTYPE 0x00000000L
|
|
||||||
#endif
|
|
||||||
#define VER_DETOURS_BITS DETOURS_STRINGIFY(DETOURS_BITS)
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
#define CALL_ORIGIN(function, ...) \
|
|
||||||
HookManager::call(function, __func__, __VA_ARGS__)
|
|
||||||
|
|
||||||
class HookManager {
|
|
||||||
public:
|
|
||||||
template <typename Fn>
|
|
||||||
static void install(Fn func, Fn handler) {
|
|
||||||
enable(func, handler);
|
|
||||||
holderMap[reinterpret_cast<void*>(handler)] = reinterpret_cast<void*>(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Fn>
|
|
||||||
[[nodiscard]] static Fn getOrigin(Fn handler, const char* callerName = nullptr) noexcept {
|
|
||||||
if (holderMap.count(reinterpret_cast<void*>(handler)) == 0) {
|
|
||||||
printf("Origin not found for handler: %s. Maybe racing bug.", callerName == nullptr ? "<Unknown>" : callerName);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return reinterpret_cast<Fn>(holderMap[reinterpret_cast<void*>(handler)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Fn>
|
|
||||||
[[nodiscard]] static void detach(Fn handler) noexcept {
|
|
||||||
disable(handler);
|
|
||||||
holderMap.erase(reinterpret_cast<void*>(handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename RType, typename... Params>
|
|
||||||
[[nodiscard]] static RType call(RType(*handler)(Params...), const char* callerName = nullptr, Params... params) {
|
|
||||||
auto origin = getOrigin(handler, callerName);
|
|
||||||
if (origin != nullptr)
|
|
||||||
return origin(params...);
|
|
||||||
|
|
||||||
return RType();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void detachAll() noexcept {
|
|
||||||
for (const auto &[key, value] : holderMap) {
|
|
||||||
disable(key);
|
|
||||||
}
|
|
||||||
holderMap.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
inline static std::map<void*, void*> holderMap{};
|
|
||||||
|
|
||||||
template <typename Fn>
|
|
||||||
static void disable(Fn handler) {
|
|
||||||
Fn origin = getOrigin(handler);
|
|
||||||
DetourTransactionBegin();
|
|
||||||
DetourUpdateThread(GetCurrentThread());
|
|
||||||
DetourDetach(&(PVOID&)origin, handler);
|
|
||||||
DetourTransactionCommit();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Fn>
|
|
||||||
static void enable(Fn& func, Fn handler) {
|
|
||||||
DetourTransactionBegin();
|
|
||||||
DetourUpdateThread(GetCurrentThread());
|
|
||||||
DetourAttach(&(PVOID&)func, handler);
|
|
||||||
DetourTransactionCommit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
54990
lib/src/Zydis.c
Normal file
54990
lib/src/Zydis.c
Normal file
File diff suppressed because one or more lines are too long
12113
lib/src/Zydis.h
Normal file
12113
lib/src/Zydis.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnAchievementAllDataNotify(LPVOID obj, const LPVOID ntf) {
|
uint16_t __fastcall BitConverter_ToUInt16(Array<uint8_t>* val, const int startIndex)
|
||||||
const auto cos = Genshin::il2cpp_object_new(*Genshin::CodedOutputStream__TypeInfo);
|
{
|
||||||
const auto len = Genshin::CalculateSize(ntf);
|
using namespace Globals;
|
||||||
const auto buf = (ByteArray*) new uint8_t[0x20 + len] {};
|
const auto ToUInt16 = reinterpret_cast<decltype(&BitConverter_ToUInt16)>(Offset.BitConverter_ToUInt16);
|
||||||
buf->max_length = len;
|
|
||||||
Genshin::CodedOutputStreamInit(cos, buf, 0, len);
|
EnterCriticalSection(&CriticalSection);
|
||||||
Genshin::ProtoWriteTo(ntf, cos);
|
SetBreakpoint((HANDLE)-2, 0, false);
|
||||||
const auto str = base64_encode(buf->vector, len) + "\n";
|
const auto ret = ToUInt16(val, startIndex);
|
||||||
WriteFile(hPipe, str.c_str(), (DWORD) str.length(), nullptr, nullptr);
|
SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true);
|
||||||
CloseHandle(hPipe);
|
LeaveCriticalSection(&CriticalSection);
|
||||||
ExitProcess(0);
|
|
||||||
|
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 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::OnAchievementAllDataNotify, Hook::OnAchievementAllDataNotify);
|
|
||||||
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
31
lib/src/globals.h
Normal 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;
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using namespace Genshin;
|
|
||||||
|
|
||||||
// DO_APP_FUNC(CN_OFFSET, OS_OFFSET, RETURN, FUNC_NAME, (ARGS...));
|
|
||||||
|
|
||||||
DO_APP_FUNC(0x4F1EF0, 0x545600, LPVOID, il2cpp_object_new, (LPVOID t));
|
|
||||||
|
|
||||||
DO_APP_FUNC(0x052818B0, 0x0529E520, ByteArray*, RecordUserData, (int32_t nType));
|
|
||||||
|
|
||||||
DO_APP_FUNC(0x05DCBE60, 0x05DF2F40, void, OnAchievementAllDataNotify, (LPVOID obj, LPVOID ntf));
|
|
||||||
|
|
||||||
DO_APP_FUNC(0x043EC0B0, 0x043FA380, int, CalculateSize, (LPVOID obj));
|
|
||||||
|
|
||||||
DO_APP_FUNC(0x043EBFF0, 0x043FA2C0, void, ProtoWriteTo, (LPVOID obj, LPVOID output));
|
|
||||||
|
|
||||||
DO_APP_FUNC(0x06F08220, 0x06F3F280, void, CodedOutputStreamInit, (LPVOID obj, LPVOID buffer, int offset, int length));
|
|
||||||
|
|
||||||
DO_UNI_FUNC(0x105970, 0x105970, ByteArray*, UnityEngine_RecordUserData, (int32_t nType));
|
|
||||||
@@ -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", §ionSize);
|
||||||
|
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", §ionSize);
|
||||||
|
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", §ionSize);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
// LFGDACFKABF
|
|
||||||
DO_TYPEDEF(0x2965BA0, 0, CodedOutputStream);
|
|
||||||
@@ -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 {
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#include "pch.h"
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// pch.h: 这是预编译标头文件。
|
|
||||||
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
|
|
||||||
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
|
|
||||||
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
|
|
||||||
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
|
|
||||||
|
|
||||||
#ifndef PCH_H
|
|
||||||
#define PCH_H
|
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
|
|
||||||
// Windows 头文件
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
// 添加要在此处预编译的标头
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <codecvt>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <iostream>
|
|
||||||
#include <detours.h>
|
|
||||||
#include "HookManager.h"
|
|
||||||
#include "il2cpp-appdata.h"
|
|
||||||
|
|
||||||
#endif //PCH_H
|
|
||||||
257
lib/src/util.cpp
257
lib/src/util.cpp
@@ -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, ¤t, 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
|
||||||
@@ -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
9
res/App.Designer.cs
generated
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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 = 11;
|
|
||||||
uint32 current = 15;
|
|
||||||
uint32 total = 3;
|
|
||||||
uint32 id = 14;
|
|
||||||
Status status = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AchievementAllDataNotify {
|
|
||||||
repeated Achievement list = 9;
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System.Net;
|
|
||||||
using YaeAchievement.AppCenterSDK.Models;
|
|
||||||
using YaeAchievement.AppCenterSDK.Models.Serialization;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK;
|
|
||||||
|
|
||||||
public static class AppCenter {
|
|
||||||
|
|
||||||
private const string AppSecret = "648b83bf-d439-49bd-97f4-e1e506bdfe39";
|
|
||||||
private const string ApiUrl = "https://in.appcenter.ms/logs?api-version=1.0.0";
|
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
|
||||||
public static Guid? SessionID { get; private set; }
|
|
||||||
public static readonly string DeviceID;
|
|
||||||
public static readonly Device DeviceInfo;
|
|
||||||
private static List<Log> Queue;
|
|
||||||
private static readonly Lazy<HttpClient> httpClient = new(() => new HttpClient(new HttpClientHandler {
|
|
||||||
Proxy = GlobalVars.DebugProxy ? new WebProxy("http://127.0.0.1:8888") : null
|
|
||||||
}) {
|
|
||||||
DefaultRequestHeaders = {
|
|
||||||
{ "Install-ID", DeviceID },
|
|
||||||
{ "App-Secret", AppSecret }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
static AppCenter() {
|
|
||||||
Queue = new List<Log>();
|
|
||||||
DeviceID = DeviceHelper.GetDeviceID();
|
|
||||||
DeviceInfo = new Device();
|
|
||||||
var running = true;
|
|
||||||
Task.Run(() => {
|
|
||||||
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
|
|
||||||
while (running) {
|
|
||||||
Upload();
|
|
||||||
Thread.Sleep(5 * 1000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
|
|
||||||
running = false;
|
|
||||||
};
|
|
||||||
LogSerializer.AddLogType(PageLog.JsonIdentifier, typeof(PageLog));
|
|
||||||
LogSerializer.AddLogType(EventLog.JsonIdentifier, typeof(EventLog));
|
|
||||||
LogSerializer.AddLogType(HandledErrorLog.JsonIdentifier, typeof(HandledErrorLog));
|
|
||||||
LogSerializer.AddLogType(ManagedErrorLog.JsonIdentifier, typeof(ManagedErrorLog));
|
|
||||||
LogSerializer.AddLogType(StartServiceLog.JsonIdentifier, typeof(StartServiceLog));
|
|
||||||
LogSerializer.AddLogType(StartSessionLog.JsonIdentifier, typeof(StartSessionLog));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper restore InconsistentNaming
|
|
||||||
public static void Upload() {
|
|
||||||
if (Queue.Count == 0) return;
|
|
||||||
var uploadStatus = "";
|
|
||||||
do {
|
|
||||||
Queue = Queue.Select(log => {
|
|
||||||
log.Status = LogStatus.Uploading;
|
|
||||||
return log;
|
|
||||||
}).ToList();
|
|
||||||
using var uploadContent = new StringContent(Queue.ToJson());
|
|
||||||
try {
|
|
||||||
using var response = httpClient.Value.PostAsync(ApiUrl, uploadContent).Result;
|
|
||||||
var result = response.Content.ReadAsStringAsync().Result;
|
|
||||||
uploadStatus = JsonConvert.DeserializeObject<LogUploadResult>(result)!.Status;
|
|
||||||
} catch (Exception) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
} while (uploadStatus != "Success");
|
|
||||||
Queue.RemoveAll(log => log.Status == LogStatus.Uploading);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Init() {
|
|
||||||
new StartServiceLog("Analytics", "Crashes").Enqueue();
|
|
||||||
SessionID = Guid.NewGuid();
|
|
||||||
new StartSessionLog().Enqueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void TrackCrash(Exception exception, bool fatal = true) {
|
|
||||||
new ManagedErrorLog(exception, fatal).Enqueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Enqueue(this Log log) {
|
|
||||||
Queue.Add(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ToJson(this IEnumerable<Log> queue) {
|
|
||||||
return LogSerializer.Serialize(new LogContainer(queue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,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
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK;
|
|
||||||
|
|
||||||
public static class ErrorLogHelper {
|
|
||||||
public static MException CreateModelExceptionAndBinaries(Exception exception) {
|
|
||||||
var modelException = new MException {
|
|
||||||
Type = exception.GetType().ToString(),
|
|
||||||
Message = exception.Message,
|
|
||||||
StackTrace = exception.StackTrace
|
|
||||||
};
|
|
||||||
if (exception is AggregateException aggregateException) {
|
|
||||||
if (aggregateException.InnerExceptions.Count != 0) {
|
|
||||||
modelException.InnerExceptions = new List<MException>();
|
|
||||||
foreach (var innerException in aggregateException.InnerExceptions) {
|
|
||||||
modelException.InnerExceptions.Add(CreateModelExceptionAndBinaries(innerException));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (exception.InnerException != null) {
|
|
||||||
modelException.InnerExceptions ??= new List<MException>();
|
|
||||||
modelException.InnerExceptions.Add(CreateModelExceptionAndBinaries(exception.InnerException));
|
|
||||||
}
|
|
||||||
return modelException;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using System.Reflection;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
public class Device {
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "sdkName")]
|
|
||||||
public string SdkName { get; set; } = "appcenter.wpf.netcore";
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "sdkVersion")]
|
|
||||||
public string SdkVersion { get; set; } = "4.5.0";
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "osName")]
|
|
||||||
public string OsName { get; set; } = "WINDOWS";
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "osVersion")]
|
|
||||||
public string OsVersion { get; set; } = DeviceHelper.GetSystemVersion();
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "osBuild")]
|
|
||||||
public string OsBuild { get; set; } = $"{DeviceHelper.GetSystemVersion()}.{DeviceHelper.GetSystemBuild()}";
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "model")]
|
|
||||||
public string? Model { get; set; } = DeviceHelper.GetModel();
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "oemName")]
|
|
||||||
public string? OemName { get; set; } = DeviceHelper.GetOem();
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "screenSize")]
|
|
||||||
public string ScreenSize { get; set; } = DeviceHelper.GetScreenSize();
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "carrierCountry")]
|
|
||||||
public string Country { get; set; } = DeviceHelper.GetCountry() ?? "CN";
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "locale")]
|
|
||||||
public string Locale { get; set; } = CultureInfo.CurrentCulture.Name;
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "timeZoneOffset")]
|
|
||||||
public int TimeZoneOffset { get; set; } = (int) TimeZoneInfo.Local.BaseUtcOffset.TotalMinutes;
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "appVersion")]
|
|
||||||
public string AppVersion { get; set; } = GlobalVars.AppVersionName;
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "appBuild")]
|
|
||||||
public string AppBuild { get; set; } = GlobalVars.AppVersionCode.ToString();
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "appNamespace")]
|
|
||||||
public string AppNamespace { get; set; } = Assembly.GetEntryAssembly()?.EntryPoint?.DeclaringType?.Namespace ?? string.Empty;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
[JsonObject(JsonIdentifier)]
|
|
||||||
public class EventLog : LogWithProperties {
|
|
||||||
|
|
||||||
public const string JsonIdentifier = "event";
|
|
||||||
|
|
||||||
public EventLog(string name) {
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "id")]
|
|
||||||
private Guid Id { get; set; } = Guid.NewGuid();
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "name")]
|
|
||||||
private string Name { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
[JsonObject(JsonIdentifier)]
|
|
||||||
public class HandledErrorLog : LogWithProperties {
|
|
||||||
|
|
||||||
public const string JsonIdentifier = "handledError";
|
|
||||||
|
|
||||||
public HandledErrorLog(MException exception) {
|
|
||||||
Id = Guid.NewGuid();
|
|
||||||
Exception = exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "id")]
|
|
||||||
public Guid? Id { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "exception")]
|
|
||||||
public MException Exception { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
public abstract class Log {
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "sid")]
|
|
||||||
private Guid? Session { get; set; } = AppCenter.SessionID;
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "timestamp")]
|
|
||||||
private DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "device")]
|
|
||||||
private Device Device { get; set; } = AppCenter.DeviceInfo;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
internal LogStatus Status = LogStatus.Pending;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LogStatus {
|
|
||||||
Pending, Uploading, Uploaded
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
public class LogContainer {
|
|
||||||
|
|
||||||
public LogContainer(IEnumerable<Log> logs) {
|
|
||||||
Logs = logs;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "logs")]
|
|
||||||
public IEnumerable<Log> Logs { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
public class LogUploadResult {
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "status")]
|
|
||||||
public string Status { get; set; } = null!;
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "validDiagnosticsIds")]
|
|
||||||
public Guid[] ValidDiagnosticsIds { get; set; } = Array.Empty<Guid>();
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "throttledDiagnosticsIds")]
|
|
||||||
public Guid[] ThrottledDiagnosticsIds { get; set; } = Array.Empty<Guid>();
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "correlationId")]
|
|
||||||
public Guid CorrelationId { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
public class LogWithProperties : Log {
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "properties")]
|
|
||||||
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
public class MException {
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "type")]
|
|
||||||
public string Type { get; set; } = "UnknownType";
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "message")]
|
|
||||||
public string? Message { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "stackTrace")]
|
|
||||||
public string? StackTrace { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "frames")]
|
|
||||||
public IList<StackFrame>? Frames { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "innerExceptions")]
|
|
||||||
public IList<MException>? InnerExceptions { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
[JsonObject(JsonIdentifier)]
|
|
||||||
public class ManagedErrorLog : Log {
|
|
||||||
|
|
||||||
public const string JsonIdentifier = "managedError";
|
|
||||||
|
|
||||||
public ManagedErrorLog(
|
|
||||||
Exception exception,
|
|
||||||
bool fatal = true
|
|
||||||
) {
|
|
||||||
var p = Process.GetCurrentProcess();
|
|
||||||
Id = Guid.NewGuid();
|
|
||||||
Fatal = fatal;
|
|
||||||
UserId = AppCenter.DeviceID;
|
|
||||||
ProcessId = p.Id;
|
|
||||||
Exception = ErrorLogHelper.CreateModelExceptionAndBinaries(exception);
|
|
||||||
ProcessName = p.ProcessName;
|
|
||||||
Architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
|
|
||||||
AppLaunchTimestamp = p.StartTime.ToUniversalTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "id")]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "userId")]
|
|
||||||
public string? UserId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "processId")]
|
|
||||||
public int ProcessId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "processName")]
|
|
||||||
public string ProcessName { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "fatal")]
|
|
||||||
public bool Fatal { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "appLaunchTimestamp")]
|
|
||||||
public DateTime? AppLaunchTimestamp { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "architecture")]
|
|
||||||
public string? Architecture { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "exception")]
|
|
||||||
public MException Exception { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
[JsonObject(JsonIdentifier)]
|
|
||||||
public class PageLog : LogWithProperties {
|
|
||||||
|
|
||||||
public const string JsonIdentifier = "page";
|
|
||||||
|
|
||||||
public PageLog(string name) {
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "name")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models.Serialization;
|
|
||||||
|
|
||||||
#pragma warning disable CS8604, CS8765
|
|
||||||
public class LogJsonConverter : JsonConverter {
|
|
||||||
|
|
||||||
private readonly Dictionary<string, Type> _logTypes = new ();
|
|
||||||
private readonly object _jsonConverterLock = new ();
|
|
||||||
private static readonly JsonSerializerSettings SerializationSettings;
|
|
||||||
|
|
||||||
static LogJsonConverter() {
|
|
||||||
SerializationSettings = new JsonSerializerSettings {
|
|
||||||
Formatting = Formatting.None,
|
|
||||||
NullValueHandling = NullValueHandling.Ignore,
|
|
||||||
DateFormatHandling = DateFormatHandling.IsoDateFormat,
|
|
||||||
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
|
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddLogType(string typeName, Type type) {
|
|
||||||
lock (_jsonConverterLock) {
|
|
||||||
_logTypes[typeName] = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanConvert(Type objectType) {
|
|
||||||
return typeof(Log).IsAssignableFrom(objectType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override object? ReadJson(JsonReader reader, Type t, object o, JsonSerializer s) {
|
|
||||||
Type logType;
|
|
||||||
var jsonObject = JObject.Load(reader);
|
|
||||||
var typeName = jsonObject.GetValue("type")?.ToString();
|
|
||||||
lock (_jsonConverterLock) {
|
|
||||||
if (typeName == null || !_logTypes.ContainsKey(typeName)) {
|
|
||||||
throw new JsonReaderException("Could not identify type of log");
|
|
||||||
}
|
|
||||||
logType = _logTypes[typeName];
|
|
||||||
jsonObject.Remove("type");
|
|
||||||
}
|
|
||||||
return jsonObject.ToObject(logType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
|
||||||
var info = value.GetType().GetTypeInfo();
|
|
||||||
if (info.GetCustomAttribute(typeof(JsonObjectAttribute)) is not JsonObjectAttribute attribute) {
|
|
||||||
throw new JsonWriterException("Log type is missing JsonObjectAttribute");
|
|
||||||
}
|
|
||||||
var jsonObject = JObject.FromObject(value, JsonSerializer.CreateDefault(SerializationSettings));
|
|
||||||
jsonObject.Add("type", JToken.FromObject(attribute.Id));
|
|
||||||
writer.WriteRawValue(jsonObject.ToString(Formatting.None));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models.Serialization;
|
|
||||||
|
|
||||||
#pragma warning disable CS8604, CS8765, CS8603
|
|
||||||
public static class LogSerializer {
|
|
||||||
|
|
||||||
private static readonly JsonSerializerSettings SerializationSettings;
|
|
||||||
private static readonly LogJsonConverter Converter = new ();
|
|
||||||
|
|
||||||
static LogSerializer() {
|
|
||||||
SerializationSettings = new JsonSerializerSettings {
|
|
||||||
Formatting = Formatting.None,
|
|
||||||
NullValueHandling = NullValueHandling.Ignore,
|
|
||||||
DateFormatHandling = DateFormatHandling.IsoDateFormat,
|
|
||||||
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
|
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
|
|
||||||
Converters = { Converter }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void AddLogType(string typeName, Type type) {
|
|
||||||
Converter.AddLogType(typeName, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Serialize(LogContainer logContainer) {
|
|
||||||
return JsonConvert.SerializeObject(logContainer, SerializationSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Serialize(Log log) {
|
|
||||||
return JsonConvert.SerializeObject(log, SerializationSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LogContainer? DeserializeLogs(string json) {
|
|
||||||
return JsonConvert.DeserializeObject<LogContainer>(json, SerializationSettings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
public class StackFrame {
|
|
||||||
|
|
||||||
public StackFrame(string address, string code, string className, string methodName, int? lineNumber, string fileName) {
|
|
||||||
Address = address;
|
|
||||||
Code = code;
|
|
||||||
ClassName = className;
|
|
||||||
MethodName = methodName;
|
|
||||||
LineNumber = lineNumber;
|
|
||||||
FileName = fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "address")]
|
|
||||||
public string Address { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "code")]
|
|
||||||
public string Code { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "className")]
|
|
||||||
public string ClassName { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "methodName")]
|
|
||||||
public string MethodName { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "lineNumber")]
|
|
||||||
public int? LineNumber { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "fileName")]
|
|
||||||
public string FileName { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
[JsonObject(JsonIdentifier)]
|
|
||||||
public class StartServiceLog : Log {
|
|
||||||
|
|
||||||
public const string JsonIdentifier = "startService";
|
|
||||||
|
|
||||||
public StartServiceLog(params string[] services) {
|
|
||||||
Services = services;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "services")]
|
|
||||||
public string[] Services { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace YaeAchievement.AppCenterSDK.Models;
|
|
||||||
|
|
||||||
[JsonObject(JsonIdentifier)]
|
|
||||||
public class StartSessionLog : Log {
|
|
||||||
public const string JsonIdentifier = "startSession";
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ using YaeAchievement.res;
|
|||||||
|
|
||||||
namespace YaeAchievement;
|
namespace YaeAchievement;
|
||||||
|
|
||||||
public static class AppConfig {
|
public static partial class AppConfig {
|
||||||
|
|
||||||
public static string GamePath { get; private set; } = null!;
|
public static string GamePath { get; private set; } = null!;
|
||||||
|
|
||||||
@@ -52,11 +52,15 @@ public static class AppConfig {
|
|||||||
try {
|
try {
|
||||||
File.Delete(copiedLogFilePath);
|
File.Delete(copiedLogFilePath);
|
||||||
} catch (Exception) { /* ignore */}
|
} catch (Exception) { /* ignore */}
|
||||||
var matchResult = Regex.Match(content, @"(?m).:/.+(GenshinImpact_Data|YuanShen_Data)");
|
var matchResult = GamePathRegex().Match(content);
|
||||||
if (!matchResult.Success) {
|
if (!matchResult.Success) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var entryName = matchResult.Groups["1"].Value.Replace("_Data", ".exe");
|
var entryName = matchResult.Groups["1"].Value.Replace("_Data", ".exe");
|
||||||
return Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
|
return Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"(?m).:/.+(GenshinImpact_Data|YuanShen_Data)", RegexOptions.IgnoreCase)]
|
||||||
|
private static partial Regex GamePathRegex();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,13 @@ 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;
|
private CacheItem? _content;
|
||||||
|
|
||||||
public DateTime LastWriteTime => Exists() ? File.GetLastWriteTimeUtc(_cacheName) : DateTime.UnixEpoch;
|
public DateTime LastWriteTime => Exists() ? File.GetLastWriteTimeUtc(_cacheName) : DateTime.UnixEpoch;
|
||||||
|
|
||||||
public CacheFile(string identifier) {
|
|
||||||
_cacheName = Path.Combine(GlobalVars.CachePath, $"{identifier.MD5Hash()[..16]}.miko");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Exists() => File.Exists(_cacheName);
|
public bool Exists() => File.Exists(_cacheName);
|
||||||
|
|
||||||
public CacheItem Read() {
|
public CacheItem Read() {
|
||||||
|
|||||||
102
src/Export.cs
102
src/Export.cs
@@ -5,10 +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.AppCenterSDK;
|
|
||||||
using YaeAchievement.res;
|
using YaeAchievement.res;
|
||||||
using static Proto.Achievement.Types;
|
|
||||||
|
|
||||||
namespace YaeAchievement;
|
namespace YaeAchievement;
|
||||||
|
|
||||||
@@ -16,6 +14,10 @@ public static class Export {
|
|||||||
|
|
||||||
public static uint ExportTo { get; set; } = uint.MaxValue;
|
public static uint ExportTo { get; set; } = uint.MaxValue;
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions JsonOpts = new () {
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
|
||||||
public static void Choose(AchievementAllDataNotify data) {
|
public static void Choose(AchievementAllDataNotify data) {
|
||||||
if (ExportTo == uint.MaxValue) {
|
if (ExportTo == uint.MaxValue) {
|
||||||
Console.Write(App.ExportChoose);
|
Console.Write(App.ExportChoose);
|
||||||
@@ -40,16 +42,15 @@ public static class Export {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class CocogoatResponse {
|
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) {
|
private static void ToCocogoat(AchievementAllDataNotify data) {
|
||||||
var result = JsonSerializer.Serialize(ExportToUIAFApp(data));
|
var result = JsonSerializer.Serialize(ExportToUIAFApp(data));
|
||||||
using var request = new HttpRequestMessage {
|
using var request = new HttpRequestMessage();
|
||||||
Method = HttpMethod.Post,
|
request.Method = HttpMethod.Post;
|
||||||
RequestUri = new Uri($"https://77.cocogoat.cn/v1/memo?source={App.AllAchievement}"),
|
request.RequestUri = new Uri($"https://77.cocogoat.cn/v1/memo?source={App.AllAchievement}");
|
||||||
Content = new StringContent(result, Encoding.UTF8, "application/json")
|
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
|
||||||
};
|
|
||||||
using var response = Utils.CHttpClient.Send(request);
|
using var response = Utils.CHttpClient.Send(request);
|
||||||
if (response.StatusCode != HttpStatusCode.Created) {
|
if (response.StatusCode != HttpStatusCode.Created) {
|
||||||
Console.WriteLine(App.ExportToCocogoatFail);
|
Console.WriteLine(App.ExportToCocogoatFail);
|
||||||
@@ -68,11 +69,10 @@ public static class Export {
|
|||||||
{ "key", id },
|
{ "key", id },
|
||||||
{ "data", ExportToUIAFApp(data) }
|
{ "data", ExportToUIAFApp(data) }
|
||||||
});
|
});
|
||||||
using var request = new HttpRequestMessage {
|
using var request = new HttpRequestMessage();
|
||||||
Method = HttpMethod.Post,
|
request.Method = HttpMethod.Post;
|
||||||
RequestUri = new Uri("https://api.qyinter.com/achievementRedis"),
|
request.RequestUri = new Uri("https://api.qyinter.com/achievementRedis");
|
||||||
Content = new StringContent(result, Encoding.UTF8, "application/json")
|
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
|
||||||
};
|
|
||||||
using var response = Utils.CHttpClient.Send(request);
|
using var response = Utils.CHttpClient.Send(request);
|
||||||
Console.WriteLine(App.ExportToWxApp1Success, id);
|
Console.WriteLine(App.ExportToWxApp1Success, id);
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ public static class Export {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void ToTeyvatGuide(AchievementAllDataNotify data) {
|
private static void ToTeyvatGuide(AchievementAllDataNotify data) {
|
||||||
if (Process.GetProcessesByName("TeyvatGuide").Any()) {
|
if (Process.GetProcessesByName("TeyvatGuide").Length != 0) {
|
||||||
Utils.CopyToClipboard(JsonSerializer.Serialize(ExportToUIAFApp(data)));
|
Utils.CopyToClipboard(JsonSerializer.Serialize(ExportToUIAFApp(data)));
|
||||||
Utils.ShellOpen("teyvatguide://import_uigf?app=YaeAchievement");
|
Utils.ShellOpen("teyvatguide://import_uigf?app=YaeAchievement");
|
||||||
Console.WriteLine(App.ExportToTauriSuccess);
|
Console.WriteLine(App.ExportToTauriSuccess);
|
||||||
@@ -119,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))) {
|
||||||
@@ -141,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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -157,24 +152,26 @@ 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
|
||||||
outList.Add(new List<object> {
|
? ach.CurrentProgress == 0 ? ach.TotalProgress : ach.CurrentProgress
|
||||||
|
: ach.CurrentProgress;
|
||||||
|
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,状态,特辑,名称,描述,当前进度,目标进度,完成时间" };
|
||||||
output.AddRange(outList.OrderBy(v => v[2]).Select(item => {
|
output.AddRange(outList.OrderBy(v => v[2]).Select(item => {
|
||||||
@@ -190,9 +187,7 @@ public static class Export {
|
|||||||
|
|
||||||
private static void ToRawJson(AchievementAllDataNotify data) {
|
private static void ToRawJson(AchievementAllDataNotify data) {
|
||||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
|
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
|
||||||
var text = JsonSerializer.Serialize(data, new JsonSerializerOptions {
|
var text = JsonSerializer.Serialize(data, JsonOpts);
|
||||||
WriteIndented = true
|
|
||||||
});
|
|
||||||
if (TryWriteToFile(path, text)) {
|
if (TryWriteToFile(path, text)) {
|
||||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||||
}
|
}
|
||||||
@@ -200,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> {
|
||||||
@@ -220,6 +215,7 @@ public static class Export {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
private static bool CheckWinUIAppScheme(string protocol) {
|
private static bool CheckWinUIAppScheme(string protocol) {
|
||||||
return (string?)Registry.ClassesRoot.OpenSubKey(protocol)?.GetValue("") == $"URL:{protocol}";
|
return (string?)Registry.ClassesRoot.OpenSubKey(protocol)?.GetValue("") == $"URL:{protocol}";
|
||||||
}
|
}
|
||||||
@@ -228,27 +224,21 @@ public static class Export {
|
|||||||
return string.Join(separator, list);
|
return string.Join(separator, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly List<uint> UnusedAchievement = new() { 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}");
|
||||||
AppCenter.TrackCrash(ex, false);
|
|
||||||
return ex.NativeErrorCode;
|
return ex.NativeErrorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 = 43;
|
public const uint AppVersionCode = 234;
|
||||||
public const string AppVersionName = "3.3";
|
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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
src/Outputs/.gitkeep
Normal file
0
src/Outputs/.gitkeep
Normal file
111
src/Parsers/AchievementAllDataNotify.cs
Normal file
111
src/Parsers/AchievementAllDataNotify.cs
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
using Proto;
|
using System.Text;
|
||||||
using YaeAchievement;
|
using YaeAchievement;
|
||||||
using YaeAchievement.AppCenterSDK;
|
using YaeAchievement.Parsers;
|
||||||
using YaeAchievement.AppCenterSDK.Models;
|
|
||||||
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();
|
||||||
@@ -22,36 +24,27 @@ AppConfig.Load(args.GetOrNull(0) ?? "auto");
|
|||||||
Export.ExportTo = ToUIntOrNull(args.GetOrNull(1)) ?? uint.MaxValue;
|
Export.ExportTo = ToUIntOrNull(args.GetOrNull(1)) ?? uint.MaxValue;
|
||||||
|
|
||||||
CheckUpdate(ToBooleanOrFalse(args.GetOrNull(2)));
|
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");
|
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);
|
Console.WriteLine(App.UsePreviousData);
|
||||||
usePreviousData = Console.ReadLine() == "yes";
|
if (Console.ReadLine()?.ToUpper() is "Y" or "YES") {
|
||||||
}
|
Export.Choose(data);
|
||||||
Export:
|
return;
|
||||||
if(usePreviousData) {
|
|
||||||
AchievementAllDataNotify data;
|
|
||||||
try {
|
|
||||||
data = AchievementAllDataNotify.Parser.ParseFrom(historyCache.Read().Content);
|
|
||||||
} catch (Exception) {
|
|
||||||
usePreviousData = false;
|
|
||||||
goto Export;
|
|
||||||
}
|
}
|
||||||
Export.Choose(data);
|
|
||||||
} else {
|
|
||||||
StartAndWaitResult(AppConfig.GamePath, str => {
|
|
||||||
GlobalVars.UnexpectedExit = false;
|
|
||||||
var data = Convert.FromBase64String(str);
|
|
||||||
var list = AchievementAllDataNotify.Parser.ParseFrom(data);
|
|
||||||
historyCache.Write(data);
|
|
||||||
Export.Choose(list);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|||||||
16
src/Utilities/Extensions/Collection.cs
Normal file
16
src/Utilities/Extensions/Collection.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
14
src/Utilities/Extensions/Enumerable.cs
Normal file
14
src/Utilities/Extensions/Enumerable.cs
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
44
src/Utils.cs
44
src/Utils.cs
@@ -11,7 +11,6 @@ using Windows.Win32;
|
|||||||
using Windows.Win32.Foundation;
|
using Windows.Win32.Foundation;
|
||||||
using Windows.Win32.System.Console;
|
using Windows.Win32.System.Console;
|
||||||
using Proto;
|
using Proto;
|
||||||
using YaeAchievement.AppCenterSDK;
|
|
||||||
using YaeAchievement.res;
|
using YaeAchievement.res;
|
||||||
|
|
||||||
namespace YaeAchievement;
|
namespace YaeAchievement;
|
||||||
@@ -31,10 +30,9 @@ public static class Utils {
|
|||||||
|
|
||||||
public static byte[] GetBucketFileAsByteArray(string path, bool cache = true) {
|
public static byte[] GetBucketFileAsByteArray(string path, bool cache = true) {
|
||||||
try {
|
try {
|
||||||
using var msg = new HttpRequestMessage {
|
using var msg = new HttpRequestMessage();
|
||||||
Method = HttpMethod.Get,
|
msg.Method = HttpMethod.Get;
|
||||||
RequestUri = new Uri($"{GlobalVars.BucketHost}/{path}")
|
msg.RequestUri = new Uri($"{GlobalVars.BucketHost}/{path}");
|
||||||
};
|
|
||||||
var cacheFile = new CacheFile(path);
|
var cacheFile = new CacheFile(path);
|
||||||
if (cache && cacheFile.Exists()) {
|
if (cache && cacheFile.Exists()) {
|
||||||
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cacheFile.Read().Etag}");
|
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cacheFile.Read().Etag}");
|
||||||
@@ -73,11 +71,11 @@ public static class Utils {
|
|||||||
if (Native.OpenClipboard(HWND.Null))
|
if (Native.OpenClipboard(HWND.Null))
|
||||||
{
|
{
|
||||||
Native.EmptyClipboard();
|
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);
|
var hPtr = (nint) Native.GlobalLock(hGlobal);
|
||||||
Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length);
|
Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length);
|
||||||
Native.GlobalUnlock(hPtr);
|
Native.GlobalUnlock((HGLOBAL) hPtr);
|
||||||
Native.SetClipboardData(13, hGlobal);
|
Native.SetClipboardData(13, new HANDLE(hPtr));
|
||||||
Marshal.FreeHGlobal(hGlobal);
|
Marshal.FreeHGlobal(hGlobal);
|
||||||
Native.CloseClipboard();
|
Native.CloseClipboard();
|
||||||
}
|
}
|
||||||
@@ -95,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));
|
||||||
@@ -201,31 +199,14 @@ public static class Utils {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Console.WriteLine(ex.ToString());
|
Console.WriteLine(ex.ToString());
|
||||||
Console.WriteLine(App.UploadError);
|
|
||||||
AppCenter.TrackCrash((Exception) e.ExceptionObject);
|
|
||||||
AppCenter.Upload();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Environment.Exit(-1);
|
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
|
// 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);
|
||||||
@@ -298,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");
|
||||||
@@ -312,12 +293,9 @@ public static class Utils {
|
|||||||
}
|
}
|
||||||
await File.WriteAllBytesAsync(pkgPath, bytes);
|
await File.WriteAllBytesAsync(pkgPath, bytes);
|
||||||
Console.WriteLine(App.VcRuntimeInstalling);
|
Console.WriteLine(App.VcRuntimeInstalling);
|
||||||
using var process = new Process {
|
using var process = new Process();
|
||||||
StartInfo = {
|
process.StartInfo.FileName = pkgPath;
|
||||||
FileName = pkgPath,
|
process.StartInfo.Arguments = "/install /passive /norestart";
|
||||||
Arguments = "/install /passive /norestart"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
process.Start();
|
process.Start();
|
||||||
await process.WaitForExitAsync();
|
await process.WaitForExitAsync();
|
||||||
File.Delete(pkgPath);
|
File.Delete(pkgPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user