23 Commits
3.1.0 ... 3.5.0

Author SHA1 Message Date
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
38 changed files with 238 additions and 353 deletions

View File

@@ -9,9 +9,22 @@
- 支持导出所有类别的成就 - 支持导出所有类别的成就
- 支持官服,渠道服与国际服 - 支持官服,渠道服与国际服
- 支持导出至[椰羊](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)
- 没有窗口大小、游戏语言等要求 - 没有窗口大小、游戏语言等要求
## 导出支持
> 按照数字键选择导出方式,<kbd>0</kbd> 为默认导出方式
0. [椰羊](https://cocogoat.work/achievement)
1. [胡桃工具箱](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements)
4. 表格文件 `.csv`
5. [寻空](https://github.com/xunkong/xunkong)
6. [原魔工具箱](https://apps.apple.com/app/id1663989619)
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON 文件
## 使用说明 ## 使用说明
→ [Tutorial.md](Tutorial.md) → [Tutorial.md](Tutorial.md)

View File

@@ -13,9 +13,22 @@
- Support for exporting all categories of achievements - Support for exporting all categories of achievements
- Supports all versions of Genshin Impact - 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. - There are no requirements for window size, game language, etc.
## Export support
> Select the export method according to the number keys, <kbd>0</kbd> is the default export method
0. [Cocogoat](https://cocogoat.work/achievement)
1. [Snap HuTao](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements)
4. Form File `.csv`
5. [XunKong](https://github.com/xunkong/xunkong)
6. [YuanmoTools](https://apps.apple.com/app/id1663989619)
7. [TeyvatGuide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON file
## Instructions for Use: ## Instructions for Use:
→ [Tutorial_EN.md](Tutorial_EN.md) → [Tutorial_EN.md](Tutorial_EN.md)

View File

@@ -71,11 +71,11 @@
### 各种工具的介绍烦请移步至各工具的官方页面进行查看(下方序号对应导出序号) ### 各种工具的介绍烦请移步至各工具的官方页面进行查看(下方序号对应导出序号)
0. [椰羊](https://cocogoat.work/achievement) 0. [椰羊](https://cocogoat.work/achievement)
1. [胡桃工具箱](https://github.com/DGP-Studio/Snap.HuTao)
1. [Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/) 2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements) 3. [Seelie.me](https://seelie.me/achievements)
4. ~~表格文件 `.csv`~~
4. [寻空](https://github.com/xunkong/xunkong) 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

@@ -65,9 +65,12 @@ At this time, you can select the Achievements in the left column to view the imp
### For the introduction of different tools, please visit the official page of each tool to see: ### For the introduction of different tools, please visit the official page of each tool to see:
1. [Snap·HuTao](https://github.com/DGP-Studio/Snap.HuTao) 0. [Cocogoat](https://cocogoat.work/achievement)
1. [Snap HuTao](https://github.com/DGP-Studio/Snap.HuTao)
2. [Paimon.moe](https://paimon.moe/achievement/) 2. [Paimon.moe](https://paimon.moe/achievement/)
3. [Seelie.me](https://seelie.me/achievements) 3. [Seelie.me](https://seelie.me/achievements)
4. ~~Form File `.csv`~~
5. [XunKong](https://github.com/xunkong/xunkong)
6. [YuanmoTools](https://apps.apple.com/app/id1663989619)
7. [Teyvat Guide](https://github.com/BTMuli/TeyvatGuide)
8. [UIAF](https://uigf.org/standards/UIAF.html) JSON file

View File

@@ -24,13 +24,11 @@
<PackageReference Include="Google.Protobuf" Version="3.22.1" /> <PackageReference Include="Google.Protobuf" Version="3.22.1" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.188-beta"> <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.188-beta">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Grpc.Tools" Version="2.53.0"> <PackageReference Include="Grpc.Tools" Version="2.53.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -110,12 +110,11 @@
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="src\HookManager.h" /> <ClInclude Include="src\HookManager.h" />
<ClInclude Include="src\il2cpp-api-functions.h" />
<ClInclude Include="src\il2cpp-appdata.h" /> <ClInclude Include="src\il2cpp-appdata.h" />
<ClInclude Include="src\il2cpp-functions.h" /> <ClInclude Include="src\il2cpp-functions.h" />
<ClInclude Include="src\il2cpp-types-ptr.h" />
<ClInclude Include="src\il2cpp-types.h" /> <ClInclude Include="src\il2cpp-types.h" />
<ClInclude Include="src\il2cpp-init.h" /> <ClInclude Include="src\il2cpp-init.h" />
<ClInclude Include="src\il2cpp-unity-functions.h" />
<ClInclude Include="src\pch.h" /> <ClInclude Include="src\pch.h" />
<ClInclude Include="src\util.h" /> <ClInclude Include="src\util.h" />
</ItemGroup> </ItemGroup>

View File

@@ -1,70 +1,47 @@
#include "pch.h" // ReSharper disable CppCStyleCast
// ReSharper disable CppInconsistentNaming
// ReSharper disable CppClangTidyModernizeUseStdPrint
// ReSharper disable CppClangTidyClangDiagnosticCastAlign
// ReSharper disable CppClangTidyHicppMultiwayPathsCovered
// ReSharper disable CppDefaultCaseNotHandledInSwitchStatement
// ReSharper disable CppClangTidyClangDiagnosticCastFunctionTypeStrict
#include "pch.h"
#include "util.h" #include "util.h"
#include "il2cpp-init.h" #include "il2cpp-init.h"
using Genshin::ByteArray, Genshin::ClientKcpEvent, Genshin::KcpPacket, Genshin::KcpEventType; using Genshin::ByteArray;
using std::to_string;
HWND unityWnd = nullptr; HWND unityWnd = nullptr;
HANDLE hPipe = nullptr; HANDLE hPipe = nullptr;
// Allow Protocol: GetPlayerTokenRsp, PlayerLoginRsp, AchievementAllDataNotify, PingRsp
std::set<UINT16> PacketWhitelist = { 1347, 4424, 20342, 25731 };
bool OnPacket(KcpPacket* pkt) {
if (pkt->data == nullptr) return true;
auto len = pkt->length;
auto data = (ByteArray*)new BYTE[len + 32];
data->max_length = len;
memcpy(data->vector, pkt->data, len);
Genshin::XorEncrypt(&data, len, nullptr);
if (ReadMapped<UINT16>(data->vector, 0) != 0x4567) {
delete[] data;
return true;
}
if (!PacketWhitelist.contains(ReadMapped<UINT16>(data->vector, 2))) {
//ifdef _DEBUG
printf("Blocked cmdid: %d\n", ReadMapped<UINT16>(data->vector, 2));
//endif
delete[] data;
return false;
}
printf("Passed cmdid: %d\n", ReadMapped<UINT16>(data->vector, 2));
if (ReadMapped<UINT16>(data->vector, 2) == 20342) {
const auto headLength = ReadMapped<UINT16>(data->vector, 4);
const auto dataLength = ReadMapped<UINT32>(data->vector, 6);
const auto cStr = base64_encode(data->vector + 10 + headLength, dataLength) + "\n";
WriteFile(hPipe, cStr.c_str(), cStr.length(), nullptr, nullptr);
CloseHandle(hPipe);
auto manager = Genshin::GetSingletonInstance(Genshin::GetSingletonManager(), il2cpp_string_new("GameManager"));
Genshin::ForceQuit(manager);
}
delete[] data;
return true;
}
std::string checksum; std::string checksum;
namespace Hook { namespace Hook {
void SetVersion(void* obj, Il2CppString* value, void* method) { ByteArray* UnityEngine_RecordUserData(const INT type) {
const auto version = ToString(value); if (type == 0) {
value = string_new(version + " YaeAchievement"); const auto arr = new ByteArray {};
CALL_ORIGIN(SetVersion, obj, value, method); const auto len = checksum.length();
} arr->max_length = len;
memcpy(&arr->vector[0], checksum.data(), len);
bool KcpRecv(void* client, ClientKcpEvent* evt, void* method) { return arr;
const auto result = CALL_ORIGIN(KcpRecv, client, evt, method);
if (result == 0 || evt->fields.type != KcpEventType::EventRecvMsg) {
return result;
} }
return OnPacket(evt->fields.packet) ? result : false;
}
ByteArray* UnityEngine_RecordUserData(INT type) {
return new ByteArray {}; return new ByteArray {};
} }
// 不再使用checksum(?
uint16_t BitConverter_ToUInt16(ByteArray* val, const int startIndex) {
const auto ret = CALL_ORIGIN(BitConverter_ToUInt16, val, startIndex);
if (ret == 0xAB89 && ReadMapped<UINT16>(val->vector, 2) == 20248) {
const auto headLength = ReadMapped<UINT16>(val->vector, 4);
const auto dataLength = ReadMapped<UINT32>(val->vector, 6);
const auto cStr = base64_encode(val->vector + 10 + headLength, dataLength) + "\n";
WriteFile(hPipe, cStr.c_str(), (DWORD) cStr.length(), nullptr, nullptr);
CloseHandle(hPipe);
ExitProcess(0);
}
return ret;
}
} }
void Run(HMODULE* phModule) { void Run(HMODULE* phModule) {
@@ -76,8 +53,11 @@ void Run(HMODULE* phModule) {
Sleep(5000); Sleep(5000);
DisableVMProtect(); DisableVMProtect();
InitIL2CPP(); InitIL2CPP();
HookManager::install(Genshin::KcpRecv, Hook::KcpRecv); for (int i = 0; i < 3; i++) {
HookManager::install(Genshin::SetVersion, Hook::SetVersion); const auto result = Genshin::RecordUserData(i);
checksum += string(reinterpret_cast<char*>(&result->vector[0]), result->max_length);
}
HookManager::install(Genshin::BitConverter_ToUInt16, Hook::BitConverter_ToUInt16);
HookManager::install(Genshin::UnityEngine_RecordUserData, Hook::UnityEngine_RecordUserData); HookManager::install(Genshin::UnityEngine_RecordUserData, Hook::UnityEngine_RecordUserData);
hPipe = CreateFile(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); hPipe = CreateFile(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (hPipe == INVALID_HANDLE_VALUE) { if (hPipe == INVALID_HANDLE_VALUE) {

View File

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

View File

@@ -1,20 +1,19 @@
// ReSharper disable CppClangTidyBugproneMacroParentheses
#pragma once #pragma once
#include "il2cpp-types.h" #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 // Application-specific functions
#define DO_APP_FUNC(ca, oa, r, n, p) extern r (*n) p #define DO_APP_FUNC(ca, oa, r, n, p) extern r (*n) p
#define DO_UNI_FUNC(ca, oa, r, n, p) extern r (*n) p
namespace Genshin { namespace Genshin {
#include "il2cpp-functions.h" #include "il2cpp-functions.h"
} }
#undef DO_UNI_FUNC
#undef DO_APP_FUNC #undef DO_APP_FUNC
#define DO_UNI_FUNC(ca, oa, r, n, p) extern r (*n) p #define DO_TYPEDEF(ca, oa, n) extern n##__Class **n##__TypeInfo
namespace Genshin { namespace Genshin {
#include "il2cpp-unity-functions.h" #include "il2cpp-types-ptr.h"
} }
#undef DO_UNI_FUNC #undef DO_TYPEDEF

View File

@@ -2,14 +2,10 @@ using namespace Genshin;
// DO_APP_FUNC(CN_OFFSET, OS_OFFSET, RETURN, FUNC_NAME, (ARGS...)); // DO_APP_FUNC(CN_OFFSET, OS_OFFSET, RETURN, FUNC_NAME, (ARGS...));
DO_APP_FUNC(0x258fd40, 0x2548e50, void, SetVersion, (void* obj, Il2CppString* value, void* method)); DO_APP_FUNC(0x569170, 0x568470, LPVOID, il2cpp_object_new, (LPVOID t));
DO_APP_FUNC(0x3220f00, 0x31c1650, void, XorEncrypt, (ByteArray** data, int length, void* method)); DO_APP_FUNC(0x06C43A80, 0x06775280, ByteArray*, RecordUserData, (int32_t nType));
DO_APP_FUNC(0x12f5df0, 0x12ddd80, bool, KcpRecv, (void* client, ClientKcpEvent* evt, void* method)); DO_APP_FUNC(0x0C87B240, 0x0C89EFB0, uint16_t, BitConverter_ToUInt16, (ByteArray* val, int startIndex));
DO_APP_FUNC(0x264ee90, 0x2606720, VOID, ForceQuit, (LPVOID obj)); DO_UNI_FUNC(0x105560, 0x105560, ByteArray*, UnityEngine_RecordUserData, (int32_t nType));
DO_APP_FUNC(0x624d630, 0x61bd630, LPVOID, GetSingletonManager, ());
DO_APP_FUNC(0x624d360, 0x61bd360, LPVOID, GetSingletonInstance, (LPVOID obj, Il2CppString* value));

View File

@@ -1,22 +1,22 @@
// ReSharper disable CppClangTidyBugproneMacroParentheses
#include "pch.h" #include "pch.h"
#include "il2cpp-init.h" #include "il2cpp-init.h"
#define DO_API(ca, oa, r, n, p) r (*n) p
#include "il2cpp-api-functions.h"
#undef DO_API
#define DO_APP_FUNC(ca, oa, r, n, p) r (*n) p #define DO_APP_FUNC(ca, oa, r, n, p) r (*n) p
#define DO_UNI_FUNC(ca, oa, r, n, p) r (*n) p
namespace Genshin { namespace Genshin {
#include "il2cpp-functions.h" #include "il2cpp-functions.h"
} }
#undef DO_UNI_FUNC
#undef DO_APP_FUNC #undef DO_APP_FUNC
#define DO_UNI_FUNC(ca, oa, r, n, p) r (*n) p #define DO_TYPEDEF(ca, oa, n) n##__Class **n##__TypeInfo
namespace Genshin { namespace Genshin {
#include "il2cpp-unity-functions.h" #include "il2cpp-types-ptr.h"
} }
#undef DO_UNI_FUNC #undef DO_TYPEDEF
using std::string; using std::string;
@@ -27,13 +27,12 @@ void InitIL2CPP() {
auto hBase = GetModuleHandle("UserAssembly.dll"); auto hBase = GetModuleHandle("UserAssembly.dll");
auto bAddr = (UINT64)hBase; auto bAddr = (UINT64)hBase;
auto cAddr = (UINT64)GetModuleHandle("UnityPlayer.dll"); 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)) #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)) #define DO_UNI_FUNC(ca, oa, r, n, p) n = (r (*) p)(cAddr + (isCN ? ca : oa))
#include "il2cpp-unity-functions.h" #include "il2cpp-functions.h"
#undef DO_UNI_FUNC #undef DO_UNI_FUNC
#undef DO_APP_FUNC
#define DO_TYPEDEF(ca, oa, n) n##__TypeInfo = (n##__Class **)(bAddr + (isCN ? ca : oa))
#include "il2cpp-types-ptr.h"
#undef DO_TYPEDEF
} }

View File

View File

@@ -1,3 +1,6 @@
// ReSharper disable CppClangTidyClangDiagnosticReservedIdentifier
// ReSharper disable CppClangTidyBugproneReservedIdentifier
#pragma once #pragma once
#pragma region IL2CPPInternalTypes #pragma region IL2CPPInternalTypes
@@ -36,30 +39,7 @@ namespace Genshin {
il2cpp_array_size_t max_length; il2cpp_array_size_t max_length;
uint8_t vector[32]; uint8_t vector[32];
}; };
struct KcpPacket { struct CodedOutputStream__Class {
BYTE* data;
UINT32 length;
}; };
enum class KcpEventType : int {
EventNotSet = -1,
EventConnect = 0,
EventConnectFailed = 1,
EventDisconnect = 2,
EventRecvMsg = 3,
EventCount = 4,
};
struct KcpEvent_Fields {
KcpEventType type;
UINT32 token;
UINT32 data;
KcpPacket* packet;
};
struct ClientKcpEvent {
KcpEvent_Fields fields;
};
} }

View File

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

View File

@@ -8,9 +8,6 @@ HWND FindMainWindowByPID(DWORD pid);
string ToString(Il2CppString* str, UINT codePage = CP_ACP); string ToString(Il2CppString* str, UINT codePage = CP_ACP);
std::string base64_encode(BYTE const* buf, unsigned int bufLen); std::string base64_encode(BYTE const* buf, unsigned int bufLen);
#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 ErrorDialogT(title, msg) MessageBox(unityWnd, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
#define ErrorDialog(msg) ErrorDialogT("YaeAchievement", msg) #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()) #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())

View File

@@ -9,13 +9,13 @@ message Achievement {
FINISHED = 2; FINISHED = 2;
REWARD_TAKEN = 3; REWARD_TAKEN = 3;
} }
uint32 timestamp = 3; uint32 timestamp = 14;
uint32 current = 14; uint32 current = 13;
uint32 total = 8; uint32 total = 9;
uint32 id = 1; uint32 id = 7;
Status status = 13; Status status = 10;
} }
message AchievementAllDataNotify { message AchievementAllDataNotify {
repeated Achievement list = 2; repeated Achievement list = 15;
} }

View File

@@ -1,5 +1,5 @@
using Newtonsoft.Json; using System.Net;
using System.Net; using System.Net.Http.Json;
using YaeAchievement.AppCenterSDK.Models; using YaeAchievement.AppCenterSDK.Models;
using YaeAchievement.AppCenterSDK.Models.Serialization; using YaeAchievement.AppCenterSDK.Models.Serialization;
@@ -39,12 +39,6 @@ public static class AppCenter {
AppDomain.CurrentDomain.ProcessExit += (_, _) => { AppDomain.CurrentDomain.ProcessExit += (_, _) => {
running = false; 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 // ReSharper restore InconsistentNaming
@@ -59,8 +53,8 @@ public static class AppCenter {
using var uploadContent = new StringContent(Queue.ToJson()); using var uploadContent = new StringContent(Queue.ToJson());
try { try {
using var response = httpClient.Value.PostAsync(ApiUrl, uploadContent).Result; using var response = httpClient.Value.PostAsync(ApiUrl, uploadContent).Result;
var result = response.Content.ReadAsStringAsync().Result; var result = response.Content.ReadFromJsonAsync<LogUploadResult>().GetAwaiter().GetResult();
uploadStatus = JsonConvert.DeserializeObject<LogUploadResult>(result)!.Status; uploadStatus = result!.Status;
} catch (Exception) { } catch (Exception) {
// ignored // ignored
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
using System.Diagnostics; using System.Diagnostics;
using Newtonsoft.Json; using YaeAchievement.AppCenterSDK.Models.Serialization;
namespace YaeAchievement.AppCenterSDK.Models; namespace YaeAchievement.AppCenterSDK.Models;
[JsonObject(JsonIdentifier)] [LogId(JsonIdentifier)]
public class ManagedErrorLog : Log { public class ManagedErrorLog : Log {
public const string JsonIdentifier = "managedError"; public const string JsonIdentifier = "managedError";
@@ -23,28 +23,20 @@ public class ManagedErrorLog : Log {
AppLaunchTimestamp = p.StartTime.ToUniversalTime(); AppLaunchTimestamp = p.StartTime.ToUniversalTime();
} }
[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; } public Guid Id { get; set; }
[JsonProperty(PropertyName = "userId")]
public string? UserId { get; set; } public string? UserId { get; set; }
[JsonProperty(PropertyName = "processId")]
public int ProcessId { get; set; } public int ProcessId { get; set; }
[JsonProperty(PropertyName = "processName")]
public string ProcessName { get; set; } public string ProcessName { get; set; }
[JsonProperty(PropertyName = "fatal")]
public bool Fatal { get; set; } public bool Fatal { get; set; }
[JsonProperty(PropertyName = "appLaunchTimestamp")]
public DateTime? AppLaunchTimestamp { get; set; } public DateTime? AppLaunchTimestamp { get; set; }
[JsonProperty(PropertyName = "architecture")]
public string? Architecture { get; set; } public string? Architecture { get; set; }
[JsonProperty(PropertyName = "exception")]
public MException Exception { get; set; } public MException Exception { get; set; }
} }

View File

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

View File

@@ -0,0 +1,8 @@
namespace YaeAchievement.AppCenterSDK.Models.Serialization;
[AttributeUsage(AttributeTargets.Class)]
public sealed class LogIdAttribute(string id) : Attribute {
public string Id { get; } = id;
}

View File

@@ -1,60 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved. using System.Reflection;
// Licensed under the MIT License. using System.Text.Json;
using System.Text.Json.Serialization;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace YaeAchievement.AppCenterSDK.Models.Serialization; namespace YaeAchievement.AppCenterSDK.Models.Serialization;
#pragma warning disable CS8604, CS8765 public class GuidConverter : JsonConverter<Guid> {
public class LogJsonConverter : JsonConverter {
public static GuidConverter Instance { get; } = new ();
private readonly Dictionary<string, Type> _logTypes = new (); public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
private readonly object _jsonConverterLock = new (); throw new NotSupportedException();
private static readonly JsonSerializerSettings SerializationSettings; }
public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) {
writer.WriteStringValue(value.ToString("D"));
}
}
public class LogJsonConverter : JsonConverter<Log> {
public static LogJsonConverter Instance { get; } = new ();
private static readonly JsonSerializerOptions SerializationSettings;
static LogJsonConverter() { static LogJsonConverter() {
SerializationSettings = new JsonSerializerSettings { SerializationSettings = new JsonSerializerOptions {
Formatting = Formatting.None, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
NullValueHandling = NullValueHandling.Ignore, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DateFormatHandling = DateFormatHandling.IsoDateFormat, ReferenceHandler = ReferenceHandler.IgnoreCycles,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
}; };
} }
public void AddLogType(string typeName, Type type) { public override bool CanConvert(Type objectType) => typeof(Log).IsAssignableFrom(objectType);
lock (_jsonConverterLock) {
_logTypes[typeName] = type; public override Log Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
} throw new NotSupportedException();
} }
public override bool CanConvert(Type objectType) { public override void Write(Utf8JsonWriter writer, Log value, JsonSerializerOptions options) {
return typeof(Log).IsAssignableFrom(objectType); var attr = value.GetType().GetCustomAttribute<LogIdAttribute>();
} if (attr == null) {
throw new JsonException("Log type is missing LogTypeAttribute");
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); var cNode = JsonSerializer.SerializeToNode((object) value, SerializationSettings)!;
} cNode["type"] = attr.Id;
writer.WriteRawValue(cNode.ToString());
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 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved. using System.Text.Json;
// Licensed under the MIT License. using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace YaeAchievement.AppCenterSDK.Models.Serialization; namespace YaeAchievement.AppCenterSDK.Models.Serialization;
#pragma warning disable CS8604, CS8765, CS8603
public static class LogSerializer { public static class LogSerializer {
private static readonly JsonSerializerSettings SerializationSettings; private static readonly JsonSerializerOptions SerializationSettings;
private static readonly LogJsonConverter Converter = new ();
static LogSerializer() { static LogSerializer() {
SerializationSettings = new JsonSerializerSettings { SerializationSettings = new JsonSerializerOptions {
Formatting = Formatting.None, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
NullValueHandling = NullValueHandling.Ignore, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DateFormatHandling = DateFormatHandling.IsoDateFormat, ReferenceHandler = ReferenceHandler.IgnoreCycles,
DateTimeZoneHandling = DateTimeZoneHandling.Utc, Converters = {
ReferenceLoopHandling = ReferenceLoopHandling.Serialize, GuidConverter.Instance,
Converters = { Converter } LogJsonConverter.Instance,
}
}; };
} }
public static void AddLogType(string typeName, Type type) {
Converter.AddLogType(typeName, type);
}
public static string Serialize(LogContainer logContainer) { public static string Serialize(LogContainer logContainer) {
return JsonConvert.SerializeObject(logContainer, SerializationSettings); return JsonSerializer.Serialize(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,6 +1,4 @@
using Newtonsoft.Json; namespace YaeAchievement.AppCenterSDK.Models;
namespace YaeAchievement.AppCenterSDK.Models;
public class StackFrame { public class StackFrame {
@@ -13,22 +11,16 @@ public class StackFrame {
FileName = fileName; FileName = fileName;
} }
[JsonProperty(PropertyName = "address")]
public string Address { get; set; } public string Address { get; set; }
[JsonProperty(PropertyName = "code")]
public string Code { get; set; } public string Code { get; set; }
[JsonProperty(PropertyName = "className")]
public string ClassName { get; set; } public string ClassName { get; set; }
[JsonProperty(PropertyName = "methodName")]
public string MethodName { get; set; } public string MethodName { get; set; }
[JsonProperty(PropertyName = "lineNumber")]
public int? LineNumber { get; set; } public int? LineNumber { get; set; }
[JsonProperty(PropertyName = "fileName")]
public string FileName { get; set; } public string FileName { get; set; }
} }

View File

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

View File

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

View File

@@ -20,8 +20,8 @@ public static class GlobalVars {
public static readonly string CachePath = Path.Combine(DataPath, "cache"); public static readonly string CachePath = Path.Combine(DataPath, "cache");
public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll"); public static readonly string LibFilePath = Path.Combine(DataPath, "YaeAchievement.dll");
public const uint AppVersionCode = 41; public const uint AppVersionCode = 45;
public const string AppVersionName = "3.1"; public const string AppVersionName = "3.5";
public const string PipeName = "YaeAchievementPipe"; public const string PipeName = "YaeAchievementPipe";
public const string BucketHost = "https://cn-cd-1259389942.file.myqcloud.com"; public const string BucketHost = "https://cn-cd-1259389942.file.myqcloud.com";

View File

@@ -45,7 +45,7 @@ public static class Injector {
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail"); return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
} }
} }
var lpStartAddress = pLoadLibrary.CreateDelegate<LPTHREAD_START_ROUTINE>(); var lpStartAddress = (delegate* unmanaged[Stdcall]<void*, uint>)pLoadLibrary.Value; //THREAD_START_ROUTINE
var hThread = Native.CreateRemoteThread(hProc, default, 0, lpStartAddress, pBase, 0); var hThread = Native.CreateRemoteThread(hProc, default, 0, lpStartAddress, pBase, 0);
if (hThread.IsNull) { if (hThread.IsNull) {
var e = new Win32Exception(); var e = new Win32Exception();

View File

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

View File

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

View File

@@ -29,29 +29,27 @@ new EventLog("AppInit") {
{ "SystemVersion", DeviceHelper.GetSystemVersion() } { "SystemVersion", DeviceHelper.GetSystemVersion() }
} }
}.Enqueue(); }.Enqueue();
var usePreviousData = false;
var historyCache = new CacheFile("ExportData"); var historyCache = new CacheFile("ExportData");
if (historyCache.LastWriteTime.AddMinutes(10) > DateTime.UtcNow) {
AchievementAllDataNotify? data = null;
try {
data = AchievementAllDataNotify.Parser.ParseFrom(historyCache.Read().Content);
} catch (Exception) { /* ignored */ }
if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null) {
Console.WriteLine(App.UsePreviousData); Console.WriteLine(App.UsePreviousData);
usePreviousData = Console.ReadLine() == "yes"; if (Console.ReadLine()?.ToUpper() is "Y" or "YES") {
} Export.Choose(data);
Export: return;
if(usePreviousData) {
AchievementAllDataNotify data;
try {
data = AchievementAllDataNotify.Parser.ParseFrom(historyCache.Read().Content);
} catch (Exception) {
usePreviousData = false;
goto Export;
} }
Export.Choose(data); }
} else {
StartAndWaitResult(AppConfig.GamePath, str => { StartAndWaitResult(AppConfig.GamePath, str => {
GlobalVars.UnexpectedExit = false; GlobalVars.UnexpectedExit = false;
var data = Convert.FromBase64String(str); var bytes = Convert.FromBase64String(str);
var list = AchievementAllDataNotify.Parser.ParseFrom(data); var list = AchievementAllDataNotify.Parser.ParseFrom(bytes);
historyCache.Write(data); historyCache.Write(bytes);
Export.Choose(list); Export.Choose(list);
return true; return true;
}); });
}