mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
63 Commits
fix/1208
...
fix/schedu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dc1e664b0 | ||
|
|
51c3dde24b | ||
|
|
2d497faaa5 | ||
|
|
4783934b92 | ||
|
|
03d235876a | ||
|
|
f49e9669af | ||
|
|
533c70caaa | ||
|
|
dd59302bb3 | ||
|
|
96e42f51f0 | ||
|
|
5a19c19759 | ||
|
|
8fb831ef7c | ||
|
|
a30c8d8678 | ||
|
|
2655e835f8 | ||
|
|
ffd74703cd | ||
|
|
584465dc45 | ||
|
|
a1e751160d | ||
|
|
d78d2cf51a | ||
|
|
24709bfbf9 | ||
|
|
9be396b175 | ||
|
|
bb83e76d33 | ||
|
|
1ca24c8a78 | ||
|
|
3d56aef221 | ||
|
|
d43f2e76c4 | ||
|
|
104fb9a3b0 | ||
|
|
d6b79584b6 | ||
|
|
fcd0b65257 | ||
|
|
802951edd7 | ||
|
|
79fc42aa3b | ||
|
|
fb0491dc57 | ||
|
|
b81d088379 | ||
|
|
553d267625 | ||
|
|
199e753103 | ||
|
|
48774960a7 | ||
|
|
7bfea0e090 | ||
|
|
f0f9e387a8 | ||
|
|
f71a34a6be | ||
|
|
e6fd0b833b | ||
|
|
d2c33cf19c | ||
|
|
1d074f5313 | ||
|
|
769a1c3812 | ||
|
|
b54717fa9b | ||
|
|
ffa0b05a12 | ||
|
|
d07a33f3e4 | ||
|
|
bbd274c391 | ||
|
|
f8a8a929ac | ||
|
|
cf3298dbd0 | ||
|
|
a8b887def2 | ||
|
|
5a937b0838 | ||
|
|
c016ae1cb8 | ||
|
|
c7fdf8001d | ||
|
|
b11526761e | ||
|
|
6ee823094a | ||
|
|
14894b0b47 | ||
|
|
6834073603 | ||
|
|
911fe57fb2 | ||
|
|
7320cf7dd0 | ||
|
|
bc6d03e442 | ||
|
|
307a49b346 | ||
|
|
9f8d80ff43 | ||
|
|
d34130b6c0 | ||
|
|
2161f12069 | ||
|
|
0bedd1894c | ||
|
|
442db0bae4 |
6
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
6
.github/ISSUE_TEMPLATE/CHS-bug-report.yml
vendored
@@ -22,7 +22,7 @@ body:
|
||||
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
|
||||
required: true
|
||||
|
||||
- label: 我的问题不是[已修复](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E4%BF%AE%E5%A4%8D)的问题也不是一个别人已发布的**重复的**问题
|
||||
- label: 我的问题不是[已完成](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E5%AE%8C%E6%88%90)的问题也不是一个别人已发布的**重复的**问题
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
@@ -51,7 +51,7 @@ body:
|
||||
description: |
|
||||
在胡桃工具箱的设置界面,你可以找到并复制你的设备 ID
|
||||
如果你的问题涉及程序崩溃,请填写该项,这将有助于我们定位问题
|
||||
如果你的程序已经无法启动,请下载并运行[此PowerShell脚本](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1),它将显示你的设备 ID
|
||||
如果你的程序已经无法启动,请下载并运行[此工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -87,7 +87,7 @@ body:
|
||||
label: 发生了什么?
|
||||
description: |
|
||||
详细的描述问题发生前后的行为,以便我们解决问题。**如果你的问题涉及程序崩溃,你应当检查 Windows 事件查看器,并将相关的 `.Net 错误`详情附上**
|
||||
如果你无法找到该日志,请下载并运行[此PowerShell脚本](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/dump_log_script/dump_log_zh.ps1),它将输出错误日志
|
||||
如果你无法找到该日志,请下载并运行[此工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将转储问题日志至工具运行目录中的 `Snap.Hutao Error Log.txt`
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
6
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
@@ -22,7 +22,7 @@ body:
|
||||
- label: I and tried **search feature** in Snap Hutao document site, and no associated article
|
||||
required: true
|
||||
|
||||
- label: My issue is not a [fixed issue](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E4%BF%AE%E5%A4%8D), and it's not a duplicated issue
|
||||
- label: My issue is not a [finished issue](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E5%AE%8C%E6%88%90), and it's not a duplicated issue
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
@@ -51,7 +51,7 @@ body:
|
||||
description: |
|
||||
In Snap Hutao's settings page, you can find and copy your device ID
|
||||
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
|
||||
If your program cannot startup, please download and run [this PowerShell script](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1), it will shows your device ID.
|
||||
If your program cannot startup, please download and run [this tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will shows your device ID.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -87,7 +87,7 @@ body:
|
||||
label: What Happened?
|
||||
description: |
|
||||
Describe your issue in detail to help us identify the issue. **If your issue is about program crash, you should check Windows Event Viewer, and attach associated `.Net Error` details here**If your program cannot startup, please download and run [this PowerShell script](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1), it will shows your device ID.
|
||||
If you cannot find it, please download and run [this PowerShell script](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/dump_log_script/dump_log_en.ps1), it will dump the error log.
|
||||
If you cannot find it, please download and run [this tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will dump the error log to `Snap.Hutao Error Log.txt` in the working directory of the tool.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/MGMT-publish.yml
vendored
40
.github/ISSUE_TEMPLATE/MGMT-publish.yml
vendored
@@ -12,50 +12,26 @@ body:
|
||||
value: |
|
||||
|
||||
## 创建版本
|
||||
|
||||
|
||||
- [ ] 同步一次 [Crowdin](https://crowdin.com/project/snap-hutao) 翻译
|
||||
- [ ] 发布 RC 版本(Optional)
|
||||
- [ ] 合并入主分支
|
||||
- [ ] 整理更新内容,等待翻译
|
||||
- [ ] 打包
|
||||
- [ ] 提交微软商店
|
||||
- [ ] 包含更新日志
|
||||
- [ ] 在 [Snap.Hutao.Docs@next-patch](https://github.com/DGP-Studio/Snap.Hutao.Docs/tree/next-patch) 分支更新文档并直接开 PR
|
||||
- [ ] 更新日志
|
||||
- [ ] 功能文档更新
|
||||
|
||||
## 发布版本 [半自动]
|
||||
|
||||
- [ ] 在 GitHub 个人设置中更新 [Publish-Automate Beta PAT](https://github.com/settings/tokens?type=beta),有效期需小于预计发版需要天数
|
||||
- [ ] 将更新的 PAT 更新至 Publish-Automate 库的 [Actions Secrets](https://github.com/DGP-Studio/Publish-Automate/settings/secrets/actions) 中
|
||||
|
||||
***
|
||||
***
|
||||
|
||||
- [ ] 运行 [Auto Publish Action](https://github.com/DGP-Studio/Publish-Automate/actions/workflows/auto-publish.yml)
|
||||
- [ ] 在 https://store.rg-adguard.net/ 下载新版本安装包
|
||||
- [ ] Store URL: https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52
|
||||
- [ ] 命名格式为 `Snap.Hutao x.x.x.msix`
|
||||
- [ ] Merge 文档 PR
|
||||
- [ ] 发布 Release
|
||||
- [ ] 更新日志格式(以 1.6.2 版本为例)
|
||||
|
||||
```jsx
|
||||
## Update log
|
||||
https://hut.ao/en/statements/update-log.html#_1-6-2
|
||||
|
||||
## 更新日志
|
||||
[此处从文档复制]
|
||||
|
||||
## What's Changed
|
||||
**Full Changelog**: https://github.com/DGP-Studio/Snap.Hutao/compare/1.6.0...1.6.2
|
||||
```
|
||||
|
||||
- [ ] 通知用户
|
||||
- [ ] 主分支合并入 release 分支
|
||||
- [ ] 等待 Release 自动发布
|
||||
- [ ] 检查极狐是否同步完成 Release
|
||||
- [ ] 通知用户
|
||||
- type: checkboxes
|
||||
id: checklist-final
|
||||
attributes:
|
||||
label: Final Check
|
||||
description: Understand what you are doing
|
||||
description: Understand what you are doing
|
||||
options:
|
||||
- label: I understand that I will get banned from repository if I don't have permission to use this template
|
||||
required: true
|
||||
required: true
|
||||
|
||||
1
.github/workflows/alpha.yml
vendored
1
.github/workflows/alpha.yml
vendored
@@ -12,6 +12,7 @@ on:
|
||||
- '.gitmodules'
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '**.yml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
73
.gitlab-ci.yml
Normal file
73
.gitlab-ci.yml
Normal file
@@ -0,0 +1,73 @@
|
||||
stages:
|
||||
- fetch
|
||||
- release
|
||||
- refresh
|
||||
|
||||
Fetch:
|
||||
stage: fetch
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
tags:
|
||||
- us3
|
||||
script:
|
||||
- apt-get update -qy
|
||||
- apt-get install -y curl jq
|
||||
- RELEASE_INFO=$(curl -sSL "https://api.github.com/repos/$CI_PROJECT_PATH/releases/latest")
|
||||
- ASSET_URL=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name | endswith(".msix")) | .browser_download_url')
|
||||
- SHA256SUMS_URL=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name == "SHA256SUMS") | .browser_download_url')
|
||||
- curl -LJO "$ASSET_URL"
|
||||
- curl -LJO "$SHA256SUMS_URL"
|
||||
- FILE_NAME=$(basename "$ASSET_URL")
|
||||
- SHA256SUMS_NAME=$(basename "$SHA256SUMS_URL")
|
||||
- echo "File name at script stage is $FILE_NAME"
|
||||
- echo "SHA256SUMS name at script stage is $SHA256SUMS_NAME"
|
||||
- echo "THIS_FILE_NAME=$FILE_NAME" >> next.env
|
||||
- echo "THIS_SHA256SUMS_NAME=$SHA256SUMS_NAME" >> next.env
|
||||
after_script:
|
||||
- echo "Current Job ID is $CI_JOB_ID"
|
||||
- echo "THIS_JOB_ID=$CI_JOB_ID" >> next.env
|
||||
artifacts:
|
||||
paths:
|
||||
- "*.msix"
|
||||
- "SHA256SUMS"
|
||||
expire_in: 180 days
|
||||
reports:
|
||||
dotenv: next.env
|
||||
|
||||
release:
|
||||
stage: release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
needs:
|
||||
- job: Fetch
|
||||
artifacts: true
|
||||
variables:
|
||||
TAG: '$CI_COMMIT_TAG'
|
||||
script:
|
||||
- echo "Create Release $TAG"
|
||||
- echo "$THIS_JOB_ID"
|
||||
- echo "$THIS_FILE_NAME"
|
||||
release:
|
||||
name: '$TAG'
|
||||
tag_name: '$TAG'
|
||||
ref: '$TAG'
|
||||
description: 'Release $TAG by CI'
|
||||
assets:
|
||||
links:
|
||||
- name: "$THIS_FILE_NAME"
|
||||
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/$THIS_FILE_NAME?inline=false"
|
||||
link_type: package
|
||||
- name: "$THIS_SHA256SUMS_NAME"
|
||||
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/$THIS_SHA256SUMS_NAME?inline=false"
|
||||
link_type: other
|
||||
|
||||
Refresh:
|
||||
stage: refresh
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
needs:
|
||||
- job: release
|
||||
script:
|
||||
- apt-get install -y curl
|
||||
- curl -X PATCH "$PURGE_URL"
|
||||
@@ -4,13 +4,15 @@
|
||||
|
||||
### Setup Snap.Hutao Project
|
||||
|
||||
1. Download and install [Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)
|
||||
2. Open Visual Studio Installer to complete Visual Studio installation
|
||||
- You need to install `.NET desktop development`, `Desktop development with C++` and `Universal Windows Platform development` components
|
||||
3. Install `Single-project MSIX Packaging Tools for VS 2022` provided by Microsoft in Visual Studio marketplace
|
||||
4. Use git to clone the project `https://github.com/DGP-Studio/Snap.Hutao.git` to your local device
|
||||
5. Switch git branch to `develop`
|
||||
6. Open project solution with your Visual Studio and then you are ready to go
|
||||
1. Download and install [Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/).
|
||||
- No need to select workloads; Visual Studio will handle it automatically.
|
||||
- Close Visual Studio Installer to ensure a smooth installation experience for workloads.
|
||||
- If using Visual Studio 2022 17.9 preview, skip step 5, as automatic extension installation is supported in this version.
|
||||
2. Use git to clone the project `https://github.com/DGP-Studio/Snap.Hutao.git` to your local device.
|
||||
3. Switch to the`develop` branch using git.
|
||||
4. Open the project solution with your Visual Studio. Visual Studio will prompt you to install the necessary workloads, closing and reopening automatically.
|
||||
5. (For Visual Studio 2022 17.8) Install the [Single-project MSIX Packaging Tools for VS 2022](https://marketplace.visualstudio.com/items?itemName=ProjectReunion.MicrosoftSingleProjectMSIXPackagingToolsDev17) provided by Microsoft in Visual Studio marketplace.
|
||||
6. Open the project solution with your Visual Studio, and you are ready to go.
|
||||
|
||||
### Start Pull Request
|
||||
|
||||
|
||||
26
README.md
26
README.md
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
|
||||
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新 功能相结合,它提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
|
||||
@@ -7,7 +7,29 @@ Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed
|
||||
|
||||
## 下载使用 / Download
|
||||
|
||||
[<img src="https://get.microsoft.com/images/zh-cn%20light.svg" width="30%" height="30%">](https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52)
|
||||
 [](https://github.com/DGP-Studio/Snap.Hutao/releases/latest) []()
|
||||
|
||||
---
|
||||
|
||||
#### 使用安装器安装 / Install with Snap.Hutao.Depolyment Installer
|
||||
|
||||
Snap.Hutao.Depolyment 是一个由 DGP-Studio 重新包装的 Windows 应用安装器,适用于缺少专业计算机知识的一般用户,可以在安装时同时解决缺少必要系统环境的问题。
|
||||
|
||||
Snap.Hutao.Depolyment is a Windows application installer repackaged by DGP-Studio for the users who lacks computer knowledge and can solve the problem of missing necessary system environment at the same time as the installation.
|
||||
|
||||
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao.Deployment/releases/latest)
|
||||
|
||||
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao.Deployment/-/releases)
|
||||
|
||||
#### 使用 MSIX 包安装 / Install with MSIX Package
|
||||
|
||||
直接使用 Snap Hutao MSIX 安装包,使用 Windows 内置的 App Installer 即可安装。如在安装中出现问题,请查阅我们的[常见问题](https://hut.ao/zh/advanced/FAQ.html)文档
|
||||
|
||||
Install with Snap Hutao MSIX package, can be installed with Windows built-in App Installer. If you faced any issue, please check our [FAQ](https://hut.ao/en/advanced/FAQ.html) document.
|
||||
|
||||
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao/releases/latest)
|
||||
|
||||
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao/-/releases)
|
||||
|
||||
## 贡献 / Contribute
|
||||
|
||||
|
||||
11
src/Snap.Hutao/.vsconfig
Normal file
11
src/Snap.Hutao/.vsconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.VisualStudio.Workload.ManagedDesktop",
|
||||
"Microsoft.VisualStudio.Workload.NativeDesktop",
|
||||
"Microsoft.VisualStudio.Workload.Universal"
|
||||
],
|
||||
"extensions": [
|
||||
"https://marketplace.visualstudio.com/items?itemName=ProjectReunion.MicrosoftSingleProjectMSIXPackagingToolsDev17"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Snap.Hutao.Test.IncomingFeature;
|
||||
|
||||
[TestClass]
|
||||
public class GameRegistryContentTest
|
||||
{
|
||||
private static readonly JsonSerializerOptions RegistryContentSerializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
};
|
||||
|
||||
[TestMethod]
|
||||
[SupportedOSPlatform("windows")]
|
||||
public void GetRegistryContent()
|
||||
{
|
||||
GetRegistryContentCore(@"Software\miHoYo\原神");
|
||||
GetRegistryContentCore(@"Software\miHoYo\Genshin Impact");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static void GetRegistryContentCore(string subkey)
|
||||
{
|
||||
using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64))
|
||||
{
|
||||
RegistryKey? gameKey = key.OpenSubKey(subkey);
|
||||
Assert.IsNotNull(gameKey);
|
||||
|
||||
Dictionary<string, object> data = [];
|
||||
foreach (string valueName in gameKey.GetValueNames())
|
||||
{
|
||||
data[valueName] = gameKey.GetValueKind(valueName) switch
|
||||
{
|
||||
RegistryValueKind.DWord => (int)gameKey.GetValue(valueName)!,
|
||||
RegistryValueKind.Binary => GetStringOrObject((byte[])gameKey.GetValue(valueName)!),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
||||
Console.WriteLine($"Subkey: {subkey}");
|
||||
Console.WriteLine(JsonSerializer.Serialize(data, RegistryContentSerializerOptions));
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe object GetStringOrObject(byte[] bytes)
|
||||
{
|
||||
fixed (byte* pByte = bytes)
|
||||
{
|
||||
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pByte);
|
||||
string temp = Encoding.UTF8.GetString(span);
|
||||
|
||||
if (temp.AsSpan()[0] is '{' or '[')
|
||||
{
|
||||
return JsonSerializer.Deserialize<JsonElement>(temp);
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9A95A964-04B1-477A-BDE7-505525B3CAD8}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.vsconfig = .vsconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}"
|
||||
@@ -87,11 +88,11 @@ Global
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
RESX_AutoApplyExistingTranslations = False
|
||||
RESX_NeutralResourcesLanguage = zh-CN
|
||||
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
|
||||
RESX_SortFileContentOnSave = True
|
||||
RESX_ShowErrorsInErrorList = False
|
||||
RESX_Rules = {"EnabledRules":["StringFormat","WhiteSpaceLead","WhiteSpaceTail","PunctuationLead"]}
|
||||
RESX_ShowErrorsInErrorList = False
|
||||
RESX_SortFileContentOnSave = True
|
||||
SolutionGuid = {E4449B1C-0E6A-4D19-955E-1CA491656ABA}
|
||||
RESX_NeutralResourcesLanguage = zh-CN
|
||||
RESX_AutoApplyExistingTranslations = False
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -69,12 +69,12 @@ public sealed partial class App : Application
|
||||
if (firstInstance.IsCurrent)
|
||||
{
|
||||
logger.LogInformation(ConsoleBanner);
|
||||
LogDiagnosticInformation();
|
||||
|
||||
// manually invoke
|
||||
activation.NonRedirectToActivate(firstInstance, activatedEventArgs);
|
||||
activation.InitializeWith(firstInstance);
|
||||
|
||||
LogDiagnosticInformation();
|
||||
serviceProvider.GetRequiredService<IJumpListInterop>().ConfigureAsync().SafeForget();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -40,12 +40,7 @@ internal sealed class CachedImage : Implementation.ImageEx
|
||||
{
|
||||
// The image is corrupted, remove it.
|
||||
imageCache.Remove(imageUri);
|
||||
return null;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// task was explicitly canceled
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Image
|
||||
Name="PlaceholderImage"
|
||||
Margin="{TemplateBinding PlaceholderMargin}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Source="{TemplateBinding PlaceholderSource}"
|
||||
|
||||
@@ -11,10 +11,6 @@ using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Image.Implementation;
|
||||
|
||||
internal delegate void ImageExFailedEventHandler(object sender, ImageExFailedEventArgs e);
|
||||
|
||||
internal delegate void ImageExOpenedEventHandler(object sender, ImageExOpenedEventArgs e);
|
||||
|
||||
[SuppressMessage("", "CA1001")]
|
||||
[SuppressMessage("", "SH003")]
|
||||
[TemplateVisualState(Name = LoadingState, GroupName = CommonGroup)]
|
||||
@@ -22,98 +18,34 @@ internal delegate void ImageExOpenedEventHandler(object sender, ImageExOpenedEve
|
||||
[TemplateVisualState(Name = UnloadedState, GroupName = CommonGroup)]
|
||||
[TemplateVisualState(Name = FailedState, GroupName = CommonGroup)]
|
||||
[TemplatePart(Name = PartImage, Type = typeof(object))]
|
||||
internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlphaMaskProvider
|
||||
[TemplatePart(Name = PartPlaceholderImage, Type = typeof(object))]
|
||||
[DependencyProperty("Stretch", typeof(Stretch), Stretch.Uniform)]
|
||||
[DependencyProperty("DecodePixelHeight", typeof(int), 0)]
|
||||
[DependencyProperty("DecodePixelWidth", typeof(int), 0)]
|
||||
[DependencyProperty("DecodePixelType", typeof(DecodePixelType), DecodePixelType.Physical)]
|
||||
[DependencyProperty("IsCacheEnabled", typeof(bool), false)]
|
||||
[DependencyProperty("EnableLazyLoading", typeof(bool), false, nameof(EnableLazyLoadingChanged))]
|
||||
[DependencyProperty("LazyLoadingThreshold", typeof(double), default(double), nameof(LazyLoadingThresholdChanged))]
|
||||
[DependencyProperty("PlaceholderSource", typeof(object), default(object))]
|
||||
[DependencyProperty("PlaceholderStretch", typeof(Stretch), Stretch.Uniform)]
|
||||
[DependencyProperty("PlaceholderMargin", typeof(Thickness))]
|
||||
[DependencyProperty("Source", typeof(object), default(object), nameof(SourceChanged))]
|
||||
internal abstract partial class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlphaMaskProvider
|
||||
{
|
||||
protected const string PartImage = "Image";
|
||||
protected const string PartPlaceholderImage = "PlaceholderImage";
|
||||
protected const string CommonGroup = "CommonStates";
|
||||
protected const string LoadingState = "Loading";
|
||||
protected const string LoadedState = "Loaded";
|
||||
protected const string UnloadedState = "Unloaded";
|
||||
protected const string FailedState = "Failed";
|
||||
|
||||
private static readonly DependencyProperty StretchProperty = DependencyProperty.Register(nameof(Stretch), typeof(Stretch), typeof(ImageExBase), new PropertyMetadata(Stretch.Uniform));
|
||||
private static readonly DependencyProperty DecodePixelHeightProperty = DependencyProperty.Register(nameof(DecodePixelHeight), typeof(int), typeof(ImageExBase), new PropertyMetadata(0));
|
||||
private static readonly DependencyProperty DecodePixelTypeProperty = DependencyProperty.Register(nameof(DecodePixelType), typeof(int), typeof(ImageExBase), new PropertyMetadata(DecodePixelType.Physical));
|
||||
private static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.Register(nameof(DecodePixelWidth), typeof(int), typeof(ImageExBase), new PropertyMetadata(0));
|
||||
private static readonly DependencyProperty IsCacheEnabledProperty = DependencyProperty.Register(nameof(IsCacheEnabled), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));
|
||||
private static readonly DependencyProperty EnableLazyLoadingProperty = DependencyProperty.Register(nameof(EnableLazyLoading), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false, EnableLazyLoadingChanged));
|
||||
private static readonly DependencyProperty LazyLoadingThresholdProperty = DependencyProperty.Register(nameof(LazyLoadingThreshold), typeof(double), typeof(ImageExBase), new PropertyMetadata(default(double), LazyLoadingThresholdChanged));
|
||||
private static readonly DependencyProperty PlaceholderSourceProperty = DependencyProperty.Register(nameof(PlaceholderSource), typeof(ImageSource), typeof(ImageExBase), new PropertyMetadata(default(ImageSource), PlaceholderSourceChanged));
|
||||
private static readonly DependencyProperty PlaceholderStretchProperty = DependencyProperty.Register(nameof(PlaceholderStretch), typeof(Stretch), typeof(ImageExBase), new PropertyMetadata(default(Stretch)));
|
||||
private static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(object), typeof(ImageExBase), new PropertyMetadata(null, SourceChanged));
|
||||
|
||||
private CancellationTokenSource? tokenSource;
|
||||
private object? lazyLoadingSource;
|
||||
private bool isInViewport;
|
||||
|
||||
public event ImageExFailedEventHandler? ImageExFailed;
|
||||
|
||||
public event ImageExOpenedEventHandler? ImageExOpened;
|
||||
|
||||
public event EventHandler? ImageExInitialized;
|
||||
|
||||
public bool IsInitialized { get; private set; }
|
||||
|
||||
public int DecodePixelHeight
|
||||
{
|
||||
get => (int)GetValue(DecodePixelHeightProperty);
|
||||
set => SetValue(DecodePixelHeightProperty, value);
|
||||
}
|
||||
|
||||
public DecodePixelType DecodePixelType
|
||||
{
|
||||
get => (DecodePixelType)GetValue(DecodePixelTypeProperty);
|
||||
set => SetValue(DecodePixelTypeProperty, value);
|
||||
}
|
||||
|
||||
public int DecodePixelWidth
|
||||
{
|
||||
get => (int)GetValue(DecodePixelWidthProperty);
|
||||
set => SetValue(DecodePixelWidthProperty, value);
|
||||
}
|
||||
|
||||
public Stretch Stretch
|
||||
{
|
||||
get => (Stretch)GetValue(StretchProperty);
|
||||
set => SetValue(StretchProperty, value);
|
||||
}
|
||||
|
||||
public bool IsCacheEnabled
|
||||
{
|
||||
get => (bool)GetValue(IsCacheEnabledProperty);
|
||||
set => SetValue(IsCacheEnabledProperty, value);
|
||||
}
|
||||
|
||||
public bool EnableLazyLoading
|
||||
{
|
||||
get => (bool)GetValue(EnableLazyLoadingProperty);
|
||||
set => SetValue(EnableLazyLoadingProperty, value);
|
||||
}
|
||||
|
||||
public double LazyLoadingThreshold
|
||||
{
|
||||
get => (double)GetValue(LazyLoadingThresholdProperty);
|
||||
set => SetValue(LazyLoadingThresholdProperty, value);
|
||||
}
|
||||
|
||||
public ImageSource PlaceholderSource
|
||||
{
|
||||
get => (ImageSource)GetValue(PlaceholderSourceProperty);
|
||||
set => SetValue(PlaceholderSourceProperty, value);
|
||||
}
|
||||
|
||||
public Stretch PlaceholderStretch
|
||||
{
|
||||
get => (Stretch)GetValue(PlaceholderStretchProperty);
|
||||
set => SetValue(PlaceholderStretchProperty, value);
|
||||
}
|
||||
|
||||
public object Source
|
||||
{
|
||||
get => GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
|
||||
public bool WaitUntilLoaded
|
||||
{
|
||||
get => true;
|
||||
@@ -121,11 +53,9 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
|
||||
protected object? Image { get; private set; }
|
||||
|
||||
public abstract CompositionBrush GetAlphaMask();
|
||||
protected object? PlaceholderImage { get; private set; }
|
||||
|
||||
protected virtual void OnPlaceholderSourceChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
}
|
||||
public abstract CompositionBrush GetAlphaMask();
|
||||
|
||||
protected virtual Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
@@ -136,61 +66,11 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
protected virtual void OnImageOpened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, LoadedState, true);
|
||||
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
|
||||
}
|
||||
|
||||
protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, FailedState, true);
|
||||
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new FileNotFoundException(e.ErrorMessage)));
|
||||
}
|
||||
|
||||
protected void AttachImageOpened(RoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageOpened += handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageOpened += handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected void RemoveImageOpened(RoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageOpened -= handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageOpened -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected void AttachImageFailed(ExceptionRoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageFailed += handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageFailed += handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected void RemoveImageFailed(ExceptionRoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageFailed -= handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageFailed -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
@@ -199,11 +79,10 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
RemoveImageFailed(OnImageFailed);
|
||||
|
||||
Image = GetTemplateChild(PartImage);
|
||||
PlaceholderImage = GetTemplateChild(PartPlaceholderImage);
|
||||
|
||||
IsInitialized = true;
|
||||
|
||||
ImageExInitialized?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
if (Source is null || !EnableLazyLoading || isInViewport)
|
||||
{
|
||||
lazyLoadingSource = null;
|
||||
@@ -218,23 +97,73 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
AttachImageFailed(OnImageFailed);
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
void AttachImageOpened(RoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageOpened += handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageOpened += handler;
|
||||
}
|
||||
}
|
||||
|
||||
void AttachImageFailed(ExceptionRoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageFailed += handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageFailed += handler;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveImageOpened(RoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageOpened -= handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageOpened -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveImageFailed(ExceptionRoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageFailed -= handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageFailed -= handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnableLazyLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ImageExBase control)
|
||||
if (d is not ImageExBase control)
|
||||
{
|
||||
bool value = (bool)e.NewValue;
|
||||
if (value)
|
||||
{
|
||||
control.LayoutUpdated += control.ImageExBase_LayoutUpdated;
|
||||
return;
|
||||
}
|
||||
|
||||
control.InvalidateLazyLoading();
|
||||
}
|
||||
else
|
||||
{
|
||||
control.LayoutUpdated -= control.ImageExBase_LayoutUpdated;
|
||||
}
|
||||
bool value = (bool)e.NewValue;
|
||||
if (value)
|
||||
{
|
||||
control.LayoutUpdated += control.OnImageExBaseLayoutUpdated;
|
||||
|
||||
control.InvalidateLazyLoading();
|
||||
}
|
||||
else
|
||||
{
|
||||
control.LayoutUpdated -= control.OnImageExBaseLayoutUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,14 +175,6 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
}
|
||||
}
|
||||
|
||||
private static void PlaceholderSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ImageExBase control)
|
||||
{
|
||||
control.OnPlaceholderSourceChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not ImageExBase control)
|
||||
@@ -261,17 +182,19 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.OldValue is null || e.NewValue is null || !e.OldValue.Equals(e.NewValue))
|
||||
if (e.OldValue is not null && e.NewValue is not null && e.OldValue.Equals(e.NewValue))
|
||||
{
|
||||
if (e.NewValue is null || !control.EnableLazyLoading || control.isInViewport)
|
||||
{
|
||||
control.lazyLoadingSource = null;
|
||||
control.SetSource(e.NewValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
control.lazyLoadingSource = e.NewValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.NewValue is null || !control.EnableLazyLoading || control.isInViewport)
|
||||
{
|
||||
control.lazyLoadingSource = null;
|
||||
control.SetSource(e.NewValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
control.lazyLoadingSource = e.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,11 +224,24 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
else if (source is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 })
|
||||
{
|
||||
VisualStateManager.GoToState(this, LoadedState, true);
|
||||
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "IDE0019")]
|
||||
private void AttachPlaceholderSource(ImageSource? source)
|
||||
{
|
||||
// Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState
|
||||
// as we register to both the ImageOpened/ImageFailed events of the underlying control.
|
||||
// We only need to call those methods if we fail in other cases before we get here.
|
||||
if (PlaceholderImage is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.Source = source;
|
||||
}
|
||||
else if (PlaceholderImage is ImageBrush brush)
|
||||
{
|
||||
brush.ImageSource = source;
|
||||
}
|
||||
}
|
||||
|
||||
private async void SetSource(object? source)
|
||||
{
|
||||
if (!IsInitialized)
|
||||
@@ -326,22 +262,19 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
|
||||
VisualStateManager.GoToState(this, LoadingState, true);
|
||||
|
||||
ImageSource? imageSource = source as ImageSource;
|
||||
if (imageSource is not null)
|
||||
if (source as ImageSource is { } imageSource)
|
||||
{
|
||||
AttachSource(imageSource);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Uri? uri = source as Uri;
|
||||
if (uri is null)
|
||||
if (source as Uri is not { } uri)
|
||||
{
|
||||
string? url = source as string ?? source.ToString();
|
||||
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri))
|
||||
{
|
||||
VisualStateManager.GoToState(this, FailedState, true);
|
||||
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new UriFormatException("Invalid uri specified.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -355,61 +288,131 @@ internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlpha
|
||||
{
|
||||
await LoadImageAsync(uri, tokenSource.Token).ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetPlaceholderSource(PlaceholderSource);
|
||||
|
||||
if (ex is OperationCanceledException)
|
||||
{
|
||||
// nothing to do as cancellation has been requested.
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, FailedState, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void SetPlaceholderSource(object? source)
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tokenSource?.Cancel();
|
||||
|
||||
tokenSource = new CancellationTokenSource();
|
||||
|
||||
AttachPlaceholderSource(null);
|
||||
|
||||
if (source is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (source as ImageSource is { } imageSource)
|
||||
{
|
||||
AttachPlaceholderSource(imageSource);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (source as Uri is not { } uri)
|
||||
{
|
||||
string? url = source as string ?? source.ToString();
|
||||
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsHttpUri(uri) && !uri.IsAbsoluteUri)
|
||||
{
|
||||
uri = new Uri("ms-appx:///" + uri.OriginalString.TrimStart('/'));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (uri is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImageSource? img = await ProvideCachedResourceAsync(uri, tokenSource.Token).ConfigureAwait(true);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(tokenSource);
|
||||
if (!tokenSource.IsCancellationRequested)
|
||||
{
|
||||
// Only attach our image if we still have a valid request.
|
||||
AttachPlaceholderSource(img);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do as cancellation has been requested.
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
VisualStateManager.GoToState(this, FailedState, true);
|
||||
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadImageAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
if (imageUri is not null)
|
||||
if (imageUri is null)
|
||||
{
|
||||
if (IsCacheEnabled)
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsCacheEnabled)
|
||||
{
|
||||
ImageSource? img = await ProvideCachedResourceAsync(imageUri, token).ConfigureAwait(true);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(tokenSource);
|
||||
if (!tokenSource.IsCancellationRequested)
|
||||
{
|
||||
ImageSource? img = await ProvideCachedResourceAsync(imageUri, token).ConfigureAwait(true);
|
||||
// Only attach our image if we still have a valid request.
|
||||
AttachSource(img);
|
||||
}
|
||||
}
|
||||
else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string source = imageUri.OriginalString;
|
||||
const string base64Head = "base64,";
|
||||
int index = source.IndexOf(base64Head, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(source[(index + base64Head.Length)..]);
|
||||
BitmapImage bitmap = new();
|
||||
await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());
|
||||
|
||||
ArgumentNullException.ThrowIfNull(tokenSource);
|
||||
if (!tokenSource.IsCancellationRequested)
|
||||
{
|
||||
// Only attach our image if we still have a valid request.
|
||||
AttachSource(img);
|
||||
AttachSource(bitmap);
|
||||
}
|
||||
}
|
||||
else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
|
||||
}
|
||||
else
|
||||
{
|
||||
AttachSource(new BitmapImage(imageUri)
|
||||
{
|
||||
string source = imageUri.OriginalString;
|
||||
const string base64Head = "base64,";
|
||||
int index = source.IndexOf(base64Head, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(source[(index + base64Head.Length)..]);
|
||||
BitmapImage bitmap = new();
|
||||
await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());
|
||||
|
||||
ArgumentNullException.ThrowIfNull(tokenSource);
|
||||
if (!tokenSource.IsCancellationRequested)
|
||||
{
|
||||
AttachSource(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AttachSource(new BitmapImage(imageUri)
|
||||
{
|
||||
CreateOptions = BitmapCreateOptions.IgnoreImageCache,
|
||||
});
|
||||
}
|
||||
CreateOptions = BitmapCreateOptions.IgnoreImageCache,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void ImageExBase_LayoutUpdated(object? sender, object e)
|
||||
private void OnImageExBaseLayoutUpdated(object? sender, object e)
|
||||
{
|
||||
InvalidateLazyLoading();
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Image.Implementation;
|
||||
|
||||
internal sealed class ImageExFailedEventArgs : EventArgs
|
||||
{
|
||||
public ImageExFailedEventArgs(Exception errorException)
|
||||
{
|
||||
ErrorMessage = ErrorException?.Message;
|
||||
ErrorException = errorException;
|
||||
}
|
||||
|
||||
public Exception? ErrorException { get; private set; }
|
||||
|
||||
public string? ErrorMessage { get; private set; }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Image.Implementation;
|
||||
|
||||
internal sealed class ImageExOpenedEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
<x:String x:Key="UI_EmotionIcon25">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon25.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon71">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon71.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon250">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon250.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon271">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon271.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon272">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon272.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon293">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon293.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon445">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon445.png</x:String>
|
||||
|
||||
@@ -13,4 +13,9 @@ internal static class RuntimeOptionsExtension
|
||||
Directory.CreateDirectory(directory);
|
||||
return Path.Combine(directory, fileName);
|
||||
}
|
||||
|
||||
public static string GetDataFolderServerCacheFolder(this RuntimeOptions options)
|
||||
{
|
||||
return Path.Combine(options.DataFolder, "ServerCache");
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,11 @@ internal sealed class ScheduleTaskInterop : IScheduleTaskInterop
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (WScriptExists(DailyNoteRefreshScriptName, out string fullPath))
|
||||
{
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
18
src/Snap.Hutao/Snap.Hutao/Core/Threading/SpinWaitPolyfill.cs
Normal file
18
src/Snap.Hutao/Snap.Hutao/Core/Threading/SpinWaitPolyfill.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Threading;
|
||||
|
||||
internal delegate bool SpinWaitPredicate<T>(ref readonly T state);
|
||||
|
||||
internal static class SpinWaitPolyfill
|
||||
{
|
||||
public static unsafe void SpinUntil<T>(ref T state, delegate*<ref readonly T, bool> condition)
|
||||
{
|
||||
SpinWait spinner = default;
|
||||
while (!condition(ref state))
|
||||
{
|
||||
spinner.SpinOnce();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,123 +8,45 @@ namespace Snap.Hutao.Model.Entity;
|
||||
/// </summary>
|
||||
internal sealed partial class SettingEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏路径
|
||||
/// </summary>
|
||||
public const string GamePath = "GamePath";
|
||||
|
||||
public const string GamePathEntries = "GamePathEntries";
|
||||
public const string Culture = "Culture";
|
||||
|
||||
[Obsolete("不再使用 PowerShell")]
|
||||
public const string PowerShellPath = "PowerShellPath";
|
||||
|
||||
/// <summary>
|
||||
/// 空的历史记录卡池是否可见
|
||||
/// </summary>
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
|
||||
/// <summary>
|
||||
/// 窗口背景类型
|
||||
/// </summary>
|
||||
public const string SystemBackdropType = "SystemBackdropType";
|
||||
|
||||
/// <summary>
|
||||
/// 启用高级功能
|
||||
/// </summary>
|
||||
public const string IsAdvancedLaunchOptionsEnabled = "IsAdvancedLaunchOptionsEnabled";
|
||||
public const string AnnouncementRegion = "AnnouncementRegion";
|
||||
|
||||
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
|
||||
|
||||
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺刷新时间
|
||||
/// </summary>
|
||||
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺提醒式通知
|
||||
/// </summary>
|
||||
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺免打扰模式
|
||||
/// </summary>
|
||||
public const string DailyNoteSilentWhenPlayingGame = "DailyNote.SilentWhenPlayingGame";
|
||||
|
||||
/// <summary>
|
||||
/// 实时便笺 WebhookUrl
|
||||
/// </summary>
|
||||
public const string DailyNoteWebhookUrl = "DailyNote.WebhookUrl";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 总开关
|
||||
/// </summary>
|
||||
public const string IsAdvancedLaunchOptionsEnabled = "IsAdvancedLaunchOptionsEnabled";
|
||||
|
||||
public const string LaunchIsLaunchOptionsEnabled = "Launch.IsLaunchOptionsEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 独占全屏
|
||||
/// </summary>
|
||||
public const string LaunchIsExclusive = "Launch.IsExclusive";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 全屏
|
||||
/// </summary>
|
||||
public const string LaunchIsFullScreen = "Launch.IsFullScreen";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 无边框
|
||||
/// </summary>
|
||||
public const string LaunchIsBorderless = "Launch.IsBorderless";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 宽度
|
||||
/// </summary>
|
||||
public const string LaunchScreenWidth = "Launch.ScreenWidth";
|
||||
|
||||
public const string LaunchIsScreenWidthEnabled = "Launch.IsScreenWidthEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 高度
|
||||
/// </summary>
|
||||
public const string LaunchScreenHeight = "Launch.ScreenHeight";
|
||||
|
||||
public const string LaunchIsScreenHeightEnabled = "Launch.IsScreenHeightEnabled";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 解锁帧率
|
||||
/// </summary>
|
||||
public const string LaunchUnlockFps = "Launch.UnlockFps";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 目标帧率
|
||||
/// </summary>
|
||||
public const string LaunchTargetFps = "Launch.TargetFps";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 显示器编号
|
||||
/// </summary>
|
||||
public const string LaunchMonitor = "Launch.Monitor";
|
||||
|
||||
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";
|
||||
|
||||
public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile";
|
||||
|
||||
public const string LaunchIsWindowsHDREnabled = "Launch.IsWindowsHDREnabled";
|
||||
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
|
||||
|
||||
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
|
||||
|
||||
/// <summary>
|
||||
/// 启动游戏 多倍启动
|
||||
/// </summary>
|
||||
[Obsolete("不再支持多开")]
|
||||
public const string MultipleInstances = "Launch.MultipleInstances";
|
||||
|
||||
/// <summary>
|
||||
/// 语言
|
||||
/// </summary>
|
||||
public const string Culture = "Culture";
|
||||
|
||||
/// <summary>
|
||||
/// 自定义极验接口
|
||||
/// </summary>
|
||||
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
|
||||
|
||||
public const string AnnouncementRegion = "AnnouncementRegion";
|
||||
[Obsolete("不再使用 PowerShell")]
|
||||
public const string PowerShellPath = "PowerShellPath";
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutao"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.9.1.0" />
|
||||
Version="1.9.4.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao</DisplayName>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutaoDev"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.9.1.0" />
|
||||
Version="1.9.4.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao Dev</DisplayName>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -120,6 +120,12 @@
|
||||
<data name="AppDevNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao Dev {0} [Administrator]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao {0} [Administrator]</value>
|
||||
</data>
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>Snap Hutao</value>
|
||||
</data>
|
||||
@@ -1547,6 +1553,9 @@
|
||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||
<value>Switch game account failed</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
|
||||
<value>Cannot select account [{1}] for Uid [{0}], the account does not belong to current server</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingActionComplete" xml:space="preserve">
|
||||
<value>Operation completed</value>
|
||||
</data>
|
||||
@@ -2138,6 +2147,9 @@
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>Pre-download</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
|
||||
<value>Select Game Path</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
|
||||
<value>This account has not been bound to the Real-time Note notification UID.</value>
|
||||
</data>
|
||||
@@ -2270,6 +2282,15 @@
|
||||
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
||||
<value>Device IP</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
|
||||
<value>Administrator mode will change some features' availability and behaviors</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
|
||||
<value>Administrator Mode</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
|
||||
<value>Restart with Administrator Mode</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
|
||||
<value>Show or hide wish event with no wish history</value>
|
||||
</data>
|
||||
@@ -2729,6 +2750,15 @@
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>Copied to clipboard</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
|
||||
<value>Completed</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
|
||||
<value>Locked</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
|
||||
<value>In Progress</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
|
||||
<value>Finished</value>
|
||||
</data>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -120,6 +120,12 @@
|
||||
<data name="AppDevNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao Dev {0} [Administrator]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao {0} [Administrator]</value>
|
||||
</data>
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>Snap Hutao</value>
|
||||
</data>
|
||||
@@ -990,7 +996,7 @@
|
||||
<value>Versi UIGF tidak didukung</value>
|
||||
</data>
|
||||
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
|
||||
<value>发现新版本 {0}</value>
|
||||
<value>Versi terbau {0} sudah tersedia.</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
|
||||
<value>Beberapa pengguna tercatat sebagai dipilih</value>
|
||||
@@ -1547,6 +1553,9 @@
|
||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||
<value>Gagal ganti akun game</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
|
||||
<value>Tidak bisa memilih akun [{1}] dengan UID [{0}], Akun itu tidak berada pada server tersebut</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingActionComplete" xml:space="preserve">
|
||||
<value>Tindakan selesai</value>
|
||||
</data>
|
||||
@@ -2091,7 +2100,7 @@
|
||||
<value>Mengatur Status Aktivitas Discord Saya Ketika Saya Sedang Bermain Game</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityHeader" xml:space="preserve">
|
||||
<value>Discord Activity</value>
|
||||
<value>Aktifitas Discord</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameFileHeader" xml:space="preserve">
|
||||
<value>Berkas</value>
|
||||
@@ -2138,6 +2147,9 @@
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>Prapengunduhan</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
|
||||
<value>Pilih Path Game</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
|
||||
<value>Akun ini belum terikat ke UID notifikasi Catatan Realtime.</value>
|
||||
</data>
|
||||
@@ -2270,6 +2282,15 @@
|
||||
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
||||
<value>ID Perangkat</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
|
||||
<value>Mode administrator akan mengubah ketersediaan dan perilaku beberapa fitur</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
|
||||
<value>Mode Admin</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
|
||||
<value>Restart menggunakan Mode Administrator</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
|
||||
<value>Tampilkan atau sembunyikan acara keinginan tanpa riwayat Wish</value>
|
||||
</data>
|
||||
@@ -2613,10 +2634,10 @@
|
||||
<value>Mengunggah Data</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
|
||||
<value>是否立即安装?</value>
|
||||
<value>Pasang sekarang?</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
|
||||
<value>胡桃 {0} 版本已准备就绪</value>
|
||||
<value>Snap Hutao versi {0} sudah siap</value>
|
||||
</data>
|
||||
<data name="ViewTitleAutoClicking" xml:space="preserve">
|
||||
<value>Auto Click</value>
|
||||
@@ -2729,6 +2750,15 @@
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>Disalin ke clipboard</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
|
||||
<value>Selesai</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
|
||||
<value>Terkunci</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
|
||||
<value>Dalam proses</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
|
||||
<value>Selesai</value>
|
||||
</data>
|
||||
@@ -2874,7 +2904,7 @@
|
||||
<value>Server Internasional Server Asia</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
|
||||
<value>国际服 港澳台服</value>
|
||||
<value>Server International: Server TW/HK/MU</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
|
||||
<value>Server Internasional Server Eropa</value>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -120,6 +120,12 @@
|
||||
<data name="AppDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃Dev {0} [管理者]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 {0} [管理者]</value>
|
||||
</data>
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>胡桃</value>
|
||||
</data>
|
||||
@@ -990,7 +996,7 @@
|
||||
<value>サポートされていないUIGFバージョン</value>
|
||||
</data>
|
||||
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
|
||||
<value>发现新版本 {0}</value>
|
||||
<value>新しいバージョン {0} が利用できます</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
|
||||
<value>ユーザー情報を複数選択しています。</value>
|
||||
@@ -1547,6 +1553,9 @@
|
||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||
<value>アカウントの切り替えができませんでした</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
|
||||
<value>UID [{0}] に対するアカウント [{1}] を選択できませんでした。このアカウントは現在のサーバーに属していません。</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingActionComplete" xml:space="preserve">
|
||||
<value>操作は完了しました</value>
|
||||
</data>
|
||||
@@ -1647,7 +1656,7 @@
|
||||
<value>重要</value>
|
||||
</data>
|
||||
<data name="ViewPageAnnouncementViewDetails" xml:space="preserve">
|
||||
<value>查看详情</value>
|
||||
<value>詳細を表示</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
|
||||
<value>聖遺物スコア</value>
|
||||
@@ -2138,6 +2147,9 @@
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>事前ダウンロード</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
|
||||
<value>ゲームのフォルダを選択</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
|
||||
<value>このアカウントはリアルタイムノート通知 UID として連携されていません。</value>
|
||||
</data>
|
||||
@@ -2270,6 +2282,15 @@
|
||||
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
||||
<value>デバイスのIP</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
|
||||
<value>管理者モードは一部の機能の使用可否と動作に影響します。</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
|
||||
<value>管理者モード</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
|
||||
<value>管理者モードで再起動</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
|
||||
<value>祈願履歴のないイベント限定祈願を祈願履歴に表示するかを変更します。</value>
|
||||
</data>
|
||||
@@ -2307,10 +2328,10 @@
|
||||
<value>CAPTCHA</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
|
||||
<value>选择想要获取公告的游戏服务器</value>
|
||||
<value>お知らせを受け取りたいサーバーを選択してください</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHomeAnnouncementRegionHeader" xml:space="preserve">
|
||||
<value>公告所属服务器</value>
|
||||
<value>お知らせのサーバー</value>
|
||||
</data>
|
||||
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
|
||||
<value>ダッシュボードを整理する</value>
|
||||
@@ -2613,10 +2634,10 @@
|
||||
<value>データをアップロード</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
|
||||
<value>是否立即安装?</value>
|
||||
<value>今すぐインストールしますか?</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
|
||||
<value>胡桃 {0} 版本已准备就绪</value>
|
||||
<value>胡桃 {0} の準備が完了</value>
|
||||
</data>
|
||||
<data name="ViewTitleAutoClicking" xml:space="preserve">
|
||||
<value>オートクリック</value>
|
||||
@@ -2729,6 +2750,15 @@
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>クリップボードにコピーしました。</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
|
||||
<value>すべて完了</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
|
||||
<value>未解放</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
|
||||
<value>進行中</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
|
||||
<value>完了しました。</value>
|
||||
</data>
|
||||
@@ -2859,28 +2889,28 @@
|
||||
<value>ダウンロードリンクのコピーに成功しました</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidRegion" xml:space="preserve">
|
||||
<value>无效的服务器</value>
|
||||
<value>無効なサーバー</value>
|
||||
</data>
|
||||
<data name="WebHoyolabInvalidUid" xml:space="preserve">
|
||||
<value>無効なUIDです</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNGF01" xml:space="preserve">
|
||||
<value>国服 官方服</value>
|
||||
<value>中国サーバー: 公式</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionCNQD01" xml:space="preserve">
|
||||
<value>国服 渠道服</value>
|
||||
<value>中国サーバー: ビリビリ(bilibili)</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSASIA" xml:space="preserve">
|
||||
<value>国际服 亚服</value>
|
||||
<value>国内サーバー: アジア</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
|
||||
<value>国际服 港澳台服</value>
|
||||
<value>海外サーバー: 台湾/香港/モンゴル</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
|
||||
<value>国际服 欧服</value>
|
||||
<value>海外サーバー: ヨーロッパ</value>
|
||||
</data>
|
||||
<data name="WebHoyolabRegionOSUSA" xml:space="preserve">
|
||||
<value>国际服 美服</value>
|
||||
<value>海外サーバー: 北アメリカ</value>
|
||||
</data>
|
||||
<data name="WebHutaoServiceUnAvailable" xml:space="preserve">
|
||||
<value>胡桃サーバがメンテナンス中です</value>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -120,6 +120,12 @@
|
||||
<data name="AppDevNameAndVersion" xml:space="preserve">
|
||||
<value>호두 Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃Dev {0} [管理员]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 {0} [管理员]</value>
|
||||
</data>
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>호두</value>
|
||||
</data>
|
||||
@@ -1547,6 +1553,9 @@
|
||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||
<value>계정 전환 살패</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
|
||||
<value>无法选择UID [{0}] 对应的账号 [{1}],该账号不属于当前服务器</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingActionComplete" xml:space="preserve">
|
||||
<value>操作完成</value>
|
||||
</data>
|
||||
@@ -2138,6 +2147,9 @@
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>사전 다운로드</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
|
||||
<value>选择游戏路径</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
|
||||
<value>该账号尚未绑定实时便笺通知 UID</value>
|
||||
</data>
|
||||
@@ -2270,6 +2282,15 @@
|
||||
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
||||
<value>设备 IP</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
|
||||
<value>管理员模式会影响部分功能的可用性与行为</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
|
||||
<value>管理员模式</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
|
||||
<value>以管理员身份重启</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
|
||||
<value>기원 기록 페이지에 기록되지 않은 오래된 기원 이벤트를 표시하거나 숨깁니다</value>
|
||||
</data>
|
||||
@@ -2729,6 +2750,15 @@
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>已复制到剪贴板</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
|
||||
<value>全部完成</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
|
||||
<value>尚未开启</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
|
||||
<value>进行中</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
|
||||
<value>已完成</value>
|
||||
</data>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<value>胡桃 Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃Dev {0} [管理员]</value>
|
||||
<value>胡桃 Dev {0} [管理员]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 {0} [管理员]</value>
|
||||
@@ -1271,6 +1271,12 @@
|
||||
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
|
||||
<value>使用米游社扫描二维码</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
|
||||
<value>请输入你正在启用的功能标题</value>
|
||||
</data>
|
||||
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
|
||||
<value>你正在启用一个危险功能</value>
|
||||
</data>
|
||||
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
|
||||
<value>该操作是不可逆的,所有用户登录状态会丢失</value>
|
||||
</data>
|
||||
@@ -1574,6 +1580,12 @@
|
||||
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
|
||||
<value>创建桌面快捷方式失败</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
|
||||
<value>后续转换会重新下载所需的文件,确定要删除吗?</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
|
||||
<value>删除转换服务器游戏客户端缓存</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
|
||||
<value>已使用磁盘空间:{0}</value>
|
||||
</data>
|
||||
@@ -2192,6 +2204,12 @@
|
||||
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
|
||||
<value>启用</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
|
||||
<value>充分利用支持高动态范围的显示器获得更亮、更生动、更精细的画面</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
|
||||
<value>Windows HDR</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>请输入你的 HoYoLab Uid</value>
|
||||
</data>
|
||||
@@ -2409,7 +2427,7 @@
|
||||
<value>在完整阅读原神和胡桃工具箱用户协议后,我选择启用「启动游戏-高级功能」</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader" xml:space="preserve">
|
||||
<value>启动高级功能</value>
|
||||
<value>高级功能</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingKeyShortcutAutoClickingDescription" xml:space="preserve">
|
||||
<value>更改自动连点功能的快捷键</value>
|
||||
@@ -2564,6 +2582,12 @@
|
||||
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
|
||||
<value>调试控制台</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
|
||||
<value>在启动游戏中转换服务器后会产生对应的游戏客户端文件用作缓存</value>
|
||||
</data>
|
||||
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
|
||||
<value>删除转换服务器缓存</value>
|
||||
</data>
|
||||
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
|
||||
<value>打开文件夹</value>
|
||||
</data>
|
||||
@@ -2747,6 +2771,9 @@
|
||||
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
|
||||
<value>{0} 小时后结束</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
|
||||
<value>打开剪贴板失败</value>
|
||||
</data>
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>已复制到剪贴板</value>
|
||||
</data>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -120,11 +120,17 @@
|
||||
<data name="AppDevNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>Ху Тао Dev {0} [Администратор]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>Ху Тао {0} [Администратор]</value>
|
||||
</data>
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>Snap Hutao</value>
|
||||
<value>Ху Тао</value>
|
||||
</data>
|
||||
<data name="AppNameAndVersion" xml:space="preserve">
|
||||
<value>Snap Hutao {0}</value>
|
||||
<value>Ху Тао {0}</value>
|
||||
</data>
|
||||
<data name="ContentDialogCancelCloseButtonText" xml:space="preserve">
|
||||
<value>Отмена</value>
|
||||
@@ -489,19 +495,19 @@
|
||||
<value>Волна 3: Волна появится только после убийства всех врагов в предыдущей волне.</value>
|
||||
</data>
|
||||
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
|
||||
<value>第四波:击败所有怪物,下一波才会出现</value>
|
||||
<value>Четвертая волна: Победите всех монстров, прежде чем появится следующая волна</value>
|
||||
</data>
|
||||
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
|
||||
<value>请更新角色橱窗数据</value>
|
||||
<value>Обновите данные витрины персонажей</value>
|
||||
</data>
|
||||
<data name="ModelNameValueDefaultName" xml:space="preserve">
|
||||
<value>暂无数据</value>
|
||||
<value>Нет данных</value>
|
||||
</data>
|
||||
<data name="ModelWeaponAffixFormat" xml:space="preserve">
|
||||
<value>精炼 {0} 阶</value>
|
||||
<value>Уровень совершенствования {0}</value>
|
||||
</data>
|
||||
<data name="MustSelectUserAndUid" xml:space="preserve">
|
||||
<value>必须登录 米游社/HoYoLAB 并选择一个用户与角色</value>
|
||||
<value>Необходимо войти в учетную запись miHoYo/HoYoLAB и выбрать пользователя с персонажем</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceDeleteEntrySucceed" xml:space="preserve">
|
||||
<value>删除了 Uid:{0} 的 {1} 条祈愿记录</value>
|
||||
@@ -1547,6 +1553,9 @@
|
||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||
<value>切换账号失败</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
|
||||
<value>无法选择UID [{0}] 对应的账号 [{1}],该账号不属于当前服务器</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingActionComplete" xml:space="preserve">
|
||||
<value>操作完成</value>
|
||||
</data>
|
||||
@@ -2138,6 +2147,9 @@
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>预下载</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
|
||||
<value>选择游戏路径</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
|
||||
<value>该账号尚未绑定实时便笺通知 UID</value>
|
||||
</data>
|
||||
@@ -2270,6 +2282,15 @@
|
||||
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
||||
<value>设备 IP</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
|
||||
<value>管理员模式会影响部分功能的可用性与行为</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
|
||||
<value>管理员模式</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
|
||||
<value>以管理员身份重启</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
|
||||
<value>在祈愿记录页面显示或隐藏无记录的历史祈愿活动</value>
|
||||
</data>
|
||||
@@ -2729,6 +2750,15 @@
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>已复制到剪贴板</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
|
||||
<value>全部完成</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
|
||||
<value>尚未开启</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
|
||||
<value>进行中</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
|
||||
<value>已完成</value>
|
||||
</data>
|
||||
|
||||
@@ -60,45 +60,45 @@
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -120,6 +120,12 @@
|
||||
<data name="AppDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃Dev {0} [管理员]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 {0} [管理员]</value>
|
||||
</data>
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>胡桃</value>
|
||||
</data>
|
||||
@@ -142,7 +148,7 @@
|
||||
<value>無效的 Uri</value>
|
||||
</data>
|
||||
<data name="ControlImageCompositionImageHttpRequest" xml:space="preserve">
|
||||
<value>HTTP GET {0}</value>
|
||||
<value>获取HTTP{0}</value>
|
||||
</data>
|
||||
<data name="ControlImageCompositionImageSystemException" xml:space="preserve">
|
||||
<value>應用 CompositionImage 的源時發生異常</value>
|
||||
@@ -292,7 +298,7 @@
|
||||
<comment>Need EXACT same string in game</comment>
|
||||
</data>
|
||||
<data name="ModelIntrinsicBodyTypeBoy" xml:space="preserve">
|
||||
<value>少男</value>
|
||||
<value>少年</value>
|
||||
</data>
|
||||
<data name="ModelIntrinsicBodyTypeGirl" xml:space="preserve">
|
||||
<value>少女</value>
|
||||
@@ -304,7 +310,7 @@
|
||||
<value>蘿莉</value>
|
||||
</data>
|
||||
<data name="ModelIntrinsicBodyTypeMale" xml:space="preserve">
|
||||
<value>成男</value>
|
||||
<value>成年男子</value>
|
||||
</data>
|
||||
<data name="ModelIntrinsicElementNameElec" xml:space="preserve">
|
||||
<value>雷</value>
|
||||
@@ -373,7 +379,7 @@
|
||||
<comment>Need EXACT same string in game</comment>
|
||||
</data>
|
||||
<data name="ModelMetadataAvatarPlayerName" xml:space="preserve">
|
||||
<value>旅行者</value>
|
||||
<value>旅人</value>
|
||||
</data>
|
||||
<data name="ModelMetadataFetterInfoBirthdayFormat" xml:space="preserve">
|
||||
<value>{0} 月 {1} 日</value>
|
||||
@@ -480,7 +486,7 @@
|
||||
<value>第一波:擊敗所有怪物,下一波才會出現</value>
|
||||
</data>
|
||||
<data name="ModelMetadataTowerWaveTypeWave1Additional" xml:space="preserve">
|
||||
<value>第一波附加:增援第一波怪物</value>
|
||||
<value>第一波追加:增援第一波怪物</value>
|
||||
</data>
|
||||
<data name="ModelMetadataTowerWaveTypeWave2" xml:space="preserve">
|
||||
<value>第二波:擊敗所有怪物,下一波才會出現</value>
|
||||
@@ -840,7 +846,7 @@
|
||||
<value>找不到原神內置瀏覽器緩存路徑:\n{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogUrlProviderCacheUrlNotFound" xml:space="preserve">
|
||||
<value>未找到可用的 Url</value>
|
||||
<value>找不到可用的 Url</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogUrlProviderManualInputInvalid" xml:space="preserve">
|
||||
<value>提供的 Url 無效</value>
|
||||
@@ -990,7 +996,7 @@
|
||||
<value>不支援的 UIGF 版本</value>
|
||||
</data>
|
||||
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
|
||||
<value>发现新版本 {0}</value>
|
||||
<value>發現新版本 {0}</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
|
||||
<value>已选中多条用户记录</value>
|
||||
@@ -1547,6 +1553,9 @@
|
||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||
<value>切換帳號失敗</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
|
||||
<value>无法选择UID [{0}] 对应的账号 [{1}],该账号不属于当前服务器</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingActionComplete" xml:space="preserve">
|
||||
<value>操作完成</value>
|
||||
</data>
|
||||
@@ -2138,6 +2147,9 @@
|
||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||
<value>預下載</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
|
||||
<value>选择游戏路径</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
|
||||
<value>該用戶尚未綁定即時便箋通知 UID</value>
|
||||
</data>
|
||||
@@ -2270,6 +2282,15 @@
|
||||
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
||||
<value>設備 IP</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
|
||||
<value>管理员模式会影响部分功能的可用性与行为</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
|
||||
<value>管理员模式</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
|
||||
<value>以管理员身份重启</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
|
||||
<value>在祈願紀錄頁面顯示或隱藏無記錄的歷史祈願活動</value>
|
||||
</data>
|
||||
@@ -2729,6 +2750,15 @@
|
||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||
<value>已複製到剪貼簿</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
|
||||
<value>全部完成</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
|
||||
<value>尚未开启</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
|
||||
<value>进行中</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
|
||||
<value>已完成</value>
|
||||
</data>
|
||||
|
||||
@@ -58,7 +58,11 @@ internal sealed partial class DailyNoteOptions : DbStoreOptions
|
||||
{
|
||||
if (SelectedRefreshTime is not null)
|
||||
{
|
||||
scheduleTaskInterop.RegisterForDailyNoteRefresh(SelectedRefreshTime.Value);
|
||||
if (!scheduleTaskInterop.RegisterForDailyNoteRefresh(SelectedRefreshTime.Value))
|
||||
{
|
||||
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.ViewModelDailyNoteRegisterTaskFail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Discord.GameSDK;
|
||||
using Snap.Discord.GameSDK.ABI;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Unicode;
|
||||
|
||||
namespace Snap.Hutao.Service.Discord;
|
||||
|
||||
@@ -18,75 +18,94 @@ internal static class DiscordController
|
||||
private static readonly CancellationTokenSource StopTokenSource = new();
|
||||
private static readonly object SyncRoot = new();
|
||||
|
||||
private static Snap.Discord.GameSDK.Discord? discordManager;
|
||||
private static long currentClientId;
|
||||
private static unsafe IDiscordCore* discordCorePtr;
|
||||
private static bool isInitialized;
|
||||
|
||||
public static async ValueTask<Result> SetDefaultActivityAsync(DateTimeOffset startTime)
|
||||
public static async ValueTask<DiscordResult> SetDefaultActivityAsync(DateTimeOffset startTime)
|
||||
{
|
||||
ResetManagerOrIgnore(HutaoAppId);
|
||||
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||
return SetDefaultActivity(startTime);
|
||||
|
||||
if (discordManager is null)
|
||||
static unsafe DiscordResult SetDefaultActivity(in DateTimeOffset startTime)
|
||||
{
|
||||
return Result.Ok;
|
||||
ResetManagerOrIgnore(HutaoAppId);
|
||||
|
||||
if (discordCorePtr is null)
|
||||
{
|
||||
return DiscordResult.Ok;
|
||||
}
|
||||
|
||||
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
|
||||
|
||||
DiscordActivity activity = default;
|
||||
activity.timestamps.start = startTime.ToUnixTimeSeconds();
|
||||
SetString(activity.assets.large_image, 128, "icon"u8);
|
||||
SetString(activity.assets.large_text, 128, SH.AppName);
|
||||
|
||||
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
|
||||
}
|
||||
|
||||
ActivityManager activityManager = discordManager.GetActivityManager();
|
||||
|
||||
Activity activity = default;
|
||||
activity.Timestamps.Start = startTime.ToUnixTimeSeconds();
|
||||
activity.Assets.LargeImage = "icon";
|
||||
activity.Assets.LargeText = SH.AppName;
|
||||
|
||||
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async ValueTask<Result> SetPlayingYuanShenAsync()
|
||||
public static async ValueTask<DiscordResult> SetPlayingYuanShenAsync()
|
||||
{
|
||||
ResetManagerOrIgnore(YuanshenId);
|
||||
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||
return SetPlayingYuanShen();
|
||||
|
||||
if (discordManager is null)
|
||||
static unsafe DiscordResult SetPlayingYuanShen()
|
||||
{
|
||||
return Result.Ok;
|
||||
ResetManagerOrIgnore(YuanshenId);
|
||||
|
||||
if (discordCorePtr is null)
|
||||
{
|
||||
return DiscordResult.Ok;
|
||||
}
|
||||
|
||||
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
|
||||
|
||||
DiscordActivity activity = default;
|
||||
SetString(activity.state, 128, SH.FormatServiceDiscordGameLaunchedBy(SH.AppName));
|
||||
SetString(activity.details, 128, SH.ServiceDiscordGameActivityDetails);
|
||||
activity.timestamps.start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
SetString(activity.assets.large_image, 128, "icon"u8);
|
||||
SetString(activity.assets.large_text, 128, "原神"u8);
|
||||
SetString(activity.assets.small_image, 128, "app"u8);
|
||||
SetString(activity.assets.small_text, 128, SH.AppName);
|
||||
|
||||
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
|
||||
}
|
||||
|
||||
ActivityManager activityManager = discordManager.GetActivityManager();
|
||||
|
||||
Activity activity = default;
|
||||
activity.State = SH.FormatServiceDiscordGameLaunchedBy(SH.AppName);
|
||||
activity.Details = SH.ServiceDiscordGameActivityDetails;
|
||||
activity.Timestamps.Start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
activity.Assets.LargeImage = "icon";
|
||||
activity.Assets.LargeText = "原神";
|
||||
activity.Assets.SmallImage = "app";
|
||||
activity.Assets.SmallText = SH.AppName;
|
||||
|
||||
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async ValueTask<Result> SetPlayingGenshinImpactAsync()
|
||||
public static async ValueTask<DiscordResult> SetPlayingGenshinImpactAsync()
|
||||
{
|
||||
ResetManagerOrIgnore(GenshinImpactId);
|
||||
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||
return SetPlayingGenshinImpact();
|
||||
|
||||
if (discordManager is null)
|
||||
static unsafe DiscordResult SetPlayingGenshinImpact()
|
||||
{
|
||||
return Result.Ok;
|
||||
ResetManagerOrIgnore(GenshinImpactId);
|
||||
|
||||
if (discordCorePtr is null)
|
||||
{
|
||||
return DiscordResult.Ok;
|
||||
}
|
||||
|
||||
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
|
||||
|
||||
DiscordActivity activity = default;
|
||||
SetString(activity.state, 128, SH.FormatServiceDiscordGameLaunchedBy(SH.AppName));
|
||||
SetString(activity.details, 128, SH.ServiceDiscordGameActivityDetails);
|
||||
activity.timestamps.start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
SetString(activity.assets.large_image, 128, "icon"u8);
|
||||
SetString(activity.assets.large_text, 128, "Genshin Impact"u8);
|
||||
SetString(activity.assets.small_image, 128, "app"u8);
|
||||
SetString(activity.assets.small_text, 128, SH.AppName);
|
||||
|
||||
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
|
||||
}
|
||||
|
||||
ActivityManager activityManager = discordManager.GetActivityManager();
|
||||
|
||||
Activity activity = default;
|
||||
activity.State = SH.FormatServiceDiscordGameLaunchedBy(SH.AppName);
|
||||
activity.Details = SH.ServiceDiscordGameActivityDetails;
|
||||
activity.Timestamps.Start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
activity.Assets.LargeImage = "icon";
|
||||
activity.Assets.LargeText = "Genshin Impact";
|
||||
activity.Assets.SmallImage = "app";
|
||||
activity.Assets.SmallText = SH.AppName;
|
||||
|
||||
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
public static unsafe void Stop()
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
@@ -98,7 +117,7 @@ internal static class DiscordController
|
||||
StopTokenSource.Cancel();
|
||||
try
|
||||
{
|
||||
discordManager?.Dispose();
|
||||
discordCorePtr = default;
|
||||
}
|
||||
catch (SEHException)
|
||||
{
|
||||
@@ -108,23 +127,30 @@ internal static class DiscordController
|
||||
|
||||
private static unsafe void ResetManagerOrIgnore(long clientId)
|
||||
{
|
||||
if (discordManager?.ClientId == clientId)
|
||||
if (currentClientId == clientId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Actually requires a discord client to be running on Windows platform.
|
||||
// If not, the following creation code will throw.
|
||||
if (System.Diagnostics.Process.GetProcessesByName("Discord").Length == 0)
|
||||
if (System.Diagnostics.Process.GetProcessesByName("Discord").Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (SyncRoot)
|
||||
{
|
||||
discordManager?.Dispose();
|
||||
discordManager = new(clientId, CreateFlags.NoRequireDiscord);
|
||||
discordManager.SetLogHook(Snap.Discord.GameSDK.LogLevel.Debug, SetLogHookHandler.Create(&DebugWriteDiscordMessage));
|
||||
DiscordCreateParams @params = default;
|
||||
Methods.DiscordCreateParamsSetDefault(&@params);
|
||||
@params.client_id = clientId;
|
||||
@params.flags = (uint)DiscordCreateFlags.Default;
|
||||
IDiscordCore* ptr = default;
|
||||
Methods.DiscordCreate(3, &@params, &ptr);
|
||||
|
||||
currentClientId = clientId;
|
||||
discordCorePtr = ptr;
|
||||
discordCorePtr->set_log_hook(discordCorePtr, DiscordLogLevel.Debug, default, &DebugWriteDiscordMessage);
|
||||
}
|
||||
|
||||
if (isInitialized)
|
||||
@@ -135,10 +161,10 @@ internal static class DiscordController
|
||||
DiscordRunCallbacksAsync(StopTokenSource.Token).SafeForget();
|
||||
isInitialized = true;
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
|
||||
static unsafe void DebugWriteDiscordMessage(Snap.Discord.GameSDK.LogLevel logLevel, byte* ptr)
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
static unsafe void DebugWriteDiscordMessage(void* state, DiscordLogLevel logLevel, sbyte* ptr)
|
||||
{
|
||||
ReadOnlySpan<byte> utf8 = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(ptr);
|
||||
ReadOnlySpan<byte> utf8 = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)ptr);
|
||||
string message = System.Text.Encoding.UTF8.GetString(utf8);
|
||||
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK]:[{logLevel}]:{message}");
|
||||
}
|
||||
@@ -146,7 +172,7 @@ internal static class DiscordController
|
||||
|
||||
private static async ValueTask DiscordRunCallbacksAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(1000)))
|
||||
using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(500)))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -161,15 +187,10 @@ internal static class DiscordController
|
||||
{
|
||||
try
|
||||
{
|
||||
discordManager?.RunCallbacks();
|
||||
}
|
||||
catch (ResultException ex)
|
||||
{
|
||||
// If result is Ok
|
||||
// Maybe the connection is reset.
|
||||
if (ex.Result is not Result.Ok)
|
||||
DiscordResult result = DiscordCoreRunRunCallbacks();
|
||||
if (result is not DiscordResult.Ok)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK ERROR]:{ex.Result:D} {ex.Result}");
|
||||
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK ERROR]:{result:D} {result}");
|
||||
}
|
||||
}
|
||||
catch (SEHException ex)
|
||||
@@ -185,5 +206,70 @@ internal static class DiscordController
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
unsafe DiscordResult DiscordCoreRunRunCallbacks()
|
||||
{
|
||||
if (discordCorePtr is not null)
|
||||
{
|
||||
return discordCorePtr->run_callbacks(discordCorePtr);
|
||||
}
|
||||
|
||||
return DiscordResult.Ok;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void SetString(sbyte* reference, int length, string source)
|
||||
{
|
||||
Span<sbyte> sbytes = new(reference, length);
|
||||
sbytes.Clear();
|
||||
Utf8.FromUtf16(source.AsSpan(), MemoryMarshal.Cast<sbyte, byte>(sbytes), out _, out _);
|
||||
}
|
||||
|
||||
private static unsafe void SetString(sbyte* reference, int length, in ReadOnlySpan<byte> source)
|
||||
{
|
||||
Span<sbyte> sbytes = new(reference, length);
|
||||
sbytes.Clear();
|
||||
source.CopyTo(MemoryMarshal.Cast<sbyte, byte>(sbytes));
|
||||
}
|
||||
|
||||
private struct DiscordAsyncAction
|
||||
{
|
||||
public DiscordResult Result;
|
||||
public bool IsCompleted;
|
||||
}
|
||||
|
||||
private unsafe struct DiscordUpdateActivityAsyncAction
|
||||
{
|
||||
private readonly IDiscordActivityManager* activityManagerPtr;
|
||||
private DiscordAsyncAction discordAsyncAction;
|
||||
|
||||
public DiscordUpdateActivityAsyncAction(IDiscordActivityManager* activityManagerPtr)
|
||||
{
|
||||
this.activityManagerPtr = activityManagerPtr;
|
||||
}
|
||||
|
||||
public DiscordResult WaitUpdateActivity(DiscordActivity activity)
|
||||
{
|
||||
fixed (DiscordAsyncAction* actionPtr = &discordAsyncAction)
|
||||
{
|
||||
activityManagerPtr->update_activity(activityManagerPtr, &activity, actionPtr, &HandleResult);
|
||||
}
|
||||
|
||||
SpinWaitPolyfill.SpinUntil(ref discordAsyncAction, &CheckActionCompleted);
|
||||
return discordAsyncAction.Result;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
private static void HandleResult(void* state, DiscordResult result)
|
||||
{
|
||||
DiscordAsyncAction* action = (DiscordAsyncAction*)state;
|
||||
action->Result = result;
|
||||
action->IsCompleted = true;
|
||||
}
|
||||
|
||||
private static bool CheckActionCompleted(ref readonly DiscordAsyncAction state)
|
||||
{
|
||||
return state.IsCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,11 @@ internal sealed partial class GameAccountService : IGameAccountService
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gameAccounts);
|
||||
|
||||
if (schemeType is SchemeType.ChineseBilibili)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
string? registrySdk = RegistryInterop.Get(schemeType);
|
||||
if (string.IsNullOrEmpty(registrySdk))
|
||||
{
|
||||
@@ -62,6 +67,11 @@ internal sealed partial class GameAccountService : IGameAccountService
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gameAccounts);
|
||||
|
||||
if (schemeType is SchemeType.ChineseBilibili)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
string? registrySdk = RegistryInterop.Get(schemeType);
|
||||
|
||||
if (string.IsNullOrEmpty(registrySdk))
|
||||
|
||||
@@ -20,6 +20,8 @@ internal static class RegistryInterop
|
||||
private const string SdkChineseValueName = "MIHOYOSDK_ADL_PROD_CN_h3123967166";
|
||||
private const string SdkOverseaValueName = "MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810";
|
||||
|
||||
private const string WindowsHDROnValueName = "WINDOWS_HDR_ON_h3132281285";
|
||||
|
||||
public static bool Set(GameAccount? account)
|
||||
{
|
||||
if (account is not null)
|
||||
@@ -56,6 +58,12 @@ internal static class RegistryInterop
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetWindowsHDR(bool isOversea)
|
||||
{
|
||||
string keyName = isOversea ? OverseaKeyName : ChineseKeyName;
|
||||
Registry.SetValue(keyName, WindowsHDROnValueName, 1);
|
||||
}
|
||||
|
||||
private static (string KeyName, string ValueName) GetKeyValueName(SchemeType scheme)
|
||||
{
|
||||
return scheme switch
|
||||
|
||||
@@ -42,6 +42,7 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
private NameValue<int>? monitor;
|
||||
private bool? isMonitorEnabled;
|
||||
private bool? isUseCloudThirdPartyMobile;
|
||||
private bool? isWindowsHDREnabled;
|
||||
private AspectRatio? selectedAspectRatio;
|
||||
private bool? useStarwardPlayTimeStatistics;
|
||||
private bool? setDiscordActivityWhenPlaying;
|
||||
@@ -198,6 +199,12 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
set => SetOption(ref isUseCloudThirdPartyMobile, SettingEntry.LaunchIsUseCloudThirdPartyMobile, value);
|
||||
}
|
||||
|
||||
public bool IsWindowsHDREnabled
|
||||
{
|
||||
get => GetOption(ref isWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, false);
|
||||
set => SetOption(ref isWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, value);
|
||||
}
|
||||
|
||||
public List<AspectRatio> AspectRatios { get; } =
|
||||
[
|
||||
new(2560, 1440),
|
||||
|
||||
@@ -17,7 +17,7 @@ internal sealed class PackageReplaceStatus
|
||||
public PackageReplaceStatus(string name)
|
||||
{
|
||||
Name = name;
|
||||
Description = default!;
|
||||
Description = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Service.Discord;
|
||||
using Snap.Hutao.Service.Game.Account;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Game.Unlocker;
|
||||
using System.IO;
|
||||
@@ -61,6 +62,11 @@ internal sealed partial class GameProcessService : IGameProcessService
|
||||
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileName);
|
||||
|
||||
if (launchOptions.IsWindowsHDREnabled)
|
||||
{
|
||||
RegistryInterop.SetWindowsHDR(isOversea);
|
||||
}
|
||||
|
||||
progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing));
|
||||
using (System.Diagnostics.Process game = InitializeGameProcess(gamePath))
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ internal sealed partial class DocumentationProvider : IDocumentationProvider
|
||||
[typeof(LoginHoyoverseUserPage)] = "https://hut.ao/features/mhy-account-switch.html",
|
||||
[typeof(LoginMihoyoUserPage)] = "https://hut.ao/features/mhy-account-switch.html",
|
||||
[typeof(SettingPage)] = "https://hut.ao/features/hutao-settings.html",
|
||||
[typeof(SpiralAbyssRecordPage)] = "https://hut.ao/features/dashboard.html",
|
||||
[typeof(SpiralAbyssRecordPage)] = "https://hut.ao/features/hutao-API.html",
|
||||
[typeof(TestPage)] = Home,
|
||||
[typeof(WikiAvatarPage)] = "https://hut.ao/features/character-wiki.html",
|
||||
[typeof(WikiMonsterPage)] = "https://hut.ao/features/monster-wiki.html",
|
||||
|
||||
@@ -173,6 +173,7 @@
|
||||
<None Remove="View\Dialog\HutaoPassportUnregisterDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGameAccountNameDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
|
||||
<None Remove="View\Dialog\ReconfirmDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserQRCodeDialog.xaml" />
|
||||
<None Remove="View\Guide\GuideView.xaml" />
|
||||
@@ -311,7 +312,7 @@
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.5.0" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
|
||||
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.9.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@@ -343,6 +344,11 @@
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\ReconfirmDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\UserQRCodeDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
21
src/Snap.Hutao/Snap.Hutao/View/Dialog/ReconfirmDialog.xaml
Normal file
21
src/Snap.Hutao/Snap.Hutao/View/Dialog/ReconfirmDialog.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.ReconfirmDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
Title="{shcm:ResourceString Name=ViewDialogReconfirmTitle}"
|
||||
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
|
||||
DefaultButton="Close"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<TextBox
|
||||
Margin="0,0,0,8"
|
||||
VerticalAlignment="Top"
|
||||
Header="{shcm:ResourceString Name=ViewDialogReconfirmTextHeader}"
|
||||
Text="{x:Bind Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
[DependencyProperty("Text", typeof(string), default(string), nameof(OnTextChanged))]
|
||||
internal sealed partial class ReconfirmDialog : ContentDialog
|
||||
{
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public ReconfirmDialog(IServiceProvider serviceProvider)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
}
|
||||
|
||||
public string ConfirmText { get; private set; } = default!;
|
||||
|
||||
public async ValueTask<bool> ConfirmAsync(string confirmText)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ConfirmText = confirmText;
|
||||
return await ShowAsync() is ContentDialogResult.Primary;
|
||||
}
|
||||
|
||||
private static void OnTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
ReconfirmDialog dialog = (ReconfirmDialog)sender;
|
||||
dialog.IsPrimaryButtonEnabled = string.Equals(dialog.Text, dialog.ConfirmText, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,12 @@
|
||||
VerticalAlignment="Top"
|
||||
cw:VisualExtensions.NormalizedCenterPoint="0.5">
|
||||
<cww:ConstrainedBox AspectRatio="1080:390" CornerRadius="{ThemeResource ControlCornerRadiusTop}">
|
||||
<shci:CachedImage Source="{Binding Banner}" Stretch="UniformToFill"/>
|
||||
<shci:CachedImage
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderMargin="16"
|
||||
PlaceholderSource="{StaticResource UI_EmotionIcon271}"
|
||||
Source="{Binding Banner}"
|
||||
Stretch="UniformToFill"/>
|
||||
</cww:ConstrainedBox>
|
||||
<cwa:Explicit.Animations>
|
||||
<cwa:AnimationSet x:Name="ImageZoomInAnimation">
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:mxim="using:Microsoft.Xaml.Interactions.Media"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shccs="using:Snap.Hutao.Control.Collection.Selector"
|
||||
@@ -201,6 +199,12 @@
|
||||
ItemsSource="{Binding GameAccountsView}"
|
||||
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
||||
</Border>
|
||||
<cwc:SettingsCard
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameWindowsHDRDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameWindowsHDRHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsWindowsHDREnabled, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<!-- 进程 -->
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameProcessHeader}"/>
|
||||
@@ -208,8 +212,7 @@
|
||||
shch:SettingsExpanderHelper.IsItemsEnabled="{Binding LaunchOptions.IsEnabled}"
|
||||
Description="{shcm:ResourceString Name=ViewPageLaunchGameArgumentsDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageLaunchGameArgumentsHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
IsExpanded="True">
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsEnabled, Mode=TwoWay}"/>
|
||||
<cwc:SettingsExpander.Items>
|
||||
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceExclusiveDescription}" Header="-window-mode exclusive">
|
||||
|
||||
@@ -374,9 +374,14 @@
|
||||
Description="{shcm:ResourceString Name=ViewPageSettingSetDataFolderDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingSetDataFolderHeader}"
|
||||
IsClickEnabled="True"/>
|
||||
<cwc:SettingsCard
|
||||
ActionIcon="{shcm:FontIcon Glyph=}"
|
||||
Command="{Binding DeleteServerCacheFolderCommand}"
|
||||
Description="{shcm:ResourceString Name=ViewSettingDeleteServerCacheFolderDescription}"
|
||||
Header="{shcm:ResourceString Name=ViewSettingDeleteServerCacheFolderHeader}"
|
||||
IsClickEnabled="True"/>
|
||||
</cwc:SettingsExpander.Items>
|
||||
</cwc:SettingsExpander>
|
||||
|
||||
<cwc:SettingsExpander
|
||||
Description="{Binding CacheFolderView.Size}"
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingCacheFolderHeader}"
|
||||
@@ -408,7 +413,6 @@
|
||||
HeaderIcon="{cw:FontIcon Glyph=}">
|
||||
<ToggleSwitch Width="120" IsOn="{Binding IsAllocConsoleDebugModeEnabled, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
|
||||
<cwc:SettingsCard
|
||||
Header="{shcm:ResourceString Name=ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader}"
|
||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||
@@ -424,7 +428,7 @@
|
||||
</cwc:SettingsCard.Description>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<shvc:Elevation Visibility="{Binding HutaoOptions.IsElevated, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
|
||||
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsAdvancedLaunchOptionsEnabled, Mode=TwoWay}"/>
|
||||
<ToggleSwitch Width="120" IsOn="{Binding IsAdvancedLaunchOptionsEnabled, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard
|
||||
@@ -443,4 +447,4 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</shc:ScopedPage>
|
||||
</shc:ScopedPage>
|
||||
@@ -13,7 +13,16 @@ internal static class LaunchGameShared
|
||||
{
|
||||
public static LaunchScheme? GetCurrentLaunchSchemeFromConfigFile(IGameServiceFacade gameService, IInfoBarService infoBarService)
|
||||
{
|
||||
ChannelOptions options = gameService.GetChannelOptions();
|
||||
ChannelOptions options;
|
||||
try
|
||||
{
|
||||
options = gameService.GetChannelOptions();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(options.ConfigFilePath))
|
||||
{
|
||||
try
|
||||
|
||||
@@ -225,7 +225,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
// Always ensure game resources
|
||||
if (!await gameService.EnsureGameResourceAsync(SelectedScheme, convertProgress).ConfigureAwait(false))
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelLaunchGameEnsureGameResourceFail, dialog.State.Name);
|
||||
infoBarService.Warning(SH.ViewModelLaunchGameEnsureGameResourceFail, dialog.State?.Name ?? string.Empty);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -122,11 +122,56 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
|
||||
public IPInformation? IPInformation { get => ipInformation; private set => SetProperty(ref ipInformation, value); }
|
||||
|
||||
[SuppressMessage("", "CA1822")]
|
||||
public bool IsAllocConsoleDebugModeEnabled
|
||||
{
|
||||
get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false);
|
||||
set => LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, value);
|
||||
set
|
||||
{
|
||||
ConfirmSetIsAllocConsoleDebugModeEnabledAsync(value).SafeForget();
|
||||
|
||||
async ValueTask ConfirmSetIsAllocConsoleDebugModeEnabledAsync(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
ReconfirmDialog dialog = await contentDialogFactory.CreateInstanceAsync<ReconfirmDialog>().ConfigureAwait(false);
|
||||
if (await dialog.ConfirmAsync(SH.ViewSettingAllocConsoleHeader).ConfigureAwait(true))
|
||||
{
|
||||
LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, true);
|
||||
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, false);
|
||||
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAdvancedLaunchOptionsEnabled
|
||||
{
|
||||
get => launchOptions.IsAdvancedLaunchOptionsEnabled;
|
||||
set
|
||||
{
|
||||
ConfirmSetIsAdvancedLaunchOptionsEnabledAsync(value).SafeForget();
|
||||
|
||||
async ValueTask ConfirmSetIsAdvancedLaunchOptionsEnabledAsync(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
ReconfirmDialog dialog = await contentDialogFactory.CreateInstanceAsync<ReconfirmDialog>().ConfigureAwait(false);
|
||||
if (await dialog.ConfirmAsync(SH.ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader).ConfigureAwait(true))
|
||||
{
|
||||
launchOptions.IsAdvancedLaunchOptionsEnabled = true;
|
||||
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
launchOptions.IsAdvancedLaunchOptionsEnabled = false;
|
||||
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
@@ -223,6 +268,26 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
[Command("DeleteServerCacheFolderCommand")]
|
||||
private async Task DeleteServerCacheFolderAsync()
|
||||
{
|
||||
ContentDialogResult result = await contentDialogFactory.CreateForConfirmCancelAsync(
|
||||
SH.ViewModelSettingDeleteServerCacheFolderTitle,
|
||||
SH.ViewModelSettingDeleteServerCacheFolderContent)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result is ContentDialogResult.Primary)
|
||||
{
|
||||
string cacheFolder = runtimeOptions.GetDataFolderServerCacheFolder();
|
||||
if (Directory.Exists(cacheFolder))
|
||||
{
|
||||
Directory.Delete(cacheFolder, true);
|
||||
}
|
||||
|
||||
infoBarService.Information(SH.ViewModelSettingActionComplete);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("CopyDeviceIdCommand")]
|
||||
private void CopyDeviceId()
|
||||
{
|
||||
|
||||
41
src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeShareContext.cs
Normal file
41
src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeShareContext.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge;
|
||||
|
||||
internal sealed class BridgeShareContext
|
||||
{
|
||||
private readonly CoreWebView2 coreWebView2;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly IClipboardProvider clipboardProvider;
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions;
|
||||
|
||||
public BridgeShareContext(CoreWebView2 coreWebView2, ITaskContext taskContext, HttpClient httpClient, IInfoBarService infoBarService, IClipboardProvider clipboardProvider, JsonSerializerOptions jsonSerializerOptions)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.taskContext = taskContext;
|
||||
this.infoBarService = infoBarService;
|
||||
this.clipboardProvider = clipboardProvider;
|
||||
this.coreWebView2 = coreWebView2;
|
||||
this.jsonSerializerOptions = jsonSerializerOptions;
|
||||
}
|
||||
|
||||
public CoreWebView2 CoreWebView2 { get => coreWebView2; }
|
||||
|
||||
public ITaskContext TaskContext { get => taskContext; }
|
||||
|
||||
public HttpClient HttpClient { get => httpClient; }
|
||||
|
||||
public IInfoBarService InfoBarService { get => infoBarService; }
|
||||
|
||||
public IClipboardProvider ClipboardProvider { get => clipboardProvider; }
|
||||
|
||||
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
|
||||
}
|
||||
108
src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeShareImplmentation.cs
Normal file
108
src/Snap.Hutao/Snap.Hutao/Web/Bridge/BridgeShareImplmentation.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Web.Bridge.Model;
|
||||
using System.IO;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge;
|
||||
|
||||
internal sealed partial class BridgeShareImplmentation
|
||||
{
|
||||
public static async ValueTask<IJsBridgeResult?> ShareAsync(JsParam<SharePayload> param, BridgeShareContext context)
|
||||
{
|
||||
SharePayload payload = param.Payload;
|
||||
switch (payload.Type)
|
||||
{
|
||||
case "image":
|
||||
{
|
||||
ShareContent content = payload.Content;
|
||||
if (content.ImageUrl is { Length: > 0 } imageUrl)
|
||||
{
|
||||
await ShareFromImageUrlAsync(context, imageUrl).ConfigureAwait(false);
|
||||
}
|
||||
else if (content.ImageBase64 is { } imageBase64)
|
||||
{
|
||||
await ShareFromImageBase64Async(context, imageBase64).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "screenshot":
|
||||
{
|
||||
await context.TaskContext.SwitchToMainThreadAsync();
|
||||
|
||||
// https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot
|
||||
string jsonParameters = """{ "format": "png", "captureBeyondViewport": true }""";
|
||||
string resultJson = await context.CoreWebView2.CallDevToolsProtocolMethodAsync("Page.captureScreenshot", jsonParameters);
|
||||
|
||||
CaptureScreenshotResult? result = JsonSerializer.Deserialize<CaptureScreenshotResult>(resultJson, context.JsonSerializerOptions);
|
||||
ArgumentNullException.ThrowIfNull(result);
|
||||
|
||||
await ShareFromRawPixelDataAsync(context, result.Data).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new JsResult<Dictionary<string, string>>()
|
||||
{
|
||||
Data = new()
|
||||
{
|
||||
["type"] = param.Payload.Type,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static async ValueTask ShareFromImageUrlAsync(BridgeShareContext context, string imageUrl)
|
||||
{
|
||||
using (Stream stream = await context.HttpClient.GetStreamAsync(imageUrl).ConfigureAwait(false))
|
||||
{
|
||||
await ShareCoreAsync(context, stream, static (stream, web) => web.CopyToAsync(stream.AsStreamForWrite())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueTask ShareFromImageBase64Async(BridgeShareContext context, string base64ImageData)
|
||||
{
|
||||
return ShareFromRawPixelDataAsync(context, Convert.FromBase64String(base64ImageData));
|
||||
}
|
||||
|
||||
private static ValueTask ShareFromRawPixelDataAsync(BridgeShareContext context, byte[] rawPixelData)
|
||||
{
|
||||
return ShareCoreAsync(context, rawPixelData, static (stream, bytes) => stream.AsStreamForWrite().WriteAsync(bytes).AsTask());
|
||||
}
|
||||
|
||||
private static async ValueTask ShareCoreAsync<TData>(BridgeShareContext context, TData data, Func<InMemoryRandomAccessStream, TData, Task> asyncWriteData)
|
||||
{
|
||||
using (InMemoryRandomAccessStream rawPixelDataStream = new())
|
||||
{
|
||||
await asyncWriteData(rawPixelDataStream, data).ConfigureAwait(false);
|
||||
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(rawPixelDataStream);
|
||||
|
||||
using (InMemoryRandomAccessStream stream = new())
|
||||
{
|
||||
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
|
||||
encoder.SetSoftwareBitmap(await decoder.GetSoftwareBitmapAsync());
|
||||
await encoder.FlushAsync();
|
||||
|
||||
await context.TaskContext.SwitchToMainThreadAsync();
|
||||
if (context.ClipboardProvider.SetBitmap(stream))
|
||||
{
|
||||
context.InfoBarService.Success(SH.WebBridgeShareCopyToClipboardSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.InfoBarService.Error(SH.WebBridgeShareCopyToClipboardFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CaptureScreenshotResult
|
||||
{
|
||||
[JsonPropertyName("data")]
|
||||
public byte[] Data { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Bridge.Model;
|
||||
@@ -12,6 +14,7 @@ using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
using Snap.Hutao.Web.Hoyolab.DataSigning;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Windows.Foundation;
|
||||
|
||||
@@ -24,7 +27,7 @@ namespace Snap.Hutao.Web.Bridge;
|
||||
[SuppressMessage("", "CA1001")]
|
||||
internal class MiHoYoJSBridge
|
||||
{
|
||||
private const string InitializeJsInterfaceScript2 = """
|
||||
private const string InitializeJsInterfaceScript = """
|
||||
window.MiHoYoJSInterface = {
|
||||
postMessage: function(arg) { chrome.webview.postMessage(arg) },
|
||||
closePage: function() { this.postMessage('{"method":"closePage"}') },
|
||||
@@ -37,8 +40,48 @@ internal class MiHoYoJSBridge
|
||||
document.querySelector('body').appendChild(st);
|
||||
""";
|
||||
|
||||
private const string ConvertMouseToTouchScript = """
|
||||
function mouseListener (e, event) {
|
||||
let touch = new Touch({
|
||||
identifier: Date.now(),
|
||||
target: e.target,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
screenX: e.screenX,
|
||||
screenY: e.screenY,
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY,
|
||||
});
|
||||
let touchEvent = new TouchEvent(event, {
|
||||
cancelable: true,
|
||||
bubbles: true,
|
||||
touches: [touch],
|
||||
targetTouches: [touch],
|
||||
changedTouches: [touch],
|
||||
});
|
||||
e.target.dispatchEvent(touchEvent);
|
||||
}
|
||||
|
||||
let mouseMoveListener = (e) => {
|
||||
mouseListener(e, 'touchmove');
|
||||
};
|
||||
|
||||
let mouseUpListener = (e) => {
|
||||
mouseListener(e, 'touchend');
|
||||
document.removeEventListener('mousemove', mouseMoveListener);
|
||||
document.removeEventListener('mouseup', mouseUpListener);
|
||||
};
|
||||
|
||||
let mouseDownListener = (e) => {
|
||||
mouseListener(e, 'touchstart');
|
||||
document.addEventListener('mousemove', mouseMoveListener);
|
||||
document.addEventListener('mouseup', mouseUpListener);
|
||||
};
|
||||
document.addEventListener('mousedown', mouseDownListener);
|
||||
""";
|
||||
|
||||
private readonly SemaphoreSlim webMessageSemaphore = new(1);
|
||||
private readonly Guid interfaceId = Guid.NewGuid();
|
||||
private readonly Guid bridgeId = Guid.NewGuid();
|
||||
private readonly UserAndUid userAndUid;
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
@@ -80,11 +123,6 @@ internal class MiHoYoJSBridge
|
||||
coreWebView2 = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual async ValueTask<IJsBridgeResult?> ClosePageAsync(JsParam param)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
@@ -100,21 +138,11 @@ internal class MiHoYoJSBridge
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整分享设置
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual IJsBridgeResult? ConfigureShare(JsParam param)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取ActionTicket
|
||||
/// </summary>
|
||||
/// <param name="jsParam">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual async ValueTask<IJsBridgeResult?> GetActionTicketAsync(JsParam<ActionTypePayload> jsParam)
|
||||
{
|
||||
return await serviceProvider
|
||||
@@ -123,11 +151,6 @@ internal class MiHoYoJSBridge
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取账户信息
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual JsResult<Dictionary<string, string>> GetCookieInfo(JsParam param)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(userAndUid.User.LToken);
|
||||
@@ -143,11 +166,6 @@ internal class MiHoYoJSBridge
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取CookieToken
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual async ValueTask<JsResult<Dictionary<string, string>>> GetCookieTokenAsync(JsParam<CookieTokenPayload> param)
|
||||
{
|
||||
IUserService userService = serviceProvider.GetRequiredService<IUserService>();
|
||||
@@ -163,11 +181,6 @@ internal class MiHoYoJSBridge
|
||||
return new() { Data = new() { [Cookie.COOKIE_TOKEN] = userAndUid.User.CookieToken[Cookie.COOKIE_TOKEN] } };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前语言和时区
|
||||
/// </summary>
|
||||
/// <param name="param">param</param>
|
||||
/// <returns>语言与时区</returns>
|
||||
protected virtual JsResult<Dictionary<string, string>> GetCurrentLocale(JsParam<PushPagePayload> param)
|
||||
{
|
||||
MetadataOptions metadataOptions = serviceProvider.GetRequiredService<MetadataOptions>();
|
||||
@@ -182,11 +195,6 @@ internal class MiHoYoJSBridge
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取1代动态密钥
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV1(JsParam param)
|
||||
{
|
||||
DataSignOptions options = DataSignOptions.CreateForGeneration1(SaltType.LK2, true);
|
||||
@@ -199,11 +207,6 @@ internal class MiHoYoJSBridge
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取2代动态密钥
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV2(JsParam<DynamicSecrect2Playload> param)
|
||||
{
|
||||
DataSignOptions options = DataSignOptions.CreateForGeneration2(SaltType.X4, false, param.Payload.Body, param.Payload.GetQueryParam());
|
||||
@@ -216,11 +219,6 @@ internal class MiHoYoJSBridge
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Http请求头
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>Http请求头</returns>
|
||||
protected virtual JsResult<Dictionary<string, string>> GetHttpRequestHeader(JsParam param)
|
||||
{
|
||||
Dictionary<string, string> headers = new()
|
||||
@@ -254,21 +252,11 @@ internal class MiHoYoJSBridge
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取状态栏高度
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>结果</returns>
|
||||
protected virtual JsResult<Dictionary<string, object>> GetStatusBarHeight(JsParam param)
|
||||
{
|
||||
return new() { Data = new() { ["statusBarHeight"] = 0 } };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户基本信息
|
||||
/// </summary>
|
||||
/// <param name="param">参数</param>
|
||||
/// <returns>响应</returns>
|
||||
protected virtual async ValueTask<JsResult<Dictionary<string, object>>> GetUserInfoAsync(JsParam param)
|
||||
{
|
||||
Response<UserFullInfoWrapper> response = await serviceProvider
|
||||
@@ -322,15 +310,19 @@ internal class MiHoYoJSBridge
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual IJsBridgeResult? Share(JsParam<SharePayload> param)
|
||||
protected virtual async ValueTask<IJsBridgeResult?> ShareAsync(JsParam<SharePayload> param)
|
||||
{
|
||||
return new JsResult<Dictionary<string, string>>()
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
Data = new()
|
||||
{
|
||||
["type"] = param.Payload.Type,
|
||||
},
|
||||
};
|
||||
JsonSerializerOptions jsonSerializerOptions = scope.ServiceProvider.GetRequiredService<JsonSerializerOptions>();
|
||||
HttpClient httpClient = scope.ServiceProvider.GetRequiredService<HttpClient>();
|
||||
IClipboardProvider clipboardProvider = scope.ServiceProvider.GetRequiredService<IClipboardProvider>();
|
||||
IInfoBarService infoBarService = scope.ServiceProvider.GetRequiredService<IInfoBarService>();
|
||||
|
||||
BridgeShareContext context = new(coreWebView2, taskContext, httpClient, infoBarService, clipboardProvider, jsonSerializerOptions);
|
||||
|
||||
return await BridgeShareImplmentation.ShareAsync(param, context).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual ValueTask<IJsBridgeResult?> ShowAlertDialogAsync(JsParam param)
|
||||
@@ -400,7 +392,7 @@ internal class MiHoYoJSBridge
|
||||
.Append(')')
|
||||
.ToString();
|
||||
|
||||
logger?.LogInformation("[{Id}][ExecuteScript: {callback}]\n{payload}", interfaceId, callback, payload);
|
||||
logger?.LogInformation("[{Id}][ExecuteScript: {callback}]\n{payload}", bridgeId, callback, payload);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
if (coreWebView2 is null || coreWebView2.IsDisposed())
|
||||
@@ -414,7 +406,7 @@ internal class MiHoYoJSBridge
|
||||
private async void OnWebMessageReceived(CoreWebView2 webView2, CoreWebView2WebMessageReceivedEventArgs args)
|
||||
{
|
||||
string message = args.TryGetWebMessageAsString();
|
||||
logger.LogInformation("[{Id}][OnRawMessage]\n{message}", interfaceId, message);
|
||||
logger.LogInformation("[{Id}][OnRawMessage]\n{message}", bridgeId, message);
|
||||
JsParam? param = JsonSerializer.Deserialize<JsParam>(message);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(param);
|
||||
@@ -463,7 +455,7 @@ internal class MiHoYoJSBridge
|
||||
"hideLoading" => null,
|
||||
"login" => null,
|
||||
"pushPage" => await PushPageAsync(param).ConfigureAwait(false),
|
||||
"share" => Share(param),
|
||||
"share" => await ShareAsync(param).ConfigureAwait(false),
|
||||
"showLoading" => null,
|
||||
_ => LogUnhandledMessage("Unhandled Message Type: {Method}", param.Method),
|
||||
};
|
||||
@@ -479,6 +471,7 @@ internal class MiHoYoJSBridge
|
||||
{
|
||||
DOMContentLoaded(coreWebView2);
|
||||
coreWebView2.ExecuteScriptAsync(HideScrollBarScript).AsTask().SafeForget(logger);
|
||||
coreWebView2.ExecuteScriptAsync(ConvertMouseToTouchScript).AsTask().SafeForget(logger);
|
||||
}
|
||||
|
||||
private void OnNavigationStarting(CoreWebView2 coreWebView2, CoreWebView2NavigationStartingEventArgs args)
|
||||
@@ -487,7 +480,7 @@ internal class MiHoYoJSBridge
|
||||
ReadOnlySpan<char> uriHostSpan = uriHost.AsSpan();
|
||||
if (uriHostSpan.EndsWith("mihoyo.com") || uriHostSpan.EndsWith("hoyolab.com"))
|
||||
{
|
||||
coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript2).AsTask().SafeForget(logger);
|
||||
coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript).AsTask().SafeForget(logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,15 @@
|
||||
|
||||
namespace Snap.Hutao.Web.Bridge.Model;
|
||||
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal sealed class ShareContent
|
||||
{
|
||||
[JsonPropertyName("preview")]
|
||||
public bool Preview { get; set; }
|
||||
}
|
||||
|
||||
[JsonPropertyName("image_url")]
|
||||
public string? ImageUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("image_base64")]
|
||||
public string? ImageBase64 { get; set; }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
||||
internal sealed class ArchonQuest
|
||||
{
|
||||
[JsonPropertyName("status")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public ArchonQuestStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,6 +3,4 @@
|
||||
|
||||
namespace Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
|
||||
internal interface IBuilder
|
||||
{
|
||||
}
|
||||
internal interface IBuilder;
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Snap.Hutao.Web.Request.Builder;
|
||||
|
||||
[Serializable]
|
||||
@@ -11,6 +13,31 @@ internal sealed class HttpContentSerializationException : Exception
|
||||
{
|
||||
}
|
||||
|
||||
private HttpContentSerializationException(Exception? innerException)
|
||||
: base(GetDefaultMessage(), innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public static async ValueTask<HttpContentSerializationException> CreateAsync(HttpContent? content, Exception? innerException)
|
||||
{
|
||||
if (content is null)
|
||||
{
|
||||
return new(innerException);
|
||||
}
|
||||
|
||||
string contentString = await content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
string message = $"""
|
||||
The (de-)serialization failed because of an arbitrary error. This most likely happened,
|
||||
because an inner serializer failed to (de-)serialize the given data.
|
||||
----- data begin -----
|
||||
{contentString}
|
||||
----- data end -----
|
||||
See the inner exception for details (if available).
|
||||
""";
|
||||
|
||||
return new(message, innerException);
|
||||
}
|
||||
|
||||
private static string GetDefaultMessage()
|
||||
{
|
||||
return """
|
||||
|
||||
@@ -49,7 +49,7 @@ internal abstract class HttpContentSerializer : IHttpContentSerializer, IHttpCon
|
||||
}
|
||||
catch (Exception ex) when (ex is not HttpContentSerializationException)
|
||||
{
|
||||
throw new HttpContentSerializationException(null, ex);
|
||||
throw await HttpContentSerializationException.CreateAsync(httpContent, ex).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Request.Builder;
|
||||
|
||||
internal enum HttpTryCatchSendStrategy
|
||||
{
|
||||
Default,
|
||||
HutaoApi,
|
||||
}
|
||||
Reference in New Issue
Block a user