165 Commits
2.5.0 ... 5.6.0

Author SHA1 Message Date
HolographicHat
4a1da61904 bump version 2025-06-18 14:44:37 +08:00
HolographicHat
9eb8955fda optimize GetGamePath 2025-06-11 01:47:34 +08:00
HolographicHat
62c08f54ab fix 2025-06-08 00:44:32 +08:00
HolographicHat
645fe38c65 fix #138 2025-06-07 20:35:24 +08:00
HolographicHat
8f9a26a237 fix #2471 2025-06-05 21:56:03 +08:00
HolographicHat
8648b3a308 bump version 2025-06-04 03:16:09 +08:00
HolographicHat
829553b3a6 sentry 2025-06-04 03:14:07 +08:00
HolographicHat
87898eedfa fix 2025-06-04 02:01:00 +08:00
HolographicHat
a10b491886 prefer using field id from config 2025-05-30 01:23:39 +08:00
HolographicHat
e3e7107b14 Update YaeAchievementLib.nuspec 2025-05-08 10:11:05 +08:00
HolographicHat
3231746aa5 Merge pull request #135 from 34736384/master 2025-05-07 15:44:48 +08:00
34736384
5c9cdd46d2 update psn pattern 2025-05-07 13:34:52 +08:00
HolographicHat
881a4bc725 [no ci] update docs 2025-04-13 03:49:58 +08:00
HolographicHat
d08ac17d10 bump version 2025-04-13 03:14:07 +08:00
HolographicHat
980a47bf43 spectre console 2025-04-11 23:57:07 +08:00
HolographicHat
0e7be25b23 allow copy cocogoat import url 2025-04-09 01:10:21 +08:00
HolographicHat
4b052cf6c7 refactor CacheFile 2025-04-09 01:09:47 +08:00
HolographicHat
4ff2b454f3 refactor updater and injector 2025-04-08 23:15:53 +08:00
HolographicHat
f718687b3f [no ci] update feedback group id 2025-04-06 15:15:36 +08:00
HolographicHat
9915b9246e disable CETCompat 2025-04-05 21:47:59 +08:00
HolographicHat
e25bc9aeba Merge pull request #133 from BTMuli/master
✏️ Typo uigf to uiaf
2025-04-01 15:50:28 +08:00
目棃
08dd6eca76 ✏️ Typo uigf to uiaf 2025-04-01 15:21:04 +08:00
HolographicHat
acd2ccd803 Merge pull request #132 from Lightczx/master
Inject side check ignore case
2025-03-28 11:15:43 +08:00
DismissedLight
13fda3ba12 bump nuspec version 2025-03-28 10:59:25 +08:00
DismissedLight
1c821620bf Inject side check ignore case 2025-03-28 10:58:39 +08:00
HolographicHat
8a39ad0a77 [skip ci] update readme 2025-03-27 16:24:00 +08:00
HolographicHat
9d42141258 bump lib version 2025-03-26 16:22:59 +08:00
HolographicHat
2a91656b2e [skip ci] update ci 2025-03-26 16:19:42 +08:00
HolographicHat
602cf06a8b bump version 2025-03-26 16:03:26 +08:00
HolographicHat
c87b8c976d update 2025-03-26 16:02:48 +08:00
HolographicHat
099c22e9e7 Merge pull request #130 from 34736384/master
update resolver
2025-03-26 15:00:05 +08:00
REL
f1fe6e1f9e update resolver 2025-03-26 02:45:33 -04:00
HolographicHat
fb0a46480f [no ci] bump version 2025-02-12 17:35:57 +08:00
HolographicHat
7b413384c3 Merge pull request #129 from 34736384/master
fix player store id resolver
2025-02-12 17:27:43 +08:00
REL
e251497edc fix player store id resolver 2025-02-12 04:24:43 -05:00
HolographicHat
b1f5d9a2b2 [skip ci] Merge pull request #128 from qhy040404/nuget 2025-01-27 21:14:55 +08:00
qhy040404
36a5b9a0e7 fix ci 2025-01-27 21:13:19 +08:00
HolographicHat
b0b70d585a [skip_ci] Merge pull request #127 from qhy040404/nuget 2025-01-27 21:04:42 +08:00
qhy040404
b9ab326d72 workflow_dispatch 2025-01-27 20:02:21 +08:00
qhy040404
5f210d4236 publish lib to nuget 2025-01-27 19:36:08 +08:00
HolographicHat
826063da60 [skip ci] Merge pull request #126 from qhy040404/patch 2025-01-26 13:43:33 +08:00
qhy040404
08697941d3 print cocogoat url 2025-01-26 12:02:39 +08:00
HolographicHat
e7d21865c7 [skip ci] support virtual item 2025-01-20 17:32:47 +08:00
HolographicHat
35773f49f4 [skip ci] Merge pull request #125 from Lightczx/master 2025-01-20 14:45:18 +08:00
DismissedLight
c4856c821f Fix inject side check 2025-01-20 11:08:04 +08:00
HolographicHat
6f1168e61e [skip ci] Merge pull request #124 from qhy040404/windowhook 2025-01-17 22:13:43 +08:00
qhy040404
d1bd0c7d7b fix 2025-01-17 21:55:50 +08:00
qhy040404
95ad187015 add pin to avoid unload 2025-01-17 21:40:59 +08:00
HolographicHat
6c0264ce5a Merge pull request #123 from qhy040404/windowhook 2025-01-17 17:13:59 +08:00
qhy040404
957c8b98e4 add window hook entry 2025-01-17 17:11:25 +08:00
REL
01a3f41323 [skip ci] fix 2025-01-11 19:51:13 +08:00
HolographicHat
32ceae074e [skip ci] export player store data 2025-01-11 19:24:42 +08:00
HolographicHat
2e2be07161 update docs 2025-01-10 16:23:36 +08:00
HolographicHat
832c82f44e Merge pull request #122 from 34736384/master 2025-01-10 14:42:02 +08:00
REL
43b38df986 fix bugs 2025-01-09 01:05:46 -05:00
REL
6e1c8f275f refactor 2025-01-07 23:56:16 -05:00
REL
e65f046520 added PlayerStoreNotify 2025-01-07 23:36:27 -05:00
HolographicHat
4025729677 ci 2025-01-08 09:32:51 +08:00
HolographicHat
07050c1c3d move sources 2025-01-08 09:30:17 +08:00
REL
e56a6228aa use cpp23 and 'modernize' 2025-01-07 20:16:14 -05:00
HolographicHat
66b29b1374 static linking 2024-11-30 14:13:51 +08:00
HolographicHat
247c401a5b aot 2024-11-30 13:52:39 +08:00
HolographicHat
cf9d601b27 rin 2024-11-30 11:56:41 +08:00
HolographicHat
5abb9e2934 Merge pull request #118 from Mikachu2333/master 2024-11-22 13:07:50 +08:00
HolographicHat
1b861712eb fix #119 #120 2024-11-21 21:35:41 +08:00
LinkChou
d62377ad96 prepare for next version 2024-11-21 10:31:53 +08:00
HolographicHat
fb4e3f8d00 fix 2024-11-21 08:11:26 +08:00
HolographicHat
b1135542c1 5.2 2024-11-21 07:33:07 +08:00
HolographicHat
7d3d0f5e14 auto identify field ids 2024-11-21 07:31:21 +08:00
HolographicHat
4268b04f3c ignore zydis 2024-10-15 20:41:04 +08:00
HolographicHat
9a9d1310a1 Merge pull request #115 from 34736384/master 2024-10-15 20:37:59 +08:00
REL
618a9189ad use sse 2024-10-15 07:32:14 -04:00
REL
f73dbdc4fe switch to amalgamated zydis 2024-10-14 20:24:32 -04:00
REL
298134c063 added dynamic resolving of CmdId and ToUInt16 2024-10-14 05:54:32 -04:00
REL
e9ace26d69 refactor 2024-10-10 05:06:57 -04:00
REL
2c15353f86 remove detours 2024-10-09 08:32:36 -04:00
REL
99fec63867 remove il2cpp bloat 2024-10-09 08:14:41 -04:00
HolographicHat
bf5525d2ea 5.1 2024-10-09 16:03:25 +08:00
HolographicHat
cf3749f887 fix #114 2024-09-12 23:47:01 +08:00
HolographicHat
21af4de1a6 5.0 2024-08-29 18:24:21 +08:00
HolographicHat
8e0fd2d27c fix #111 2024-08-05 18:11:51 +08:00
HolographicHat
0348cfa365 Merge pull request #107 from eltociear/add-japanese-readme-tutorial 2024-07-31 07:58:19 +08:00
Ikko Eltociear Ashimine
494eda32c2 docs: add Japanese README and tutorial 2024-07-23 00:47:25 +09:00
HolographicHat
975638c1ee Merge pull request #104 from canmengxian/master 2024-07-17 18:33:19 +08:00
HolographicHat
793ad075fe 4.8 2024-07-17 18:12:24 +08:00
残梦
c82a10353f Update Utils.cs 2024-06-23 23:16:13 +08:00
HolographicHat
f737122247 4.7 2024-06-06 02:07:56 +08:00
HolographicHat
520167ef85 fix #82 #101 2024-04-29 14:38:13 +08:00
HolographicHat
faee6f6121 Update README_EN.md 2024-04-25 21:50:15 +08:00
HolographicHat
06c5468118 update readme 2024-04-25 21:42:14 +08:00
HolographicHat
b7c2204f68 Update CI 2024-04-25 02:52:46 +08:00
HolographicHat
5dc5e646d6 fix #87 2024-04-25 02:45:30 +08:00
HolographicHat
9cab7e8702 4.6 2024-04-24 23:26:24 +08:00
HolographicHat
1f080fe084 Merge pull request #98 from Anong0u0/patch 2024-04-22 14:27:29 +08:00
Anong0u0
c8497243c0 Fix GamePathRegex match to case insensitive 2024-04-22 13:44:20 +08:00
HolographicHat
9abdd123ee #93
remove appcenter
2024-04-02 15:06:05 +08:00
HolographicHat
e1429289ad refactor appcenter 2024-03-15 00:08:34 +08:00
HolographicHat
1f311ed987 4.5 2024-03-14 23:55:36 +08:00
HolographicHat
cc346915e3 Merge pull request #85 from BTMuli/master
️ 延长刷新间隔,Y/YES均可使用旧数据(大小写不敏感)
2024-02-06 23:27:16 +08:00
HolographicHat
cd0f49d83d shorten code 2024-02-06 23:19:41 +08:00
目棃
d0b7d15894 Merge branch 'master' into master 2024-02-06 22:54:51 +08:00
目棃
504c8a2a9a 👔 默认不使用旧数据 2024-02-06 22:46:37 +08:00
HolographicHat
b3162052da refactor 2024-02-06 21:31:14 +08:00
目棃
034d999d25 ️ 延长刷新间隔,回车/Y也能使用旧数据了 2024-02-02 19:30:03 +08:00
HolographicHat
45d5620e83 4.4 2024-01-31 12:46:31 +08:00
HolographicHat
fa13f9c8e5 fix 2023-12-25 00:21:48 +08:00
HolographicHat
2210a97d61 4.3 2023-12-24 23:59:24 +08:00
HolographicHat
feb7ac44da 4.2 2023-11-09 21:39:05 +08:00
HolographicHat
3924129560 Merge pull request #74 from Lightczx/master
Disable marshalling
2023-10-30 14:04:24 +08:00
Lightczx
4f7f0cdfd2 disable marshalling 2023-10-30 13:51:01 +08:00
HolographicHat
cf0753c676 Merge pull request #73 from BTMuli/master
🐛 修复 TeyvatGuide 链接错误
2023-10-16 13:42:22 +08:00
BTMuli
0b895d47ca 🐛 修复 TeyvatGuide 链接错误 2023-10-16 12:39:32 +08:00
HolographicHat
78d2722e20 Merge pull request #72 from BTMuli/master
更新文档
2023-10-15 01:16:19 +08:00
BTMuli
385c673323 🚨 revert,删除多余空格 2023-10-15 00:31:18 +08:00
BTMuli
50beb2cce7 🚨 修复 CI 报错 2023-10-15 00:18:39 +08:00
BTMuli
324a4153e0 📝 删除混入其中的表格文件 2023-10-14 23:56:24 +08:00
BTMuli
3de459aceb 📝 更新部分信息 2023-10-14 23:54:49 +08:00
BTMuli
295bb89177 📝 更新导出说明 2023-10-14 23:50:31 +08:00
HolographicHat
baaf4e8227 rollback 2023-10-13 21:22:54 +08:00
HolographicHat
f41fe6fb12 4.1
* #71
2023-09-28 18:48:00 +08:00
HolographicHat
78bda3f49c Merge pull request #70 from BTMuli/master
#67
2023-09-23 00:00:30 +08:00
BTMuli
a10dc22461 🎨 修正检测方式 2023-09-22 23:39:37 +08:00
HolographicHat
74dda750ef Merge pull request #69 from Masterain98/master
Update GitLab CI configuration
2023-09-22 21:15:21 +08:00
Masterain
099270ad29 Update .gitlab-ci.yml 2023-09-21 23:42:47 -07:00
Masterain
fe5b2c0c12 Update .gitlab-ci.yml 2023-09-21 23:35:17 -07:00
HolographicHat
ed5d99745c Merge pull request #68 from Masterain98/master
Create GitLab CI Config File
2023-09-21 23:46:58 +08:00
Masterain
7175cd7427 Create .gitlab-ci.yml 2023-09-21 00:01:13 -07:00
HolographicHat
73747bcce5 #67 2023-09-19 22:28:58 +08:00
HolographicHat
b12c3209d7 fix ci 2023-08-27 11:39:40 +08:00
HolographicHat
5805070627 4.0.1 (fix #64) 2023-08-24 00:16:44 +08:00
HolographicHat
811daba164 4.0 2023-08-16 20:00:06 +08:00
HolographicHat
849f379ca5 3.8 2023-07-06 14:31:55 +08:00
HolographicHat
29c57cbc1e Merge pull request #62 from Lightczx/master
Improve export experience for Snap Hutao
2023-06-29 21:30:58 +08:00
DismissedLight
8fdb10898a Improve export experience for Snap Hutao 2023-06-29 14:38:19 +08:00
HolographicHat
f4f1f14651 Merge pull request #61 from prpjzz/master
Add Readme_EN.md
2023-06-22 15:03:23 +08:00
prpjzz
390af6cd85 Update Tutorial_EN.md 2023-06-19 22:00:38 +07:00
prpjzz
a78f36dfa8 Update README_EN.md 2023-06-19 21:56:51 +07:00
prpjzz
e3d7152da2 Update Tutorial_EN.md 2023-06-19 21:14:20 +07:00
prpjzz
60a4dd04c4 Merge branch 'HolographicHat:master' into master 2023-06-19 18:49:45 +07:00
HolographicHat
69c402417b 3.7 2023-05-24 17:32:27 +08:00
prpjzz
d9034ec635 Update Tutorial_EN.md 2023-05-03 14:06:04 +07:00
prpjzz
f742cf37d5 Create Tutorial_EN.md 2023-05-03 13:27:26 +07:00
prpjzz
4136a1b0c4 Update README_EN.md 2023-05-03 13:26:38 +07:00
prpjzz
e039d2f944 Update README_EN.md 2023-05-03 13:26:21 +07:00
prpjzz
0e23a05a78 Update README_EN.md 2023-05-03 13:25:26 +07:00
prpjzz
5f5a0614a6 Update README_EN.md 2023-05-03 13:20:02 +07:00
HolographicHat
c60d3a3b82 v2.7 2023-04-12 12:28:03 +08:00
HolographicHat
58dcd5b228 Fix 2023-04-02 18:23:58 +08:00
HolographicHat
a72007ffa6 Merge remote-tracking branch 'origin/master' 2023-04-02 16:20:23 +08:00
HolographicHat
d3b9d10d01 #55 2023-04-02 16:17:11 +08:00
HolographicHat
69184fa59d Auto compile protos 2023-03-30 08:54:50 +08:00
HolographicHat
8473336b37 Update Tutorial.md 2023-03-01 20:57:42 +08:00
HolographicHat
ffc0854291 bump version to 2.6.0 2023-03-01 20:48:45 +08:00
HolographicHat
3b2b1fba49 fix ci 2023-02-28 19:59:46 +08:00
HolographicHat
c325b5f754 3.5 2023-02-28 19:16:51 +08:00
HolographicHat
8e2e438c96 fix ci 2023-02-27 20:01:23 +08:00
HolographicHat
9bc2cdb443 Update actions .net version 2023-02-27 19:47:06 +08:00
HolographicHat
826ed661cd Merge pull request #51 from Lightczx/master
Use Microsoft.Windows.CsWin32 to replace manual PInvoke
2023-02-27 19:45:11 +08:00
DismissedLight
5f8ff734f2 fixup native call 2023-02-26 14:41:24 +08:00
DismissedLight
50007c2c53 Use CsWin32 2023-02-26 13:46:03 +08:00
HolographicHat
7512c6fca2 Merge pull request #50 from peaceshi/master
open export dir.
2023-02-21 18:09:06 +08:00
peaceshi
2feae6ddb9 open export dir. 2023-02-20 18:13:05 +08:00
HolographicHat
52ae44f467 fix #49 2023-02-09 21:18:51 +08:00
HolographicHat
d93f6f92c0 Merge pull request #48 from xzm2000/patch-1
Update Tutorial.md
2023-01-25 22:52:54 +08:00
xzm2000
20b59eab7e Update Tutorial.md
update url
2023-01-25 11:23:51 +08:00
105 changed files with 70720 additions and 7890 deletions

2
.gitattributes vendored
View File

@@ -1,4 +1,4 @@
lib/src/il2cpp-types.h linguist-generated=true
lib/src/Zydis.* linguist-generated=true
# Auto detect text files and perform LF normalization
* text=auto

View File

@@ -12,19 +12,19 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v2
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
run: dotnet build -c Release --no-restore
- name: Publish
run: dotnet publish -o .\publish\
run: dotnet publish --property:OutputPath=.\publish\
- name: Upload artifact
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v4
with:
name: Artifacts
path: publish
path: YaeAchievement\publish\publish

30
.github/workflows/lib-nuget.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: YaeLib NuGet Publish
on:
workflow_dispatch:
release:
types: [released]
jobs:
publish:
runs-on: windows-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Restore NuGet Packages
run: nuget restore lib\YaeAchievementLib.sln
- name: Build
continue-on-error: true
run: msbuild lib\YaeAchievementLib.sln /p:Configuration=Release
- name: Pack
run: nuget pack lib\YaeAchievementLib.nuspec
- name: Publish to NuGet
run: nuget push *.nupkg ${{ secrets.NUGET_API_KEY }} -src https://api.nuget.org/v3/index.json

17
.gitignore vendored
View File

@@ -1,13 +1,6 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
bin
obj
.idea
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
.vs/
.vs
publish
sync

64
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,64 @@
stages:
- test
- build
- release
Test:
stage: test
image: mcr.microsoft.com/windows/server
tags:
- windows
script:
- dotnet restore
- dotnet build -c Release --no-restore
- dotnet publish --property:OutputPath=.\publish\
- Move-Item -Path .\publish\publish\*.exe -Destination ..\ -Force
Build:
stage: build
only:
- tags
tags:
- windows
needs:
- job: Test
script:
- echo "This is build stage."
- Move-Item -Path ..\YaeAchievement.exe .\ -Force
after_script:
- echo "Current Job ID is $CI_JOB_ID"
- echo "THIS_JOB_ID=$CI_JOB_ID" >> build.env
artifacts:
paths:
- .\*.exe
expire_in: 90 days
reports:
dotenv: build.env
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
only:
- tags
needs:
- job: Build
artifacts: true
variables:
TAG: '$CI_COMMIT_TAG'
script:
- echo "Create Release $TAG"
- echo "$THIS_JOB_ID"
release:
name: '$TAG'
tag_name: '$TAG'
ref: '$TAG'
description: 'Release $TAG by CI'
assets:
links:
- name: "YaeAchievement.exe"
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/YaeAchievement.exe?inline=false"
link_type: package
- name: ".NET 7.0 Desktop Runtime"
url: "https://dotnet.microsoft.com/zh-cn/download/dotnet/thank-you/runtime-desktop-7.0.11-windows-x64-installer"
link_type: other

View File

@@ -1,29 +1,37 @@
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
# YaeAchievement
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/YaeAchievement?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/YaeAchievement?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/YaeAchievement/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
简体中文 | [English](README_EN.md)
</div>
- 支持导出所有类别的成就
- 支持官服,渠道服与国际服
- 支持导出至[椰羊](https://cocogoat.work/achievement)、[Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao)、[Paimon.moe](https://paimon.moe/achievement/)、[Seelie.me](https://seelie.me/achievements)、[寻空](https://github.com/xunkong/xunkong)和表格文件(csv)
- 没有窗口大小、游戏语言等要求
## 使用说明
→ [Tutorial.md](Tutorial.md)
## 下载地址
[releases/latest](https://github.com/HolographicHat/YaeAchievement/releases/latest)
## 问题反馈
[issues](https://github.com/HolographicHat/YaeAchievement/issues)或[QQ群: 913777414](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
## 常见问题
0. Q: 打不开
A: 安装 [.NET Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-6.0.7-windows-x64-installer)
1. Q: 原神启动时报错: 数据异常(31-4302)
A: 不要把软件和原神主程序放一起
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/YaeAchievement/res/icon.ico" alt="AppIcon">
# YaeAchievement
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/YaeAchievement?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/YaeAchievement?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/YaeAchievement/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
简体中文 | [English](README_EN.md) | [日本語](README_JP.md)
</div>
- 支持导出所有类别的成就
- 支持官服,渠道服与国际服
- 没有窗口大小、游戏语言等要求
## 导出支持
0. [椰羊](https://cocogoat.work/achievement)
1. [胡桃工具箱](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements)
4. 表格文件 `.csv`
5. [寻空](https://github.com/xunkong/xunkong)
6. [原魔工具箱](https://apps.apple.com/app/id1663989619)
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON 文件
## 使用说明
→ [Tutorial.md](Tutorial.md)
## 下载地址
[releases/latest](https://github.com/HolographicHat/YaeAchievement/releases/latest)
## 问题反馈
[issues](https://github.com/HolographicHat/YaeAchievement/issues)或[QQ群: 598720036](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
## 常见问题
1. Q: 原神启动时报错: 数据异常(31-4302)
A: 不要把软件和原神主程序放一起

View File

@@ -1,37 +1,38 @@
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/icon.ico">
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/YaeAchievement/res/icon.ico" alt="AppIcon">
# YaeAchievement
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/YaeAchievement?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/YaeAchievement?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/YaeAchievement/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
[简体中文](README.md) | English
**I18n support currently in [snapshot version](https://github.com/HolographicHat/YaeAchievement/actions/)**
**Next release: 2022/09/28 (Genshin 3.1)**
[简体中文](README.md) | English | [日本語](README_JP.md)
</div>
- Support for exporting all categories of achievements
- Supports all versions of Genshin Impact
- Support for exporting to [Cocogoat](https://cocogoat.work/achievement), [Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao), [Paimon.moe](https://paimon.moe/achievement/), [Seelie.me](https://seelie.me/achievements)、[XunKong](https://github.com/xunkong/xunkong) and form files (csv)
- There are no requirements for window size, game language, etc.
## How to use:
The first time you open it, you need to set the path that contains the Genshin main program (YuanShen.exe / GenshinIimpact.exe)
![alt](https://upload-bbs.mihoyo.com/upload/2022/04/06/165631158/e540a5a6d50cd5fdee19665435548e00_514247033566841954.jpg)
After setting up, wait for the game to start.
When you see the door, press in to enter the game, the game will automatically exit.
After the game exits, enter a number inside the program to select the site/application to export to
## Export support
0. [Cocogoat](https://cocogoat.work/achievement)
1. [Snap HuTao](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements)
4. Form File `.csv`
5. [XunKong](https://github.com/xunkong/xunkong)
6. [YuanmoTools](https://apps.apple.com/app/id1663989619)
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON file
## Instructions for Use:
→ [Tutorial_EN.md](Tutorial_EN.md)
## Download: [Here](https://github.com/HolographicHat/YaeAchievement/releases/latest)
## Feedback or Problem?
[issues](https://github.com/HolographicHat/YaeAchievement/issues) or [QQ群: 913777414](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
[issues](https://github.com/HolographicHat/YaeAchievement/issues) or [QQ群: 598720036](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
## Frequently asked questions
0. Q: Unable to start
A: Download and install [.NET Runtime 6](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-6.0.8-windows-x64-installer)
1. Q: Error while Genshin started: Data Exception (31-4302)
A: Don't place software in the directory containing Genshin Impact.

38
README_JP.md Normal file
View File

@@ -0,0 +1,38 @@
<div align="center"><img width="100" src="https://github.com/HolographicHat/YaeAchievement/blob/master/YaeAchievement/res/icon.ico" alt="AppIcon">
# YaeAchievement
![GitHub](https://img.shields.io/badge/License-GPL--3.0-brightgreen?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/HolographicHat/YaeAchievement?color=brightgreen&label=Release&style=flat-square) ![GitHub issues](https://img.shields.io/github/issues/HolographicHat/YaeAchievement?label=Issues&style=flat-square) ![Downloads](https://img.shields.io/github/downloads/HolographicHat/YaeAchievement/total?color=brightgreen&label=Downloads&style=flat-square) ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)
[简体中文](README.md) | [English](README_EN.md) | 日本語
</div>
- すべてのカテゴリの実績のエクスポートをサポート
- すべてのバージョンの原神をサポート
- ウィンドウサイズ、ゲーム言語などの要件はありません
## エクスポートサポート
0. [椰羊](https://cocogoat.work/achievement)
1. [胡桃ツールボックス](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements)
4. フォームファイル `.csv`
5. [尋空](https://github.com/xunkong/xunkong)
6. [原魔ツールボックス](https://apps.apple.com/app/id1663989619)
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSONファイル
## 使用説明書:
→ [Tutorial_JP.md](Tutorial_JP.md)
## ダウンロード: [こちら](https://github.com/HolographicHat/YaeAchievement/releases/latest)
## フィードバックや問題?
[issues](https://github.com/HolographicHat/YaeAchievement/issues) または [QQ群: 598720036](https://qm.qq.com/cgi-bin/qm/qr?k=9UGz-chQVTjZa4b82RA_A41vIcBVNpms&jump_from=webapi)
## よくある質問
1. Q: 原神を起動中にエラーが発生しました: データ例外 (31-4302)
A: ソフトウェアを原神のディレクトリに配置しないでください。

View File

@@ -6,31 +6,19 @@
点击图中红框圈中的名称为“YaeAchievement.exe”的文件浏览器会自动弹出下载。建议将该文件保存在桌面或者其它易于寻找的文件夹内。
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/1.png)
![1](https://github.com/user-attachments/assets/8b98c018-b179-4681-992d-367a0f522dae)
2.安装启动软件所需文件(若已安装该运行时可忽略此步骤)
点击该网址https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-6.0.9-windows-x64-installer。
进入网页后浏览器会自动弹出下载,同样地,将文件保存在桌面或者其它易于寻找的文件夹内。
下载完成后打开名称形如dotnet-runtime-x.x.x-win-x64.exe的文件会弹出安装窗口如下图所示。
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/2.png)
直接点击安装即可。
3.打开主程序所需的操作以及成就导出的选择
2.打开主程序所需的操作以及成就导出的选择
双击在第一步下载的名称为“YaeAchievement.exe”的文件成功打开后会提示原神正在启动如下图所示。
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/3.png)
![3](https://github.com/user-attachments/assets/3d9eb78b-187f-4ada-90b7-5a951c9d0ee1)
原神启动完成后,点击进入游戏即可。
点击进入游戏后原神闪退,工具会提示您选择导出至何种工具,如下图所示。
点击进入游戏后原神自动退出,工具会提示您选择导出至何种工具,如下图所示。
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/4.png)
![4](https://github.com/user-attachments/assets/76b64d42-2865-4f61-9ceb-8e317af50e7e)
此时可根据自己的需要进行选择,一般推荐导出至[0]椰羊以及[4]表格文件(.csv
@@ -38,44 +26,44 @@
#### 椰羊:
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/5.png)
![5](https://github.com/user-attachments/assets/9e3188ab-cfad-4cfc-8db9-51ae22ff7caa)
#### Snap.Hutao
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main//images/6.png)
![6](https://github.com/user-attachments/assets/8d8cabe1-1ebc-4329-898b-0c725a5b10e4)
#### Seelie.me
此时YaeAchievement会提示成就数据已导出。请在保存YaeAchievement的文件夹内找到名称形如export-20xxxxxxxxxxxx-seelie.json的文件。
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/7.png)
![7](https://github.com/user-attachments/assets/c0c6724e-90cd-4e58-b441-a3af97493455)
然后点击该网址https://seelie.me/settings, 进入网页后选择导入,如下图所示。
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/8.png)
![8](https://github.com/user-attachments/assets/4182f3dd-723d-452b-aa32-2246ad710ac1)
点击导入后选中名称形如export-20xxxxxxxxxxxx-seelie.json的文件如下图所示。
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/9.png)
![9](https://github.com/user-attachments/assets/9466ee0b-a5e9-4bf5-9f95-5ac86ea0c8f6)
当弹出如下图所示的提示时表示导入成功。
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/10.png)
![A](https://github.com/user-attachments/assets/755193d6-41f6-4108-b9ca-867e92107a03)
此时可选择左栏成就,查看导入的成就数据。
#### 寻空:
![image](https://github.com/Finchaos/yae-markdown-230119/blob/main/images/11.png)
![B](https://github.com/user-attachments/assets/f78c9a70-0b81-4c19-a034-5ed9f8e6eff4)
### 各种工具的介绍烦请移步至各工具的官方页面进行查看(下方序号对应导出序号)
0. [椰羊](https://cocogoat.work/achievement)
1. [Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao)
1. [胡桃工具箱](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements)
4. [寻空](https://github.com/xunkong/xunkong)
4. [表格文件 `.csv`](https://en.wikipedia.org/wiki/Comma-separated_values)
5. [寻空](https://github.com/xunkong/xunkong)
6. [原魔工具箱](https://apps.apple.com/app/id1663989619)
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON 文件

62
Tutorial_EN.md Normal file
View File

@@ -0,0 +1,62 @@
# Instructions for Use
1.Download YaeAchievementLatest Version
Click Here<https://github.com/HolographicHat/YaeAchievement/releases>
Click on the file named "YaeAchievement.exe" in the red box to automatically pop up and download.It is recommended that
you save this file in a desktop or other easy-to-see folder.
![Step1](https://github.com/user-attachments/assets/dbe32d1f-3a73-4948-b854-1fb6151ad7f3)
3.The actions required to open the main program and the options for the achievement export
Double-click the file named "YaeAchievement.exe" downloaded in the first step to open it successfully, indicating that the original god is starting, as shown below.
![Guide3](https://github.com/user-attachments/assets/c3375188-1fa3-4a0b-9007-358afbcaae91)
Once the primordial startup is complete, click to enter the game.
When you click into the game, the tool prompts you to choose which tool to export, as shown below.
![Guide4](https://github.com/user-attachments/assets/c806582a-3608-4c86-af26-ce8e631ff610)
For global user, you should select [3] Seelie.me or [4] Export to csv file。
After selecting, each page exports the tool as follows:
#### Snap.Hutao
![Guide5](https://github.com/user-attachments/assets/40d547d8-fe04-4462-8b78-284394a44c36)
#### Seelie.me
At this point, Yae Achievement will remind that performance data has been exported. Please find the file named export-20xxxxxxxxxxxx-seelie.json in the Yae Achievement save directory.
![Guide6](https://github.com/user-attachments/assets/91cdb0e6-883d-4f5e-9f1f-416eb8c16433)
Then click on the URL: https://seelie.me/settings, enter the website and select Import Account, as illustrated in the figure below.
![Guide7](https://github.com/user-attachments/assets/e6a9ddb1-b075-4f0b-9e42-a1d61b4808bc)
After clicking Import, select a file named export-20xxxxxxxxxxxx-seelie.json, as shown in the figure below.
![Guide8](https://github.com/user-attachments/assets/1b7edb51-ff0d-415c-bd96-0e6c9ac7a238)
When the prompt as shown in the image below pops up, the import process succeeds.
![Guide9](https://github.com/user-attachments/assets/e155b4e5-ce15-4dd8-9633-a83b9759bce1)
At this time, you can select the Achievements in the left column to view the imported performance data.
### For the introduction of different tools, please visit the official page of each tool to see:
0. [Cocogoat](https://cocogoat.work/achievement)
1. [Snap HuTao](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements)
4. [Form File `.csv`](https://en.wikipedia.org/wiki/Comma-separated_values)
5. [XunKong](https://github.com/xunkong/xunkong)
6. [YuanmoTools](https://apps.apple.com/app/id1663989619)
7. [Teyvat Guide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON file

61
Tutorial_JP.md Normal file
View File

@@ -0,0 +1,61 @@
## 使用説明書
1. YaeAchievement最新バージョンをダウンロード
こちらをクリックhttps://github.com/HolographicHat/YaeAchievement/releases
赤枠で囲まれた「YaeAchievement.exe」という名前のファイルをクリックすると、自動的にポップアップしてダウンロードされます。このファイルをデスクトップや他の見やすいフォルダに保存することをお勧めします。
![Guide1](https://github.com/user-attachments/assets/dbe32d1f-3a73-4948-b854-1fb6151ad7f3)
3. メインプログラムを開くための操作と実績エクスポートのオプション
最初のステップでダウンロードした「YaeAchievement.exe」という名前のファイルをダブルクリックして開くと、原神が起動していることを示します。以下の図のように表示されます。
![Guide3](https://github.com/user-attachments/assets/c3375188-1fa3-4a0b-9007-358afbcaae91)
原神の起動が完了したら、ゲームに入ります。
ゲームに入ると、ツールがどのツールにエクスポートするかを選択するように促します。以下の図のように表示されます。
![Guide4](https://github.com/user-attachments/assets/c806582a-3608-4c86-af26-ce8e631ff610)
グローバルユーザーの場合、[3] Seelie.meまたは[4] csvファイルにエクスポートを選択する必要があります。
選択後、各ページは次のようにツールをエクスポートします:
#### Snap.Hutao
![Guide5](https://github.com/user-attachments/assets/40d547d8-fe04-4462-8b78-284394a44c36)
#### Seelie.me
この時点で、Yae Achievementは実績データがエクスポートされたことを通知します。Yae Achievement保存ディレクトリにexport-20xxxxxxxxxxxx-seelie.jsonという名前のファイルを見つけてください。
![Guide6](https://github.com/user-attachments/assets/91cdb0e6-883d-4f5e-9f1f-416eb8c16433)
次に、URLhttps://seelie.me/settings をクリックし、ウェブサイトにアクセスしてインポートアカウントを選択します。以下の図のように表示されます。
![Guide7](https://github.com/user-attachments/assets/e6a9ddb1-b075-4f0b-9e42-a1d61b4808bc)
インポートをクリックした後、export-20xxxxxxxxxxxx-seelie.jsonという名前のファイルを選択します。以下の図のように表示されます。
![Guide8](https://github.com/user-attachments/assets/1b7edb51-ff0d-415c-bd96-0e6c9ac7a238)
以下の図のようなプロンプトが表示されたら、インポートプロセスは成功です。
![Guide9](https://github.com/user-attachments/assets/e155b4e5-ce15-4dd8-9633-a83b9759bce1)
この時点で、左側の列のAchievementsを選択して、インポートされた実績データを表示できます。
### 各ツールの紹介については、各ツールの公式ページをご覧ください:
0. [椰羊](https://cocogoat.work/achievement)
1. [胡桃ツールボックス](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements)
4. [フォームファイル `.csv`](https://ja.wikipedia.org/wiki/Comma-separated_values)
5. [尋空](https://github.com/xunkong/xunkong)
6. [原魔ツールボックス](https://apps.apple.com/app/id1663989619)
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSONファイル

View File

@@ -1,47 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>res\app.manifest</ApplicationManifest>
<AssemblyVersion>2.0.0</AssemblyVersion>
<FileVersion>2.0.0</FileVersion>
<ApplicationIcon>icon.ico</ApplicationIcon>
<DebugType>embedded</DebugType>
<SelfContained>false</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.21.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="res\App.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>App.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Remove="res\Updater.exe" />
<EmbeddedResource Include="res\Updater.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="res\App.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>App.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -1,16 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YaeAchievement", "YaeAchievement.csproj", "{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA5FA5E1-3B38-4AF9-8CBA-A7E9193D660C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

3
YaeAchievement.slnx Normal file
View File

@@ -0,0 +1,3 @@
<Solution>
<Project Path="YaeAchievement\YaeAchievement.csproj" Type="Classic C#" />
</Solution>

View File

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

View File

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

View File

@@ -0,0 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>net9.0-windows</TargetFramework>
<FileVersion>5.3.0</FileVersion>
<AssemblyVersion>5.3.0</AssemblyVersion>
<ApplicationIcon>res\icon.ico</ApplicationIcon>
<ApplicationManifest>res\app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup>
<PublishAot>true</PublishAot>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<OptimizationPreference>Size</OptimizationPreference>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Google.Protobuf" Version="3.30.2" />
<PackageReference Include="Grpc.Tools" Version="2.71.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Sentry" Version="5.6.0" />
<PackageReference Include="Spectre.Console" Version="0.50.1-preview.0.3" />
<PackageReference Include="Spectre.Console.Analyzer" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="res\App.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>App.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Remove="res\updater.exe"/>
<EmbeddedResource Include="res\updater.exe" LogicalName="updater"/>
</ItemGroup>
<ItemGroup>
<Compile Update="res\App.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>App.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Protobuf Include="res/proto/*.proto" ProtoRoot="res/proto" GrpcServices="None"/>
</ItemGroup>
<PropertyGroup>
<CETCompat>false</CETCompat>
<!-- <TrimmerSingleWarn>false</TrimmerSingleWarn>-->
</PropertyGroup>
</Project>

View File

@@ -87,7 +87,25 @@ namespace YaeAchievement.res {
}
/// <summary>
/// Looks up a localized string similar to You need to login genshin impact before exporting..
/// Looks up a localized string similar to No.
/// </summary>
internal static string CommonNo {
get {
return ResourceManager.GetString("CommonNo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Yes.
/// </summary>
internal static string CommonYes {
get {
return ResourceManager.GetString("CommonYes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please launch GenshinImpact to continue..
/// </summary>
internal static string ConfigNeedStartGenshin {
get {
@@ -114,14 +132,7 @@ namespace YaeAchievement.res {
}
/// <summary>
/// Looks up a localized string similar to Export to:
///[0] Cocogoat (https://cocogoat.work/achievement, Default)
///[1] Snap.HuTao
///[2] Paimon.moe
///[3] Seelie.me
///[4] Csv file
///[5] Xunkong
///Input a number (0-5): .
/// Looks up a localized string similar to Export to:.
/// </summary>
internal static string ExportChoose {
get {
@@ -129,6 +140,87 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to Cocogoat (https://cocogoat.work/achievement).
/// </summary>
internal static string ExportTargetCocogoat {
get {
return ResourceManager.GetString("ExportTargetCocogoat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Csv file.
/// </summary>
internal static string ExportTargetCsv {
get {
return ResourceManager.GetString("ExportTargetCsv", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Snap.HuTao.
/// </summary>
internal static string ExportTargetHuTao {
get {
return ResourceManager.GetString("ExportTargetHuTao", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Paimon.moe.
/// </summary>
internal static string ExportTargetPaimon {
get {
return ResourceManager.GetString("ExportTargetPaimon", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Seelie.me.
/// </summary>
internal static string ExportTargetSeelie {
get {
return ResourceManager.GetString("ExportTargetSeelie", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Teyvat Guide.
/// </summary>
internal static string ExportTargetTeyvatGuide {
get {
return ResourceManager.GetString("ExportTargetTeyvatGuide", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to UIAF JSON File.
/// </summary>
internal static string ExportTargetUIAFJson {
get {
return ResourceManager.GetString("ExportTargetUIAFJson", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
internal static string ExportTargetWxApp1 {
get {
return ResourceManager.GetString("ExportTargetWxApp1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Xunkong.
/// </summary>
internal static string ExportTargetXunkong {
get {
return ResourceManager.GetString("ExportTargetXunkong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Fail, please contact developer to get help information.
/// </summary>
@@ -157,7 +249,7 @@ namespace YaeAchievement.res {
}
/// <summary>
/// Looks up a localized string similar to Please update SnapGenshin and retry..
/// Looks up a localized string similar to Please update Snap Hutao and retry..
/// </summary>
internal static string ExportToSnapGenshinNeedUpdate {
get {
@@ -166,7 +258,7 @@ namespace YaeAchievement.res {
}
/// <summary>
/// Looks up a localized string similar to Successfully exported to snap genshin..
/// Looks up a localized string similar to Successfully exported to Snap Hutao..
/// </summary>
internal static string ExportToSnapGenshinSuccess {
get {
@@ -174,6 +266,24 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to Please launch/update Teyvat Guide and retry..
/// </summary>
internal static string ExportToTauriFail {
get {
return ResourceManager.GetString("ExportToTauriFail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Successfully exported to Teyvat Guide..
/// </summary>
internal static string ExportToTauriSuccess {
get {
return ResourceManager.GetString("ExportToTauriSuccess", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}.
/// </summary>
@@ -246,6 +356,15 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to No write permission on {0}..
/// </summary>
internal static string NoWritePermission {
get {
return ResourceManager.GetString("NoWritePermission", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Press any key to exit..
/// </summary>
@@ -255,6 +374,42 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to Use the keyboard arrow keys to move the cursor and the Enter key to select.
/// </summary>
internal static string SelectionPromptCompatAnsiTip {
get {
return ResourceManager.GetString("SelectionPromptCompatAnsiTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Choose an option:.
/// </summary>
internal static string SelectionPromptCompatChooseOne {
get {
return ResourceManager.GetString("SelectionPromptCompatChooseOne", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please enter a number between 0 and {0}.
/// </summary>
internal static string SelectionPromptCompatInvalidChoice {
get {
return ResourceManager.GetString("SelectionPromptCompatInvalidChoice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type a number and press Enter to select.
/// </summary>
internal static string SelectionPromptCompatNonAnsiTip {
get {
return ResourceManager.GetString("SelectionPromptCompatNonAnsiTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reward not taken.
/// </summary>
@@ -291,6 +446,24 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to An error occurred while reading the data. Please try again..
/// </summary>
internal static string StreamReadDataFail {
get {
return ResourceManager.GetString("StreamReadDataFail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Checking update....
/// </summary>
internal static string UpdateChecking {
get {
return ResourceManager.GetString("UpdateChecking", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Description:
///{0}.
@@ -310,6 +483,15 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized string similar to The process cannot access the file &apos;{0}&apos; because it is being used by another process. Please restart your computer and try again..
/// </summary>
internal static string UpdateFileShareViolation {
get {
return ResourceManager.GetString("UpdateFileShareViolation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Has update: {0} =&gt; {1}.
/// </summary>
@@ -319,16 +501,6 @@ namespace YaeAchievement.res {
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] Updater {
get {
object obj = ResourceManager.GetObject("Updater", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized string similar to Upload error to appcenter....
/// </summary>
@@ -339,7 +511,7 @@ namespace YaeAchievement.res {
}
/// <summary>
/// Looks up a localized string similar to Use previous fetched data? (yes|no).
/// Looks up a localized string similar to Use previous fetched data?.
/// </summary>
internal static string UsePreviousData {
get {
@@ -364,5 +536,14 @@ namespace YaeAchievement.res {
return ResourceManager.GetString("VcRuntimeInstalling", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please update game and retry..
/// </summary>
internal static string WaitMetadataUpdate {
get {
return ResourceManager.GetString("WaitMetadataUpdate", resourceCulture);
}
}
}
}

View File

@@ -3,7 +3,7 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
@@ -25,14 +25,7 @@
<value>all achievement</value>
</data>
<data name="ExportChoose" xml:space="preserve">
<value>Export to:
[0] Cocogoat (https://cocogoat.work/achievement, Default)
[1] Snap.HuTao
[2] Paimon.moe
[3] Seelie.me
[4] Csv file
[5] Xunkong
Input a number (0-5): </value>
<value>Export to:</value>
</data>
<data name="ExportToCocogoatSuccess" xml:space="preserve">
<value>Successfully exported to cocogoat.</value>
@@ -41,10 +34,10 @@ Input a number (0-5): </value>
<value>{0}</value>
</data>
<data name="ExportToSnapGenshinSuccess" xml:space="preserve">
<value>Successfully exported to snap genshin.</value>
<value>Successfully exported to Snap Hutao.</value>
</data>
<data name="ExportToSnapGenshinNeedUpdate" xml:space="preserve">
<value>Please update SnapGenshin and retry.</value>
<value>Please update Snap Hutao and retry.</value>
</data>
<data name="ExportToFileSuccess" xml:space="preserve">
<value>Successfully exported to {0}</value>
@@ -68,7 +61,7 @@ Input a number (0-5): </value>
<value>Reward not taken</value>
</data>
<data name="ConfigNeedStartGenshin" xml:space="preserve">
<value>You need to login genshin impact before exporting.</value>
<value>Please launch GenshinImpact to continue.</value>
</data>
<data name="DownloadLink" xml:space="preserve">
<value>Download: {0}</value>
@@ -105,7 +98,7 @@ Input a number (0-5): </value>
<value>YaeAchievement ({0})</value>
</data>
<data name="UsePreviousData" xml:space="preserve">
<value>Use previous fetched data? (yes|no)</value>
<value>Use previous fetched data?</value>
</data>
<data name="NetworkError" xml:space="preserve">
<value>Network error:</value>
@@ -119,11 +112,73 @@ Input a number (0-5): </value>
<data name="ExceptionNetwork" xml:space="preserve">
<value>Network error ({0}: {1})</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="Updater" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Updater.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="GenshinHashError" xml:space="preserve">
<value>Please update genshin and retry.</value>
</data>
<data name="NoWritePermission" xml:space="preserve">
<value>No write permission on {0}.</value>
</data>
<data name="ExportToTauriSuccess" xml:space="preserve">
<value>Successfully exported to Teyvat Guide.</value>
</data>
<data name="ExportToTauriFail" xml:space="preserve">
<value>Please launch/update Teyvat Guide and retry.</value>
</data>
<data name="WaitMetadataUpdate" xml:space="preserve">
<value>Please update game and retry.</value>
</data>
<data name="UpdateChecking" xml:space="preserve">
<value>Checking update...</value>
</data>
<data name="CommonYes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="CommonNo" xml:space="preserve">
<value>No</value>
</data>
<data name="ExportTargetCocogoat" xml:space="preserve">
<value>Cocogoat (https://cocogoat.work/achievement)</value>
</data>
<data name="ExportTargetHuTao" xml:space="preserve">
<value>Snap.HuTao</value>
</data>
<data name="ExportTargetPaimon" xml:space="preserve">
<value>Paimon.moe</value>
</data>
<data name="ExportTargetSeelie" xml:space="preserve">
<value>Seelie.me</value>
</data>
<data name="ExportTargetCsv" xml:space="preserve">
<value>Csv file</value>
</data>
<data name="ExportTargetXunkong" xml:space="preserve">
<value>Xunkong</value>
</data>
<data name="ExportTargetTeyvatGuide" xml:space="preserve">
<value>Teyvat Guide</value>
</data>
<data name="ExportTargetUIAFJson" xml:space="preserve">
<value>UIAF JSON File</value>
</data>
<data name="ExportTargetWxApp1" xml:space="preserve">
<value />
</data>
<data name="SelectionPromptCompatChooseOne" xml:space="preserve">
<value>Choose an option:</value>
</data>
<data name="SelectionPromptCompatAnsiTip" xml:space="preserve">
<value>Use the keyboard arrow keys to move the cursor and the Enter key to select</value>
</data>
<data name="SelectionPromptCompatNonAnsiTip" xml:space="preserve">
<value>Type a number and press Enter to select</value>
</data>
<data name="SelectionPromptCompatInvalidChoice" xml:space="preserve">
<value>Please enter a number between 0 and {0}</value>
</data>
<data name="StreamReadDataFail" xml:space="preserve">
<value>An error occurred while reading the data. Please try again.</value>
</data>
<data name="UpdateFileShareViolation" xml:space="preserve">
<value>The process cannot access the file '{0}' because it is being used by another process. Please restart your computer and try again.</value>
</data>
</root>

View File

@@ -18,15 +18,7 @@
<value>全部成就</value>
</data>
<data name="ExportChoose" xml:space="preserve">
<value>导出至:
[0] 椰羊 (https://cocogoat.work/achievement, 默认)
[1] Snap.HuTao
[2] Paimon.moe
[3] Seelie.me
[4] 表格文件
[5] 寻空
[6] 原魔工具箱
输入一个数字 (0-6): </value>
<value>导出到哪里?</value>
</data>
<data name="ExportToCocogoatSuccess" xml:space="preserve">
<value>在浏览器内进行下一步操作</value>
@@ -35,10 +27,10 @@
<value>在小程序导入页面输入以下代码: {0}</value>
</data>
<data name="ExportToSnapGenshinSuccess" xml:space="preserve">
<value>在 SnapHutao 进行下一步操作</value>
<value>在 Snap Hutao 进行下一步操作</value>
</data>
<data name="ExportToSnapGenshinNeedUpdate" xml:space="preserve">
<value>更新 SnapHutao 最新版本后重试</value>
<value>更新/安装 Snap Hutao 最新版本后重试</value>
</data>
<data name="ExportToFileSuccess" xml:space="preserve">
<value>成就数据已导出至 {0}</value>
@@ -62,7 +54,7 @@
<value>已完成</value>
</data>
<data name="ConfigNeedStartGenshin" xml:space="preserve">
<value>在导出前你需要先完成一次登入流程.</value>
<value>请打开 原神 后继续操作</value>
</data>
<data name="DownloadLink" xml:space="preserve">
<value>下载地址: {0}</value>
@@ -99,7 +91,7 @@
<value>YaeAchievement - 原神成就导出工具 ({0})</value>
</data>
<data name="UsePreviousData" xml:space="preserve">
<value>要使用上一次获取到的成就数据吗? (yes|no)</value>
<value>要使用上一次获取到的成就数据吗</value>
</data>
<data name="NetworkError" xml:space="preserve">
<value>网络错误: {0}</value>
@@ -114,6 +106,72 @@
<value>网络错误,请检查网络后重试 ({0}: {1})</value>
</data>
<data name="GenshinHashError" xml:space="preserve">
<value>请将原神更新至最新版本后重试</value>
<value>当前适配版本不匹配,请更新原神至最新版本后重试或等待工具更新。</value>
</data>
<data name="NoWritePermission" xml:space="preserve">
<value>无法写入文件,请更换软件所在目录后重试</value>
</data>
<data name="ExportToTauriFail" xml:space="preserve">
<value>启动 Teyvat Guide 或更新 Teyvat Guide 至最新版本后重试</value>
</data>
<data name="ExportToTauriSuccess" xml:space="preserve">
<value>在 Teyvat Guide 进行下一步操作</value>
</data>
<data name="WaitMetadataUpdate" xml:space="preserve">
<value>当前元数据版本不匹配,请更新原神至最新版本或等待元数据更新后重试。</value>
</data>
<data name="UpdateChecking" xml:space="preserve">
<value>正在检查更新...</value>
</data>
<data name="CommonYes" xml:space="preserve">
<value>是</value>
</data>
<data name="CommonNo" xml:space="preserve">
<value>否</value>
</data>
<data name="ExportTargetCocogoat" xml:space="preserve">
<value>椰羊 (https://cocogoat.work/achievement)</value>
</data>
<data name="ExportTargetHuTao" xml:space="preserve">
<value>Snap Hutao</value>
</data>
<data name="ExportTargetPaimon" xml:space="preserve">
<value>Paimon.moe</value>
</data>
<data name="ExportTargetSeelie" xml:space="preserve">
<value>Seelie.me</value>
</data>
<data name="ExportTargetCsv" xml:space="preserve">
<value>表格文件</value>
</data>
<data name="ExportTargetXunkong" xml:space="preserve">
<value>寻空</value>
</data>
<data name="ExportTargetUIAFJson" xml:space="preserve">
<value>UIAF JSON 文件</value>
</data>
<data name="ExportTargetTeyvatGuide" xml:space="preserve">
<value>Teyvat Guide</value>
</data>
<data name="ExportTargetWxApp1" xml:space="preserve">
<value>原魔工具箱</value>
</data>
<data name="SelectionPromptCompatChooseOne" xml:space="preserve">
<value>选择一个选项:</value>
</data>
<data name="SelectionPromptCompatAnsiTip" xml:space="preserve">
<value>键盘上下键移动光标,键盘回车键选择</value>
</data>
<data name="SelectionPromptCompatNonAnsiTip" xml:space="preserve">
<value>输入数字并回车以选择选项</value>
</data>
<data name="SelectionPromptCompatInvalidChoice" xml:space="preserve">
<value>请输入 0 到 {0} 之间的数字</value>
</data>
<data name="StreamReadDataFail" xml:space="preserve">
<value>读取数据时发生错误,请重试</value>
</data>
<data name="UpdateFileShareViolation" xml:space="preserve">
<value>文件 {0} 被其它程序占用,请 重启电脑 或 解除文件占用 后重试。</value>
</data>
</root>

Binary file not shown.

View File

@@ -49,7 +49,7 @@
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI无需
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">

View File

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

@@ -0,0 +1,25 @@
syntax = "proto3";
option csharp_namespace = "Proto";
message AchievementProtoFieldInfo {
uint32 id = 1;
uint32 status = 2;
uint32 total_progress = 3;
uint32 current_progress = 4;
uint32 finish_timestamp = 5;
}
message AchievementItem {
uint32 pre = 1;
uint32 group = 2;
string name = 3;
string description = 4;
}
message AchievementInfo {
string version = 1;
map<uint32, string> group = 2;
map<uint32, AchievementItem> items = 3;
AchievementProtoFieldInfo pb_info = 4;
}

View File

@@ -0,0 +1,10 @@
syntax = "proto3";
option csharp_namespace = "Proto";
message CacheItem {
uint32 version = 1;
string checksum = 2;
string etag = 3;
bytes content = 4;
}

View File

@@ -0,0 +1,79 @@
syntax = "proto3";
option csharp_namespace = "Proto";
enum StoreType {
STORE_TYPE_NONE = 0;
STORE_TYPE_PACK = 1;
STORE_TYPE_DEPOT = 2;
}
message MaterialDeleteInfo {
message CountDownDelete {
map<uint32, uint32> delete_time_num_map = 1;
uint32 config_count_down_time = 2;
}
message DateTimeDelete {
uint32 delete_time = 1;
}
message DelayWeekCountDownDelete {
map<uint32, uint32> delete_time_num_map = 1;
uint32 config_delay_week = 2;
uint32 config_count_down_time = 3;
}
bool has_delete_config = 1;
oneof delete_info {
CountDownDelete count_down_delete = 2;
DateTimeDelete date_delete = 3;
DelayWeekCountDownDelete delay_week_count_down_delete = 4;
}
}
message Material {
uint32 count = 1;
MaterialDeleteInfo delete_info = 2;
}
message Reliquary {
uint32 level = 1;
uint32 exp = 2;
uint32 promote_level = 3;
uint32 main_prop_id = 4;
repeated uint32 append_prop_id_list = 5;
bool is_marked = 6;
}
message Weapon {
uint32 level = 1;
uint32 exp = 2;
uint32 promote_level = 3;
map<uint32, uint32> affix_map = 4;
bool is_arkhe_ousia = 5;
}
message Equip {
oneof detail {
Reliquary reliquary = 1;
Weapon weapon = 2;
}
bool is_locked = 3;
}
message Furniture {
uint32 count = 1;
}
message VirtualItem {
int64 count = 1;
}
message Item {
uint32 item_id = 1;
uint64 guid = 2;
oneof detail {
Material material = 5;
Equip equip = 6;
Furniture furniture = 7;
VirtualItem virtual_item = 255;
}
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
option csharp_namespace = "Proto";
message UpdateInfo {
uint32 version_code = 1;
string version_name = 2;
string description = 3;
string package_link = 4;
bool force_update = 5;
bool enable_lib_download = 6;
bool enable_auto_update = 7;
string current_cn_hash = 8;
string current_os_hash = 9;
}

View File

@@ -0,0 +1,97 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Spectre.Console;
using YaeAchievement.Utilities;
namespace YaeAchievement;
public static partial class AppConfig {
public static string GamePath { get; private set; } = null!;
private static readonly string[] ProductNames = [ "原神", "Genshin Impact" ];
internal static void Load(string argumentPath) {
if (argumentPath != "auto" && File.Exists(argumentPath)) {
GamePath = argumentPath;
} else if (TryReadGamePathFromCache(out var cachedPath)) {
GamePath = cachedPath;
} else if (TryReadGamePathFromUnityLog(out var loggedPath)) {
GamePath = loggedPath;
} else {
GamePath = ReadGamePathFromProcess();
}
Span<byte> buffer = stackalloc byte[0x10000];
using var stream = File.OpenRead(GamePath);
if (stream.Read(buffer) == buffer.Length) {
var hash = Convert.ToHexString(MD5.HashData(buffer));
CacheFile.Write("genshin_impact_game_path_v2", Encoding.UTF8.GetBytes($"{GamePath}\u1145{hash}"));
}
SentrySdk.AddBreadcrumb(GamePath.EndsWith("YuanShen.exe") ? "CN" : "OS", "GamePath");
return;
static bool TryReadGamePathFromCache([NotNullWhen(true)] out string? path) {
path = null;
try {
if (!CacheFile.TryRead("genshin_impact_game_path_v2", out var cacheFile)) {
return false;
}
var cacheData = cacheFile.Content.ToStringUtf8().Split("\u1145");
Span<byte> buffer = stackalloc byte[0x10000];
using var stream = File.OpenRead(cacheData[0]);
if (stream.Read(buffer) != buffer.Length || Convert.ToHexString(MD5.HashData(buffer)) != cacheData[1]) {
return false;
}
path = cacheData[0];
return true;
} catch (Exception) {
return false;
}
}
static bool TryReadGamePathFromUnityLog([NotNullWhen(true)] out string? path) {
path = null;
try {
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var logPath = ProductNames
.Select(name => $"{appDataPath}/../LocalLow/miHoYo/{name}/output_log.txt")
.Where(File.Exists)
.MaxBy(File.GetLastWriteTime);
if (logPath == null) {
return false;
}
return (path = GetGamePathFromLogFile(logPath) ?? GetGamePathFromLogFile($"{logPath}.last")) != null;
} catch (Exception) {
return false;
}
}
static string ReadGamePathFromProcess() {
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).Start(App.ConfigNeedStartGenshin, _ => {
Process? proc;
while ((proc = Utils.GetGameProcess()) == null) {
Thread.Sleep(250);
}
var fileName = proc.GetFileName()!;
proc.Kill();
return fileName;
});
}
}
private static string? GetGamePathFromLogFile(string path) {
if (!File.Exists(path)) {
return null;
}
var content = File.ReadAllText(path);
var matchResult = GamePathRegex().Match(content);
if (!matchResult.Success) {
return null;
}
var entryName = matchResult.Groups["1"].Value.Replace("_Data", ".exe");
return Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
}
[GeneratedRegex(@"(?m).:(?:\\|/).+(GenshinImpact_Data|YuanShen_Data)", RegexOptions.IgnoreCase)]
private static partial Regex GamePathRegex();
}

View File

@@ -0,0 +1,237 @@
using System.ComponentModel;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Win32;
using Spectre.Console;
using YaeAchievement.Outputs;
using YaeAchievement.Parsers;
using YaeAchievement.Utilities;
// ReSharper disable UnusedMember.Local
namespace YaeAchievement;
public static class Export {
public static int ExportTo { get; set; } = 114514;
public static void Choose(AchievementAllDataNotify data) {
var targets = new Dictionary<string, Action<AchievementAllDataNotify>> {
{ App.ExportTargetCocogoat, ToCocogoat },
{ App.ExportTargetHuTao, ToHuTao },
{ App.ExportTargetPaimon, ToPaimon },
{ App.ExportTargetSeelie, ToSeelie },
{ App.ExportTargetCsv, ToCSV },
{ App.ExportTargetXunkong, ToXunkong },
// { App.ExportTargetWxApp1, ToWxApp1 },
{ App.ExportTargetTeyvatGuide, ToTeyvatGuide },
{ App.ExportTargetUIAFJson, ToUIAFJson },
// { "", ToRawJson }
};
Action<AchievementAllDataNotify> action;
if (ExportTo == 114514) {
var prompt = new SelectionPromptCompat<string>().Title(App.ExportChoose).AddChoices(targets.Keys);
action = targets[prompt.Prompt()];
} else {
action = targets.ElementAtOrDefault(ExportTo).Value ?? ToCocogoat;
}
action(data);
}
private static void ToCocogoat(AchievementAllDataNotify data) {
var result = UIAFSerializer.Serialize(data);
using var request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
request.RequestUri = new Uri($"https://77.cocogoat.cn/v1/memo?source={App.AllAchievement}");
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
using var response = Utils.CHttpClient.Send(request);
if (response.StatusCode != HttpStatusCode.Created) {
AnsiConsole.WriteLine(App.ExportToCocogoatFail);
return;
}
var responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var responseJson = JsonSerializer.Deserialize(responseText, CocogoatResponseContext.Default.CocogoatResponse)!;
var cocogoatUrl = $"https://cocogoat.work/achievement?memo={responseJson.Key}";
Utils.SetQuickEditMode(true);
AnsiConsole.MarkupLineInterpolated($"[link]{cocogoatUrl}[/]");
if (Utils.ShellOpen(cocogoatUrl))
{
AnsiConsole.WriteLine(App.ExportToCocogoatSuccess);
}
}
private static void ToWxApp1(AchievementAllDataNotify data) {
var id = Guid.NewGuid().ToString("N").Substring(20, 8);
var result = WxApp1Serializer.Serialize(data, id);
using var request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
request.RequestUri = new Uri("https://api.qyinter.com/achievementRedis");
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
using var response = Utils.CHttpClient.Send(request);
AnsiConsole.WriteLine(App.ExportToWxApp1Success, id);
}
private static void ToHuTao(AchievementAllDataNotify data) {
if (CheckWinUIAppScheme("hutao")) {
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
Utils.ShellOpen("hutao://achievement/import");
AnsiConsole.WriteLine(App.ExportToSnapGenshinSuccess);
} else {
AnsiConsole.WriteLine(App.ExportToSnapGenshinNeedUpdate);
Utils.ShellOpen("ms-windows-store://pdp/?productid=9PH4NXJ2JN52");
}
}
private static void ToXunkong(AchievementAllDataNotify data) {
if (CheckWinUIAppScheme("xunkong")) {
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
Utils.ShellOpen("xunkong://import-achievement?caller=YaeAchievement&from=clipboard");
AnsiConsole.WriteLine(App.ExportToXunkongSuccess);
} else {
AnsiConsole.WriteLine(App.ExportToXunkongNeedUpdate);
Utils.ShellOpen("ms-windows-store://pdp/?productid=9N2SVG0JMT12");
}
}
private static void ToTeyvatGuide(AchievementAllDataNotify data) {
if (Process.GetProcessesByName("TeyvatGuide").Length != 0) {
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
Utils.ShellOpen("teyvatguide://import_uiaf?app=Yae");
AnsiConsole.WriteLine(App.ExportToTauriSuccess);
} else {
AnsiConsole.WriteLine(App.ExportToTauriFail);
Utils.ShellOpen("ms-windows-store://pdp/?productid=9NLBNNNBNSJN");
}
}
// ReSharper disable once InconsistentNaming
private static void ToUIAFJson(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"uiaf-{DateTime.Now:yyyyMMddHHmmss}.json");
if (TryWriteToFile(path, UIAFSerializer.Serialize(data))) {
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
}
}
private static void ToPaimon(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json");
if (TryWriteToFile(path, PaimonSerializer.Serialize(data))) {
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
}
}
private static void ToSeelie(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-seelie.json");
if (TryWriteToFile(path, SeelieSerializer.Serialize(data))) {
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
}
}
// ReSharper disable once InconsistentNaming
private static void ToCSV(AchievementAllDataNotify data) {
var info = GlobalVars.AchievementInfo;
var outList = new List<List<object>>();
foreach (var ach in data.AchievementList.OrderBy(a => a.Id)) {
if (UnusedAchievement.Contains(ach.Id)) continue;
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
AnsiConsole.WriteLine($@"Unable to find {ach.Id} in metadata.");
continue;
}
var finishAt = "";
if (ach.FinishTimestamp != 0) {
var ts = Convert.ToInt64(ach.FinishTimestamp);
finishAt = DateTimeOffset.FromUnixTimeSeconds(ts).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
}
var current = ach.Status != AchievementStatus.Unfinished
? ach.CurrentProgress == 0 ? ach.TotalProgress : ach.CurrentProgress
: ach.CurrentProgress;
outList.Add([
ach.Id, ach.Status.ToDesc(), achInfo.Group, achInfo.Name,
achInfo.Description, current, ach.TotalProgress, finishAt
]);
}
var output = new List<string> { "ID,状态,特辑,名称,描述,当前进度,目标进度,完成时间" };
output.AddRange(outList.OrderBy(v => v[2]).Select(item => {
item[2] = info.Group[(uint) item[2]];
return item.JoinToString(",");
}));
var path = Path.GetFullPath($"achievement-{DateTime.Now:yyyyMMddHHmmss}.csv");
if (TryWriteToFile(path, $"\uFEFF{string.Join("\n", output)}")) {
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
Process.Start("explorer.exe", $"{Path.GetDirectoryName(path)}");
}
}
private static void ToRawJson(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
var text = AchievementRawDataSerializer.Serialize(data);
if (TryWriteToFile(path, text)) {
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
}
}
// ReSharper disable once InconsistentNaming
private static bool CheckWinUIAppScheme(string protocol) {
return (string?)Registry.ClassesRoot.OpenSubKey(protocol)?.GetValue("") == $"URL:{protocol}";
}
private static string JoinToString(this IEnumerable<object> list, string separator) {
return string.Join(separator, list);
}
private static readonly List<uint> UnusedAchievement = [ 84517 ];
private static string ToDesc(this AchievementStatus status) {
return status switch {
AchievementStatus.Invalid => App.StatusInvalid,
AchievementStatus.Finished => App.StatusFinished,
AchievementStatus.Unfinished => App.StatusUnfinished,
AchievementStatus.RewardTaken => App.StatusRewardTaken,
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
};
}
public static int PrintMsgAndReturnErrCode(this Win32Exception ex, string msg) {
// ReSharper disable once LocalizableElement
AnsiConsole.WriteLine($"{msg}: {ex.Message}");
return ex.NativeErrorCode;
}
private static bool TryWriteToFile(string path, string contents) {
try {
File.WriteAllText(path, contents);
return true;
} catch (UnauthorizedAccessException) {
AnsiConsole.WriteLine(App.NoWritePermission, path);
return false;
}
}
}
public sealed class WxApp1Root {
public string Key { get; init; } = null!;
public UIAFRoot Data { get; init; } = null!;
}
[JsonSerializable(typeof(WxApp1Root))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public sealed partial class WxApp1Serializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf, string key) => JsonSerializer.Serialize(new WxApp1Root {
Key = key,
Data = Outputs.UIAFRoot.FromNotify(ntf)
}, Default.WxApp1Root);
}
public sealed record CocogoatResponse(string Key);
[JsonSerializable(typeof(CocogoatResponse))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public sealed partial class CocogoatResponseContext : JsonSerializerContext;

View File

@@ -1,33 +1,35 @@
using System.Reflection;
global using System.Diagnostics;
global using YaeAchievement.res;
namespace YaeAchievement;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Proto;
// ReSharper disable InconsistentNaming
// ReSharper disable ConvertToConstant.Global
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable once MemberCanBePrivate.Global
namespace YaeAchievement;
public static class GlobalVars {
public static bool DebugProxy => false;
public static bool UnexpectedExit { get; set; } = true;
public static bool PauseOnExit { get; set; } = true;
public static Version AppVersion { get; } = Assembly.GetEntryAssembly()!.GetName().Version!;
public static readonly string AppPath = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string CommonData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
public static readonly string DataPath = Path.Combine(CommonData, "Yae");
public static readonly string CachePath = Path.Combine(DataPath, "cache");
public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll");
public const uint AppVersionCode = 35;
public const string AppVersionName = "2.5";
public const uint AppVersionCode = 238;
public const string AppVersionName = "5.6";
public const string PipeName = "YaeAchievementPipe";
public const string BucketHost = "https://cn-cd-1259389942.file.myqcloud.com";
[field:MaybeNull]
public static AchievementInfo AchievementInfo =>
field ??= AchievementInfo.Parser.ParseFrom(Utils.GetBucketFile("schicksal/metadata").GetAwaiter().GetResult());
static GlobalVars() {
Directory.CreateDirectory(DataPath);
Directory.CreateDirectory(CachePath);
}
}

View File

@@ -0,0 +1,37 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using YaeAchievement.Parsers;
namespace YaeAchievement.Outputs;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable PropertyCanBeMadeInitOnly.Global
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
public sealed class PaimonRoot {
public Dictionary<uint, Dictionary<uint, bool>> Achievement { get; set; } = null!;
public static PaimonRoot FromNotify(AchievementAllDataNotify ntf) {
var info = GlobalVars.AchievementInfo.Items.ToDictionary(pair => pair.Key, pair => pair.Value.Group);
return new PaimonRoot {
Achievement = ntf.AchievementList
.Where(a => a.Status >= AchievementStatus.Finished && info.ContainsKey(a.Id))
.GroupBy(a => info[a.Id], a => a.Id)
.OrderBy(g => g.Key)
.ToDictionary(g => g.Key, g => g.ToDictionary(id => id, _ => true))
};
}
}
[JsonSerializable(typeof(PaimonRoot))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public sealed partial class PaimonSerializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf) {
return JsonSerializer.Serialize(Outputs.PaimonRoot.FromNotify(ntf), Default.PaimonRoot);
}
}

View File

@@ -0,0 +1,39 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using YaeAchievement.Parsers;
namespace YaeAchievement.Outputs;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable PropertyCanBeMadeInitOnly.Global
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
public sealed class SeelieRoot {
public sealed class AchievementFinishStatus {
public bool Done => true;
}
public Dictionary<uint, AchievementFinishStatus> Achievements { get; set; } = null!;
public static SeelieRoot FromNotify(AchievementAllDataNotify ntf) => new () {
Achievements = ntf.AchievementList
.Where(a => a.Status >= AchievementStatus.Finished)
.OrderBy(a => a.Id)
.ToDictionary(a => a.Id, _ => new AchievementFinishStatus())
};
}
[JsonSerializable(typeof(SeelieRoot))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public sealed partial class SeelieSerializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf) {
return JsonSerializer.Serialize(Outputs.SeelieRoot.FromNotify(ntf), Default.SeelieRoot);
}
}

View File

@@ -0,0 +1,65 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using YaeAchievement.Parsers;
namespace YaeAchievement.Outputs;
// ReSharper disable InconsistentNaming
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable PropertyCanBeMadeInitOnly.Global
#pragma warning disable CA1822 // ReSharper disable MemberCanBeMadeStatic.Global
public sealed class UApplicationInfo {
public string ExportApp => "YaeAchievement";
public string ExportAppVersion => GlobalVars.AppVersionName;
public long ExportTimestamp => DateTimeOffset.Now.ToUnixTimeSeconds();
public string UIAFVersion => "v1.1";
}
public sealed class UAchievementInfo {
public uint Id { get; set; }
public uint Status { get; set; }
public uint Current { get; set; }
public uint Timestamp { get; set; }
}
public sealed class UIAFRoot {
public UApplicationInfo Info => new ();
public IEnumerable<UAchievementInfo> List { get; set; } = null!;
public static UIAFRoot FromNotify(AchievementAllDataNotify ntf) => new () {
List = ntf.AchievementList
.Where(a => a.Status >= AchievementStatus.Finished || a.CurrentProgress > 0)
.Select(a => new UAchievementInfo {
Id = a.Id,
Status = (uint) a.Status,
Current = a.CurrentProgress,
Timestamp = a.FinishTimestamp
})
};
}
[JsonSerializable(typeof(UIAFRoot))]
[JsonSourceGenerationOptions(
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public sealed partial class UIAFSerializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf) {
return JsonSerializer.Serialize(Outputs.UIAFRoot.FromNotify(ntf), Default.UIAFRoot);
}
}

View File

@@ -0,0 +1,151 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using Spectre.Console;
using YaeAchievement.Utilities;
namespace YaeAchievement.Parsers;
public enum AchievementStatus {
Invalid,
Unfinished,
Finished,
RewardTaken,
}
public sealed class AchievementItem {
public uint Id { get; init; }
public uint TotalProgress { get; init; }
public uint CurrentProgress { get; init; }
public uint FinishTimestamp { get; init; }
public AchievementStatus Status { get; init; }
}
public sealed class AchievementAllDataNotify {
public List<AchievementItem> AchievementList { get; private init; } = [];
private static AchievementAllDataNotify? Instance { get; set; }
public static bool OnReceive(BinaryReader reader) {
var bytes = reader.ReadBytes();
CacheFile.Write("achievement_data", bytes);
Instance = ParseFrom(bytes);
return true;
}
public static void OnFinish() {
if (Instance == null) {
throw new ApplicationException("No data received");
}
Export.Choose(Instance);
}
public static AchievementAllDataNotify ParseFrom(byte[] bytes) {
using var stream = new CodedInputStream(bytes);
var data = new List<Dictionary<uint, uint>>();
var errTimes = 0;
try {
uint tag;
while ((tag = stream.ReadTag()) != 0) {
if ((tag & 7) == 2) { // is LengthDelimited
var dict = new Dictionary<uint, uint>();
using var eStream = stream.ReadLengthDelimitedAsStream();
try {
while ((tag = eStream.ReadTag()) != 0) {
if ((tag & 7) != 0) { // not VarInt
dict = null;
break;
}
dict[tag >> 3] = eStream.ReadUInt32();
}
if (dict is { Count: > 2 }) { // at least 3 fields
data.Add(dict);
}
} catch (InvalidProtocolBufferException) {
if (errTimes++ > 0) { // allows 1 fail on 'reward_taken_goal_id_list'
throw;
}
}
}
}
} catch (InvalidProtocolBufferException) {
// ReSharper disable once LocalizableElement
AnsiConsole.WriteLine("Parse failed");
File.WriteAllBytes("achievement_raw_data.bin", bytes);
Environment.Exit(0);
}
if (data.Count == 0) {
return new AchievementAllDataNotify();
}
uint tId, sId, iId, currentId, totalId;
if (data.All(CheckKnownFieldIdIsValid)) {
var info = GlobalVars.AchievementInfo.PbInfo;
iId = info.Id;
tId = info.FinishTimestamp;
sId = info.Status;
totalId = info.TotalProgress;
currentId = info.CurrentProgress;
} else if (data.Count > 20) {
(tId, var cnt) = data // ↓ 2020-09-15 04:15:14
.GroupKeys(value => value > 1600114514).Select(g => (g.Key, g.Count())).MaxBy(p => p.Item2);
sId = data // FINISHED ↓ ↓ REWARD_TAKEN
.GroupKeys(value => value is 2 or 3).First(g => g.Count() == cnt).Key;
iId = data // ↓ id: 8xxxx
.GroupKeys(value => value / 10000 % 10 == 8).MaxBy(g => g.Count())!.Key;
(currentId, totalId) = data
.Where(d => d[sId] is 2 or 3)
.Select(d => d.ToDictionary().RemoveValues(tId, sId, iId).ToArray())
.Where(d => d.Length == 2 && d[0].Value != d[1].Value)
.GroupBy(a => a[0].Value > a[1].Value ? (a[0].Key, a[1].Key) : (a[1].Key, a[0].Key))
.Select(g => (FieldIds: g.Key, Count: g.Count()))
.MaxBy(p => p.Count)
.FieldIds;
#if DEBUG
// ReSharper disable once LocalizableElement
AnsiConsole.WriteLine($"Id={iId}, Status={sId}, Total={totalId}, Current={currentId}, Timestamp={tId}");
#endif
} else {
AnsiConsole.WriteLine(App.WaitMetadataUpdate);
Environment.Exit(0);
return null!;
}
return new AchievementAllDataNotify {
AchievementList = data.Select(dict => new AchievementItem {
Id = dict[iId],
Status = (AchievementStatus) dict[sId],
TotalProgress = dict[totalId],
CurrentProgress = dict.GetValueOrDefault(currentId),
FinishTimestamp = dict.GetValueOrDefault(tId),
}).ToList()
};
// ReSharper disable once ConvertIfStatementToSwitchStatement
static bool CheckKnownFieldIdIsValid(Dictionary<uint, uint> data) {
var info = GlobalVars.AchievementInfo;
var status = data.GetValueOrDefault(info.PbInfo.Status, 114514u);
if (status is 0 or > 3) {
return false;
}
if (status > 1 && data.GetValueOrDefault(info.PbInfo.FinishTimestamp) < 1600114514) { // 2020-09-15 04:15:14
return false;
}
return info.Items.ContainsKey(data.GetValueOrDefault(info.PbInfo.Id));
}
}
}
[JsonSerializable(typeof(AchievementAllDataNotify))]
[JsonSourceGenerationOptions(
WriteIndented = true,
GenerationMode = JsonSourceGenerationMode.Serialization,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower
)]
public sealed partial class AchievementRawDataSerializer : JsonSerializerContext {
public static string Serialize(AchievementAllDataNotify ntf) {
return JsonSerializer.Serialize(ntf, Default.AchievementAllDataNotify);
}
}

View File

@@ -0,0 +1,106 @@
using Proto;
using static YaeAchievement.Parsers.PropType;
namespace YaeAchievement.Parsers;
public enum PropType {
None = 0,
Exp = 1001,
BreakLevel = 1002,
SatiationVal = 1003,
SatiationPenaltyTime = 1004,
GearStartVal = 2001,
GearStopVal = 2002,
Level = 4001,
LastChangeAvatarTime = 10001,
MaxSpringVolume = 10002,
CurSpringVolume = 10003,
IsSpringAutoUse = 10004,
SpringAutoUsePercent = 10005,
IsFlyable = 10006,
IsWeatherLocked = 10007,
IsGameTimeLocked = 10008,
IsTransferable = 10009,
MaxStamina = 10010,
CurPersistStamina = 10011,
CurTemporaryStamina = 10012,
PlayerLevel = 10013,
PlayerExp = 10014,
PlayerHCoin = 10015,
PlayerSCoin = 10016,
PlayerMpSettingType = 10017,
IsMpModeAvailable = 10018,
PlayerWorldLevel = 10019,
PlayerResin = 10020,
PlayerWaitSubHCoin = 10022,
PlayerWaitSubSCoin = 10023,
IsOnlyMpWithPsPlayer = 10024,
PlayerMCoin = 10025,
PlayerWaitSubMCoin = 10026,
PlayerLegendaryKey = 10027,
IsHasFirstShare = 10028,
PlayerForgePoint = 10029,
CurClimateMeter = 10035,
CurClimateType = 10036,
CurClimateAreaId = 10037,
CurClimateAreaClimateType = 10038,
PlayerWorldLevelLimit = 10039,
PlayerWorldLevelAdjustCd = 10040,
PlayerLegendaryDailyTaskNum = 10041,
PlayerHomeCoin = 10042,
PlayerWaitSubHomeCoin = 10043,
IsAutoUnlockSpecificEquip = 10044,
PlayerGCGCoin = 10045,
PlayerWaitSubGCGCoin = 10046,
PlayerOnlineTime = 10047,
IsDiveable = 10048,
MaxDiveStamina = 10049,
CurPersistDiveStamina = 10050,
IsCanPutFiveStarReliquary = 10051,
IsAutoLockFiveStarReliquary = 10052,
PlayerRoleCombatCoin = 10053,
CurPhlogiston = 10054,
ReliquaryTemporaryExp = 10055,
IsMpCrossPlatformEnabled = 10056,
IsOnlyMpWithPlatformPlayer = 10057,
PlayerMusicGameBookCoin = 10058,
IsNotShowReliquaryRecommendProp = 10059,
}
public static class PlayerPropNotify {
private static readonly Dictionary<PropType, double> PropMap = [];
public static bool OnReceive(BinaryReader reader) {
var propType = (PropType) reader.ReadInt32();
var propValue = reader.ReadDouble();
PropMap.Add(propType, propValue);
return false;
}
public static void OnFinish() {
PlayerStoreNotify.Instance.ItemList.AddRange([
CreateVirtualItem(201, GetPropValue(PlayerHCoin) - GetPropValue(PlayerWaitSubHCoin)),
CreateVirtualItem(202, GetPropValue(PlayerSCoin) - GetPropValue(PlayerWaitSubSCoin)),
CreateVirtualItem(203, GetPropValue(PlayerMCoin) - GetPropValue(PlayerWaitSubMCoin)),
CreateVirtualItem(204, GetPropValue(PlayerHomeCoin) - GetPropValue(PlayerWaitSubHomeCoin)),
CreateVirtualItem(206, GetPropValue(PlayerRoleCombatCoin)),
CreateVirtualItem(207, GetPropValue(PlayerMusicGameBookCoin)),
]);
}
private static Item CreateVirtualItem(uint id, double count) {
return new Item {
ItemId = id,
VirtualItem = new VirtualItem {
Count = (long) count
}
};
}
private static double GetPropValue(PropType propType) {
return PropMap.GetValueOrDefault(propType);
}
}

View File

@@ -0,0 +1,61 @@
using Google.Protobuf;
using Proto;
using Spectre.Console;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable CollectionNeverQueried.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
namespace YaeAchievement.Parsers;
public sealed class PlayerStoreNotify {
public uint WeightLimit { get; set; }
public StoreType StoreType { get; set; }
public List<Item> ItemList { get; set; } = [];
public static PlayerStoreNotify Instance { get; } = new ();
public static bool OnReceive(BinaryReader reader) {
var bytes = reader.ReadBytes();
Instance.ParseFrom(bytes);
return true;
}
private void ParseFrom(byte[] bytes) {
using var stream = new CodedInputStream(bytes);
try {
uint tag;
while ((tag = stream.ReadTag()) != 0) {
var wireType = tag & 7;
switch (wireType) {
case 0: { // is VarInt
var value = stream.ReadUInt32();
if (value < 10) {
StoreType = (StoreType) value;
} else {
WeightLimit = value;
}
continue;
}
case 2: { // is LengthDelimited
using var eStream = stream.ReadLengthDelimitedAsStream();
while (eStream.PeekTag() != 0) {
ItemList.Add(Item.Parser.ParseFrom(eStream));
}
break;
}
}
}
} catch (InvalidProtocolBufferException) {
// ReSharper disable once LocalizableElement
AnsiConsole.WriteLine("Parse failed");
File.WriteAllBytes("store_raw_data.bin", bytes);
Environment.Exit(0);
}
}
}

View File

@@ -0,0 +1,97 @@
using System.Runtime.CompilerServices;
using System.Text;
using Spectre.Console;
using YaeAchievement.Parsers;
using YaeAchievement.Utilities;
using static YaeAchievement.Utils;
namespace YaeAchievement;
// TODO: WndHook
internal static class Program {
public static async Task Main(string[] args) {
AnsiConsole.WriteLine(@"----------------------------------------------------");
AnsiConsole.WriteLine(App.AppBanner, GlobalVars.AppVersionName);
AnsiConsole.WriteLine(@"https://github.com/HolographicHat/YaeAchievement");
AnsiConsole.WriteLine(@"----------------------------------------------------");
if (!new Mutex(true, @"Global\YaeMiku~uwu").WaitOne(0, false)) {
AnsiConsole.WriteLine(App.AnotherInstance);
Environment.Exit(302);
}
SentrySdk.Init(options => {
options.Dsn = "https://92f11b64b0ef52cabc94f21df0428f5b@sentry.snapgenshin.com/9";
#if DEBUG
options.Debug = true;
#endif
options.TracesSampleRate = 1.0;
options.AutoSessionTracking = true;
options.SetBeforeSend(static e => {
e.Release = GlobalVars.AppVersionName;
return e;
});
options.SetBeforeSendTransaction(static e => {
e.Release = GlobalVars.AppVersionName;
return e;
});
options.CacheDirectoryPath = GlobalVars.DataPath;
});
InstallExitHook();
InstallExceptionHook();
if (GetGameProcess() != null) {
AnsiConsole.WriteLine(App.GenshinIsRunning, 0);
Environment.Exit(-1);
}
await CheckUpdate(ToBooleanOrDefault(args.GetOrNull(2)));
AppConfig.Load(args.GetOrNull(0) ?? "auto");
Export.ExportTo = ToIntOrDefault(args.GetOrNull(1), 114514);
AchievementAllDataNotify? data = null;
try {
if (CacheFile.TryRead("achievement_data", out var cache)) {
data = AchievementAllDataNotify.ParseFrom(cache.Content.ToByteArray());
}
} catch (Exception) { /* ignored */ }
if (CacheFile.GetLastWriteTime("achievement_data").AddMinutes(60) > DateTime.UtcNow && data != null) {
var prompt = new SelectionPromptCompat<string>()
.Title(App.UsePreviousData)
.AddChoices(App.CommonYes, App.CommonNo);
if (prompt.Prompt() == App.CommonYes) {
Export.Choose(data);
return;
}
}
StartAndWaitResult(AppConfig.GamePath, new Dictionary<int, Func<BinaryReader, bool>> {
{ 1, AchievementAllDataNotify.OnReceive },
{ 2, PlayerStoreNotify.OnReceive },
{ 100, PlayerPropNotify.OnReceive },
}, () => {
#if DEBUG_EX
PlayerPropNotify.OnFinish();
File.WriteAllText("store_data.json", JsonSerializer.Serialize(PlayerStoreNotify.Instance, new JsonSerializerOptions {
WriteIndented = true,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
}));
#endif
AchievementAllDataNotify.OnFinish();
Environment.Exit(0);
});
while (true) {}
}
[ModuleInitializer]
internal static void SetupConsole() {
SetQuickEditMode(false);
Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8;
FixTerminalFont();
}
}

View File

@@ -0,0 +1,53 @@
using System.Diagnostics.CodeAnalysis;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using Google.Protobuf;
using Proto;
namespace YaeAchievement.Utilities;
public static class CacheFile {
static CacheFile() {
// remove deprecated cache
foreach (var file in Directory.EnumerateFiles(GlobalVars.CachePath, "*.miko")) {
File.Delete(file);
}
}
public static DateTime GetLastWriteTime(string id) {
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
return File.Exists(fileName) ? File.GetLastWriteTimeUtc(fileName) : DateTime.UnixEpoch;
}
public static bool TryRead(string id, [NotNullWhen(true)] out CacheItem? item) {
item = null;
try {
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
using var fileStream = File.OpenRead(fileName);
using var zipStream = new GZipStream(fileStream, CompressionMode.Decompress);
item = CacheItem.Parser.ParseFrom(zipStream);
return true;
} catch (Exception) {
return false;
}
}
public static void Write(string id, byte[] data, string? etag = null) {
var fileName = Path.Combine(GlobalVars.CachePath, $"{GetStrHash(id)}.nyan");
using var fileStream = File.Open(fileName, FileMode.Create);
using var zipStream = new GZipStream(fileStream, CompressionLevel.SmallestSize);
new CacheItem {
Etag = etag ?? string.Empty,
Version = 3,
Checksum = GetBinHash(data),
Content = ByteString.CopyFrom(data)
}.WriteTo(zipStream);
}
private static string GetStrHash(string value) => GetBinHash(Encoding.UTF8.GetBytes(value));
private static string GetBinHash(byte[] value) => Convert.ToHexStringLower(MD5.HashData(value));
}

View File

@@ -0,0 +1,31 @@
using System.ComponentModel;
namespace System.Collections.Generic {
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class CollectionExtensions {
public static IDictionary<TKey, TValue> RemoveValues<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary, params TKey[] keys
) {
foreach (var key in keys) {
dictionary.Remove(key);
}
return dictionary;
}
}
}
namespace System.Linq {
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class EnumerableExtensions {
public static IEnumerable<IGrouping<TKey, TKey>> GroupKeys<TKey, TValue>(
this IEnumerable<Dictionary<TKey, TValue>> source,
Func<TValue, bool> condition
) where TKey : notnull => source
.SelectMany(dict => dict.Where(pair => condition(pair.Value)).Select(pair => pair.Key))
.GroupBy(x => x);
}
}

View File

@@ -0,0 +1,24 @@
using System.ComponentModel;
using Windows.Win32;
using Windows.Win32.Foundation;
using static Windows.Win32.System.Threading.PROCESS_ACCESS_RIGHTS;
// ReSharper disable CheckNamespace
namespace System.Diagnostics;
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class ProcessExtensions {
public static unsafe string? GetFileName(this Process process) {
using var hProc = Native.OpenProcess_SafeHandle(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (uint) process.Id);
if (hProc.IsInvalid) {
return null;
}
var sProcPath = stackalloc char[32767];
return Native.GetModuleFileNameEx((HANDLE) hProc.DangerousGetHandle(), HMODULE.Null, sProcPath, 32767) == 0
? null
: new string(sProcPath);
}
}

View File

@@ -0,0 +1,38 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Spectre.Console;
// ReSharper disable CheckNamespace
namespace Google.Protobuf;
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class BinaryReaderExtensions {
public static byte[] ReadBytes(this BinaryReader reader) {
try {
var length = reader.ReadInt32();
if (length is < 0 or > 114514 * 2) {
throw new ArgumentException(nameof(length));
}
return reader.ReadBytes(length);
} catch (Exception e) when (e is IOException or ArgumentException) {
AnsiConsole.WriteLine(App.StreamReadDataFail);
Environment.Exit(-1);
throw new UnreachableException();
}
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class CodedInputStreamExtensions {
[UnsafeAccessor(UnsafeAccessorKind.Method)]
private static extern byte[] ReadRawBytes(CodedInputStream stream, int size);
public static CodedInputStream ReadLengthDelimitedAsStream(this CodedInputStream stream) {
return new CodedInputStream(ReadRawBytes(stream, stream.ReadLength()));
}
}

View File

@@ -0,0 +1,82 @@
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Threading;
using static Windows.Win32.System.Memory.VIRTUAL_ALLOCATION_TYPE;
using static Windows.Win32.System.Memory.PAGE_PROTECTION_FLAGS;
using static Windows.Win32.System.Memory.VIRTUAL_FREE_TYPE;
// ReSharper disable MemberCanBePrivate.Global
namespace YaeAchievement.Utilities;
public sealed unsafe class GameProcess {
public uint Id { get; }
public HANDLE Handle { get; }
public HANDLE MainThreadHandle { get; }
public event Action? OnExit;
public GameProcess(string path) {
const PROCESS_CREATION_FLAGS flags = PROCESS_CREATION_FLAGS.CREATE_SUSPENDED;
Span<char> cmdLines = stackalloc char[1]; // "\0"
var si = new STARTUPINFOW {
cb = (uint) sizeof(STARTUPINFOW)
};
var wd = Path.GetDirectoryName(path)!;
if (!Native.CreateProcess(path, ref cmdLines, null, null, false, flags, null, wd, si, out var pi)) {
var argumentData = new Dictionary<string, object> {
{ "path", path },
{ "workdir", wd },
{ "file_exists", File.Exists(path) },
};
throw new ApplicationException($"CreateProcess fail: {Marshal.GetLastPInvokeErrorMessage()}") {
Data = {
{ "sentry:context:Arguments", argumentData }
}
};
}
Id = pi.dwProcessId;
Handle = pi.hProcess;
MainThreadHandle = pi.hThread;
Task.Run(() => {
Native.WaitForSingleObject(Handle, 0xFFFFFFFF); // INFINITE
OnExit?.Invoke();
}).ContinueWith(task => { if (task.IsFaulted) Utils.OnUnhandledException(task.Exception!); });
}
public void LoadLibrary(string libPath) {
var hKrnl32 = NativeLibrary.Load("kernel32");
var mLoadLibraryW = NativeLibrary.GetExport(hKrnl32, "LoadLibraryW");
var libPathLen = (uint) libPath.Length * sizeof(char);
var lpLibPath = Native.VirtualAllocEx(Handle, null, libPathLen + 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (lpLibPath == null) {
throw new ApplicationException($"VirtualAllocEx fail: {Marshal.GetLastPInvokeErrorMessage()}");
}
fixed (void* lpBuffer = libPath) {
if (!Native.WriteProcessMemory(Handle, lpLibPath, lpBuffer, libPathLen)) {
throw new ApplicationException($"WriteProcessMemory fail: {Marshal.GetLastPInvokeErrorMessage()}");
}
}
var lpStartAddress = (delegate*unmanaged[Stdcall]<void*, uint>) mLoadLibraryW; // THREAD_START_ROUTINE
var hThread = Native.CreateRemoteThread(Handle, null, 0, lpStartAddress, lpLibPath, 0);
if (hThread.IsNull) {
var error = Marshal.GetLastPInvokeErrorMessage();
Native.VirtualFreeEx(Handle, lpLibPath, 0, MEM_RELEASE);
throw new ApplicationException($"CreateRemoteThread fail: {error}");
}
if (Native.WaitForSingleObject(hThread, 2000) == 0) {
Native.VirtualFreeEx(Handle, lpLibPath, 0, MEM_RELEASE);
}
Native.CloseHandle(hThread);
}
public bool ResumeMainThread() => Native.ResumeThread(MainThreadHandle) != 0xFFFFFFFF;
public bool Terminate(uint exitCode) => Native.TerminateProcess(Handle, exitCode);
}

View File

@@ -0,0 +1,47 @@
using Spectre.Console;
namespace YaeAchievement.Utilities;
public sealed class SelectionPromptCompat<T> where T : notnull {
private readonly List<T> _choices = [];
private readonly SelectionPrompt<T> _prompt = new ();
public SelectionPromptCompat<T> Title(string? title) {
_prompt.Title = title;
return this;
}
public SelectionPromptCompat<T> AddChoices(params IEnumerable<T> choices) {
foreach (var choice in choices) {
_prompt.AddChoice(choice);
_choices.Add(choice);
}
return this;
}
public T Prompt() {
if (AnsiConsole.Profile.Capabilities.Ansi) {
var title = _prompt.Title;
_prompt.Title += $" ({App.SelectionPromptCompatAnsiTip})";
var result = AnsiConsole.Prompt(_prompt);
_prompt.Title = title;
return result;
}
if (_prompt.Title != null) {
AnsiConsole.WriteLine(_prompt.Title + $" ({App.SelectionPromptCompatNonAnsiTip})");
}
for (var i = 0; i < _choices.Count; i++) {
var choice = _choices[i];
AnsiConsole.WriteLine($"[{i}] {choice}");
}
var choosePrompt = new TextPrompt<int>(App.SelectionPromptCompatChooseOne).Validate(i => {
if (i < 0 || i >= _choices.Count) {
return ValidationResult.Error(string.Format(App.SelectionPromptCompatInvalidChoice, _choices.Count - 1));
}
return ValidationResult.Success();
});
var resultIndex = AnsiConsole.Prompt(choosePrompt);
return _choices[resultIndex];
}
}

263
YaeAchievement/src/Utils.cs Normal file
View File

@@ -0,0 +1,263 @@
using System.ComponentModel;
using System.Globalization;
using System.IO.Pipes;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Console;
using Proto;
using Spectre.Console;
using YaeAchievement.Utilities;
namespace YaeAchievement;
public static class Utils {
public static HttpClient CHttpClient { get; } = new (new SentryHttpMessageHandler(new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip
})) {
DefaultRequestHeaders = {
UserAgent = {
new ProductInfoHeaderValue("YaeAchievement", GlobalVars.AppVersion.ToString(2))
}
}
};
public static async Task<byte[]> GetBucketFile(string path, bool useCache = true) {
var transaction = SentrySdk.StartTransaction(path, "bucket.get");
SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);
try {
var data = await GetFile("https://api.qhy04.com/hutaocdn/download?filename={0}", path, useCache);
transaction.Finish();
return data;
} catch (Exception e) when (e is SocketException or TaskCanceledException or HttpRequestException) {
}
try {
var data = await Task.WhenAny(
GetFile("https://rin.holohat.work/{0}", path, useCache),
GetFile("https://cn-cd-1259389942.file.myqcloud.com/{0}", path, useCache)
).Unwrap();
transaction.Finish();
return data;
} catch (Exception ex) when (ex is HttpRequestException or SocketException or TaskCanceledException) {
transaction.Finish();
AnsiConsole.WriteLine(App.NetworkError, ex.Message);
Environment.Exit(-1);
}
throw new UnreachableException();
static async Task<byte[]> GetFile(string baseUrl, string objectKey, bool useCache) {
using var reqwest = new HttpRequestMessage(HttpMethod.Get, string.Format(baseUrl, objectKey));
CacheItem? cache = null;
if (useCache && CacheFile.TryRead(objectKey, out cache)) {
reqwest.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
}
using var response = await CHttpClient.SendAsync(reqwest);
if (cache != null && response.StatusCode == HttpStatusCode.NotModified) {
return cache.Content.ToByteArray();
}
response.EnsureSuccessStatusCode();
var bytes = await response.Content.ReadAsByteArrayAsync();
if (useCache) {
var etag = response.Headers.ETag!.Tag;
CacheFile.Write(objectKey, bytes, etag);
}
return bytes;
}
}
public static T? GetOrNull<T>(this T[] array, uint index) where T : class {
return array.Length > index ? array[index] : null;
}
public static int ToIntOrDefault(string? value, int defaultValue = 0) {
return value != null && int.TryParse(value, out var result) ? result : defaultValue;
}
public static bool ToBooleanOrDefault(string? value, bool defaultValue = false) {
return value != null && bool.TryParse(value, out var result) ? result : defaultValue;
}
public static unsafe void CopyToClipboard(string text) {
if (Native.OpenClipboard(HWND.Null)) {
Native.EmptyClipboard();
var hGlobal = (HGLOBAL) Marshal.AllocHGlobal((text.Length + 1) * 2);
var hPtr = (nint) Native.GlobalLock(hGlobal);
Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length);
Native.GlobalUnlock((HGLOBAL) hPtr);
Native.SetClipboardData(13, new HANDLE(hPtr));
Marshal.FreeHGlobal(hGlobal);
Native.CloseClipboard();
} else {
throw new Win32Exception();
}
}
// ReSharper disable once NotAccessedField.Local
private static UpdateInfo _updateInfo = null!;
public static Task StartSpinnerAsync(string status, Func<StatusContext, Task> func) {
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
}
public static Task<T> StartSpinnerAsync<T>(string status, Func<StatusContext, Task<T>> func) {
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
}
public static async Task CheckUpdate(bool useLocalLib) {
try {
var versionData = await StartSpinnerAsync(App.UpdateChecking, _ => GetBucketFile("schicksal/version"));
var versionInfo = UpdateInfo.Parser.ParseFrom(versionData)!;
if (GlobalVars.AppVersionCode < versionInfo.VersionCode) {
AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName);
AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description);
if (versionInfo.EnableAutoUpdate) {
var newBin = await StartSpinnerAsync(App.UpdateDownloading, _ => GetBucketFile(versionInfo.PackageLink));
var tmpPath = Path.GetTempFileName();
var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe");
await using (var dstStream = File.Open($"{GlobalVars.DataPath}/update.exe", FileMode.Create)) {
await using var srcStream = typeof(Program).Assembly.GetManifestResourceStream("updater")!;
await srcStream.CopyToAsync(dstStream);
}
await File.WriteAllBytesAsync(tmpPath, newBin);
ShellOpen(updaterPath, $"{Environment.ProcessId} \"{tmpPath}\"");
await StartSpinnerAsync(App.UpdateChecking, _ => Task.Delay(1919810));
GlobalVars.PauseOnExit = false;
Environment.Exit(0);
}
AnsiConsole.MarkupLine($"[link]{App.DownloadLink}[/]", versionInfo.PackageLink);
if (versionInfo.ForceUpdate) {
Environment.Exit(0);
}
}
if (versionInfo.EnableLibDownload && !useLocalLib) {
var data = await GetBucketFile("schicksal/lic.dll");
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data); // 要求重启电脑
}
_updateInfo = versionInfo;
} catch (IOException e) when ((uint) e.HResult == 0x80070020) { // ERROR_SHARING_VIOLATION
// IO_SharingViolation_File
// The process cannot access the file '{0}' because it is being used by another process.
AnsiConsole.WriteLine("文件 {0} 被其它程序占用,请 重启电脑 或 解除文件占用 后重试。", e.Message[36..^46]);
Environment.Exit(-1);
}
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static bool ShellOpen(string path, string? args = null) {
try {
var startInfo = new ProcessStartInfo {
FileName = path,
UseShellExecute = true
};
if (args != null) {
startInfo.Arguments = args;
}
return new Process {
StartInfo = startInfo
}.Start();
} catch (Exception) {
return false;
}
}
internal static Process? GetGameProcess() => Process.GetProcessesByName("YuanShen")
.Concat(Process.GetProcessesByName("GenshinImpact"))
.FirstOrDefault(p => File.Exists($"{p.GetFileName()}/../HoYoKProtect.sys"));
private static GameProcess? _proc;
public static void InstallExitHook() {
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
_proc?.Terminate(0);
if (GlobalVars.PauseOnExit) {
AnsiConsole.WriteLine(App.PressKeyToExit);
Console.ReadKey();
}
};
}
public static void InstallExceptionHook() {
AppDomain.CurrentDomain.UnhandledException += (_, e) => OnUnhandledException((Exception) e.ExceptionObject);
}
public static void OnUnhandledException(Exception ex) {
SentrySdk.CaptureException(ex);
switch (ex) {
case ApplicationException ex1:
AnsiConsole.WriteLine(ex1.Message);
break;
case SocketException ex2:
AnsiConsole.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
break;
case HttpRequestException ex3:
AnsiConsole.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
break;
default:
AnsiConsole.WriteLine(ex.ToString());
break;
}
Environment.Exit(-1);
}
private static bool _isUnexpectedExit = true;
// ReSharper disable once UnusedMethodReturnValue.Global
public static void StartAndWaitResult(string exePath, Dictionary<int, Func<BinaryReader, bool>> handlers, Action onFinish) {
_proc = new GameProcess(exePath);
_proc.OnExit += () => {
if (_isUnexpectedExit) {
_proc = null;
AnsiConsole.WriteLine(App.GameProcessExit);
Environment.Exit(114514);
}
};
_proc.LoadLibrary(GlobalVars.LibFilePath);
_proc.ResumeMainThread();
AnsiConsole.WriteLine(App.GameLoading, _proc.Id);
Task.Run(() => {
using var stream = new NamedPipeServerStream(GlobalVars.PipeName);
using var reader = new BinaryReader(stream);
stream.WaitForConnection();
int type;
while ((type = stream.ReadByte()) != -1) {
if (type == 0xFF) {
_isUnexpectedExit = false;
onFinish();
break;
}
if (handlers.TryGetValue(type, out var handler)) {
if (handler(reader)) {
handlers.Remove(type);
}
}
}
}).ContinueWith(task => { if (task.IsFaulted) OnUnhandledException(task.Exception!); });
}
internal static unsafe void SetQuickEditMode(bool enable) {
var handle = Native.GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
CONSOLE_MODE mode = default;
Native.GetConsoleMode(handle, &mode);
mode = enable ? mode | CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE : mode &~CONSOLE_MODE.ENABLE_QUICK_EDIT_MODE;
Native.SetConsoleMode(handle, mode);
}
internal static unsafe void FixTerminalFont() {
if (!CultureInfo.CurrentCulture.Name.StartsWith("zh")) {
return;
}
var handle = Native.GetStdHandle(STD_HANDLE.STD_OUTPUT_HANDLE);
var fontInfo = new CONSOLE_FONT_INFOEX {
cbSize = (uint) sizeof(CONSOLE_FONT_INFOEX)
};
if (!Native.GetCurrentConsoleFontEx(handle, false, &fontInfo)) {
return;
}
if (fontInfo.FaceName.ToString() == "Terminal") { // 点阵字体
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US"); // todo: use better way like auto set console font etc.
}
}
}

View File

@@ -0,0 +1,17 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>Yae.Lib</id>
<version>5.3.4</version>
<authors>HolographicHat</authors>
<developmentDependency>true</developmentDependency>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">GPL-3.0-only</license>
<licenseUrl>https://licenses.nuget.org/GPL-3.0-only</licenseUrl>
<projectUrl>https://github.com/HolographicHat/Yae</projectUrl>
<description>Yae Lib</description>
<repository type="git" url="https://github.com/HolographicHat/Yae" commit="$commit$" />
</metadata>
<files>
<file src="build\x64\Release\YaeLib.dll" target="runtimes\win-x64\native" />
</files>
</package>

View File

@@ -20,9 +20,9 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
@@ -30,7 +30,6 @@
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<EnableASAN>false</EnableASAN>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
@@ -45,88 +44,69 @@
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir>
<IntDir>build\$(Platform)\$(Configuration)\</IntDir>
<TargetName>YaeLib</TargetName>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\$(Platform)\$(Configuration)\</OutDir>
<IntDir>build\$(Platform)\$(Configuration)\</IntDir>
<GenerateManifest>false</GenerateManifest>
<TargetName>YaeLib</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<AdditionalIncludeDirectories>$(ProjectDir)lib\detours\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>$(ProjectDir)lib\detours\;$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>detours-x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>NotSet</SubSystem>
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
</Link>
<PostBuildEvent>
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0</Command>
</PostBuildEvent>
<PostBuildEvent />
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<DebugInformationFormat>None</DebugInformationFormat>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>$(ProjectDir)lib\detours\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<SDLCheck>true</SDLCheck>
<LanguageStandard_C>stdc11</LanguageStandard_C>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<SubSystem>NotSet</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>$(ProjectDir)lib\detours\;$(OutDir)</AdditionalLibraryDirectories>
<AdditionalDependencies>detours-x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
</Link>
<PostBuildEvent>
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0\win-x64\YaeAchievementLib.dll /y</Command>
</PostBuildEvent>
<PostBuildEvent />
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="src\HookManager.h" />
<ClInclude Include="src\il2cpp-api-functions.h" />
<ClInclude Include="src\il2cpp-appdata.h" />
<ClInclude Include="src\il2cpp-functions.h" />
<ClInclude Include="src\globals.h" />
<ClInclude Include="src\il2cpp-types.h" />
<ClInclude Include="src\il2cpp-init.h" />
<ClInclude Include="src\il2cpp-unity-functions.h" />
<ClInclude Include="src\pch.h" />
<ClInclude Include="src\NamedPipe.h" />
<ClInclude Include="src\ntprivate.h" />
<ClInclude Include="src\util.h" />
<ClInclude Include="src\Zydis.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\dllmain.cpp" />
<ClCompile Include="src\il2cpp-init.cpp" />
<ClCompile Include="src\pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="src\util.cpp" />
<ClCompile Include="src\Zydis.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +0,0 @@
//////////////////////////////////////////////////////////////////////////////
//
// Common version parameters.
//
// Microsoft Research Detours Package, Version 4.0.1
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#define _USING_V110_SDK71_ 1
#include "winver.h"
#if 0
#include <windows.h>
#include <detours.h>
#else
#ifndef DETOURS_STRINGIFY
#define DETOURS_STRINGIFY_(x) #x
#define DETOURS_STRINGIFY(x) DETOURS_STRINGIFY_(x)
#endif
#define VER_FILEFLAGSMASK 0x3fL
#define VER_FILEFLAGS 0x0L
#define VER_FILEOS 0x00040004L
#define VER_FILETYPE 0x00000002L
#define VER_FILESUBTYPE 0x00000000L
#endif
#define VER_DETOURS_BITS DETOURS_STRINGIFY(DETOURS_BITS)

View File

@@ -1,66 +0,0 @@
#pragma once
#include "pch.h"
#define CALL_ORIGIN(function, ...) \
HookManager::call(function, __func__, __VA_ARGS__)
class HookManager {
public:
template <typename Fn>
static void install(Fn func, Fn handler) {
enable(func, handler);
holderMap[reinterpret_cast<void*>(handler)] = reinterpret_cast<void*>(func);
}
template <typename Fn>
[[nodiscard]] static Fn getOrigin(Fn handler, const char* callerName = nullptr) noexcept {
if (holderMap.count(reinterpret_cast<void*>(handler)) == 0) {
printf("Origin not found for handler: %s. Maybe racing bug.", callerName == nullptr ? "<Unknown>" : callerName);
return nullptr;
}
return reinterpret_cast<Fn>(holderMap[reinterpret_cast<void*>(handler)]);
}
template <typename Fn>
[[nodiscard]] static void detach(Fn handler) noexcept {
disable(handler);
holderMap.erase(reinterpret_cast<void*>(handler));
}
template <typename RType, typename... Params>
[[nodiscard]] static RType call(RType(*handler)(Params...), const char* callerName = nullptr, Params... params) {
auto origin = getOrigin(handler, callerName);
if (origin != nullptr)
return origin(params...);
return RType();
}
static void detachAll() noexcept {
for (const auto &[key, value] : holderMap) {
disable(key);
}
holderMap.clear();
}
private:
inline static std::map<void*, void*> holderMap{};
template <typename Fn>
static void disable(Fn handler) {
Fn origin = getOrigin(handler);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)origin, handler);
DetourTransactionCommit();
}
template <typename Fn>
static void enable(Fn& func, Fn handler) {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)func, handler);
DetourTransactionCommit();
}
};

46
lib/src/NamedPipe.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <Windows.h>
#include <span>
template <typename T>
concept IsSpan = requires(T t) {
{ t.data() } -> std::convertible_to<const void*>;
{ t.size() } -> std::convertible_to<std::size_t>;
{ t.size_bytes() } -> std::convertible_to<std::size_t>;
};
class NamedPipe
{
HANDLE m_hPipe = INVALID_HANDLE_VALUE;
public:
NamedPipe(HANDLE hPipe) : m_hPipe(hPipe) {}
~NamedPipe() { if (m_hPipe != INVALID_HANDLE_VALUE) CloseHandle(m_hPipe); }
operator HANDLE() const { return m_hPipe; }
operator bool() const { return m_hPipe != INVALID_HANDLE_VALUE && m_hPipe != nullptr; }
NamedPipe& operator= (HANDLE hPipe) {
m_hPipe = hPipe;
return *this;
}
bool Write(const void* data, size_t size) const
{
DWORD bytesWritten;
if (!WriteFile(m_hPipe, data, static_cast<DWORD>(size), &bytesWritten, nullptr) || bytesWritten != size)
return false;
return true;
}
template <IsSpan T>
bool Write(const T& data) const
{
return Write(data.data(), data.size_bytes());
}
template <typename T>
bool Write(const T& data) const
{
return Write(&data, sizeof(T));
}
};

54990
lib/src/Zydis.c Normal file

File diff suppressed because one or more lines are too long

12113
lib/src/Zydis.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,113 +1,275 @@
#include "pch.h"
// ReSharper disable CppClangTidyCertErr33C
#include <Windows.h>
#include <print>
#include <string>
#include <future>
#include <TlHelp32.h>
#include "globals.h"
#include "util.h"
#include "il2cpp-init.h"
#include "il2cpp-types.h"
#include "ntprivate.h"
using Genshin::ByteArray, Genshin::ClientKcpEvent, Genshin::KcpPacket, Genshin::KcpEventType;
using std::to_string;
CRITICAL_SECTION CriticalSection;
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index);
HWND unityWnd = 0;
HANDLE hPipe = 0;
namespace
{
PacketType GetPacketType(const PacketMeta* packet)
{
using namespace Globals;
const auto cmdid = packet->CmdId;
if (AchievementId && cmdid == AchievementId)
return PacketType::Achivement;
std::set<UINT16> PacketWhitelist = { 105, 155, 187, 198, 2688, 20, 74 }; // Allow Protocol: GetPlayerToken, PlayerLogin, AchievementAllDataNotify, Ping
if (AchievementIdSet.contains(cmdid) && packet->DataLength > 500)
return PacketType::Achivement;
bool OnPacket(KcpPacket* pkt) {
if (pkt->data == nullptr) return true;
auto len = pkt->length;
auto data = (ByteArray*)new BYTE[len + 32];
data->max_length = len;
memcpy(data->vector, pkt->data, len);
Genshin::XorEncrypt(&data, len, nullptr);
if (ReadMapped<UINT16>(data->vector, 0) != 0x4567) {
delete[] data;
return true;
if (PlayerStoreId && cmdid == PlayerStoreId)
return PacketType::Inventory;
return PacketType::None;
}
if (!PacketWhitelist.contains(ReadMapped<UINT16>(data->vector, 2))) {
//ifdef _DEBUG
printf("Blocked cmdid: %d\n", ReadMapped<UINT16>(data->vector, 2));
//endif
delete[] data;
return false;
}
printf("Passed cmdid: %d\n", ReadMapped<UINT16>(data->vector, 2));
if (ReadMapped<UINT16>(data->vector, 2) == 2688) {
auto headLength = ReadMapped<UINT16>(data->vector, 4);
auto dataLength = ReadMapped<UINT32>(data->vector, 6);
auto iStr = Genshin::ToBase64String(data, 10 + headLength, dataLength, nullptr);
auto cStr = IlStringToString(iStr) + "\n";
WriteFile(hPipe, cStr.c_str(), cStr.length(), nullptr, nullptr);
CloseHandle(hPipe);
ExitProcess(0);
}
delete[] data;
return true;
}
std::map<INT, UINT> signatures;
namespace Hook {
int KcpSend(void* client, KcpPacket* pkt, void* method) {
return OnPacket(pkt) ? CALL_ORIGIN(KcpSend, client, pkt, method) : 0;
}
void SetVersion(void* obj, Il2CppString* value, void* method) {
auto version = IlStringToString(value);
value = string_new(version + " YaeAchievement");
CALL_ORIGIN(SetVersion, obj, value, method);
}
uint16_t __fastcall BitConverter_ToUInt16(Array<uint8_t>* val, const int startIndex)
{
using namespace Globals;
const auto ToUInt16 = reinterpret_cast<decltype(&BitConverter_ToUInt16)>(Offset.BitConverter_ToUInt16);
bool KcpRecv(void* client, ClientKcpEvent* evt, void* method) {
auto result = CALL_ORIGIN(KcpRecv, client, evt, method);
if (result == 0 || evt->fields.type != KcpEventType::EventRecvMsg) {
return result;
EnterCriticalSection(&CriticalSection);
SetBreakpoint((HANDLE)-2, 0, false, 0);
const auto ret = ToUInt16(val, startIndex);
SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true, 0);
LeaveCriticalSection(&CriticalSection);
if (ret != 0xAB89)
return ret;
const auto packet = val->As<PacketMeta*>();
const auto packetType = GetPacketType(packet);
if (packetType == PacketType::None)
return ret;
#ifdef _DEBUG
std::println("PacketType: {}", static_cast<uint8_t>(packetType));
std::println("CmdId: {}", packet->CmdId);
std::println("DataLength: {}", packet->DataLength);
//std::println("Data: {}", Util::Base64Encode(packet->AsSpan()));
#endif
if (!MessagePipe.Write(packetType))
Util::Win32ErrorDialog(1002, GetLastError());
if (!MessagePipe.Write(packet->DataLength))
Util::Win32ErrorDialog(1003, GetLastError());
if (!MessagePipe.Write(packet->AsSpan()))
Util::Win32ErrorDialog(1004, GetLastError());
if (!AchievementsWritten)
AchievementsWritten = packetType == PacketType::Achivement;
if (!PlayerStoreWritten)
PlayerStoreWritten = packetType == PacketType::Inventory;
if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0)
{
if (!MessagePipe.Write(PacketType::End))
Util::Win32ErrorDialog(9001, GetLastError());
#ifdef _DEBUG
system("pause");
#endif
ExitProcess(0);
}
return OnPacket(evt->fields.packet) ? result : false;
return ret;
}
ByteArray* UnityEngine_RecordUserData(INT type) {
return GCHandle_GetObject<ByteArray>(signatures[type]);
void __fastcall AccountDataItem_UpdateNormalProp(const void* __this, const int type, const double value, const double lastValue, const int state)
{
using namespace Globals;
const auto UpdateNormalProp = reinterpret_cast<decltype(&AccountDataItem_UpdateNormalProp)>(Offset.AccountDataItem_UpdateNormalProp);
EnterCriticalSection(&CriticalSection);
SetBreakpoint((HANDLE)-2, 0, false, 1);
UpdateNormalProp(__this, type, value, lastValue, state);
SetBreakpoint((HANDLE)-2, Offset.AccountDataItem_UpdateNormalProp, true, 1);
LeaveCriticalSection(&CriticalSection);
#ifdef _DEBUG
std::println("PropType: {}", type);
std::println("PropState: {}", state);
std::println("PropValue: {}", value);
std::println("PropLastValue: {}", lastValue);
#endif
if (RequiredPlayerProperties.erase(type) != 0)
{
if (!MessagePipe.Write(PacketType::PropData))
Util::Win32ErrorDialog(2002, GetLastError());
if (!MessagePipe.Write(type))
Util::Win32ErrorDialog(2003, GetLastError());
if (!MessagePipe.Write(value))
Util::Win32ErrorDialog(2004, GetLastError());
}
if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0)
{
if (!MessagePipe.Write(PacketType::End))
Util::Win32ErrorDialog(9001, GetLastError());
#ifdef _DEBUG
system("pause");
#endif
ExitProcess(0);
}
}
}
void Run(HMODULE* phModule) {
//AllocConsole();
//freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
while (
GetModuleHandle("UserAssembly.dll") == nullptr ||
(unityWnd = FindMainWindowByPID(GetCurrentProcessId())) == 0
) {
Sleep(1000);
LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep)
{
using namespace Globals;
const auto exceptionRecord = ep->ExceptionRecord;
const auto contextRecord = ep->ContextRecord;
if (exceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
{
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.BitConverter_ToUInt16)) {
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::BitConverter_ToUInt16);
contextRecord->EFlags &= ~0x100; // clear the trap flag
return EXCEPTION_CONTINUE_EXECUTION;
}
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.AccountDataItem_UpdateNormalProp)) {
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::AccountDataItem_UpdateNormalProp);
contextRecord->EFlags &= ~0x100; // clear the trap flag
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
Sleep(5000);
DisableVMProtect();
InitIL2CPP();
for (int i = 0; i < 4; i++) {
auto result = Genshin::RecordUserData(i, nullptr);
signatures[i] = GCHandle_New(result, true);
}
signatures[3] = signatures[2];
HookManager::install(Genshin::KcpSend, Hook::KcpSend);
HookManager::install(Genshin::KcpRecv, Hook::KcpRecv);
HookManager::install(Genshin::SetVersion, Hook::SetVersion);
HookManager::install(Genshin::UnityEngine_RecordUserData, Hook::UnityEngine_RecordUserData);
hPipe = CreateFile(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (hPipe == INVALID_HANDLE_VALUE) {
Win32ErrorDialog(1001);
ExitProcess(0);
return EXCEPTION_CONTINUE_SEARCH;
}
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index)
{
using namespace Globals;
if (index > 3) {
return;
}
CONTEXT ctx{};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(thread, &ctx);
DWORD64* dr = &ctx.Dr0;
dr[index] = enable ? address : 0;
const auto mask = 1ull << (index * 2);
ctx.Dr7 |= mask;
SetThreadContext(thread, &ctx);
}
DWORD __stdcall ThreadProc(LPVOID hInstance)
{
#ifdef _DEBUG
AllocConsole();
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
system("pause");
#endif
InitializeCriticalSection(&CriticalSection);
auto initFuture = std::async(std::launch::async, InitIL2CPP);
using namespace Globals;
const auto pid = GetCurrentProcessId();
while ((GameWindow = Util::FindMainWindowByPID(pid)) == nullptr) {
SwitchToThread();
}
if (!initFuture.get())
ExitProcess(0);
MessagePipe = CreateFileA(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (!MessagePipe)
{
#ifdef _DEBUG
std::println("CreateFile failed: {}", GetLastError());
#else
Util::Win32ErrorDialog(1001, GetLastError());
ExitProcess(0);
#endif
}
AddVectoredExceptionHandler(1, VectoredExceptionHandler);
while (true)
{
THREADENTRY32 te32{};
te32.dwSize = sizeof(THREADENTRY32);
const auto hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
for (Thread32First(hSnapshot, &te32); Thread32Next(hSnapshot, &te32);)
{
if (te32.th32OwnerProcessID != pid) {
continue;
}
if (const auto hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID))
{
EnterCriticalSection(&CriticalSection);
SetBreakpoint(hThread, Offset.BitConverter_ToUInt16, true, 0);
SetBreakpoint(hThread, Offset.AccountDataItem_UpdateNormalProp, true, 1);
CloseHandle(hThread);
LeaveCriticalSection(&CriticalSection);
}
}
CloseHandle(hSnapshot);
Sleep(1);
}
return 0;
}
// DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReasonForCall, LPVOID lpReserved) {
switch (ulReasonForCall) {
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Run, new HMODULE(hModule), 0, NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
BOOL __stdcall DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved)
{
// Check injectee
WCHAR szFileName[MAX_PATH]{};
DWORD length = 0;
GetModuleFileNameW(NULL, szFileName, MAX_PATH);
CharUpperBuffW(szFileName, wcslen(szFileName));
if (!(wcsstr(szFileName, L"YUANSHEN.EXE") || wcsstr(szFileName, L"GENSHINIMPACT.EXE")))
{
return TRUE;
}
if (fdwReason == DLL_PROCESS_ATTACH)
{
if (hInstance)
{
LdrAddRefDll(LDR_ADDREF_DLL_PIN, hInstance);
}
if (const auto hThread = CreateThread(nullptr, 0, ThreadProc, hInstance, 0, nullptr)) {
CloseHandle(hThread);
}
}
return TRUE;
}
static LRESULT WINAPI YaeGetWindowHookImpl(int code, WPARAM wParam, LPARAM lParam)
{
return CallNextHookEx(NULL, code, wParam, lParam);
}
EXTERN_C __declspec(dllexport) HRESULT WINAPI YaeGetWindowHook(_Out_ HOOKPROC* pHookProc)
{
*pHookProc = YaeGetWindowHookImpl;
return S_OK;
}

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

@@ -0,0 +1,54 @@
#pragma once
#include <Windows.h>
#include <unordered_set>
#include "NamedPipe.h"
#define PROPERTY2(type, name, cn, os) \
type name##_cn = cn; \
type name##_os = os; \
type get_##name() { return Globals::IsCNREL ? name##_cn : name##_os; } \
void set_##name(type value) { if (Globals::IsCNREL) name##_cn = value; else name##_os = value; } \
__declspec(property(get = get_##name, put = set_##name)) type name;
namespace Globals
{
inline HWND GameWindow = nullptr;
inline NamedPipe MessagePipe = nullptr;
inline bool IsCNREL = true;
inline uintptr_t BaseAddress = 0;
// 5.1.0 - 24082
inline uint16_t AchievementId = 0; // use non-zero to override dynamic search
inline std::unordered_set<uint16_t> AchievementIdSet;
// 5.3.0 - 23233
inline uint16_t PlayerStoreId = 0; // use non-zero to override dynamic search
inline bool AchievementsWritten = false;
inline bool PlayerStoreWritten = false;
/*
* PROP_PLAYER_HCOIN = 10015,
* PROP_PLAYER_WAIT_SUB_HCOIN = 10022,
* PROP_PLAYER_SCOIN = 10016,
* PROP_PLAYER_WAIT_SUB_SCOIN = 10023,
* PROP_PLAYER_MCOIN = 10025,
* PROP_PLAYER_WAIT_SUB_MCOIN = 10026,
* PROP_PLAYER_HOME_COIN = 10042,
* PROP_PLAYER_WAIT_SUB_HOME_COIN = 10043,
* PROP_PLAYER_ROLE_COMBAT_COIN = 10053,
* PROP_PLAYER_MUSIC_GAME_BOOK_COIN = 10058,
*/
inline std::unordered_set<int> RequiredPlayerProperties = { 10015, 10022, 10016, 10023, 10025, 10026, 10042, 10043, 10053, 10058 };
class Offsets
{
public:
PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0, 0);
//PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0x0F826CF0, 0x0F825F10); // use non-zero to override dynamic search
PROPERTY2(uintptr_t, AccountDataItem_UpdateNormalProp, 0, 0);
//PROPERTY2(uintptr_t, AccountDataItem_UpdateNormalProp, 0x0D9FE060, 0x0D94D910); // use non-zero to override dynamic search
};
inline Offsets Offset;
}

View File

@@ -1,3 +0,0 @@
DO_API(0x02D2B620, 0x02D290A0, uint32_t, il2cpp_gchandle_new, (Il2CppObject* obj, bool pinned)); // search GCHandle.cpp in strings 2974550
DO_API(0x02D2B330, 0x02D28DB0, Il2CppObject*, il2cpp_gchandle_get_target, (uint32_t gchandle));
DO_API(0x02D2E840, 0x02D2C0A0, Il2CppString*, il2cpp_string_new, (const char* str));

View File

@@ -1,20 +0,0 @@
#pragma once
#include "il2cpp-types.h"
// IL2CPP APIs
#define DO_API(ca, oa, r, n, p) extern r (*n) p
#include "il2cpp-api-functions.h"
#undef DO_API
// Application-specific functions
#define DO_APP_FUNC(ca, oa, r, n, p) extern r (*n) p
namespace Genshin {
#include "il2cpp-functions.h"
}
#undef DO_APP_FUNC
#define DO_UNI_FUNC(ca, oa, r, n, p) extern r (*n) p
namespace Genshin {
#include "il2cpp-unity-functions.h"
}
#undef DO_UNI_FUNC

View File

@@ -1,31 +0,0 @@
using namespace Genshin;
// DO_APP_FUNC(CN_OFFSET, OS_OFFSET, RETURN, FUNC_NAME, (ARGS...));
// N: System.Convert$ToBase64String
// L: mscorlib
DO_APP_FUNC(0x08F056A0, 0x08F1A0F0, Il2CppString*, ToBase64String, (ByteArray* value, int offset, int length, void* method));
// N: MoleMole.MonoLoginMainPage.version$set
// L: Assembly-CSharp
DO_APP_FUNC(0X05265A70, 0x052704C0, void, SetVersion, (void* obj, Il2CppString* value, void* method));
// N: UnityEngine.Application$RecordUserData
// L: UnityEngine.CoreModule
DO_APP_FUNC(0x09932F30, 0x09947590, ByteArray*, RecordUserData, (int32_t nType, void* method));
// N: MoleMole.Packet$XorEncrypt [Obfuscated]
// L: Assembly-CSharp
DO_APP_FUNC(0x054B3120, 0x054BD670, void, XorEncrypt, (ByteArray** data, int length, void* method)); // OHOBJBNAMJM
// N: Kcp.KcpNative$kcp_client_send_packet [Obfuscated]
// L: Assembly-CSharp
DO_APP_FUNC(0x050BB390, 0x050C5AC0, int, KcpSend, (void* client, KcpPacket* pkt, void* method)); // CKKPANHPFAP
// N: MoleMole.KcpClient$TryDequeueEvent [Obfuscated]
// L: Assembly-CSharp
DO_APP_FUNC(0x04496B50, 0x0449DBF0, bool, KcpRecv, (void* client, ClientKcpEvent* evt, void* method)); // GFFOOBHMCOJ
DO_APP_FUNC(0x09254010, 0x09269550, LPVOID, GetDefaultEncoding, (void* method));
DO_APP_FUNC(0x092538C0, 0x09268DE0, Il2CppString*, GetString, (LPVOID encoding, LPVOID bytes, void* method));

View File

@@ -1,39 +1,588 @@
#include "pch.h"
#include <Windows.h>
#include <print>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
#include <ranges>
#include <unordered_set>
#include <unordered_map>
#include <future>
#include <mutex>
#include <immintrin.h>
#include "il2cpp-init.h"
#include "globals.h"
#include "Zydis.h"
#include "util.h"
#define DO_API(ca, oa, r, n, p) r (*n) p
#include "il2cpp-api-functions.h"
#undef DO_API
namespace
{
class DecodedInstruction
{
public:
DecodedInstruction() = default;
~DecodedInstruction() = default;
DecodedInstruction(const ZydisDecodedInstruction& instruction) : Instruction(instruction) {}
DecodedInstruction(const ZydisDecodedInstruction& instruction, ZydisDecodedOperand* operands, uint8_t operandCount) : Instruction(instruction) {
Operands = { operands, operands + operandCount };
}
DecodedInstruction(const uint32_t rva, const ZydisDecodedInstruction& instruction, ZydisDecodedOperand* operands, uint8_t operandCount) : RVA(rva), Instruction(instruction) {
Operands = { operands, operands + operandCount };
}
// copy constructor
DecodedInstruction(const DecodedInstruction& other) = default;
// move constructor
DecodedInstruction(DecodedInstruction&& other) noexcept : RVA(other.RVA), Instruction(other.Instruction), Operands(std::move(other.Operands)) {}
uint32_t RVA = 0;
ZydisDecodedInstruction Instruction;
std::vector<ZydisDecodedOperand> Operands;
};
std::span<uint8_t> GetSection(LPCSTR name)
{
using namespace Globals;
if (BaseAddress == 0)
return {};
const auto dosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
const auto ntHeader = (PIMAGE_NT_HEADERS)((uintptr_t)dosHeader + dosHeader->e_lfanew);
const auto sectionHeader = IMAGE_FIRST_SECTION(ntHeader);
for (auto i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
{
if (strcmp((char*)sectionHeader[i].Name, name) == 0)
{
const auto sectionSize = sectionHeader[i].Misc.VirtualSize;
const auto virtualAddress = BaseAddress + sectionHeader[i].VirtualAddress;
return std::span(reinterpret_cast<uint8_t*>(virtualAddress), sectionSize);
}
}
return {};
}
/// <summary>
/// decodes all instruction until next push, ignores branching
/// </summary>
/// <param name="address"></param>
/// <param name="maxInstructions"></param>
/// <returns>std::vector DecodedInstruction</returns>
std::vector<DecodedInstruction> DecodeFunction(uintptr_t address, int32_t maxInstructions = -1)
{
using namespace Globals;
std::vector<DecodedInstruction> instructions;
ZydisDecoder decoder{};
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
ZydisDecodedInstruction instruction{};
ZydisDecoderContext context{};
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT_VISIBLE]{};
while (true)
{
const auto data = reinterpret_cast<uint8_t*>(address);
auto status = ZydisDecoderDecodeInstruction(&decoder, &context, data, ZYDIS_MAX_INSTRUCTION_LENGTH, &instruction);
if (!ZYAN_SUCCESS(status))
{
// for skipping jump tables
address += 1;
continue;
}
status = ZydisDecoderDecodeOperands(&decoder, &context, &instruction, operands, instruction.operand_count_visible);
if (!ZYAN_SUCCESS(status))
{
// for skipping jump tables
address += 1;
continue;
}
if (instruction.mnemonic == ZYDIS_MNEMONIC_PUSH && !instructions.empty()) {
break;
}
const auto rva = static_cast<uint32_t>(address - BaseAddress);
instructions.emplace_back(rva, instruction, operands, instruction.operand_count_visible);
address += instruction.length;
if (maxInstructions != -1 && instructions.size() >= maxInstructions)
break;
}
return instructions;
}
/// <summary>
/// get the count of data references in the instructions (only second oprand of mov)
/// </summary>
/// <param name="instructions"></param>
/// <returns></returns>
int32_t GetDataReferenceCount(const std::vector<DecodedInstruction>& instructions)
{
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
if (instr.Instruction.mnemonic != ZYDIS_MNEMONIC_MOV)
return false;
if (instr.Operands.size() != 2)
return false;
const auto& op = instr.Operands[1];
// access to memory, based off of rip, 32-bit displacement
return op.type == ZYDIS_OPERAND_TYPE_MEMORY && op.mem.base == ZYDIS_REGISTER_RIP && op.mem.disp.has_displacement;
}));
}
int32_t GetCallCount(const std::vector<DecodedInstruction>& instructions)
{
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
return instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL;
}));
}
int32_t GetUniqueCallCount(const std::vector<DecodedInstruction>& instructions)
{
std::unordered_set<uint32_t> calls;
for (const auto& instr : instructions)
{
if (instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) {
uint32_t destination = instr.Operands[0].imm.value.s + instr.RVA + instr.Instruction.length;
calls.insert(destination);
}
}
return static_cast<int32_t>(calls.size());
}
int32_t GetCmpImmCount(const std::vector<DecodedInstruction>& instructions)
{
return static_cast<int32_t>(std::ranges::count_if(instructions, [](const DecodedInstruction& instr) {
return instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP && instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && instr.Operands[1].imm.value.u;
}));
}
void ResolveAchivementCmdId()
{
if (Globals::AchievementId != 0)
return;
const auto il2cppSection = GetSection("il2cpp");
std::println("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
if (il2cppSection.empty())
return; // message box?
const auto candidates = Util::PatternScanAll(il2cppSection, "56 48 83 EC 20 48 89 D0 48 89 CE 80 3D ? ? ? ? 00");
std::println("Candidates: {}", candidates.size());
std::vector<std::vector<DecodedInstruction>> filteredInstructions;
std::ranges::copy_if(
candidates | std::views::transform([](auto va) { return DecodeFunction(va); }),
std::back_inserter(filteredInstructions),
[](const std::vector<DecodedInstruction>& instr) {
return GetDataReferenceCount(instr) == 5 && GetCallCount(instr) == 10 &&
GetUniqueCallCount(instr) == 6 && GetCmpImmCount(instr) == 5;
});
// should have only one result
if (filteredInstructions.size() != 1)
{
std::println("Filtered Instructions: {}", filteredInstructions.size());
return;
}
const auto& instructions = filteredInstructions[0];
std::println("RVA: 0x{:08X}", instructions.front().RVA);
// extract all the non-zero immediate values from the cmp instructions
std::vector<uint32_t> cmdIds;
std::ranges::for_each(instructions, [&cmdIds](const DecodedInstruction& instr) {
if (instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP &&
instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
instr.Operands[1].imm.value.u != 0) {
cmdIds.push_back(static_cast<uint32_t>(instr.Operands[1].imm.value.u));
}
});
for (const auto& cmdId : cmdIds)
{
std::println("AchievementId: {}", cmdId);
Globals::AchievementIdSet.insert(static_cast<uint16_t>(cmdId));
}
}
std::vector<uintptr_t> GetCalls(uint8_t* target)
{
const auto il2cppSection = GetSection("il2cpp");
const auto sectionAddress = reinterpret_cast<uintptr_t>(il2cppSection.data());
const auto sectionSize = il2cppSection.size();
std::vector<uintptr_t> callSites;
const __m128i callOpcode = _mm_set1_epi8(0xE8);
const size_t simdEnd = sectionSize / 16 * 16;
for (size_t i = 0; i < simdEnd; i += 16) {
// load 16 bytes from the current address
const __m128i chunk = _mm_loadu_si128((__m128i*)(sectionAddress + i));
// compare the loaded chunk with 0xE8 in all 16 bytes
const __m128i result = _mm_cmpeq_epi8(chunk, callOpcode);
// move the comparison results into a mask
int mask = _mm_movemask_epi8(result);
while (mask != 0) {
DWORD first_match_idx = 0;
_BitScanForward(&first_match_idx, mask); // index of the first set bit (match)
// index of the instruction
const size_t instruction_index = i + first_match_idx;
const int32_t delta = *(int32_t*)(sectionAddress + instruction_index + 1);
const uintptr_t dest = sectionAddress + instruction_index + 5 + delta;
if (dest == (uintptr_t)target) {
callSites.push_back(sectionAddress + instruction_index);
}
// clear the bit we just processed and continue with the next match
mask &= ~(1 << first_match_idx);
}
}
return callSites;
}
uintptr_t FindFunctionEntry(uintptr_t address) // not a correct way to find function entry
{
__try
{
while (true)
{
// go back to 'sub rsp' instruction
uint32_t code = *(uint32_t*)address;
code &= ~0xFF000000;
if (_byteswap_ulong(code) == 0x4883EC00) { // sub rsp, ??
return address;
}
address--;
}
}
__except (1) {}
return address;
}
void Resolve_BitConverter_ToUInt16()
{
if (Globals::Offset.BitConverter_ToUInt16 != 0) {
Globals::Offset.BitConverter_ToUInt16 += Globals::BaseAddress;
return;
}
const auto il2cppSection = GetSection("il2cpp");
std::print("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
/*
mov ecx, 0Fh
call ThrowHelper.ThrowArgumentNullException
mov ecx, 0Eh
mov edx, 16h
call ThrowHelper.ThrowArgumentOutOfRangeException
mov ecx, 5
call ThrowHelper.ThrowArgumentException
*/
auto candidates = Util::PatternScanAll(il2cppSection, "B9 0F 00 00 00 E8 ? ? ? ? B9 0E 00 00 00 BA 16 00 00 00 E8 ? ? ? ? B9 05 00 00 00 E8 ? ? ? ?");
std::println("Candidates: {}", candidates.size());
std::vector<uintptr_t> filteredEntries;
std::ranges::copy_if(candidates, std::back_inserter(filteredEntries), [](uintptr_t& entry) {
entry = FindFunctionEntry(entry);
return entry % 16 == 0;
});
for (const auto& entry : filteredEntries)
{
std::println("Entry: 0x{:X}", entry);
}
std::println("Looking for call counts...");
std::mutex mutex;
std::unordered_map<uintptr_t, int32_t> callCounts;
// find the call counts to candidate functions
std::vector<std::future<void>> futures;
std::ranges::transform(filteredEntries, std::back_inserter(futures), [&](uintptr_t entry) {
return std::async(std::launch::async, [&](uintptr_t e) {
const auto callSites = GetCalls((uint8_t*)e);
std::lock_guard lock(mutex);
callCounts[e] = callSites.size();
}, entry);
});
for (auto& future : futures) {
future.get();
}
uintptr_t targetEntry = 0;
for (const auto& [entry, count] : callCounts)
{
std::println("Entry: 0x{:X}, RVA: 0x{:08X}, Count: {}", entry, entry - Globals::BaseAddress, count);
if (count == 3) {
targetEntry = entry;
}
}
Globals::Offset.BitConverter_ToUInt16 = targetEntry;
}
void ResolveInventoryCmdId()
{
if (Globals::PlayerStoreId != 0)
return;
const auto il2cppSection = GetSection("il2cpp");
std::println("Section Address: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data()));
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(il2cppSection.data() + il2cppSection.size()));
/*
cmp r8d, 2
jz 0x3B
cmd r8d, 1
mov rax
*/
// look for ItemModule.GetBagManagerByStoreType <- mf got inlined in 5.5
// we just gon to look for OnPlayerStoreNotify
const auto candidates = Util::PatternScanAll(il2cppSection, "41 83 F8 02 B8 ? ? ? ? B9 ? ? ? ? 48 0F 45 C1");
std::println("Candidates: {}", candidates.size());
if (candidates.empty())
return;
uintptr_t pOnPlayerStoreNotify = 0;
{
// one of the candidates is OnPlayerStoreNotify
// search after the pattern to find an arbirary branch
auto decodedInstructions = candidates | std::views::transform([](auto va) { return DecodeFunction(va, 20); });
// find the call site with an arbitrary branch (JMP or CALL) after the call
auto targetInstructions = std::ranges::find_if(decodedInstructions, [](const auto& instr) {
return std::ranges::any_of(instr, [](const DecodedInstruction& i) {
return (i.Instruction.mnemonic == ZYDIS_MNEMONIC_JMP || i.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) &&
i.Operands.size() == 1 && i.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER;
});
});
if (targetInstructions == decodedInstructions.end()) {
std::println("Failed to find target instruction");
return;
}
// ItemModule.OnPlayerStoreNotify
const auto& instructions = *targetInstructions;
pOnPlayerStoreNotify = Globals::BaseAddress + instructions.front().RVA;
const auto isFunctionEntry = [](uintptr_t va) -> bool {
auto* code = reinterpret_cast<uint8_t*>(va);
return (va % 16 == 0 &&
code[0] == 0x56 && // push rsi
(*reinterpret_cast<uint32_t*>(&code[1]) & ~0xFF000000) == _byteswap_ulong(0x4883EC00)); // sub rsp, ??
};
auto range = std::views::iota(0, 126);
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pOnPlayerStoreNotify - i); });
it != range.end())
{
pOnPlayerStoreNotify -= *it;
}
else {
std::println("Failed to find function entry");
return;
}
std::println("OnPlayerStoreNotify: 0x{:X}", pOnPlayerStoreNotify);
}
uintptr_t pOnPacket = 0;
{
// get all calls to OnPlayerStoreNotify
const auto calls = GetCalls(reinterpret_cast<uint8_t*>(pOnPlayerStoreNotify));
if (calls.size() != 1) {
std::println("Failed to find call site");
return;
}
// ItemModule.OnPacket - search backwards for function entry
pOnPacket = calls.front();
const auto isFunctionEntry = [](uintptr_t va) -> bool {
auto* code = reinterpret_cast<uint8_t*>(va);
return (va % 16 == 0 &&
code[0] == 0x56 && // push rsi
(*reinterpret_cast<uint32_t*>(&code[1]) & ~0xFF000000) == _byteswap_ulong(0x4883EC00)); // sub rsp, ??
};
auto range = std::views::iota(0, 3044);
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pOnPacket - i); });
it != range.end())
{
pOnPacket -= *it;
}
else {
std::println("Failed to find function entry");
return;
}
std::println("OnPacket: 0x{:X}", pOnPacket);
}
const auto decodedInstructions = DecodeFunction(pOnPacket);
uint32_t cmdid = 0;
std::ranges::for_each(decodedInstructions, [&cmdid, pOnPlayerStoreNotify](const DecodedInstruction& i) {
static uint32_t immValue = 0; // keep track of the last immediate value
if (i.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP &&
i.Operands.size() == 2 &&
i.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE)
{
immValue = static_cast<uint32_t>(i.Operands[1].imm.value.u);
}
if (i.Instruction.meta.branch_type == ZYDIS_BRANCH_TYPE_NEAR && i.Operands.size() == 1 &&
(i.Instruction.mnemonic == ZYDIS_MNEMONIC_JZ || i.Instruction.mnemonic == ZYDIS_MNEMONIC_JNZ)) // jz for true branch, jnz for false branch
{
// assume the branching is jz
uintptr_t branchAddr = Globals::BaseAddress + i.RVA + i.Instruction.length + i.Operands[0].imm.value.s;
// check if the branch is jnz and adjust the branch address
if (i.Instruction.mnemonic == ZYDIS_MNEMONIC_JNZ) {
branchAddr = Globals::BaseAddress + i.RVA + i.Instruction.length;
}
// decode the branch address immediately
const auto instructions = DecodeFunction(branchAddr, 10);
const auto isMatch = std::ranges::any_of(instructions, [pOnPlayerStoreNotify](const DecodedInstruction& instr) {
if (instr.Instruction.mnemonic != ZYDIS_MNEMONIC_CALL)
return false;
uintptr_t destination = 0;
ZydisCalcAbsoluteAddress(&instr.Instruction, instr.Operands.data(), Globals::BaseAddress + instr.RVA, &destination);
return destination == pOnPlayerStoreNotify;
});
if (isMatch) {
cmdid = immValue;
}
}
return cmdid == 0; // stop processing if cmdid is found
});
Globals::PlayerStoreId = static_cast<uint16_t>(cmdid);
std::println("PlayerStoreId: {}", Globals::PlayerStoreId);
}
void Resolve_AccountDataItem_UpdateNormalProp()
{
if (Globals::Offset.AccountDataItem_UpdateNormalProp != 0) {
Globals::Offset.AccountDataItem_UpdateNormalProp += Globals::BaseAddress;
return;
}
const auto il2cppSection = GetSection("il2cpp");
/*
add ??, 0FFFFD8EEh
cmp ??, 30h
*/
auto candidates = Util::PatternScanAll(il2cppSection, "81 ? EE D8 FF FF ? 83 ? 30");
// should have only one result
if (candidates.size() != 1)
{
std::println("Filtered Instructions: {}", candidates.size());
return;
}
auto fp = candidates[0];
const auto isFunctionEntry = [](uintptr_t va) -> bool {
auto* code = reinterpret_cast<uint8_t*>(va);
/* push rsi */
/* push rdi */
return (va % 16 == 0 && code[0] == 0x56 && code[1] == 0x57);
};
auto range = std::views::iota(0, 213);
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(fp - i); }); it != range.end()) {
fp -= *it;
} else {
std::println("Failed to find function entry");
return;
}
Globals::Offset.AccountDataItem_UpdateNormalProp = fp;
}
#define DO_APP_FUNC(ca, oa, r, n, p) r (*n) p
namespace Genshin {
#include "il2cpp-functions.h"
}
#undef DO_APP_FUNC
#define DO_UNI_FUNC(ca, oa, r, n, p) r (*n) p
namespace Genshin {
#include "il2cpp-unity-functions.h"
}
#undef DO_UNI_FUNC
using std::string;
void InitIL2CPP() {
TCHAR szFileName[MAX_PATH];
GetModuleFileName(NULL, szFileName, MAX_PATH);
auto isCN = strstr(szFileName, "YuanShen.exe");//string(szFileName).contains();
auto hBase = GetModuleHandle("UserAssembly.dll");
auto bAddr = (UINT64)hBase;
auto cAddr = (UINT64)GetModuleHandle("UnityPlayer.dll");
#define DO_API(ca, oa, r, n, p) n = (r (*) p)(bAddr + (isCN ? ca : oa))
#include "il2cpp-api-functions.h"
#undef DO_API
#define DO_APP_FUNC(ca, oa, r, n, p) n = (r (*) p)(bAddr + (isCN ? ca : oa))
#include "il2cpp-functions.h"
#undef DO_APP_FUNC
#define DO_UNI_FUNC(ca, oa, r, n, p) n = (r (*) p)(cAddr + (isCN ? ca : oa))
#include "il2cpp-unity-functions.h"
#undef DO_UNI_FUNC
bool InitIL2CPP()
{
std::string buffer;
buffer.resize(MAX_PATH);
ZeroMemory(buffer.data(), MAX_PATH);
const auto pathLength = GetModuleFileNameA(nullptr, buffer.data(), MAX_PATH);
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
buffer.resize(pathLength);
ZeroMemory(buffer.data(), pathLength);
GetModuleFileNameA(nullptr, buffer.data(), pathLength);
}
buffer.shrink_to_fit();
using namespace Globals;
IsCNREL = buffer.find("YuanShen.exe") != std::string::npos;
BaseAddress = (uintptr_t)GetModuleHandleA(nullptr);
std::future<void> resolveFuncFuture = std::async(std::launch::async, Resolve_BitConverter_ToUInt16);
std::future<void> resolveCmdIdFuture = std::async(std::launch::async, ResolveAchivementCmdId);
std::future<void> resolveInventoryFuture = std::async(std::launch::async, ResolveInventoryCmdId);
std::future<void> resolveUpdatePropFuture = std::async(std::launch::async, Resolve_AccountDataItem_UpdateNormalProp);
resolveFuncFuture.get();
resolveCmdIdFuture.get();
resolveInventoryFuture.get();
resolveUpdatePropFuture.get();
std::println("BaseAddress: 0x{:X}", BaseAddress);
std::println("IsCNREL: {:d}", IsCNREL);
std::println("BitConverter_ToUInt16: 0x{:X}", Offset.BitConverter_ToUInt16);
std::println("AccountDataItem_UpdateNormalProp: 0x{:X}", Offset.AccountDataItem_UpdateNormalProp);
if (!AchievementId && AchievementIdSet.empty())
{
Util::ErrorDialog("Failed to resolve achievement data");
return false;
}
if (!PlayerStoreId)
{
Util::ErrorDialog("Failed to resolve inventory data");
return false;
}
return true;
}

View File

@@ -1,4 +1,4 @@
#pragma once
// IL2CPP application initializer
void InitIL2CPP();
bool InitIL2CPP();

File diff suppressed because it is too large Load Diff

View File

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

9
lib/src/ntprivate.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <Windows.h>
#include <bcrypt.h>
#pragma comment(lib, "ntdll.lib")
#define LDR_ADDREF_DLL_PIN 0x00000001
EXTERN_C NTSYSAPI NTSTATUS NTAPI LdrAddRefDll(_In_ ULONG Flags, _In_ PVOID DllHandle);

View File

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

View File

@@ -1,27 +0,0 @@
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
#ifndef PCH_H
#define PCH_H
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
// 添加要在此处预编译的标头
#include <map>
#include <set>
#include <string>
#include <vector>
#include <codecvt>
#include <cstdint>
#include <iostream>
#include <detours.h>
#include "HookManager.h"
#include "il2cpp-appdata.h"
#endif //PCH_H

View File

@@ -1,78 +1,210 @@
#include "pch.h"
#include <string>
#include <array>
#include <ranges>
#include <intrin.h>
#include "util.h"
VOID DisableVMProtect() {
DWORD oldProtect = 0;
auto ntdll = GetModuleHandleA("ntdll.dll");
BYTE callcode = ((BYTE*)GetProcAddress(ntdll, "NtQuerySection"))[4] - 1;
BYTE restore[] = { 0x4C, 0x8B, 0xD1, 0xB8, callcode };
auto nt_vp = (BYTE*)GetProcAddress(ntdll, "NtProtectVirtualMemory");
VirtualProtect(nt_vp, sizeof(restore), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(nt_vp, restore, sizeof(restore));
VirtualProtect(nt_vp, sizeof(restore), oldProtect, &oldProtect);
}
#include "globals.h"
#pragma region StringConvert
string IlStringToString(Il2CppString* str, UINT codePage) {
auto chars = reinterpret_cast<const wchar_t*>(str->chars);
auto len = WideCharToMultiByte(codePage, 0, chars, -1, nullptr, 0, nullptr, nullptr);
auto buffer = new char[len];
WideCharToMultiByte(codePage, 0, chars, -1, buffer, len, nullptr, nullptr);
return string(buffer);
}
#pragma endregion
#pragma region GC
UINT32 GCHandle_New(void* object, bool pinned) {
return il2cpp_gchandle_new((Il2CppObject*)object, pinned);
}
#pragma endregion
#pragma region ByteUtils
bool IsLittleEndian() {
UINT i = 1;
char* c = (char*)&i;
return (*c);
}
#pragma endregion
#ifdef _DEBUG
#pragma runtime_checks("", off)
#endif
#pragma region FindMainWindowByPID
struct HandleData {
DWORD pid;
HWND hwnd;
};
namespace
{
struct HandleData {
DWORD Pid;
HWND Hwnd;
};
BOOL IsMainWindow(HWND handle) {
return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle) == TRUE;
}
bool IsMainWindow(HWND handle) {
return GetWindow(handle, GW_OWNER) == nullptr && IsWindowVisible(handle) == TRUE;
}
BOOL IsUnityWindow(HWND handle) {
TCHAR name[256];
GetClassName(handle, name, 256);
return _strcmpi(name, "UnityWndClass") == 0;
}
bool IsUnityWindow(HWND handle) {
char szName[256]{};
GetClassNameA(handle, szName, 256);
return _stricmp(szName, "UnityWndClass") == 0;
}
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) {
HandleData& data = *(HandleData*)lParam;
DWORD pid = 0;
GetWindowThreadProcessId(handle, &pid);
if (data.pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle))
return TRUE;
data.hwnd = handle;
return FALSE;
}
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) {
HandleData& data = *(HandleData*)lParam;
DWORD pid = 0;
GetWindowThreadProcessId(handle, &pid);
if (data.Pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle))
return TRUE;
data.Hwnd = handle;
return FALSE;
}
std::tuple<std::vector<uint8_t>, std::string> PatternToBytes(const char* pattern)
{
std::vector<uint8_t> bytes;
std::string mask;
const auto start = const_cast<char*>(pattern);
const auto end = const_cast<char*>(pattern) + strlen(pattern);
for (auto current = start; current < end; ++current) {
if (*current == '?') {
++current;
if (*current == '?')
++current;
bytes.push_back(-1);
mask.push_back('?');
}
else {
bytes.push_back(strtoul(current, &current, 16));
mask.push_back('x');
}
}
return { bytes, mask };
}
HWND FindMainWindowByPID(DWORD pid) {
HandleData data = { pid, 0 };
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
return data.hwnd;
}
#pragma endregion
static constexpr LPCSTR base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
namespace Util
{
HWND FindMainWindowByPID(DWORD pid)
{
HandleData data = {
.Pid = pid,
.Hwnd = nullptr
};
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
return data.Hwnd;
}
std::string Base64Encode(std::span<uint8_t> data)
{
return Base64Encode(data.data(), data.size());
}
std::string Base64Encode(uint8_t const* buf, size_t bufLen)
{
std::string ret;
int i = 0;
uint8_t char_array_3[3];
uint8_t char_array_4[4];
while (bufLen--) {
char_array_3[i++] = *buf++;
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; (i < 4); i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i) {
int j;
for (j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; j < i + 1; j++)
ret += base64_chars[char_array_4[j]];
while (i++ < 3)
ret += '=';
}
return ret;
}
void ErrorDialog(LPCSTR title, LPCSTR msg)
{
MessageBoxA(Globals::GameWindow, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
}
void ErrorDialog(LPCSTR msg)
{
ErrorDialog("YaeAchievement", msg);
}
void Win32ErrorDialog(DWORD code, DWORD winerrcode)
{
const std::string msg = "CRITICAL ERROR!\nError code: " + std::to_string(winerrcode) + "-" + std::to_string(code) +
"\n\nPlease take the screenshot and contact developer by GitHub Issue to solve this problem\nNOT MIHOYO/COGNOSPHERE CUSTOMER SERVICE!";
ErrorDialog("YaeAchievement", msg.c_str());
}
std::vector<uintptr_t> PatternScanAll(std::span<uint8_t> bytes, const char* pattern)
{
std::vector<uintptr_t> results;
const auto [patternBytes, patternMask] = PatternToBytes(pattern);
constexpr std::size_t chunkSize = 16;
const auto maskCount = static_cast<std::size_t>(std::ceil(patternMask.size() / chunkSize));
std::array<int32_t, 32> masks{};
auto chunks = patternMask | std::views::chunk(chunkSize);
for (std::size_t i = 0; auto chunk : chunks) {
int32_t mask = 0;
for (std::size_t j = 0; j < chunk.size(); ++j) {
if (chunk[j] == 'x') {
mask |= 1 << j;
}
}
masks[i++] = mask;
}
__m128i xmm1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(patternBytes.data()));
__m128i xmm2, xmm3, mask;
auto pData = bytes.data();
const auto end = pData + bytes.size() - patternMask.size();
while (pData < end)
{
_mm_prefetch(reinterpret_cast<const char*>(pData + 64), _MM_HINT_NTA);
if (patternBytes[0] == pData[0])
{
xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pData));
mask = _mm_cmpeq_epi8(xmm1, xmm2);
if ((_mm_movemask_epi8(mask) & masks[0]) == masks[0])
{
bool found = true;
for (int i = 1; i < maskCount; ++i)
{
xmm2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pData + i * chunkSize));
xmm3 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(patternBytes.data() + i * chunkSize));
mask = _mm_cmpeq_epi8(xmm2, xmm3);
if ((_mm_movemask_epi8(mask) & masks[i]) != masks[i])
{
found = false;
break;
}
}
if (found) {
results.push_back(reinterpret_cast<uintptr_t>(pData));
}
}
}
++pData;
}
return results;
}
}
#ifdef _DEBUG
#pragma runtime_checks("", restore)
#endif

View File

@@ -1,34 +1,19 @@
// ReSharper disable CppClangTidyClangDiagnosticLanguageExtensionToken
#pragma once
#include <Windows.h>
#include <type_traits>
#include <vector>
#include <span>
using std::string;
namespace Util
{
HWND FindMainWindowByPID(DWORD pid);
std::string Base64Encode(std::span<uint8_t> data);
std::string Base64Encode(uint8_t const* buf, size_t bufLen);
VOID DisableVMProtect();
bool IsLittleEndian();
HWND FindMainWindowByPID(DWORD pid);
UINT32 GCHandle_New(LPVOID object, bool pinned);
string IlStringToString(Il2CppString* str, UINT codePage = CP_ACP);
void ErrorDialog(LPCSTR title, LPCSTR msg);
void ErrorDialog(LPCSTR msg);
void Win32ErrorDialog(DWORD code, DWORD winerrcode);
#define cstring_new(str) il2cpp_string_new(str)
#define string_new(str) cstring_new((str).c_str())
#define ErrorDialogT(title, msg) MessageBox(unityWnd, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
#define ErrorDialog(msg) ErrorDialogT("YaeAchievement", msg)
#define Win32ErrorDialog(code) ErrorDialogT("YaeAchievement", ("CRITICAL ERROR!\nError code: " + std::to_string(GetLastError()) + "-"#code"\n\nPlease take the screenshot and contact developer by GitHub Issue to solve this problem\nNOT MIHOYO/COGNOSPHERE CUSTOMER SERVICE!").c_str())
template<class T>
static T ReadMapped(void* data, int offset, bool littleEndian = false) {
char* cData = (char*)data;
T result = {};
if (IsLittleEndian() != littleEndian) {
for (int i = 0; i < sizeof(T); i++)
((char*)&result)[i] = cData[offset + sizeof(T) - i - 1];
return result;
}
memcpy(&result, cData + offset, sizeof(result));
return result;
}
template<class T>
static T* GCHandle_GetObject(UINT handle) {
return (T*) il2cpp_gchandle_get_target(handle);
}
std::vector<uintptr_t> PatternScanAll(std::span<uint8_t> bytes, const char* pattern);
}

Binary file not shown.

View File

@@ -1,88 +0,0 @@
using Newtonsoft.Json;
using System.Net;
using YaeAchievement.AppCenterSDK.Models;
using YaeAchievement.AppCenterSDK.Models.Serialization;
namespace YaeAchievement.AppCenterSDK;
public static class AppCenter {
private const string AppSecret = "648b83bf-d439-49bd-97f4-e1e506bdfe39";
private const string ApiUrl = "https://in.appcenter.ms/logs?api-version=1.0.0";
// ReSharper disable InconsistentNaming
public static Guid? SessionID { get; private set; }
public static readonly string DeviceID;
public static readonly Device DeviceInfo;
private static List<Log> Queue;
private static readonly Lazy<HttpClient> httpClient = new(() => new HttpClient(new HttpClientHandler {
Proxy = GlobalVars.DebugProxy ? new WebProxy("http://127.0.0.1:8888") : null
}) {
DefaultRequestHeaders = {
{ "Install-ID", DeviceID },
{ "App-Secret", AppSecret }
}
});
static AppCenter() {
Queue = new List<Log>();
DeviceID = DeviceHelper.GetDeviceID();
DeviceInfo = new Device();
var running = true;
Task.Run(() => {
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while (running) {
Upload();
Thread.Sleep(5 * 1000);
}
});
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
running = false;
};
LogSerializer.AddLogType(PageLog.JsonIdentifier, typeof(PageLog));
LogSerializer.AddLogType(EventLog.JsonIdentifier, typeof(EventLog));
LogSerializer.AddLogType(HandledErrorLog.JsonIdentifier, typeof(HandledErrorLog));
LogSerializer.AddLogType(ManagedErrorLog.JsonIdentifier, typeof(ManagedErrorLog));
LogSerializer.AddLogType(StartServiceLog.JsonIdentifier, typeof(StartServiceLog));
LogSerializer.AddLogType(StartSessionLog.JsonIdentifier, typeof(StartSessionLog));
}
// ReSharper restore InconsistentNaming
public static void Upload() {
if (Queue.Count == 0) return;
var uploadStatus = "";
do {
Queue = Queue.Select(log => {
log.Status = LogStatus.Uploading;
return log;
}).ToList();
using var uploadContent = new StringContent(Queue.ToJson());
try {
using var response = httpClient.Value.PostAsync(ApiUrl, uploadContent).Result;
var result = response.Content.ReadAsStringAsync().Result;
uploadStatus = JsonConvert.DeserializeObject<LogUploadResult>(result)!.Status;
} catch (Exception) {
// ignored
}
} while (uploadStatus != "Success");
Queue.RemoveAll(log => log.Status == LogStatus.Uploading);
}
public static void Init() {
new StartServiceLog("Analytics", "Crashes").Enqueue();
SessionID = Guid.NewGuid();
new StartSessionLog().Enqueue();
}
public static void TrackCrash(Exception exception, bool fatal = true) {
new ManagedErrorLog(exception, fatal).Enqueue();
}
public static void Enqueue(this Log log) {
Queue.Add(log);
}
private static string ToJson(this IEnumerable<Log> queue) {
return LogSerializer.Serialize(new LogContainer(queue));
}
}

View File

@@ -1,68 +0,0 @@
using Microsoft.Win32;
using YaeAchievement.Win32;
namespace YaeAchievement.AppCenterSDK;
#pragma warning disable CA1416
public static class DeviceHelper {
public static string? GetOem() {
using var root = Registry.LocalMachine;
using var sub = root.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\BIOS");
var oem = sub?.GetValue("SystemManufacturer") as string;
return oem == "System manufacturer" ? null : oem;
}
public static string? GetModel() {
using var root = Registry.LocalMachine;
using var sub = root.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\BIOS");
var model = sub?.GetValue("SystemProductName") as string;
return model == "System Product Name" ? null : model;
}
public static string GetScreenSize() {
var desktop = Native.GetDC(IntPtr.Zero);
var size = $"{Native.GetDeviceCaps(desktop, 118)}x{Native.GetDeviceCaps(desktop, 117)}";
Native.ReleaseDC(IntPtr.Zero, desktop);
return size;
}
public static string? GetCountry() {
using var root = Registry.CurrentUser;
using var sub = root.OpenSubKey("Control Panel\\International\\Geo");
return sub?.GetValue("Name") as string;
}
public static string GetSystemVersion() {
using var root = Registry.LocalMachine;
using var sub = root.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
var majorVersion = sub?.GetValue("CurrentMajorVersionNumber");
if (majorVersion != null) {
var minorVersion = sub?.GetValue("CurrentMinorVersionNumber", "0");
var buildNumber = sub?.GetValue("CurrentBuildNumber", "0");
return $"{majorVersion}.{minorVersion}.{buildNumber}";
} else {
var version = sub?.GetValue("CurrentVersion", "0.0");
var buildNumber = sub?.GetValue("CurrentBuild", "0");
return $"{version}.{buildNumber}";
}
}
public static int GetSystemBuild() {
using var root = Registry.LocalMachine;
using var sub = root.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
return (int) (sub?.GetValue("UBR") ?? 0);
}
// ReSharper disable once InconsistentNaming
public static string GetDeviceID() {
using var sdk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\miHoYoSDK");
if (sdk?.GetValue("MIHOYOSDK_DEVICE_ID") is not string id) {
id = $"{Random.Shared.NextInt64().ToString().SHA1Hash().ToLower()}{DateTimeOffset.Now.ToUnixTimeMilliseconds()}";
sdk?.SetValue("MIHOYOSDK_DEVICE_ID", id);
}
id = id.MD5Hash().ToLower();
return $"{id[..8]}-{id[8..12]}-{id[12..16]}-{id[16..20]}-{id[20..]}";
}
}
#pragma warning restore CA1416

View File

@@ -1,25 +0,0 @@
using YaeAchievement.AppCenterSDK.Models;
namespace YaeAchievement.AppCenterSDK;
public static class ErrorLogHelper {
public static MException CreateModelExceptionAndBinaries(Exception exception) {
var modelException = new MException {
Type = exception.GetType().ToString(),
Message = exception.Message,
StackTrace = exception.StackTrace
};
if (exception is AggregateException aggregateException) {
if (aggregateException.InnerExceptions.Count != 0) {
modelException.InnerExceptions = new List<MException>();
foreach (var innerException in aggregateException.InnerExceptions) {
modelException.InnerExceptions.Add(CreateModelExceptionAndBinaries(innerException));
}
}
} else if (exception.InnerException != null) {
modelException.InnerExceptions ??= new List<MException>();
modelException.InnerExceptions.Add(CreateModelExceptionAndBinaries(exception.InnerException));
}
return modelException;
}
}

View File

@@ -1,51 +0,0 @@
using System.Globalization;
using System.Reflection;
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
public class Device {
[JsonProperty(PropertyName = "sdkName")]
public string SdkName { get; set; } = "appcenter.wpf.netcore";
[JsonProperty(PropertyName = "sdkVersion")]
public string SdkVersion { get; set; } = "4.5.0";
[JsonProperty(PropertyName = "osName")]
public string OsName { get; set; } = "WINDOWS";
[JsonProperty(PropertyName = "osVersion")]
public string OsVersion { get; set; } = DeviceHelper.GetSystemVersion();
[JsonProperty(PropertyName = "osBuild")]
public string OsBuild { get; set; } = $"{DeviceHelper.GetSystemVersion()}.{DeviceHelper.GetSystemBuild()}";
[JsonProperty(PropertyName = "model")]
public string? Model { get; set; } = DeviceHelper.GetModel();
[JsonProperty(PropertyName = "oemName")]
public string? OemName { get; set; } = DeviceHelper.GetOem();
[JsonProperty(PropertyName = "screenSize")]
public string ScreenSize { get; set; } = DeviceHelper.GetScreenSize();
[JsonProperty(PropertyName = "carrierCountry")]
public string Country { get; set; } = DeviceHelper.GetCountry() ?? "CN";
[JsonProperty(PropertyName = "locale")]
public string Locale { get; set; } = CultureInfo.CurrentCulture.Name;
[JsonProperty(PropertyName = "timeZoneOffset")]
public int TimeZoneOffset { get; set; } = (int) TimeZoneInfo.Local.BaseUtcOffset.TotalMinutes;
[JsonProperty(PropertyName = "appVersion")]
public string AppVersion { get; set; } = GlobalVars.AppVersionName;
[JsonProperty(PropertyName = "appBuild")]
public string AppBuild { get; set; } = GlobalVars.AppVersionCode.ToString();
[JsonProperty(PropertyName = "appNamespace")]
public string AppNamespace { get; set; } = Assembly.GetEntryAssembly()?.EntryPoint?.DeclaringType?.Namespace ?? string.Empty;
}

View File

@@ -1,20 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
[JsonObject(JsonIdentifier)]
public class EventLog : LogWithProperties {
public const string JsonIdentifier = "event";
public EventLog(string name) {
Name = name;
}
[JsonProperty(PropertyName = "id")]
private Guid Id { get; set; } = Guid.NewGuid();
[JsonProperty(PropertyName = "name")]
private string Name { get; set; }
}

View File

@@ -1,21 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
[JsonObject(JsonIdentifier)]
public class HandledErrorLog : LogWithProperties {
public const string JsonIdentifier = "handledError";
public HandledErrorLog(MException exception) {
Id = Guid.NewGuid();
Exception = exception;
}
[JsonProperty(PropertyName = "id")]
public Guid? Id { get; set; }
[JsonProperty(PropertyName = "exception")]
public MException Exception { get; set; }
}

View File

@@ -1,23 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
public abstract class Log {
[JsonProperty(PropertyName = "sid")]
private Guid? Session { get; set; } = AppCenter.SessionID;
[JsonProperty(PropertyName = "timestamp")]
private DateTime Timestamp { get; set; } = DateTime.UtcNow;
[JsonProperty(PropertyName = "device")]
private Device Device { get; set; } = AppCenter.DeviceInfo;
[JsonIgnore]
internal LogStatus Status = LogStatus.Pending;
}
public enum LogStatus {
Pending, Uploading, Uploaded
}

View File

@@ -1,14 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
public class LogContainer {
public LogContainer(IEnumerable<Log> logs) {
Logs = logs;
}
[JsonProperty(PropertyName = "logs")]
public IEnumerable<Log> Logs { get; set; }
}

View File

@@ -1,19 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
public class LogUploadResult {
[JsonProperty(PropertyName = "status")]
public string Status { get; set; } = null!;
[JsonProperty(PropertyName = "validDiagnosticsIds")]
public Guid[] ValidDiagnosticsIds { get; set; } = Array.Empty<Guid>();
[JsonProperty(PropertyName = "throttledDiagnosticsIds")]
public Guid[] ThrottledDiagnosticsIds { get; set; } = Array.Empty<Guid>();
[JsonProperty(PropertyName = "correlationId")]
public Guid CorrelationId { get; set; }
}

View File

@@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
public class LogWithProperties : Log {
[JsonProperty(PropertyName = "properties")]
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
}

View File

@@ -1,22 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
public class MException {
[JsonProperty(PropertyName = "type")]
public string Type { get; set; } = "UnknownType";
[JsonProperty(PropertyName = "message")]
public string? Message { get; set; }
[JsonProperty(PropertyName = "stackTrace")]
public string? StackTrace { get; set; }
[JsonProperty(PropertyName = "frames")]
public IList<StackFrame>? Frames { get; set; }
[JsonProperty(PropertyName = "innerExceptions")]
public IList<MException>? InnerExceptions { get; set; }
}

View File

@@ -1,50 +0,0 @@
using System.Diagnostics;
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
[JsonObject(JsonIdentifier)]
public class ManagedErrorLog : Log {
public const string JsonIdentifier = "managedError";
public ManagedErrorLog(
Exception exception,
bool fatal = true
) {
var p = Process.GetCurrentProcess();
Id = Guid.NewGuid();
Fatal = fatal;
UserId = AppCenter.DeviceID;
ProcessId = p.Id;
Exception = ErrorLogHelper.CreateModelExceptionAndBinaries(exception);
ProcessName = p.ProcessName;
Architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
AppLaunchTimestamp = p.StartTime.ToUniversalTime();
}
[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; }
[JsonProperty(PropertyName = "userId")]
public string? UserId { get; set; }
[JsonProperty(PropertyName = "processId")]
public int ProcessId { get; set; }
[JsonProperty(PropertyName = "processName")]
public string ProcessName { get; set; }
[JsonProperty(PropertyName = "fatal")]
public bool Fatal { get; set; }
[JsonProperty(PropertyName = "appLaunchTimestamp")]
public DateTime? AppLaunchTimestamp { get; set; }
[JsonProperty(PropertyName = "architecture")]
public string? Architecture { get; set; }
[JsonProperty(PropertyName = "exception")]
public MException Exception { get; set; }
}

View File

@@ -1,17 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
[JsonObject(JsonIdentifier)]
public class PageLog : LogWithProperties {
public const string JsonIdentifier = "page";
public PageLog(string name) {
Name = name;
}
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
}

View File

@@ -1,60 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace YaeAchievement.AppCenterSDK.Models.Serialization;
#pragma warning disable CS8604, CS8765
public class LogJsonConverter : JsonConverter {
private readonly Dictionary<string, Type> _logTypes = new ();
private readonly object _jsonConverterLock = new ();
private static readonly JsonSerializerSettings SerializationSettings;
static LogJsonConverter() {
SerializationSettings = new JsonSerializerSettings {
Formatting = Formatting.None,
NullValueHandling = NullValueHandling.Ignore,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
};
}
public void AddLogType(string typeName, Type type) {
lock (_jsonConverterLock) {
_logTypes[typeName] = type;
}
}
public override bool CanConvert(Type objectType) {
return typeof(Log).IsAssignableFrom(objectType);
}
public override object? ReadJson(JsonReader reader, Type t, object o, JsonSerializer s) {
Type logType;
var jsonObject = JObject.Load(reader);
var typeName = jsonObject.GetValue("type")?.ToString();
lock (_jsonConverterLock) {
if (typeName == null || !_logTypes.ContainsKey(typeName)) {
throw new JsonReaderException("Could not identify type of log");
}
logType = _logTypes[typeName];
jsonObject.Remove("type");
}
return jsonObject.ToObject(logType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
var info = value.GetType().GetTypeInfo();
if (info.GetCustomAttribute(typeof(JsonObjectAttribute)) is not JsonObjectAttribute attribute) {
throw new JsonWriterException("Log type is missing JsonObjectAttribute");
}
var jsonObject = JObject.FromObject(value, JsonSerializer.CreateDefault(SerializationSettings));
jsonObject.Add("type", JToken.FromObject(attribute.Id));
writer.WriteRawValue(jsonObject.ToString(Formatting.None));
}
}

View File

@@ -1,40 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models.Serialization;
#pragma warning disable CS8604, CS8765, CS8603
public static class LogSerializer {
private static readonly JsonSerializerSettings SerializationSettings;
private static readonly LogJsonConverter Converter = new ();
static LogSerializer() {
SerializationSettings = new JsonSerializerSettings {
Formatting = Formatting.None,
NullValueHandling = NullValueHandling.Ignore,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
Converters = { Converter }
};
}
public static void AddLogType(string typeName, Type type) {
Converter.AddLogType(typeName, type);
}
public static string Serialize(LogContainer logContainer) {
return JsonConvert.SerializeObject(logContainer, SerializationSettings);
}
public static string Serialize(Log log) {
return JsonConvert.SerializeObject(log, SerializationSettings);
}
public static LogContainer? DeserializeLogs(string json) {
return JsonConvert.DeserializeObject<LogContainer>(json, SerializationSettings);
}
}

View File

@@ -1,34 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
public class StackFrame {
public StackFrame(string address, string code, string className, string methodName, int? lineNumber, string fileName) {
Address = address;
Code = code;
ClassName = className;
MethodName = methodName;
LineNumber = lineNumber;
FileName = fileName;
}
[JsonProperty(PropertyName = "address")]
public string Address { get; set; }
[JsonProperty(PropertyName = "code")]
public string Code { get; set; }
[JsonProperty(PropertyName = "className")]
public string ClassName { get; set; }
[JsonProperty(PropertyName = "methodName")]
public string MethodName { get; set; }
[JsonProperty(PropertyName = "lineNumber")]
public int? LineNumber { get; set; }
[JsonProperty(PropertyName = "fileName")]
public string FileName { get; set; }
}

View File

@@ -1,17 +0,0 @@
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models;
[JsonObject(JsonIdentifier)]
public class StartServiceLog : Log {
public const string JsonIdentifier = "startService";
public StartServiceLog(params string[] services) {
Services = services;
}
[JsonProperty(PropertyName = "services")]
public string[] Services { get; set; }
}

View File

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

View File

@@ -1,62 +0,0 @@
using System.Text.RegularExpressions;
using YaeAchievement.res;
namespace YaeAchievement;
public static class AppConfig {
public static string GamePath { get; private set; } = null!;
internal static void Load(string argumentPath) {
if (argumentPath != "auto" && File.Exists(argumentPath)) {
GamePath = argumentPath;
return;
}
var pathCacheFile = new CacheFile("genshin_impact_game_path");
if (pathCacheFile.Exists()) {
var path = pathCacheFile.Read().Content.ToStringUtf8();
if (path != null && File.Exists(path)) {
GamePath = path;
return;
}
}
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var cnLogPath = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt");
var osLogPath = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\Genshin Impact\output_log.txt");
if (!File.Exists(cnLogPath) && !File.Exists(osLogPath)) {
throw new ApplicationException(App.ConfigNeedStartGenshin);
}
string finalLogPath;
if (!File.Exists(osLogPath)) {
finalLogPath = cnLogPath;
} else if (!File.Exists(cnLogPath)) {
finalLogPath = osLogPath;
} else {
var cnLastWriteTime = File.GetLastWriteTime(cnLogPath);
var osLastWriteTime = File.GetLastWriteTime(osLogPath);
finalLogPath = cnLastWriteTime > osLastWriteTime ? cnLogPath : osLogPath;
}
GamePath = GetGamePathFromLogFile(finalLogPath)
?? GetGamePathFromLogFile($"{finalLogPath}.last")
?? throw new ApplicationException(App.ConfigNeedStartGenshin);
pathCacheFile.Write(GamePath);
}
private static string? GetGamePathFromLogFile(string path) {
if (!File.Exists(path)) {
return null;
}
var copiedLogFilePath = Path.GetTempFileName();
File.Copy(path, copiedLogFilePath, true);
var content = File.ReadAllText(copiedLogFilePath);
try {
File.Delete(copiedLogFilePath);
} catch (Exception) { /* ignore */}
var matchResult = Regex.Match(content, @"(?m).:/.+(GenshinImpact_Data|YuanShen_Data)");
if (!matchResult.Success) {
return null;
}
var entryName = matchResult.Groups["1"].Value.Replace("_Data", ".exe");
return Path.GetFullPath(Path.Combine(matchResult.Value, "..", entryName));
}
}

View File

@@ -1,42 +0,0 @@
using System.IO.Compression;
using Google.Protobuf;
namespace YaeAchievement;
public class CacheFile {
private readonly string _cacheName;
private CacheItem? _content;
public DateTime LastWriteTime => Exists() ? File.GetLastWriteTimeUtc(_cacheName) : DateTime.UnixEpoch;
public CacheFile(string identifier) {
_cacheName = Path.Combine(GlobalVars.CachePath, $"{identifier.MD5Hash()[..16]}.miko");
}
public bool Exists() => File.Exists(_cacheName);
public CacheItem Read() {
if (_content == null) {
using var fInput = File.OpenRead(_cacheName);
using var dInput = new GZipStream(fInput, CompressionMode.Decompress);
_content = CacheItem.Parser.ParseFrom(dInput);
}
return _content;
}
public void Write(string data, string? etag = null) => Write(ByteString.CopyFromUtf8(data), data.MD5Hash(), etag);
public void Write(byte[] data, string? etag = null) => Write(ByteString.CopyFrom(data), data.MD5Hash(), etag);
private void Write(ByteString data, string hash, string? etag) {
using var fOut = File.OpenWrite(_cacheName);
using var cOut = new GZipStream(fOut, CompressionLevel.SmallestSize);
new CacheItem {
Etag = etag ?? string.Empty,
Version = 3,
Checksum = hash,
Content = data
}.WriteTo(cOut);
}
}

View File

@@ -1,213 +0,0 @@
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Win32;
using YaeAchievement.res;
using static AchievementAllDataNotify.Types.Achievement.Types;
namespace YaeAchievement;
public static class Export {
public static uint ExportTo { get; set; } = uint.MaxValue;
public static void Choose(AchievementAllDataNotify data) {
if (ExportTo == uint.MaxValue) {
Console.Write(App.ExportChoose);
while (Console.KeyAvailable) {
Console.ReadKey(false);
}
if (!uint.TryParse(Console.ReadLine(), out var num)) num = 0;
ExportTo = num;
}
((Action<AchievementAllDataNotify>) (ExportTo switch {
1 => ToHuTao,
2 => ToPaimon,
3 => ToSeelie,
4 => ToCSV,
5 => ToXunkong,
6 => ToWxApp1,
7 => ToRawJson,
_ => ToCocogoat
})).Invoke(data);
}
private class CocogoatResponse {
[JsonPropertyName("key")] public string Code { get; set; } = null!;
}
private static void ToCocogoat(AchievementAllDataNotify data) {
var result = JsonSerializer.Serialize(ExportToUIAFApp(data));
using var request = new HttpRequestMessage {
Method = HttpMethod.Post,
RequestUri = new Uri($"https://77.cocogoat.cn/v1/memo?source={App.AllAchievement}"),
Content = new StringContent(result, Encoding.UTF8, "application/json")
};
using var response = Utils.CHttpClient.Send(request);
if (response.StatusCode != HttpStatusCode.Created) {
Console.WriteLine(App.ExportToCocogoatFail);
return;
}
var responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var responseJson = JsonSerializer.Deserialize<CocogoatResponse>(responseText)!;
Console.WriteLine(Utils.ShellOpen($"https://cocogoat.work/achievement?memo={responseJson.Code}")
? App.ExportToCocogoatSuccess
: $"https://cocogoat.work/achievement?memo={responseJson.Code}");
}
private static void ToWxApp1(AchievementAllDataNotify data) {
var id = Guid.NewGuid().ToString("N").Substring(20, 8);
var result = JsonSerializer.Serialize(new Dictionary<string, object> {
{ "key", id },
{ "data", ExportToUIAFApp(data) }
});
using var request = new HttpRequestMessage {
Method = HttpMethod.Post,
RequestUri = new Uri("https://api.qyinter.com/achievementRedis"),
Content = new StringContent(result, Encoding.UTF8, "application/json")
};
using var response = Utils.CHttpClient.Send(request);
Console.WriteLine(App.ExportToWxApp1Success, id);
}
private static void ToHuTao(AchievementAllDataNotify data) {
Utils.CopyToClipboard(JsonSerializer.Serialize(ExportToUIAFApp(data)));
Utils.ShellOpen("hutao://achievement/import");
Console.WriteLine(App.ExportToSnapGenshinSuccess);
}
private static void ToPaimon(AchievementAllDataNotify data) {
var info = LoadAchievementInfo();
var output = new Dictionary<uint, Dictionary<uint, bool>>();
foreach (var ach in data.List.Where(a => a.Status is Status.Finished or Status.RewardTaken)) {
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
continue;
}
var map = output.GetValueOrDefault(achInfo.Group, new Dictionary<uint, bool>());
map[ach.Id == 81222 ? 81219 : ach.Id] = true;
output[achInfo.Group] = map;
}
var final = new Dictionary<string, Dictionary<uint, Dictionary<uint, bool>>> {
["achievement"] = output.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value)
};
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json");
File.WriteAllText(path, JsonSerializer.Serialize(final));
Console.WriteLine(App.ExportToFileSuccess, path);
}
private static void ToSeelie(AchievementAllDataNotify data) {
var output = new Dictionary<uint, Dictionary<string, bool>>();
foreach (var ach in data.List.Where(a => a.Status is Status.Finished or Status.RewardTaken)) {
output[ach.Id == 81222 ? 81219 : ach.Id] = new Dictionary<string, bool> {
["done"] = true
};
}
var final = new Dictionary<string, Dictionary<uint, Dictionary<string, bool>>> {
["achievements"] = output.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value)
};
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-seelie.json");
File.WriteAllText(path, JsonSerializer.Serialize(final));
Console.WriteLine(App.ExportToFileSuccess, path);
}
// ReSharper disable once InconsistentNaming
private static void ToCSV(AchievementAllDataNotify data) {
var info = LoadAchievementInfo();
var outList = new List<List<object>>();
foreach (var ach in data.List.OrderBy(a => a.Id)) {
if (UnusedAchievement.Contains(ach.Id)) continue;
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
continue;
}
var finishAt = "";
if (ach.Timestamp != 0) {
var ts = Convert.ToInt64(ach.Timestamp);
finishAt = DateTimeOffset.FromUnixTimeSeconds(ts).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
}
var current = ach.Status != Status.Unfinished ? ach.Current == 0 ? ach.Total : ach.Current : ach.Current;
outList.Add(new List<object> {
ach.Id, ach.Status.ToDesc(), achInfo.Group, achInfo.Name,
achInfo.Description, current, ach.Total, finishAt
});
}
var output = new List<string> { "ID,状态,特辑,名称,描述,当前进度,目标进度,完成时间" };
output.AddRange(outList.OrderBy(v => v[2]).Select(item => {
item[2] = info.Group[(uint) item[2]];
return item.JoinToString(",");
}));
var path = Path.GetFullPath($"achievement-{DateTime.Now:yyyyMMddHHmmss}.csv");
File.WriteAllText(path, $"\uFEFF{string.Join("\n", output)}");
Console.WriteLine(App.ExportToFileSuccess, path);
}
private static void ToXunkong(AchievementAllDataNotify data) {
if (CheckXunkongScheme()) {
Utils.CopyToClipboard(JsonSerializer.Serialize(ExportToUIAFApp(data)));
Utils.ShellOpen("xunkong://import-achievement?caller=YaeAchievement&from=clipboard");
Console.WriteLine(App.ExportToXunkongSuccess);
} else {
Console.WriteLine(App.ExportToXunkongNeedUpdate);
Utils.ShellOpen("ms-windows-store://pdp/?productid=9N2SVG0JMT12");
}
}
private static void ToRawJson(AchievementAllDataNotify data) {
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
File.WriteAllText(path, JsonSerializer.Serialize(data, new JsonSerializerOptions {
WriteIndented = true
}));
Console.WriteLine(App.ExportToFileSuccess, path);
}
// ReSharper disable once InconsistentNaming
private static Dictionary<string, object> ExportToUIAFApp(AchievementAllDataNotify data) {
var output = data.List
.Where(a => (uint)a.Status > 1 || a.Current > 0)
.Select(ach => new Dictionary<string, uint> {
["id"] = ach.Id,
["status"] = (uint) ach.Status,
["current"] = ach.Current,
["timestamp"] = ach.Timestamp
})
.ToList();
return new Dictionary<string, object> {
["info"] = new Dictionary<string, object> {
["export_app"] = "YaeAchievement",
["export_timestamp"] = DateTimeOffset.Now.ToUnixTimeSeconds(),
["export_app_version"] = GlobalVars.AppVersionName,
["uiaf_version"] = "v1.1"
},
["list"] = output
};
}
#pragma warning disable CA1416
private static bool CheckXunkongScheme() {
return (string?)Registry.ClassesRoot.OpenSubKey("xunkong")?.GetValue("") == "URL:xunkong";
}
#pragma warning restore CA1416
private static string JoinToString(this IEnumerable<object> list, string separator) {
return string.Join(separator, list);
}
private static readonly List<uint> UnusedAchievement = new() { 84517 };
private static string ToDesc(this Status status) {
return status switch {
Status.Invalid => App.StatusInvalid,
Status.Finished => App.StatusFinished,
Status.Unfinished => App.StatusUnfinished,
Status.RewardTaken => App.StatusRewardTaken,
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null)
};
}
private static AchievementInfo LoadAchievementInfo() {
var b = Utils.GetBucketFileAsByteArray("schicksal/metadata");
return AchievementInfo.Parser.ParseFrom(b);
}
}

View File

@@ -1,41 +0,0 @@
using System.Security.Cryptography;
using System.Text;
namespace YaeAchievement;
// ReSharper disable MemberCanBePrivate.Global
public static class Extensions {
// ReSharper disable once InconsistentNaming
private static readonly Lazy<MD5> md5 = new (MD5.Create);
// ReSharper disable once InconsistentNaming
private static readonly Lazy<SHA1> sha1 = new (SHA1.Create);
public static byte[] ToBytes(this string text) {
return Encoding.UTF8.GetBytes(text);
}
// ReSharper disable once InconsistentNaming
public static string MD5Hash(this string text) {
return text.ToBytes().MD5Hash();
}
// ReSharper disable once InconsistentNaming
public static string MD5Hash(this byte[] data) {
return md5.Value.ComputeHash(data).ToHex().ToLower();
}
// ReSharper disable once InconsistentNaming
public static string SHA1Hash(this string text, bool base64 = true) {
var bytes = sha1.Value.ComputeHash(text.ToBytes());
return base64 ? bytes.ToBase64() : bytes.ToHex();
}
public static string ToHex(this byte[] bytes) {
return Convert.ToHexString(bytes);
}
public static string ToBase64(this byte[] bytes) {
return Convert.ToBase64String(bytes);
}
}

View File

@@ -1,52 +0,0 @@
using System.ComponentModel;
using YaeAchievement.Win32;
using static YaeAchievement.Win32.Native;
namespace YaeAchievement;
public static class Injector {
public static unsafe bool CreateProcess(string path, out IntPtr hProc, out IntPtr hThread, out uint pid) {
var si = new StartupInfo();
SecurityAttributes* attr = null;
var dir = Path.GetDirectoryName(path)!;
var result = Native.CreateProcess(
path, null, ref *attr, ref *attr, false,
CreationFlags.CreateSuspended, IntPtr.Zero, dir, ref si, out var pi
);
pid = pi.dwProcessID;
hProc = pi.hProcess;
hThread = pi.hThread;
return result;
}
// todo: refactor
public static int LoadLibraryAndInject(IntPtr hProc, string libPath) {
var hKernel = GetModuleHandle("kernel32.dll");
if (hKernel == IntPtr.Zero) {
return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail");
}
var pLoadLibrary = GetProcAddress(hKernel, "LoadLibraryA");
if (pLoadLibrary == IntPtr.Zero) {
return new Win32Exception().PrintMsgAndReturnErrCode("GetProcAddress fail");
}
var pBase = VirtualAllocEx(hProc, IntPtr.Zero, libPath.Length + 1, AllocationType.Reserve | AllocationType.Commit, MemoryProtection.ReadWrite);
if (pBase == IntPtr.Zero) {
return new Win32Exception().PrintMsgAndReturnErrCode("VirtualAllocEx fail");
}
if (!WriteProcessMemory(hProc, pBase, libPath.ToCharArray(), libPath.Length, out _)) {
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
}
var hThread = CreateRemoteThread(hProc, IntPtr.Zero, 0, pLoadLibrary, pBase, 0, out _);
if (hThread == IntPtr.Zero) {
var e = new Win32Exception();
VirtualFreeEx(hProc, pBase, 0, AllocationType.Release);
return e.PrintMsgAndReturnErrCode("CreateRemoteThread fail");
}
if (WaitForSingleObject(hThread, 2000) == 0) {
VirtualFreeEx(hProc, pBase, 0, AllocationType.Release);
}
return !CloseHandle(hThread) ? new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail") : 0;
}
}

View File

@@ -1,56 +0,0 @@
using YaeAchievement;
using YaeAchievement.AppCenterSDK;
using YaeAchievement.AppCenterSDK.Models;
using YaeAchievement.res;
using static YaeAchievement.Utils;
InstallExitHook();
InstallExceptionHook();
await CheckVcRuntime();
CheckSelfIsRunning();
TryDisableQuickEdit();
CheckGenshinIsRunning();
Console.WriteLine(@"----------------------------------------------------");
Console.WriteLine(App.AppBanner, GlobalVars.AppVersionName);
Console.WriteLine(@"https://github.com/HolographicHat/YaeAchievement");
Console.WriteLine(@"----------------------------------------------------");
AppConfig.Load(args.GetOrNull(0) ?? "auto");
Export.ExportTo = ToUIntOrNull(args.GetOrNull(1)) ?? uint.MaxValue;
CheckUpdate(ToBooleanOrFalse(args.GetOrNull(2)));
AppCenter.Init();
new EventLog("AppInit") {
Properties = {
{ "AppVersion", GlobalVars.AppVersionName },
{ "SystemVersion", DeviceHelper.GetSystemVersion() }
}
}.Enqueue();
var usePreviousData = false;
var historyCache = new CacheFile("ExportData");
if (historyCache.LastWriteTime.AddMinutes(10) > DateTime.UtcNow) {
Console.WriteLine(App.UsePreviousData);
usePreviousData = Console.ReadLine() == "yes";
}
Export:
if(usePreviousData) {
AchievementAllDataNotify data;
try {
data = AchievementAllDataNotify.Parser.ParseFrom(historyCache.Read().Content);
} catch (Exception) {
usePreviousData = false;
goto Export;
}
Export.Choose(data);
} else {
StartAndWaitResult(AppConfig.GamePath, str => {
GlobalVars.UnexpectedExit = false;
var data = Convert.FromBase64String(str);
var list = AchievementAllDataNotify.Parser.ParseFrom(data);
historyCache.Write(data);
Export.Choose(list);
return true;
});
}

View File

@@ -1,584 +0,0 @@
// <auto-generated>
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: AchievementAllDataNotify.proto
// </auto-generated>
#pragma warning disable 1591, 0612, 3021, 8981
#region Designer generated code
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
/// <summary>Holder for reflection information generated from AchievementAllDataNotify.proto</summary>
public static partial class AchievementAllDataNotifyReflection {
#region Descriptor
/// <summary>File descriptor for AchievementAllDataNotify.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static AchievementAllDataNotifyReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"Ch5BY2hpZXZlbWVudEFsbERhdGFOb3RpZnkucHJvdG8iowIKGEFjaGlldmVt",
"ZW50QWxsRGF0YU5vdGlmeRIzCgRsaXN0GAQgAygLMiUuQWNoaWV2ZW1lbnRB",
"bGxEYXRhTm90aWZ5LkFjaGlldmVtZW50GtEBCgtBY2hpZXZlbWVudBIRCgl0",
"aW1lc3RhbXAYBiABKA0SDwoHY3VycmVudBgKIAEoDRINCgV0b3RhbBgOIAEo",
"DRIKCgJpZBgNIAEoDRI8CgZzdGF0dXMYBSABKA4yLC5BY2hpZXZlbWVudEFs",
"bERhdGFOb3RpZnkuQWNoaWV2ZW1lbnQuU3RhdHVzIkUKBlN0YXR1cxILCgdJ",
"TlZBTElEEAASDgoKVU5GSU5JU0hFRBABEgwKCEZJTklTSEVEEAISEAoMUkVX",
"QVJEX1RBS0VOEANiBnByb3RvMw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::AchievementAllDataNotify), global::AchievementAllDataNotify.Parser, new[]{ "List" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::AchievementAllDataNotify.Types.Achievement), global::AchievementAllDataNotify.Types.Achievement.Parser, new[]{ "Timestamp", "Current", "Total", "Id", "Status" }, null, new[]{ typeof(global::AchievementAllDataNotify.Types.Achievement.Types.Status) }, null, null)})
}));
}
#endregion
}
#region Messages
public sealed partial class AchievementAllDataNotify : pb::IMessage<AchievementAllDataNotify>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
, pb::IBufferMessage
#endif
{
private static readonly pb::MessageParser<AchievementAllDataNotify> _parser = new pb::MessageParser<AchievementAllDataNotify>(() => new AchievementAllDataNotify());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pb::MessageParser<AchievementAllDataNotify> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pbr::MessageDescriptor Descriptor {
get { return global::AchievementAllDataNotifyReflection.Descriptor.MessageTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementAllDataNotify() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementAllDataNotify(AchievementAllDataNotify other) : this() {
list_ = other.list_.Clone();
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementAllDataNotify Clone() {
return new AchievementAllDataNotify(this);
}
/// <summary>Field number for the "list" field.</summary>
public const int ListFieldNumber = 4;
private static readonly pb::FieldCodec<global::AchievementAllDataNotify.Types.Achievement> _repeated_list_codec
= pb::FieldCodec.ForMessage(34, global::AchievementAllDataNotify.Types.Achievement.Parser);
private readonly pbc::RepeatedField<global::AchievementAllDataNotify.Types.Achievement> list_ = new pbc::RepeatedField<global::AchievementAllDataNotify.Types.Achievement>();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public pbc::RepeatedField<global::AchievementAllDataNotify.Types.Achievement> List {
get { return list_; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
return Equals(other as AchievementAllDataNotify);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool Equals(AchievementAllDataNotify other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if(!list_.Equals(other.list_)) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override int GetHashCode() {
int hash = 1;
hash ^= list_.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void WriteTo(pb::CodedOutputStream output) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
output.WriteRawMessage(this);
#else
list_.WriteTo(output, _repeated_list_codec);
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
list_.WriteTo(ref output, _repeated_list_codec);
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
}
#endif
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int CalculateSize() {
int size = 0;
size += list_.CalculateSize(_repeated_list_codec);
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(AchievementAllDataNotify other) {
if (other == null) {
return;
}
list_.Add(other.list_);
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 34: {
list_.AddEntriesFrom(input, _repeated_list_codec);
break;
}
}
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
break;
case 34: {
list_.AddEntriesFrom(ref input, _repeated_list_codec);
break;
}
}
}
}
#endif
#region Nested types
/// <summary>Container for nested types declared in the AchievementAllDataNotify message type.</summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static partial class Types {
public sealed partial class Achievement : pb::IMessage<Achievement>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
, pb::IBufferMessage
#endif
{
private static readonly pb::MessageParser<Achievement> _parser = new pb::MessageParser<Achievement>(() => new Achievement());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pb::MessageParser<Achievement> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pbr::MessageDescriptor Descriptor {
get { return global::AchievementAllDataNotify.Descriptor.NestedTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public Achievement() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public Achievement(Achievement other) : this() {
timestamp_ = other.timestamp_;
current_ = other.current_;
total_ = other.total_;
id_ = other.id_;
status_ = other.status_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public Achievement Clone() {
return new Achievement(this);
}
/// <summary>Field number for the "timestamp" field.</summary>
public const int TimestampFieldNumber = 6;
private uint timestamp_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public uint Timestamp {
get { return timestamp_; }
set {
timestamp_ = value;
}
}
/// <summary>Field number for the "current" field.</summary>
public const int CurrentFieldNumber = 10;
private uint current_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public uint Current {
get { return current_; }
set {
current_ = value;
}
}
/// <summary>Field number for the "total" field.</summary>
public const int TotalFieldNumber = 14;
private uint total_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public uint Total {
get { return total_; }
set {
total_ = value;
}
}
/// <summary>Field number for the "id" field.</summary>
public const int IdFieldNumber = 13;
private uint id_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public uint Id {
get { return id_; }
set {
id_ = value;
}
}
/// <summary>Field number for the "status" field.</summary>
public const int StatusFieldNumber = 5;
private global::AchievementAllDataNotify.Types.Achievement.Types.Status status_ = global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public global::AchievementAllDataNotify.Types.Achievement.Types.Status Status {
get { return status_; }
set {
status_ = value;
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
return Equals(other as Achievement);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool Equals(Achievement other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (Timestamp != other.Timestamp) return false;
if (Current != other.Current) return false;
if (Total != other.Total) return false;
if (Id != other.Id) return false;
if (Status != other.Status) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override int GetHashCode() {
int hash = 1;
if (Timestamp != 0) hash ^= Timestamp.GetHashCode();
if (Current != 0) hash ^= Current.GetHashCode();
if (Total != 0) hash ^= Total.GetHashCode();
if (Id != 0) hash ^= Id.GetHashCode();
if (Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) hash ^= Status.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void WriteTo(pb::CodedOutputStream output) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
output.WriteRawMessage(this);
#else
if (Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) {
output.WriteRawTag(40);
output.WriteEnum((int) Status);
}
if (Timestamp != 0) {
output.WriteRawTag(48);
output.WriteUInt32(Timestamp);
}
if (Current != 0) {
output.WriteRawTag(80);
output.WriteUInt32(Current);
}
if (Id != 0) {
output.WriteRawTag(104);
output.WriteUInt32(Id);
}
if (Total != 0) {
output.WriteRawTag(112);
output.WriteUInt32(Total);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
if (Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) {
output.WriteRawTag(40);
output.WriteEnum((int) Status);
}
if (Timestamp != 0) {
output.WriteRawTag(48);
output.WriteUInt32(Timestamp);
}
if (Current != 0) {
output.WriteRawTag(80);
output.WriteUInt32(Current);
}
if (Id != 0) {
output.WriteRawTag(104);
output.WriteUInt32(Id);
}
if (Total != 0) {
output.WriteRawTag(112);
output.WriteUInt32(Total);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
}
#endif
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int CalculateSize() {
int size = 0;
if (Timestamp != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Timestamp);
}
if (Current != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Current);
}
if (Total != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Total);
}
if (Id != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Id);
}
if (Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) {
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Status);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(Achievement other) {
if (other == null) {
return;
}
if (other.Timestamp != 0) {
Timestamp = other.Timestamp;
}
if (other.Current != 0) {
Current = other.Current;
}
if (other.Total != 0) {
Total = other.Total;
}
if (other.Id != 0) {
Id = other.Id;
}
if (other.Status != global::AchievementAllDataNotify.Types.Achievement.Types.Status.Invalid) {
Status = other.Status;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 40: {
Status = (global::AchievementAllDataNotify.Types.Achievement.Types.Status) input.ReadEnum();
break;
}
case 48: {
Timestamp = input.ReadUInt32();
break;
}
case 80: {
Current = input.ReadUInt32();
break;
}
case 104: {
Id = input.ReadUInt32();
break;
}
case 112: {
Total = input.ReadUInt32();
break;
}
}
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
break;
case 40: {
Status = (global::AchievementAllDataNotify.Types.Achievement.Types.Status) input.ReadEnum();
break;
}
case 48: {
Timestamp = input.ReadUInt32();
break;
}
case 80: {
Current = input.ReadUInt32();
break;
}
case 104: {
Id = input.ReadUInt32();
break;
}
case 112: {
Total = input.ReadUInt32();
break;
}
}
}
}
#endif
#region Nested types
/// <summary>Container for nested types declared in the Achievement message type.</summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static partial class Types {
public enum Status {
[pbr::OriginalName("INVALID")] Invalid = 0,
[pbr::OriginalName("UNFINISHED")] Unfinished = 1,
[pbr::OriginalName("FINISHED")] Finished = 2,
[pbr::OriginalName("REWARD_TAKEN")] RewardTaken = 3,
}
}
#endregion
}
}
#endregion
}
#endregion
#endregion Designer generated code

View File

@@ -1,588 +0,0 @@
// <auto-generated>
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: AchievementInfo.proto
// </auto-generated>
#pragma warning disable 1591, 0612, 3021, 8981
#region Designer generated code
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
/// <summary>Holder for reflection information generated from AchievementInfo.proto</summary>
public static partial class AchievementInfoReflection {
#region Descriptor
/// <summary>File descriptor for AchievementInfo.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static AchievementInfoReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"ChVBY2hpZXZlbWVudEluZm8ucHJvdG8iUAoPQWNoaWV2ZW1lbnRJdGVtEgsK",
"A3ByZRgBIAEoDRINCgVncm91cBgCIAEoDRIMCgRuYW1lGAMgASgJEhMKC2Rl",
"c2NyaXB0aW9uGAQgASgJIugBCg9BY2hpZXZlbWVudEluZm8SDwoHdmVyc2lv",
"bhgBIAEoCRIqCgVncm91cBgCIAMoCzIbLkFjaGlldmVtZW50SW5mby5Hcm91",
"cEVudHJ5EioKBWl0ZW1zGAMgAygLMhsuQWNoaWV2ZW1lbnRJbmZvLkl0ZW1z",
"RW50cnkaLAoKR3JvdXBFbnRyeRILCgNrZXkYASABKA0SDQoFdmFsdWUYAiAB",
"KAk6AjgBGj4KCkl0ZW1zRW50cnkSCwoDa2V5GAEgASgNEh8KBXZhbHVlGAIg",
"ASgLMhAuQWNoaWV2ZW1lbnRJdGVtOgI4AWIGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::AchievementItem), global::AchievementItem.Parser, new[]{ "Pre", "Group", "Name", "Description" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::AchievementInfo), global::AchievementInfo.Parser, new[]{ "Version", "Group", "Items" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { null, null, })
}));
}
#endregion
}
#region Messages
public sealed partial class AchievementItem : pb::IMessage<AchievementItem>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
, pb::IBufferMessage
#endif
{
private static readonly pb::MessageParser<AchievementItem> _parser = new pb::MessageParser<AchievementItem>(() => new AchievementItem());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pb::MessageParser<AchievementItem> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pbr::MessageDescriptor Descriptor {
get { return global::AchievementInfoReflection.Descriptor.MessageTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementItem() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementItem(AchievementItem other) : this() {
pre_ = other.pre_;
group_ = other.group_;
name_ = other.name_;
description_ = other.description_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementItem Clone() {
return new AchievementItem(this);
}
/// <summary>Field number for the "pre" field.</summary>
public const int PreFieldNumber = 1;
private uint pre_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public uint Pre {
get { return pre_; }
set {
pre_ = value;
}
}
/// <summary>Field number for the "group" field.</summary>
public const int GroupFieldNumber = 2;
private uint group_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public uint Group {
get { return group_; }
set {
group_ = value;
}
}
/// <summary>Field number for the "name" field.</summary>
public const int NameFieldNumber = 3;
private string name_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Name {
get { return name_; }
set {
name_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "description" field.</summary>
public const int DescriptionFieldNumber = 4;
private string description_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Description {
get { return description_; }
set {
description_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
return Equals(other as AchievementItem);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool Equals(AchievementItem other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (Pre != other.Pre) return false;
if (Group != other.Group) return false;
if (Name != other.Name) return false;
if (Description != other.Description) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override int GetHashCode() {
int hash = 1;
if (Pre != 0) hash ^= Pre.GetHashCode();
if (Group != 0) hash ^= Group.GetHashCode();
if (Name.Length != 0) hash ^= Name.GetHashCode();
if (Description.Length != 0) hash ^= Description.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void WriteTo(pb::CodedOutputStream output) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
output.WriteRawMessage(this);
#else
if (Pre != 0) {
output.WriteRawTag(8);
output.WriteUInt32(Pre);
}
if (Group != 0) {
output.WriteRawTag(16);
output.WriteUInt32(Group);
}
if (Name.Length != 0) {
output.WriteRawTag(26);
output.WriteString(Name);
}
if (Description.Length != 0) {
output.WriteRawTag(34);
output.WriteString(Description);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
if (Pre != 0) {
output.WriteRawTag(8);
output.WriteUInt32(Pre);
}
if (Group != 0) {
output.WriteRawTag(16);
output.WriteUInt32(Group);
}
if (Name.Length != 0) {
output.WriteRawTag(26);
output.WriteString(Name);
}
if (Description.Length != 0) {
output.WriteRawTag(34);
output.WriteString(Description);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
}
#endif
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int CalculateSize() {
int size = 0;
if (Pre != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Pre);
}
if (Group != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(Group);
}
if (Name.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
}
if (Description.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Description);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(AchievementItem other) {
if (other == null) {
return;
}
if (other.Pre != 0) {
Pre = other.Pre;
}
if (other.Group != 0) {
Group = other.Group;
}
if (other.Name.Length != 0) {
Name = other.Name;
}
if (other.Description.Length != 0) {
Description = other.Description;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 8: {
Pre = input.ReadUInt32();
break;
}
case 16: {
Group = input.ReadUInt32();
break;
}
case 26: {
Name = input.ReadString();
break;
}
case 34: {
Description = input.ReadString();
break;
}
}
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
break;
case 8: {
Pre = input.ReadUInt32();
break;
}
case 16: {
Group = input.ReadUInt32();
break;
}
case 26: {
Name = input.ReadString();
break;
}
case 34: {
Description = input.ReadString();
break;
}
}
}
}
#endif
}
public sealed partial class AchievementInfo : pb::IMessage<AchievementInfo>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
, pb::IBufferMessage
#endif
{
private static readonly pb::MessageParser<AchievementInfo> _parser = new pb::MessageParser<AchievementInfo>(() => new AchievementInfo());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pb::MessageParser<AchievementInfo> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pbr::MessageDescriptor Descriptor {
get { return global::AchievementInfoReflection.Descriptor.MessageTypes[1]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementInfo() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementInfo(AchievementInfo other) : this() {
version_ = other.version_;
group_ = other.group_.Clone();
items_ = other.items_.Clone();
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public AchievementInfo Clone() {
return new AchievementInfo(this);
}
/// <summary>Field number for the "version" field.</summary>
public const int VersionFieldNumber = 1;
private string version_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Version {
get { return version_; }
set {
version_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "group" field.</summary>
public const int GroupFieldNumber = 2;
private static readonly pbc::MapField<uint, string>.Codec _map_group_codec
= new pbc::MapField<uint, string>.Codec(pb::FieldCodec.ForUInt32(8, 0), pb::FieldCodec.ForString(18, ""), 18);
private readonly pbc::MapField<uint, string> group_ = new pbc::MapField<uint, string>();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public pbc::MapField<uint, string> Group {
get { return group_; }
}
/// <summary>Field number for the "items" field.</summary>
public const int ItemsFieldNumber = 3;
private static readonly pbc::MapField<uint, global::AchievementItem>.Codec _map_items_codec
= new pbc::MapField<uint, global::AchievementItem>.Codec(pb::FieldCodec.ForUInt32(8, 0), pb::FieldCodec.ForMessage(18, global::AchievementItem.Parser), 26);
private readonly pbc::MapField<uint, global::AchievementItem> items_ = new pbc::MapField<uint, global::AchievementItem>();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public pbc::MapField<uint, global::AchievementItem> Items {
get { return items_; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
return Equals(other as AchievementInfo);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool Equals(AchievementInfo other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (Version != other.Version) return false;
if (!Group.Equals(other.Group)) return false;
if (!Items.Equals(other.Items)) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override int GetHashCode() {
int hash = 1;
if (Version.Length != 0) hash ^= Version.GetHashCode();
hash ^= Group.GetHashCode();
hash ^= Items.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void WriteTo(pb::CodedOutputStream output) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
output.WriteRawMessage(this);
#else
if (Version.Length != 0) {
output.WriteRawTag(10);
output.WriteString(Version);
}
group_.WriteTo(output, _map_group_codec);
items_.WriteTo(output, _map_items_codec);
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
if (Version.Length != 0) {
output.WriteRawTag(10);
output.WriteString(Version);
}
group_.WriteTo(ref output, _map_group_codec);
items_.WriteTo(ref output, _map_items_codec);
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
}
#endif
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int CalculateSize() {
int size = 0;
if (Version.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Version);
}
size += group_.CalculateSize(_map_group_codec);
size += items_.CalculateSize(_map_items_codec);
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(AchievementInfo other) {
if (other == null) {
return;
}
if (other.Version.Length != 0) {
Version = other.Version;
}
group_.Add(other.group_);
items_.Add(other.items_);
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 10: {
Version = input.ReadString();
break;
}
case 18: {
group_.AddEntriesFrom(input, _map_group_codec);
break;
}
case 26: {
items_.AddEntriesFrom(input, _map_items_codec);
break;
}
}
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
break;
case 10: {
Version = input.ReadString();
break;
}
case 18: {
group_.AddEntriesFrom(ref input, _map_group_codec);
break;
}
case 26: {
items_.AddEntriesFrom(ref input, _map_items_codec);
break;
}
}
}
}
#endif
}
#endregion
#endregion Designer generated code

View File

@@ -1,341 +0,0 @@
// <auto-generated>
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: CacheItem.proto
// </auto-generated>
#pragma warning disable 1591, 0612, 3021, 8981
#region Designer generated code
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
/// <summary>Holder for reflection information generated from CacheItem.proto</summary>
public static partial class CacheItemReflection {
#region Descriptor
/// <summary>File descriptor for CacheItem.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static CacheItemReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"Cg9DYWNoZUl0ZW0ucHJvdG8iTQoJQ2FjaGVJdGVtEg8KB3ZlcnNpb24YASAB",
"KAUSEAoIY2hlY2tzdW0YAiABKAkSDAoEZXRhZxgDIAEoCRIPCgdjb250ZW50",
"GAQgASgMYgZwcm90bzM="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::CacheItem), global::CacheItem.Parser, new[]{ "Version", "Checksum", "Etag", "Content" }, null, null, null, null)
}));
}
#endregion
}
#region Messages
public sealed partial class CacheItem : pb::IMessage<CacheItem>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
, pb::IBufferMessage
#endif
{
private static readonly pb::MessageParser<CacheItem> _parser = new pb::MessageParser<CacheItem>(() => new CacheItem());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pb::MessageParser<CacheItem> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pbr::MessageDescriptor Descriptor {
get { return global::CacheItemReflection.Descriptor.MessageTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public CacheItem() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public CacheItem(CacheItem other) : this() {
version_ = other.version_;
checksum_ = other.checksum_;
etag_ = other.etag_;
content_ = other.content_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public CacheItem Clone() {
return new CacheItem(this);
}
/// <summary>Field number for the "version" field.</summary>
public const int VersionFieldNumber = 1;
private int version_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int Version {
get { return version_; }
set {
version_ = value;
}
}
/// <summary>Field number for the "checksum" field.</summary>
public const int ChecksumFieldNumber = 2;
private string checksum_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Checksum {
get { return checksum_; }
set {
checksum_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "etag" field.</summary>
public const int EtagFieldNumber = 3;
private string etag_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Etag {
get { return etag_; }
set {
etag_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "content" field.</summary>
public const int ContentFieldNumber = 4;
private pb::ByteString content_ = pb::ByteString.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public pb::ByteString Content {
get { return content_; }
set {
content_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
return Equals(other as CacheItem);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool Equals(CacheItem other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (Version != other.Version) return false;
if (Checksum != other.Checksum) return false;
if (Etag != other.Etag) return false;
if (Content != other.Content) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override int GetHashCode() {
int hash = 1;
if (Version != 0) hash ^= Version.GetHashCode();
if (Checksum.Length != 0) hash ^= Checksum.GetHashCode();
if (Etag.Length != 0) hash ^= Etag.GetHashCode();
if (Content.Length != 0) hash ^= Content.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void WriteTo(pb::CodedOutputStream output) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
output.WriteRawMessage(this);
#else
if (Version != 0) {
output.WriteRawTag(8);
output.WriteInt32(Version);
}
if (Checksum.Length != 0) {
output.WriteRawTag(18);
output.WriteString(Checksum);
}
if (Etag.Length != 0) {
output.WriteRawTag(26);
output.WriteString(Etag);
}
if (Content.Length != 0) {
output.WriteRawTag(34);
output.WriteBytes(Content);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
if (Version != 0) {
output.WriteRawTag(8);
output.WriteInt32(Version);
}
if (Checksum.Length != 0) {
output.WriteRawTag(18);
output.WriteString(Checksum);
}
if (Etag.Length != 0) {
output.WriteRawTag(26);
output.WriteString(Etag);
}
if (Content.Length != 0) {
output.WriteRawTag(34);
output.WriteBytes(Content);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
}
#endif
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int CalculateSize() {
int size = 0;
if (Version != 0) {
size += 1 + pb::CodedOutputStream.ComputeInt32Size(Version);
}
if (Checksum.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Checksum);
}
if (Etag.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Etag);
}
if (Content.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeBytesSize(Content);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(CacheItem other) {
if (other == null) {
return;
}
if (other.Version != 0) {
Version = other.Version;
}
if (other.Checksum.Length != 0) {
Checksum = other.Checksum;
}
if (other.Etag.Length != 0) {
Etag = other.Etag;
}
if (other.Content.Length != 0) {
Content = other.Content;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 8: {
Version = input.ReadInt32();
break;
}
case 18: {
Checksum = input.ReadString();
break;
}
case 26: {
Etag = input.ReadString();
break;
}
case 34: {
Content = input.ReadBytes();
break;
}
}
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
break;
case 8: {
Version = input.ReadInt32();
break;
}
case 18: {
Checksum = input.ReadString();
break;
}
case 26: {
Etag = input.ReadString();
break;
}
case 34: {
Content = input.ReadBytes();
break;
}
}
}
}
#endif
}
#endregion
#endregion Designer generated code

View File

@@ -1,529 +0,0 @@
// <auto-generated>
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: UpdateInfo.proto
// </auto-generated>
#pragma warning disable 1591, 0612, 3021, 8981
#region Designer generated code
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
/// <summary>Holder for reflection information generated from UpdateInfo.proto</summary>
public static partial class UpdateInfoReflection {
#region Descriptor
/// <summary>File descriptor for UpdateInfo.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static UpdateInfoReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"ChBVcGRhdGVJbmZvLnByb3RvIuIBCgpVcGRhdGVJbmZvEhMKC3ZlcnNpb25D",
"b2RlGAEgASgNEhMKC3ZlcnNpb25OYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9u",
"GAMgASgJEhMKC3BhY2thZ2VMaW5rGAQgASgJEhMKC2ZvcmNlVXBkYXRlGAUg",
"ASgIEhkKEWVuYWJsZUxpYkRvd25sb2FkGAYgASgIEhoKEmVuYWJsZUF1dG9E",
"b3dubG9hZBgHIAEoCBIZChFjdXJyZW50Q05HYW1lSGFzaBgIIAEoCRIZChFj",
"dXJyZW50T1NHYW1lSGFzaBgJIAEoCWIGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::UpdateInfo), global::UpdateInfo.Parser, new[]{ "VersionCode", "VersionName", "Description", "PackageLink", "ForceUpdate", "EnableLibDownload", "EnableAutoDownload", "CurrentCNGameHash", "CurrentOSGameHash" }, null, null, null, null)
}));
}
#endregion
}
#region Messages
public sealed partial class UpdateInfo : pb::IMessage<UpdateInfo>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
, pb::IBufferMessage
#endif
{
private static readonly pb::MessageParser<UpdateInfo> _parser = new pb::MessageParser<UpdateInfo>(() => new UpdateInfo());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pb::MessageParser<UpdateInfo> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pbr::MessageDescriptor Descriptor {
get { return global::UpdateInfoReflection.Descriptor.MessageTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public UpdateInfo() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public UpdateInfo(UpdateInfo other) : this() {
versionCode_ = other.versionCode_;
versionName_ = other.versionName_;
description_ = other.description_;
packageLink_ = other.packageLink_;
forceUpdate_ = other.forceUpdate_;
enableLibDownload_ = other.enableLibDownload_;
enableAutoDownload_ = other.enableAutoDownload_;
currentCNGameHash_ = other.currentCNGameHash_;
currentOSGameHash_ = other.currentOSGameHash_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public UpdateInfo Clone() {
return new UpdateInfo(this);
}
/// <summary>Field number for the "versionCode" field.</summary>
public const int VersionCodeFieldNumber = 1;
private uint versionCode_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public uint VersionCode {
get { return versionCode_; }
set {
versionCode_ = value;
}
}
/// <summary>Field number for the "versionName" field.</summary>
public const int VersionNameFieldNumber = 2;
private string versionName_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string VersionName {
get { return versionName_; }
set {
versionName_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "description" field.</summary>
public const int DescriptionFieldNumber = 3;
private string description_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Description {
get { return description_; }
set {
description_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "packageLink" field.</summary>
public const int PackageLinkFieldNumber = 4;
private string packageLink_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string PackageLink {
get { return packageLink_; }
set {
packageLink_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "forceUpdate" field.</summary>
public const int ForceUpdateFieldNumber = 5;
private bool forceUpdate_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool ForceUpdate {
get { return forceUpdate_; }
set {
forceUpdate_ = value;
}
}
/// <summary>Field number for the "enableLibDownload" field.</summary>
public const int EnableLibDownloadFieldNumber = 6;
private bool enableLibDownload_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool EnableLibDownload {
get { return enableLibDownload_; }
set {
enableLibDownload_ = value;
}
}
/// <summary>Field number for the "enableAutoDownload" field.</summary>
public const int EnableAutoDownloadFieldNumber = 7;
private bool enableAutoDownload_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool EnableAutoDownload {
get { return enableAutoDownload_; }
set {
enableAutoDownload_ = value;
}
}
/// <summary>Field number for the "currentCNGameHash" field.</summary>
public const int CurrentCNGameHashFieldNumber = 8;
private string currentCNGameHash_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string CurrentCNGameHash {
get { return currentCNGameHash_; }
set {
currentCNGameHash_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "currentOSGameHash" field.</summary>
public const int CurrentOSGameHashFieldNumber = 9;
private string currentOSGameHash_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string CurrentOSGameHash {
get { return currentOSGameHash_; }
set {
currentOSGameHash_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
return Equals(other as UpdateInfo);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool Equals(UpdateInfo other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (VersionCode != other.VersionCode) return false;
if (VersionName != other.VersionName) return false;
if (Description != other.Description) return false;
if (PackageLink != other.PackageLink) return false;
if (ForceUpdate != other.ForceUpdate) return false;
if (EnableLibDownload != other.EnableLibDownload) return false;
if (EnableAutoDownload != other.EnableAutoDownload) return false;
if (CurrentCNGameHash != other.CurrentCNGameHash) return false;
if (CurrentOSGameHash != other.CurrentOSGameHash) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override int GetHashCode() {
int hash = 1;
if (VersionCode != 0) hash ^= VersionCode.GetHashCode();
if (VersionName.Length != 0) hash ^= VersionName.GetHashCode();
if (Description.Length != 0) hash ^= Description.GetHashCode();
if (PackageLink.Length != 0) hash ^= PackageLink.GetHashCode();
if (ForceUpdate != false) hash ^= ForceUpdate.GetHashCode();
if (EnableLibDownload != false) hash ^= EnableLibDownload.GetHashCode();
if (EnableAutoDownload != false) hash ^= EnableAutoDownload.GetHashCode();
if (CurrentCNGameHash.Length != 0) hash ^= CurrentCNGameHash.GetHashCode();
if (CurrentOSGameHash.Length != 0) hash ^= CurrentOSGameHash.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void WriteTo(pb::CodedOutputStream output) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
output.WriteRawMessage(this);
#else
if (VersionCode != 0) {
output.WriteRawTag(8);
output.WriteUInt32(VersionCode);
}
if (VersionName.Length != 0) {
output.WriteRawTag(18);
output.WriteString(VersionName);
}
if (Description.Length != 0) {
output.WriteRawTag(26);
output.WriteString(Description);
}
if (PackageLink.Length != 0) {
output.WriteRawTag(34);
output.WriteString(PackageLink);
}
if (ForceUpdate != false) {
output.WriteRawTag(40);
output.WriteBool(ForceUpdate);
}
if (EnableLibDownload != false) {
output.WriteRawTag(48);
output.WriteBool(EnableLibDownload);
}
if (EnableAutoDownload != false) {
output.WriteRawTag(56);
output.WriteBool(EnableAutoDownload);
}
if (CurrentCNGameHash.Length != 0) {
output.WriteRawTag(66);
output.WriteString(CurrentCNGameHash);
}
if (CurrentOSGameHash.Length != 0) {
output.WriteRawTag(74);
output.WriteString(CurrentOSGameHash);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
if (VersionCode != 0) {
output.WriteRawTag(8);
output.WriteUInt32(VersionCode);
}
if (VersionName.Length != 0) {
output.WriteRawTag(18);
output.WriteString(VersionName);
}
if (Description.Length != 0) {
output.WriteRawTag(26);
output.WriteString(Description);
}
if (PackageLink.Length != 0) {
output.WriteRawTag(34);
output.WriteString(PackageLink);
}
if (ForceUpdate != false) {
output.WriteRawTag(40);
output.WriteBool(ForceUpdate);
}
if (EnableLibDownload != false) {
output.WriteRawTag(48);
output.WriteBool(EnableLibDownload);
}
if (EnableAutoDownload != false) {
output.WriteRawTag(56);
output.WriteBool(EnableAutoDownload);
}
if (CurrentCNGameHash.Length != 0) {
output.WriteRawTag(66);
output.WriteString(CurrentCNGameHash);
}
if (CurrentOSGameHash.Length != 0) {
output.WriteRawTag(74);
output.WriteString(CurrentOSGameHash);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
}
#endif
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int CalculateSize() {
int size = 0;
if (VersionCode != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(VersionCode);
}
if (VersionName.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(VersionName);
}
if (Description.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Description);
}
if (PackageLink.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(PackageLink);
}
if (ForceUpdate != false) {
size += 1 + 1;
}
if (EnableLibDownload != false) {
size += 1 + 1;
}
if (EnableAutoDownload != false) {
size += 1 + 1;
}
if (CurrentCNGameHash.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(CurrentCNGameHash);
}
if (CurrentOSGameHash.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(CurrentOSGameHash);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(UpdateInfo other) {
if (other == null) {
return;
}
if (other.VersionCode != 0) {
VersionCode = other.VersionCode;
}
if (other.VersionName.Length != 0) {
VersionName = other.VersionName;
}
if (other.Description.Length != 0) {
Description = other.Description;
}
if (other.PackageLink.Length != 0) {
PackageLink = other.PackageLink;
}
if (other.ForceUpdate != false) {
ForceUpdate = other.ForceUpdate;
}
if (other.EnableLibDownload != false) {
EnableLibDownload = other.EnableLibDownload;
}
if (other.EnableAutoDownload != false) {
EnableAutoDownload = other.EnableAutoDownload;
}
if (other.CurrentCNGameHash.Length != 0) {
CurrentCNGameHash = other.CurrentCNGameHash;
}
if (other.CurrentOSGameHash.Length != 0) {
CurrentOSGameHash = other.CurrentOSGameHash;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 8: {
VersionCode = input.ReadUInt32();
break;
}
case 18: {
VersionName = input.ReadString();
break;
}
case 26: {
Description = input.ReadString();
break;
}
case 34: {
PackageLink = input.ReadString();
break;
}
case 40: {
ForceUpdate = input.ReadBool();
break;
}
case 48: {
EnableLibDownload = input.ReadBool();
break;
}
case 56: {
EnableAutoDownload = input.ReadBool();
break;
}
case 66: {
CurrentCNGameHash = input.ReadString();
break;
}
case 74: {
CurrentOSGameHash = input.ReadString();
break;
}
}
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
break;
case 8: {
VersionCode = input.ReadUInt32();
break;
}
case 18: {
VersionName = input.ReadString();
break;
}
case 26: {
Description = input.ReadString();
break;
}
case 34: {
PackageLink = input.ReadString();
break;
}
case 40: {
ForceUpdate = input.ReadBool();
break;
}
case 48: {
EnableLibDownload = input.ReadBool();
break;
}
case 56: {
EnableAutoDownload = input.ReadBool();
break;
}
case 66: {
CurrentCNGameHash = input.ReadString();
break;
}
case 74: {
CurrentOSGameHash = input.ReadString();
break;
}
}
}
}
#endif
}
#endregion
#endregion Designer generated code

View File

@@ -1,306 +0,0 @@
using System.ComponentModel;
using System.Diagnostics;
using System.IO.Pipes;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using YaeAchievement.AppCenterSDK;
using YaeAchievement.res;
using YaeAchievement.Win32;
namespace YaeAchievement;
public static class Utils {
public static readonly HttpClient CHttpClient = new (new HttpClientHandler {
Proxy = GlobalVars.DebugProxy ? new WebProxy("http://127.0.0.1:8888") : null,
AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.GZip
}) {
DefaultRequestHeaders = {
UserAgent = {
new ProductInfoHeaderValue("YaeAchievement", GlobalVars.AppVersion.ToString(2))
}
}
};
public static byte[] GetBucketFileAsByteArray(string path, bool cache = true) {
try {
using var msg = new HttpRequestMessage {
Method = HttpMethod.Get,
RequestUri = new Uri($"{GlobalVars.BucketHost}/{path}")
};
var cacheFile = new CacheFile(path);
if (cache && cacheFile.Exists()) {
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cacheFile.Read().Etag}");
}
using var response = CHttpClient.Send(msg);
if (cache && response.StatusCode == HttpStatusCode.NotModified) {
return cacheFile.Read().Content.ToByteArray();
}
response.EnsureSuccessStatusCode();
var responseBytes = response.Content.ReadAsByteArrayAsync().Result;
if (cache) {
var etag = response.Headers.ETag!.Tag;
cacheFile.Write(responseBytes, etag);
}
return responseBytes;
} catch (Exception e) {
Console.WriteLine(App.NetworkError, e.Message);
Environment.Exit(-1);
return null!;
}
}
public static T? GetOrNull<T>(this T[] array, uint index) where T : class {
return array.Length > index ? array[index] : null;
}
public static uint? ToUIntOrNull(string? value) {
return value != null ? uint.TryParse(value, out var result) ? result : null : null;
}
public static bool ToBooleanOrFalse(string? value) {
return value != null && bool.TryParse(value, out var result) && result;
}
public static void CopyToClipboard(string text) {
if (Native.OpenClipboard(IntPtr.Zero)) {
Native.EmptyClipboard();
var hGlobal = Marshal.AllocHGlobal((text.Length + 1) * 2);
var hPtr = Native.GlobalLock(hGlobal);
Marshal.Copy(text.ToCharArray(), 0, hPtr, text.Length);
Native.GlobalUnlock(hPtr);
Native.SetClipboardData(13, hGlobal);
Marshal.FreeHGlobal(hGlobal);
Native.CloseClipboard();
} else {
throw new Win32Exception();
}
}
// ReSharper disable once NotAccessedField.Local
private static UpdateInfo _updateInfo = null!;
public static void CheckUpdate(bool useLocalLib) {
var info = UpdateInfo.Parser.ParseFrom(GetBucketFileAsByteArray("schicksal/version"))!;
if (GlobalVars.AppVersionCode != info.VersionCode) {
Console.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, info.VersionName);
Console.WriteLine(App.UpdateDescription, info.Description);
if (info.EnableAutoDownload) {
Console.WriteLine(App.UpdateDownloading);
var tmpPath = Path.GetTempFileName();
File.WriteAllBytes(tmpPath, GetBucketFileAsByteArray(info.PackageLink));
var updaterArgs = $"{Environment.ProcessId}|{Environment.ProcessPath}|{tmpPath}";
var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe");
var updaterHash = App.Updater.MD5Hash();
if (!File.Exists(updaterPath) || File.ReadAllBytes(updaterPath).MD5Hash() != updaterHash) {
File.WriteAllBytes(updaterPath, App.Updater);
}
ShellOpen(updaterPath, updaterArgs.ToBytes().ToBase64());
GlobalVars.PauseOnExit = false;
Environment.Exit(0);
}
Console.WriteLine(App.DownloadLink, info.PackageLink);
if (info.ForceUpdate) {
Environment.Exit(0);
}
}
if (useLocalLib) {
Console.WriteLine(@"[DEBUG] Use local native lib.");
File.Copy(Path.Combine(GlobalVars.AppPath, "YaeAchievementLib.dll"), GlobalVars.LibFilePath, true);
} else if (info.EnableLibDownload) {
File.WriteAllBytes(GlobalVars.LibFilePath, GetBucketFileAsByteArray("schicksal/lib.dll"));
}
_updateInfo = info;
}
public static void CheckSelfIsRunning() {
Process.EnterDebugMode();
var cur = Process.GetCurrentProcess();
foreach (var process in Process.GetProcesses().Where(process => process.Id != cur.Id)) {
if (process.ProcessName == cur.ProcessName) {
Console.WriteLine(App.AnotherInstance);
Environment.Exit(302);
}
}
Process.LeaveDebugMode();
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static bool ShellOpen(string path, string? args = null) {
try {
var startInfo = new ProcessStartInfo {
FileName = path,
UseShellExecute = true
};
if (args != null) {
startInfo.Arguments = args;
}
return new Process {
StartInfo = startInfo
}.Start();
} catch (Exception) {
return false;
}
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static bool TryDisableQuickEdit() {
var handle = Native.GetStdHandle();
return Native.GetConsoleMode(handle, out var mode) && Native.SetConsoleMode(handle, mode&~64);
}
public static void CheckGenshinIsRunning() {
Process.EnterDebugMode();
foreach (var process in Process.GetProcesses()) {
if (process.ProcessName is "GenshinImpact" or "YuanShen"
&& !process.HasExited
&& process.MainWindowHandle != IntPtr.Zero
) {
Console.WriteLine(App.GenshinIsRunning, process.Id);
Environment.Exit(301);
}
}
Process.LeaveDebugMode();
}
// ReSharper disable once InconsistentNaming
private static Process? proc;
public static void InstallExitHook() {
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
proc?.Kill();
if (GlobalVars.PauseOnExit) {
Console.WriteLine(App.PressKeyToExit);
Console.ReadKey();
}
};
}
public static void InstallExceptionHook() {
AppDomain.CurrentDomain.UnhandledException += (_, e) => {
var ex = e.ExceptionObject;
switch (ex) {
case ApplicationException ex1:
Console.WriteLine(ex1.Message);
break;
case SocketException ex2:
Console.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
break;
case HttpRequestException ex3:
Console.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
break;
default:
Console.WriteLine(ex.ToString());
Console.WriteLine(App.UploadError);
AppCenter.TrackCrash((Exception) e.ExceptionObject);
AppCenter.Upload();
break;
}
Environment.Exit(-1);
};
}
private static bool CheckGenshinIsLatestVersion(string path) {
#if DEBUG
return true;
#else
var hash = File.ReadAllBytes(path).MD5Hash();
return File.Exists(path) && (hash == _updateInfo.CurrentCNGameHash || hash == _updateInfo.CurrentOSGameHash);
#endif
}
// ReSharper disable once UnusedMethodReturnValue.Global
public static Thread StartAndWaitResult(string exePath, Func<string, bool> onReceive) {
if (!CheckGenshinIsLatestVersion(exePath)) {
Console.WriteLine(App.GenshinHashError);
Environment.Exit(0);
}
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
try {
File.Delete(GlobalVars.LibFilePath);
} catch (Exception) { /* ignored */ }
};
if (!Injector.CreateProcess(exePath, out var hProcess, out var hThread, out var pid)) {
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("ICreateProcess fail"));
}
if (Injector.LoadLibraryAndInject(hProcess, GlobalVars.LibFilePath) != 0) {
if (!Native.TerminateProcess(hProcess, 0)) {
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail"));
}
}
Console.WriteLine(App.GameLoading, pid);
proc = Process.GetProcessById(Convert.ToInt32(pid));
proc.EnableRaisingEvents = true;
proc.Exited += (_, _) => {
if (GlobalVars.UnexpectedExit) {
proc = null;
Console.WriteLine(App.GameProcessExit);
Environment.Exit(114514);
}
};
if (Native.ResumeThread(hThread) == 0xFFFFFFFF) {
var e = new Win32Exception();
if (!Native.TerminateProcess(hProcess, 0)) {
new Win32Exception().PrintMsgAndReturnErrCode("TerminateProcess fail");
}
Environment.Exit(e.PrintMsgAndReturnErrCode("ResumeThread fail"));
}
if (!Native.CloseHandle(hProcess)) {
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("CloseHandle fail"));
}
var ts = new ThreadStart(() => {
var server = new NamedPipeServerStream(GlobalVars.PipeName);
server.WaitForConnection();
using var reader = new StreamReader(server);
while (!proc.HasExited) {
var line = reader.ReadLine();
if (line?.Length > 0) {
if (onReceive(line)) {
break;
}
server.Disconnect();
server.WaitForConnection();
}
}
});
var th = new Thread(ts);
th.Start();
return th;
}
#pragma warning disable CA1416
public static async Task CheckVcRuntime() {
using var root = Registry.LocalMachine;
using var sub = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall")!;
var installed = sub.GetSubKeyNames()
.Select(subKeyName => {
try {
return sub.OpenSubKey(subKeyName);
} catch (Exception) {
return null;
}
})
.Select(item => item?.GetValue("DisplayName") as string ?? string.Empty)
.Any(name => name.Contains("Microsoft Visual C++ 2022 X64 "));
if (!installed) {
Console.WriteLine(App.VcRuntimeDownload);
var pkgPath = Path.Combine(GlobalVars.DataPath, "vc_redist.x64.exe");
var bytes = await CHttpClient.GetByteArrayAsync("https://aka.ms/vs/17/release/vc_redist.x64.exe");
await File.WriteAllBytesAsync(pkgPath, bytes);
Console.WriteLine(App.VcRuntimeInstalling);
using var process = new Process {
StartInfo = {
FileName = pkgPath,
Arguments = "/install /passive /norestart"
}
};
process.Start();
await process.WaitForExitAsync();
File.Delete(pkgPath);
}
}
}

View File

@@ -1,16 +0,0 @@
namespace YaeAchievement.Win32;
[Flags]
public enum AllocationType : uint {
Commit = 0x00001000,
Reserve = 0x00002000,
Reset = 0x00080000,
TopDown = 0x00100000,
WriteWatch = 0x00200000,
Physical = 0x00400000,
Rotate = 0x00800000,
ResetUndo = 0x01000000,
LargePages = 0x20000000,
Decommit = 0x00004000,
Release = 0x00008000
}

View File

@@ -1,9 +0,0 @@
namespace YaeAchievement.Win32;
[Flags]
public enum CreationFlags : uint {
CreateSuspended = 0x00000004,
DetachedProcess = 0x00000008,
CreateNoWindow = 0x08000000,
ExtendedStartupInfoPresent = 0x00080000
}

View File

@@ -1,14 +0,0 @@
using System.ComponentModel;
using YaeAchievement.AppCenterSDK;
namespace YaeAchievement.Win32;
public static class Extensions {
public static int PrintMsgAndReturnErrCode(this Win32Exception ex, string msg) {
Console.WriteLine($"{msg}: {ex.Message}");
AppCenter.TrackCrash(ex, false);
return ex.NativeErrorCode;
}
}

Some files were not shown because too many files have changed in this diff Show More