mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
|
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
|
||||||
required: true
|
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
|
required: true
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
@@ -51,7 +51,7 @@ body:
|
|||||||
description: |
|
description: |
|
||||||
在胡桃工具箱的设置界面,你可以找到并复制你的设备 ID
|
在胡桃工具箱的设置界面,你可以找到并复制你的设备 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:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ body:
|
|||||||
label: 发生了什么?
|
label: 发生了什么?
|
||||||
description: |
|
description: |
|
||||||
详细的描述问题发生前后的行为,以便我们解决问题。**如果你的问题涉及程序崩溃,你应当检查 Windows 事件查看器,并将相关的 `.Net 错误`详情附上**
|
详细的描述问题发生前后的行为,以便我们解决问题。**如果你的问题涉及程序崩溃,你应当检查 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:
|
validations:
|
||||||
required: true
|
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
|
- label: I and tried **search feature** in Snap Hutao document site, and no associated article
|
||||||
required: true
|
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
|
required: true
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
@@ -51,7 +51,7 @@ body:
|
|||||||
description: |
|
description: |
|
||||||
In Snap Hutao's settings page, you can find and copy your device ID
|
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 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:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ body:
|
|||||||
label: What Happened?
|
label: What Happened?
|
||||||
description: |
|
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.
|
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:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
40
.github/ISSUE_TEMPLATE/MGMT-publish.yml
vendored
40
.github/ISSUE_TEMPLATE/MGMT-publish.yml
vendored
@@ -12,50 +12,32 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
|
|
||||||
## 创建版本
|
## 创建版本
|
||||||
|
|
||||||
- [ ] 同步一次 [Crowdin](https://crowdin.com/project/snap-hutao) 翻译
|
- [ ] 同步一次 [Crowdin](https://crowdin.com/project/snap-hutao) 翻译
|
||||||
- [ ] 发布 RC 版本(Optional)
|
- [ ] 发布 RC 版本(Optional)
|
||||||
- [ ] 合并入主分支
|
- [ ] 合并入主分支
|
||||||
- [ ] 整理更新内容,等待翻译
|
- [ ] 整理更新内容,等待翻译
|
||||||
- [ ] 打包
|
|
||||||
- [ ] 提交微软商店
|
|
||||||
- [ ] 包含更新日志
|
|
||||||
- [ ] 在 [Snap.Hutao.Docs@next-patch](https://github.com/DGP-Studio/Snap.Hutao.Docs/tree/next-patch) 分支更新文档并直接开 PR
|
- [ ] 在 [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),有效期需小于预计发版需要天数
|
- [ ] 在 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) 中
|
- [ ] 将更新的 PAT 更新至 Publish-Automate 库的 [Actions Secrets](https://github.com/DGP-Automation/Publish-Automate/settings/secrets/actions) 中
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
- [ ] 运行 [Auto Publish Action](https://github.com/DGP-Studio/Publish-Automate/actions/workflows/auto-publish.yml)
|
- [ ] 主分支合并入 release 分支
|
||||||
- [ ] 在 https://store.rg-adguard.net/ 下载新版本安装包
|
- [ ] 等待 Release 自动发布
|
||||||
- [ ] Store URL: https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52
|
- [ ] 检查极狐是否同步完成 Release
|
||||||
- [ ] 命名格式为 `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
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] 通知用户
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: checklist-final
|
id: checklist-final
|
||||||
attributes:
|
attributes:
|
||||||
label: Final Check
|
label: Final Check
|
||||||
description: Understand what you are doing
|
description: Understand what you are doing
|
||||||
options:
|
options:
|
||||||
- label: I understand that I will get banned from repository if I don't have permission to use this template
|
- label: I understand that I will get banned from repository if I don't have permission to use this template
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
62
.gitlab-ci.yml
Normal file
62
.gitlab-ci.yml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
stages:
|
||||||
|
- fetch
|
||||||
|
- release
|
||||||
|
|
||||||
|
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
|
||||||
@@ -61,7 +61,6 @@ FileSaveDialog
|
|||||||
IFileOpenDialog
|
IFileOpenDialog
|
||||||
IFileSaveDialog
|
IFileSaveDialog
|
||||||
IPersistFile
|
IPersistFile
|
||||||
IShellLinkDataList
|
|
||||||
IShellLinkW
|
IShellLinkW
|
||||||
ShellLink
|
ShellLink
|
||||||
SHELL_LINK_DATA_FLAGS
|
SHELL_LINK_DATA_FLAGS
|
||||||
@@ -70,7 +69,6 @@ SHELL_LINK_DATA_FLAGS
|
|||||||
IMemoryBufferByteAccess
|
IMemoryBufferByteAccess
|
||||||
|
|
||||||
// Const value
|
// Const value
|
||||||
E_FAIL
|
|
||||||
INFINITE
|
INFINITE
|
||||||
RPC_E_WRONG_THREAD
|
RPC_E_WRONG_THREAD
|
||||||
MAX_PATH
|
MAX_PATH
|
||||||
|
|||||||
@@ -18,10 +18,4 @@
|
|||||||
TargetType="FlyoutPresenter">
|
TargetType="FlyoutPresenter">
|
||||||
<Setter Property="Padding" Value="6"/>
|
<Setter Property="Padding" Value="6"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style
|
|
||||||
x:Key="FlyoutPresenterPadding16And10Style"
|
|
||||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
|
|
||||||
TargetType="FlyoutPresenter">
|
|
||||||
<Setter Property="Padding" Value="16,10"/>
|
|
||||||
</Style>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -31,5 +31,4 @@
|
|||||||
<x:String x:Key="UI_EmotionIcon250">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon250.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_EmotionIcon272">https://api.snapgenshin.com/static/raw/EmotionIcon/UI_EmotionIcon272.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_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>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ internal sealed class CommandLineBuilder
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当符合条件时添加参数
|
/// 当符合条件时添加参数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="condition">条件</param>
|
|
||||||
/// <param name="name">参数名称</param>
|
/// <param name="name">参数名称</param>
|
||||||
|
/// <param name="condition">条件</param>
|
||||||
/// <param name="value">值</param>
|
/// <param name="value">值</param>
|
||||||
/// <returns>命令行建造器</returns>
|
/// <returns>命令行建造器</returns>
|
||||||
public CommandLineBuilder AppendIf(bool condition, string name, object? value = null)
|
public CommandLineBuilder AppendIf(string name, bool condition, object? value = null)
|
||||||
{
|
{
|
||||||
return condition ? Append(name, value) : this;
|
return condition ? Append(name, value) : this;
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ internal sealed class CommandLineBuilder
|
|||||||
/// <returns>命令行建造器</returns>
|
/// <returns>命令行建造器</returns>
|
||||||
public CommandLineBuilder AppendIfNotNull(string name, object? value = null)
|
public CommandLineBuilder AppendIfNotNull(string name, object? value = null)
|
||||||
{
|
{
|
||||||
return AppendIf(value is not null, name, value);
|
return AppendIf(name, value is not null, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 指示此特性附加的属性会在属性改变后会异步地设置的其他属性
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
|
|
||||||
internal sealed class AlsoAsyncSetsAttribute : Attribute
|
|
||||||
{
|
|
||||||
public AlsoAsyncSetsAttribute(string propertyName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlsoAsyncSetsAttribute(string propertyName1, string propertyName2)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlsoAsyncSetsAttribute(params string[] propertyNames)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 指示此特性附加的属性会在属性改变后会设置的其他属性
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
|
|
||||||
internal sealed class AlsoSetsAttribute : Attribute
|
|
||||||
{
|
|
||||||
public AlsoSetsAttribute(string propertyName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlsoSetsAttribute(string propertyName1, string propertyName2)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlsoSetsAttribute(params string[] propertyNames)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.ExceptionService;
|
|
||||||
|
|
||||||
internal sealed class HutaoException : Exception
|
|
||||||
{
|
|
||||||
public HutaoException(HutaoExceptionKind kind, string message, Exception? innerException)
|
|
||||||
: this(message, innerException)
|
|
||||||
{
|
|
||||||
Kind = kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HutaoException(string message, Exception? innerException)
|
|
||||||
: base($"{message}\n{innerException?.Message}", innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public HutaoExceptionKind Kind { get; private set; }
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.ExceptionService;
|
|
||||||
|
|
||||||
internal enum HutaoExceptionKind
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
@@ -49,13 +49,6 @@ internal static class ThrowHelper
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
[DoesNotReturn]
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public static NotSupportedException NotSupported(string message)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[DoesNotReturn]
|
[DoesNotReturn]
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)
|
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core;
|
|
||||||
|
|
||||||
internal static class RuntimeOptionsExtension
|
|
||||||
{
|
|
||||||
public static string GetDataFolderUpdateCacheFolderFile(this RuntimeOptions options, string fileName)
|
|
||||||
{
|
|
||||||
string directory = Path.Combine(options.DataFolder, "UpdateCache");
|
|
||||||
Directory.CreateDirectory(directory);
|
|
||||||
return Path.Combine(directory, fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,30 +7,30 @@ namespace Snap.Hutao.Core.Setting;
|
|||||||
/// 设置键
|
/// 设置键
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[SuppressMessage("", "SA1124")]
|
|
||||||
internal static class SettingKeys
|
internal static class SettingKeys
|
||||||
{
|
{
|
||||||
#region MainWindow
|
|
||||||
public const string WindowRect = "WindowRect";
|
public const string WindowRect = "WindowRect";
|
||||||
|
|
||||||
public const string IsNavPaneOpen = "IsNavPaneOpen";
|
public const string IsNavPaneOpen = "IsNavPaneOpen";
|
||||||
public const string IsInfoBarToggleChecked = "IsInfoBarToggleChecked";
|
|
||||||
public const string ExcludedAnnouncementIds = "ExcludedAnnouncementIds";
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Application
|
|
||||||
public const string LaunchTimes = "LaunchTimes";
|
public const string LaunchTimes = "LaunchTimes";
|
||||||
|
|
||||||
public const string DataFolderPath = "DataFolderPath";
|
public const string DataFolderPath = "DataFolderPath";
|
||||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
|
||||||
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
|
|
||||||
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled";
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Passport
|
|
||||||
public const string PassportUserName = "PassportUserName";
|
public const string PassportUserName = "PassportUserName";
|
||||||
public const string PassportPassword = "PassportPassword";
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Cultivation
|
public const string PassportPassword = "PassportPassword";
|
||||||
|
|
||||||
|
public const string IsInfoBarToggleChecked = "IsInfoBarToggleChecked";
|
||||||
|
|
||||||
|
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||||
|
|
||||||
|
public const string ExcludedAnnouncementIds = "ExcludedAnnouncementIds";
|
||||||
|
|
||||||
|
public const string SuppressMetadataInitialization = "SuppressMetadataInitialization";
|
||||||
|
|
||||||
|
public const string OverrideElevationRequirement = "OverrideElevationRequirement";
|
||||||
|
|
||||||
public const string CultivationAvatarLevelCurrent = "CultivationAvatarLevelCurrent";
|
public const string CultivationAvatarLevelCurrent = "CultivationAvatarLevelCurrent";
|
||||||
public const string CultivationAvatarLevelTarget = "CultivationAvatarLevelTarget";
|
public const string CultivationAvatarLevelTarget = "CultivationAvatarLevelTarget";
|
||||||
public const string CultivationAvatarSkillACurrent = "CultivationAvatarSkillACurrent";
|
public const string CultivationAvatarSkillACurrent = "CultivationAvatarSkillACurrent";
|
||||||
@@ -43,18 +43,13 @@ internal static class SettingKeys
|
|||||||
public const string CultivationWeapon90LevelTarget = "CultivationWeapon90LevelTarget";
|
public const string CultivationWeapon90LevelTarget = "CultivationWeapon90LevelTarget";
|
||||||
public const string CultivationWeapon70LevelCurrent = "CultivationWeapon70LevelCurrent";
|
public const string CultivationWeapon70LevelCurrent = "CultivationWeapon70LevelCurrent";
|
||||||
public const string CultivationWeapon70LevelTarget = "CultivationWeapon70LevelTarget";
|
public const string CultivationWeapon70LevelTarget = "CultivationWeapon70LevelTarget";
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region HomeCard Dashboard
|
|
||||||
public const string IsHomeCardLaunchGamePresented = "IsHomeCardLaunchGamePresented";
|
public const string IsHomeCardLaunchGamePresented = "IsHomeCardLaunchGamePresented";
|
||||||
public const string IsHomeCardGachaStatisticsPresented = "IsHomeCardGachaStatisticsPresented";
|
public const string IsHomeCardGachaStatisticsPresented = "IsHomeCardGachaStatisticsPresented";
|
||||||
public const string IsHomeCardAchievementPresented = "IsHomeCardAchievementPresented";
|
public const string IsHomeCardAchievementPresented = "IsHomeCardAchievementPresented";
|
||||||
public const string IsHomeCardDailyNotePresented = "IsHomeCardDailyNotePresented";
|
public const string IsHomeCardDailyNotePresented = "IsHomeCardDailyNotePresented";
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region DevTool
|
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
|
||||||
public const string SuppressMetadataInitialization = "SuppressMetadataInitialization";
|
|
||||||
public const string OverrideElevationRequirement = "OverrideElevationRequirement";
|
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled";
|
||||||
public const string OverrideUpdateVersionComparison = "OverrideUpdateVersionComparison";
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Service;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
@@ -18,16 +19,23 @@ namespace Snap.Hutao.Core.Shell;
|
|||||||
internal sealed partial class ShellLinkInterop : IShellLinkInterop
|
internal sealed partial class ShellLinkInterop : IShellLinkInterop
|
||||||
{
|
{
|
||||||
private readonly RuntimeOptions runtimeOptions;
|
private readonly RuntimeOptions runtimeOptions;
|
||||||
|
private readonly AppOptions appOptions;
|
||||||
|
|
||||||
public async ValueTask<bool> TryCreateDesktopShoutcutForElevatedLaunchAsync()
|
public async ValueTask<bool> TryCreateDesktopShoutcutForElevatedLaunchAsync()
|
||||||
{
|
{
|
||||||
|
Uri sourceLogoUri = "ms-appx:///Assets/Logo.ico".ToUri();
|
||||||
string targetLogoPath = Path.Combine(runtimeOptions.DataFolder, "ShellLinkLogo.ico");
|
string targetLogoPath = Path.Combine(runtimeOptions.DataFolder, "ShellLinkLogo.ico");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Uri sourceLogoUri = "ms-appx:///Assets/Logo.ico".ToUri();
|
|
||||||
StorageFile iconFile = await StorageFile.GetFileFromApplicationUriAsync(sourceLogoUri);
|
StorageFile iconFile = await StorageFile.GetFileFromApplicationUriAsync(sourceLogoUri);
|
||||||
await iconFile.OverwriteCopyAsync(targetLogoPath).ConfigureAwait(false);
|
using (Stream inputStream = (await iconFile.OpenReadAsync()).AsStream())
|
||||||
|
{
|
||||||
|
using (FileStream outputStream = File.Create(targetLogoPath))
|
||||||
|
{
|
||||||
|
await inputStream.CopyToAsync(outputStream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -37,15 +45,12 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
|
|||||||
HRESULT result = CoCreateInstance<ShellLink, IShellLinkW>(null, CLSCTX.CLSCTX_INPROC_SERVER, out IShellLinkW shellLink);
|
HRESULT result = CoCreateInstance<ShellLink, IShellLinkW>(null, CLSCTX.CLSCTX_INPROC_SERVER, out IShellLinkW shellLink);
|
||||||
Marshal.ThrowExceptionForHR(result);
|
Marshal.ThrowExceptionForHR(result);
|
||||||
|
|
||||||
shellLink.SetPath($"shell:AppsFolder\\{runtimeOptions.FamilyName}!App");
|
shellLink.SetPath(appOptions.PowerShellPath);
|
||||||
shellLink.SetShowCmd(SHOW_WINDOW_CMD.SW_NORMAL);
|
shellLink.SetArguments($"""
|
||||||
|
-Command "Start-Process shell:AppsFolder\{runtimeOptions.FamilyName}!App -verb runas"
|
||||||
|
""");
|
||||||
|
shellLink.SetShowCmd(SHOW_WINDOW_CMD.SW_SHOWMINNOACTIVE);
|
||||||
shellLink.SetIconLocation(targetLogoPath, 0);
|
shellLink.SetIconLocation(targetLogoPath, 0);
|
||||||
|
|
||||||
IShellLinkDataList shellLinkDataList = (IShellLinkDataList)shellLink;
|
|
||||||
shellLinkDataList.GetFlags(out uint flags);
|
|
||||||
flags |= (uint)SHELL_LINK_DATA_FLAGS.SLDF_RUNAS_USER;
|
|
||||||
shellLinkDataList.SetFlags(flags);
|
|
||||||
|
|
||||||
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||||
string target = Path.Combine(desktop, $"{SH.FormatAppNameAndVersion(runtimeOptions.Version)}.lnk");
|
string target = Path.Combine(desktop, $"{SH.FormatAppNameAndVersion(runtimeOptions.Version)}.lnk");
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,45 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.UI.Dispatching;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Threading;
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
internal class DispatcherQueueProgress<T> : IProgress<T>
|
internal class DispatcherQueueProgress<T> : IProgress<T>
|
||||||
{
|
{
|
||||||
private readonly DispatcherQueue dispatcherQueue;
|
private readonly SynchronizationContext synchronizationContext;
|
||||||
private readonly Action<T> handler;
|
private readonly Action<T>? handler;
|
||||||
|
private readonly SendOrPostCallback invokeHandlers;
|
||||||
|
|
||||||
public DispatcherQueueProgress(Action<T> handler, DispatcherQueue dispatcherQueue)
|
public DispatcherQueueProgress(Action<T> handler, SynchronizationContext synchronizationContext)
|
||||||
{
|
{
|
||||||
this.dispatcherQueue = dispatcherQueue;
|
this.synchronizationContext = synchronizationContext;
|
||||||
|
invokeHandlers = new SendOrPostCallback(InvokeHandlers);
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(handler);
|
||||||
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event EventHandler<T>? ProgressChanged;
|
||||||
|
|
||||||
public void Report(T value)
|
public void Report(T value)
|
||||||
{
|
{
|
||||||
Action<T> handler = this.handler;
|
Action<T>? handler = this.handler;
|
||||||
dispatcherQueue.TryEnqueue(() => handler(value));
|
EventHandler<T>? changedEvent = ProgressChanged;
|
||||||
|
if (handler is not null || changedEvent is not null)
|
||||||
|
{
|
||||||
|
synchronizationContext.Post(invokeHandlers, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("", "SH007")]
|
||||||
|
private void InvokeHandlers(object? state)
|
||||||
|
{
|
||||||
|
T value = (T)state!;
|
||||||
|
|
||||||
|
Action<T>? handler = this.handler;
|
||||||
|
EventHandler<T>? changedEvent = ProgressChanged;
|
||||||
|
|
||||||
|
handler?.Invoke(value);
|
||||||
|
changedEvent?.Invoke(this, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@ namespace Snap.Hutao.Core.Threading;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface ITaskContext
|
internal interface ITaskContext
|
||||||
{
|
{
|
||||||
|
SynchronizationContext SynchronizationContext { get; }
|
||||||
|
|
||||||
void BeginInvokeOnMainThread(Action action);
|
void BeginInvokeOnMainThread(Action action);
|
||||||
|
|
||||||
void InvokeOnMainThread(Action action);
|
void InvokeOnMainThread(Action action);
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Dispatching;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core.Threading;
|
|
||||||
|
|
||||||
internal interface ITaskContextUnsafe
|
|
||||||
{
|
|
||||||
DispatcherQueue DispatcherQueue { get; }
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Threading;
|
|||||||
/// 任务上下文
|
/// 任务上下文
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injection(InjectAs.Singleton, typeof(ITaskContext))]
|
[Injection(InjectAs.Singleton, typeof(ITaskContext))]
|
||||||
internal sealed class TaskContext : ITaskContext, ITaskContextUnsafe
|
internal sealed class TaskContext : ITaskContext
|
||||||
{
|
{
|
||||||
private readonly DispatcherQueueSynchronizationContext synchronizationContext;
|
private readonly DispatcherQueueSynchronizationContext synchronizationContext;
|
||||||
private readonly DispatcherQueue dispatcherQueue;
|
private readonly DispatcherQueue dispatcherQueue;
|
||||||
@@ -24,7 +24,7 @@ internal sealed class TaskContext : ITaskContext, ITaskContextUnsafe
|
|||||||
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
|
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DispatcherQueue DispatcherQueue { get => dispatcherQueue; }
|
public SynchronizationContext SynchronizationContext { get => synchronizationContext; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ThreadPoolSwitchOperation SwitchToBackgroundAsync()
|
public ThreadPoolSwitchOperation SwitchToBackgroundAsync()
|
||||||
|
|||||||
19
src/Snap.Hutao/Snap.Hutao/Core/Threading/Throttler.cs
Normal file
19
src/Snap.Hutao/Snap.Hutao/Core/Threading/Throttler.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
|
internal sealed class Throttler
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, SemaphoreSlim> methodSemaphoreMap = new();
|
||||||
|
|
||||||
|
public ValueTask<SemaphoreSlimToken> ThrottleAsync(CancellationToken token = default, [CallerMemberName] string callerName = default!, [CallerLineNumber] int callerLine = 0)
|
||||||
|
{
|
||||||
|
string key = $"{callerName}L{callerLine}";
|
||||||
|
SemaphoreSlim semaphore = methodSemaphoreMap.GetOrAdd(key, name => new SemaphoreSlim(1));
|
||||||
|
return semaphore.EnterAsync(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Extension;
|
|
||||||
|
|
||||||
internal static class StorageFileExtension
|
|
||||||
{
|
|
||||||
public static async ValueTask OverwriteCopyAsync(this StorageFile file, string targetFile)
|
|
||||||
{
|
|
||||||
using (Stream outputStream = (await file.OpenReadAsync()).AsStreamForRead())
|
|
||||||
{
|
|
||||||
using (FileStream inputStream = File.Create(targetFile))
|
|
||||||
{
|
|
||||||
await outputStream.CopyToAsync(inputStream).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Factory.Progress;
|
namespace Snap.Hutao.Factory.Progress;
|
||||||
|
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
@@ -13,11 +11,6 @@ internal sealed partial class ProgressFactory : IProgressFactory
|
|||||||
|
|
||||||
public IProgress<T> CreateForMainThread<T>(Action<T> handler)
|
public IProgress<T> CreateForMainThread<T>(Action<T> handler)
|
||||||
{
|
{
|
||||||
if (taskContext is not ITaskContextUnsafe @unsafe)
|
return new DispatcherQueueProgress<T>(handler, taskContext.SynchronizationContext);
|
||||||
{
|
|
||||||
throw ThrowHelper.NotSupported();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DispatcherQueueProgress<T>(handler, @unsafe.DispatcherQueue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
ItemsSource="{Binding GameAccountsView}"
|
ItemsSource="{Binding GameAccounts}"
|
||||||
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}">
|
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}">
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
|
using Snap.Hutao.Control;
|
||||||
using Snap.Hutao.Core.Windowing;
|
using Snap.Hutao.Core.Windowing;
|
||||||
|
using Windows.Foundation;
|
||||||
using Windows.Win32.UI.WindowsAndMessaging;
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
namespace Snap.Hutao;
|
namespace Snap.Hutao;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Entity;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[Table("game_accounts")]
|
[Table("game_accounts")]
|
||||||
internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount, string, string, SchemeType>
|
internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount, string, string>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 内部Id
|
/// 内部Id
|
||||||
@@ -40,17 +40,21 @@ internal sealed class GameAccount : ObservableObject, IMappingFrom<GameAccount,
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// [MIHOYOSDK_ADL_PROD_CN_h3123967166]
|
/// [MIHOYOSDK_ADL_PROD_CN_h3123967166]
|
||||||
/// [MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810]
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string MihoyoSDK { get; set; } = default!;
|
public string MihoyoSDK { get; set; } = default!;
|
||||||
|
|
||||||
public static GameAccount From(string name, string sdk, SchemeType type)
|
/// <summary>
|
||||||
|
/// 构造一个新的游戏内账号
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">名称</param>
|
||||||
|
/// <param name="sdk">sdk</param>
|
||||||
|
/// <returns>游戏内账号</returns>
|
||||||
|
public static GameAccount From(string name, string sdk)
|
||||||
{
|
{
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
MihoyoSDK = sdk,
|
MihoyoSDK = sdk,
|
||||||
Type = type,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ namespace Snap.Hutao.Model.Entity.Primitive;
|
|||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal enum SchemeType
|
internal enum SchemeType
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 国服官服
|
|
||||||
/// </summary>
|
|
||||||
ChineseOfficial,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 国际服
|
/// 国际服
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Oversea,
|
Hoyoverse,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 国服官服
|
||||||
|
/// </summary>
|
||||||
|
Official,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 渠道服
|
/// 渠道服
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ChineseBilibili,
|
Bilibili,
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,9 @@ internal sealed partial class SettingEntry
|
|||||||
|
|
||||||
public const string GamePathEntries = "GamePathEntries";
|
public const string GamePathEntries = "GamePathEntries";
|
||||||
|
|
||||||
[Obsolete("不再使用 PowerShell")]
|
/// <summary>
|
||||||
|
/// PowerShell 路径
|
||||||
|
/// </summary>
|
||||||
public const string PowerShellPath = "PowerShellPath";
|
public const string PowerShellPath = "PowerShellPath";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -93,10 +93,6 @@ internal static class AvatarIds
|
|||||||
public static readonly AvatarId Neuvillette = 10000087;
|
public static readonly AvatarId Neuvillette = 10000087;
|
||||||
public static readonly AvatarId Charlotte = 10000088;
|
public static readonly AvatarId Charlotte = 10000088;
|
||||||
public static readonly AvatarId Furina = 10000089;
|
public static readonly AvatarId Furina = 10000089;
|
||||||
public static readonly AvatarId Chevreuse = 10000090;
|
|
||||||
public static readonly AvatarId Navia = 10000091;
|
|
||||||
public static readonly AvatarId Gaming = 10000092;
|
|
||||||
public static readonly AvatarId Xianyun = 10000093;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查该角色是否为主角
|
/// 检查该角色是否为主角
|
||||||
|
|||||||
@@ -4,22 +4,20 @@
|
|||||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
|
||||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
IgnorableNamespaces="com uap desktop desktop6 rescap mp">
|
IgnorableNamespaces="com uap desktop rescap mp">
|
||||||
|
|
||||||
<Identity
|
<Identity
|
||||||
Name="60568DGPStudio.SnapHutao"
|
Name="60568DGPStudio.SnapHutao"
|
||||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||||
Version="1.9.1.0" />
|
Version="1.9.0.0" />
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Snap Hutao</DisplayName>
|
<DisplayName>Snap Hutao</DisplayName>
|
||||||
<PublisherDisplayName>DGP Studio</PublisherDisplayName>
|
<PublisherDisplayName>DGP Studio</PublisherDisplayName>
|
||||||
<Logo>Assets\StoreLogo.png</Logo>
|
<Logo>Assets\StoreLogo.png</Logo>
|
||||||
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
|
|
||||||
</Properties>
|
</Properties>
|
||||||
|
|
||||||
<Dependencies>
|
<Dependencies>
|
||||||
@@ -66,6 +64,5 @@
|
|||||||
|
|
||||||
<Capabilities>
|
<Capabilities>
|
||||||
<rescap:Capability Name="runFullTrust"/>
|
<rescap:Capability Name="runFullTrust"/>
|
||||||
<rescap:Capability Name="unvirtualizedResources"/>
|
|
||||||
</Capabilities>
|
</Capabilities>
|
||||||
</Package>
|
</Package>
|
||||||
|
|||||||
@@ -4,22 +4,20 @@
|
|||||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
|
||||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
IgnorableNamespaces="com uap desktop desktop6 rescap mp">
|
IgnorableNamespaces="com uap desktop rescap mp">
|
||||||
|
|
||||||
<Identity
|
<Identity
|
||||||
Name="60568DGPStudio.SnapHutaoDev"
|
Name="60568DGPStudio.SnapHutaoDev"
|
||||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||||
Version="1.9.1.0" />
|
Version="1.9.0.0" />
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Snap Hutao Dev</DisplayName>
|
<DisplayName>Snap Hutao Dev</DisplayName>
|
||||||
<PublisherDisplayName>DGP Studio</PublisherDisplayName>
|
<PublisherDisplayName>DGP Studio</PublisherDisplayName>
|
||||||
<Logo>Assets\StoreLogo.png</Logo>
|
<Logo>Assets\StoreLogo.png</Logo>
|
||||||
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
|
|
||||||
</Properties>
|
</Properties>
|
||||||
|
|
||||||
<Dependencies>
|
<Dependencies>
|
||||||
@@ -66,6 +64,5 @@
|
|||||||
|
|
||||||
<Capabilities>
|
<Capabilities>
|
||||||
<rescap:Capability Name="runFullTrust"/>
|
<rescap:Capability Name="runFullTrust"/>
|
||||||
<rescap:Capability Name="unvirtualizedResources"/>
|
|
||||||
</Capabilities>
|
</Capabilities>
|
||||||
</Package>
|
</Package>
|
||||||
|
|||||||
@@ -60,45 +60,45 @@
|
|||||||
: and then encoded with base64 encoding.
|
: 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: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:element name="root" msdata:IsDataSet="true">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="metadata">
|
<xsd:element name="metadata">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
<xsd:attribute name="type" type="xsd:string"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="assembly">
|
<xsd:element name="assembly">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
<xsd:attribute name="alias" type="xsd:string"/>
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
<xsd:attribute name="name" type="xsd:string"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="data">
|
<xsd:element name="data">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
<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="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="resheader">
|
<xsd:element name="resheader">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
@@ -186,6 +186,9 @@
|
|||||||
<data name="FilePickerImportCommit" xml:space="preserve">
|
<data name="FilePickerImportCommit" xml:space="preserve">
|
||||||
<value>Import</value>
|
<value>Import</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FilePickerPowerShellCommit" xml:space="preserve">
|
||||||
|
<value>Select PowerShell</value>
|
||||||
|
</data>
|
||||||
<data name="GuideWindowTitle" xml:space="preserve">
|
<data name="GuideWindowTitle" xml:space="preserve">
|
||||||
<value>Welcome to Snap Hutao, Traveler ~</value>
|
<value>Welcome to Snap Hutao, Traveler ~</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -929,6 +932,9 @@
|
|||||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||||
<value>Unable to set registry key without enabling long path</value>
|
<value>Unable to set registry key without enabling long path</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ServiceGameRegisteryInteropPowershellNotFound" xml:space="preserve">
|
||||||
|
<value>PowerShell installation directory not found</value>
|
||||||
|
</data>
|
||||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||||
<value>Unable to read game config file {0}, file may be not exist</value>
|
<value>Unable to read game config file {0}, file may be not exist</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2426,6 +2432,12 @@
|
|||||||
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
||||||
<value>When setting the game path, please select the game program (Yuanshen.exe or GenshinImpact.exe) instead of the game launcher (launcher.exe)</value>
|
<value>When setting the game path, please select the game program (Yuanshen.exe or GenshinImpact.exe) instead of the game launcher (launcher.exe)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellDescription" xml:space="preserve">
|
||||||
|
<value>Snap Hutao uses PowerShell to modify information in registry to change game accounts</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve">
|
||||||
|
<value>PowerShell Path</value>
|
||||||
|
</data>
|
||||||
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
||||||
<value>Shell Experience</value>
|
<value>Shell Experience</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -60,45 +60,45 @@
|
|||||||
: and then encoded with base64 encoding.
|
: 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: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:element name="root" msdata:IsDataSet="true">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="metadata">
|
<xsd:element name="metadata">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
<xsd:attribute name="type" type="xsd:string"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="assembly">
|
<xsd:element name="assembly">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
<xsd:attribute name="alias" type="xsd:string"/>
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
<xsd:attribute name="name" type="xsd:string"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="data">
|
<xsd:element name="data">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
<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="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="resheader">
|
<xsd:element name="resheader">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
@@ -186,6 +186,9 @@
|
|||||||
<data name="FilePickerImportCommit" xml:space="preserve">
|
<data name="FilePickerImportCommit" xml:space="preserve">
|
||||||
<value>Impor</value>
|
<value>Impor</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FilePickerPowerShellCommit" xml:space="preserve">
|
||||||
|
<value>Pilih PowerShell</value>
|
||||||
|
</data>
|
||||||
<data name="GuideWindowTitle" xml:space="preserve">
|
<data name="GuideWindowTitle" xml:space="preserve">
|
||||||
<value>Selamat Datang di Snap Hutao, Traveler ~</value>
|
<value>Selamat Datang di Snap Hutao, Traveler ~</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -929,6 +932,9 @@
|
|||||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||||
<value>Tidak dapat mengatur kunci registri tanpa mengaktifkan path panjang</value>
|
<value>Tidak dapat mengatur kunci registri tanpa mengaktifkan path panjang</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ServiceGameRegisteryInteropPowershellNotFound" xml:space="preserve">
|
||||||
|
<value>Direktori instalasi PowerShell tidak ditemukan</value>
|
||||||
|
</data>
|
||||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||||
<value>Tidak dapat membaca file konfigurasi game {0}, file mungkin tidak ada</value>
|
<value>Tidak dapat membaca file konfigurasi game {0}, file mungkin tidak ada</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2426,6 +2432,12 @@
|
|||||||
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
||||||
<value>Saat mengatur jalur permainan, pilih program permainan (Yuanshen.exe atau GenshinImpact.exe) bukan peluncur permainan (launcher.exe)</value>
|
<value>Saat mengatur jalur permainan, pilih program permainan (Yuanshen.exe atau GenshinImpact.exe) bukan peluncur permainan (launcher.exe)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellDescription" xml:space="preserve">
|
||||||
|
<value>Snap Hutao menggunakan PowerShell untuk memodifikasi informasi di registri untuk mengubah akun Game</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve">
|
||||||
|
<value>Path PowerShell</value>
|
||||||
|
</data>
|
||||||
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
||||||
<value>Pengalaman Shell</value>
|
<value>Pengalaman Shell</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -60,45 +60,45 @@
|
|||||||
: and then encoded with base64 encoding.
|
: 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: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:element name="root" msdata:IsDataSet="true">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="metadata">
|
<xsd:element name="metadata">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
<xsd:attribute name="type" type="xsd:string"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="assembly">
|
<xsd:element name="assembly">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
<xsd:attribute name="alias" type="xsd:string"/>
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
<xsd:attribute name="name" type="xsd:string"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="data">
|
<xsd:element name="data">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
<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="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="resheader">
|
<xsd:element name="resheader">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
@@ -186,6 +186,9 @@
|
|||||||
<data name="FilePickerImportCommit" xml:space="preserve">
|
<data name="FilePickerImportCommit" xml:space="preserve">
|
||||||
<value>インポート</value>
|
<value>インポート</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FilePickerPowerShellCommit" xml:space="preserve">
|
||||||
|
<value>PowerShellを選択</value>
|
||||||
|
</data>
|
||||||
<data name="GuideWindowTitle" xml:space="preserve">
|
<data name="GuideWindowTitle" xml:space="preserve">
|
||||||
<value>胡桃へようこそ</value>
|
<value>胡桃へようこそ</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -929,6 +932,9 @@
|
|||||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||||
<value>長いパスのサポートがオフになっているため、レジストリキーを編集できません。</value>
|
<value>長いパスのサポートがオフになっているため、レジストリキーを編集できません。</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ServiceGameRegisteryInteropPowershellNotFound" xml:space="preserve">
|
||||||
|
<value>PowerShellのインストールディレクトリが見つかりません</value>
|
||||||
|
</data>
|
||||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||||
<value>ゲーム設定ファイル {0} の読み込みに失敗しました。ファイルが存在していない可能性があります。</value>
|
<value>ゲーム設定ファイル {0} の読み込みに失敗しました。ファイルが存在していない可能性があります。</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2426,6 +2432,12 @@
|
|||||||
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
||||||
<value>ゲームのパスを設定する際、本体(YuanShen.exe または GenshinImpact.exe)を選んでください。ランチャー(launcher.exe)ではありません</value>
|
<value>ゲームのパスを設定する際、本体(YuanShen.exe または GenshinImpact.exe)を選んでください。ランチャー(launcher.exe)ではありません</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellDescription" xml:space="preserve">
|
||||||
|
<value>胡桃のゲームランチャーはPowershellを介してレジストリを変更し、ゲームで使用するアカウントを変更します。</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve">
|
||||||
|
<value>PowerShell パス</value>
|
||||||
|
</data>
|
||||||
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
||||||
<value>Shell エクスペリエンス</value>
|
<value>Shell エクスペリエンス</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -60,45 +60,45 @@
|
|||||||
: and then encoded with base64 encoding.
|
: 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: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:element name="root" msdata:IsDataSet="true">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="metadata">
|
<xsd:element name="metadata">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
<xsd:attribute name="type" type="xsd:string"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="assembly">
|
<xsd:element name="assembly">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
<xsd:attribute name="alias" type="xsd:string"/>
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
<xsd:attribute name="name" type="xsd:string"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="data">
|
<xsd:element name="data">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
<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="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="resheader">
|
<xsd:element name="resheader">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
@@ -186,6 +186,9 @@
|
|||||||
<data name="FilePickerImportCommit" xml:space="preserve">
|
<data name="FilePickerImportCommit" xml:space="preserve">
|
||||||
<value>가져오기</value>
|
<value>가져오기</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FilePickerPowerShellCommit" xml:space="preserve">
|
||||||
|
<value>选择 PowerShell</value>
|
||||||
|
</data>
|
||||||
<data name="GuideWindowTitle" xml:space="preserve">
|
<data name="GuideWindowTitle" xml:space="preserve">
|
||||||
<value>欢迎使用胡桃</value>
|
<value>欢迎使用胡桃</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -929,6 +932,9 @@
|
|||||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||||
<value>긴 경로 기능이 켜지지 않아 레지스트리 키 값을 설정할 수 없습니다</value>
|
<value>긴 경로 기능이 켜지지 않아 레지스트리 키 값을 설정할 수 없습니다</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ServiceGameRegisteryInteropPowershellNotFound" xml:space="preserve">
|
||||||
|
<value>PowerShell 설치 경로를 찾을 수 없습니다</value>
|
||||||
|
</data>
|
||||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||||
<value>无法读取游戏配置文件 {0},可能是文件不存在</value>
|
<value>无法读取游戏配置文件 {0},可能是文件不存在</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2426,6 +2432,12 @@
|
|||||||
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
||||||
<value>게임 경로를 설정할 때 런쳐(launcher.exe) 대신 게임(YuanShen.exe 또는 GenshinImpact.exe)를 선택하세요</value>
|
<value>게임 경로를 설정할 때 런쳐(launcher.exe) 대신 게임(YuanShen.exe 또는 GenshinImpact.exe)를 선택하세요</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellDescription" xml:space="preserve">
|
||||||
|
<value>胡桃使用 PowerShell 更改注册表中的信息以修改游戏内账号</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve">
|
||||||
|
<value>PowerShell 路径</value>
|
||||||
|
</data>
|
||||||
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
||||||
<value>Shell 体验</value>
|
<value>Shell 体验</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -120,12 +120,6 @@
|
|||||||
<data name="AppDevNameAndVersion" xml:space="preserve">
|
<data name="AppDevNameAndVersion" xml:space="preserve">
|
||||||
<value>胡桃 Dev {0}</value>
|
<value>胡桃 Dev {0}</value>
|
||||||
</data>
|
</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">
|
<data name="AppName" xml:space="preserve">
|
||||||
<value>胡桃</value>
|
<value>胡桃</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -192,6 +186,9 @@
|
|||||||
<data name="FilePickerImportCommit" xml:space="preserve">
|
<data name="FilePickerImportCommit" xml:space="preserve">
|
||||||
<value>导入</value>
|
<value>导入</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FilePickerPowerShellCommit" xml:space="preserve">
|
||||||
|
<value>选择 PowerShell</value>
|
||||||
|
</data>
|
||||||
<data name="GuideWindowTitle" xml:space="preserve">
|
<data name="GuideWindowTitle" xml:space="preserve">
|
||||||
<value>欢迎使用胡桃</value>
|
<value>欢迎使用胡桃</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -935,6 +932,9 @@
|
|||||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||||
<value>未开启长路径功能,无法设置注册表键值</value>
|
<value>未开启长路径功能,无法设置注册表键值</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ServiceGameRegisteryInteropPowershellNotFound" xml:space="preserve">
|
||||||
|
<value>找不到 PowerShell 的安装目录</value>
|
||||||
|
</data>
|
||||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||||
<value>无法读取游戏配置文件 {0},可能是文件不存在</value>
|
<value>无法读取游戏配置文件 {0},可能是文件不存在</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1553,9 +1553,6 @@
|
|||||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||||
<value>切换账号失败</value>
|
<value>切换账号失败</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
|
|
||||||
<value>无法选择UID [{0}] 对应的账号 [{1}],该账号不属于当前服务器</value>
|
|
||||||
</data>
|
|
||||||
<data name="ViewModelSettingActionComplete" xml:space="preserve">
|
<data name="ViewModelSettingActionComplete" xml:space="preserve">
|
||||||
<value>操作完成</value>
|
<value>操作完成</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2147,9 +2144,6 @@
|
|||||||
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
|
||||||
<value>预下载</value>
|
<value>预下载</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
|
|
||||||
<value>选择游戏路径</value>
|
|
||||||
</data>
|
|
||||||
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
|
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
|
||||||
<value>该账号尚未绑定实时便笺通知 UID</value>
|
<value>该账号尚未绑定实时便笺通知 UID</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2238,7 +2232,7 @@
|
|||||||
<value>创建</value>
|
<value>创建</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewPageSettingCreateDesktopShortcutDescription" xml:space="preserve">
|
<data name="ViewPageSettingCreateDesktopShortcutDescription" xml:space="preserve">
|
||||||
<value>在桌面上创建默认以管理员身份启动的快捷方式</value>
|
<value>在桌面上创建默认以管理员方式启动的快捷方式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ViewPageSettingCreateDesktopShortcutHeader" xml:space="preserve">
|
<data name="ViewPageSettingCreateDesktopShortcutHeader" xml:space="preserve">
|
||||||
<value>创建快捷方式</value>
|
<value>创建快捷方式</value>
|
||||||
@@ -2282,15 +2276,6 @@
|
|||||||
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
|
||||||
<value>设备 IP</value>
|
<value>设备 IP</value>
|
||||||
</data>
|
</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">
|
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
|
||||||
<value>在祈愿记录页面显示或隐藏无记录的历史祈愿活动</value>
|
<value>在祈愿记录页面显示或隐藏无记录的历史祈愿活动</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2447,6 +2432,12 @@
|
|||||||
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
||||||
<value>设置游戏路径时,请选择游戏本体(YuanShen.exe 或 GenshinImpact.exe)而不是启动器(launcher.exe)</value>
|
<value>设置游戏路径时,请选择游戏本体(YuanShen.exe 或 GenshinImpact.exe)而不是启动器(launcher.exe)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellDescription" xml:space="preserve">
|
||||||
|
<value>胡桃使用 PowerShell 更改注册表中的信息以修改游戏内账号</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve">
|
||||||
|
<value>PowerShell 路径</value>
|
||||||
|
</data>
|
||||||
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
||||||
<value>Shell 体验</value>
|
<value>Shell 体验</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2750,15 +2741,6 @@
|
|||||||
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
|
||||||
<value>已复制到剪贴板</value>
|
<value>已复制到剪贴板</value>
|
||||||
</data>
|
</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">
|
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
|
||||||
<value>已完成</value>
|
<value>已完成</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -60,45 +60,45 @@
|
|||||||
: and then encoded with base64 encoding.
|
: 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: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:element name="root" msdata:IsDataSet="true">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="metadata">
|
<xsd:element name="metadata">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
<xsd:attribute name="type" type="xsd:string"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="assembly">
|
<xsd:element name="assembly">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
<xsd:attribute name="alias" type="xsd:string"/>
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
<xsd:attribute name="name" type="xsd:string"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="data">
|
<xsd:element name="data">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
<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="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="resheader">
|
<xsd:element name="resheader">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
@@ -186,6 +186,9 @@
|
|||||||
<data name="FilePickerImportCommit" xml:space="preserve">
|
<data name="FilePickerImportCommit" xml:space="preserve">
|
||||||
<value>Импорт</value>
|
<value>Импорт</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FilePickerPowerShellCommit" xml:space="preserve">
|
||||||
|
<value>Выберите PowerShell</value>
|
||||||
|
</data>
|
||||||
<data name="GuideWindowTitle" xml:space="preserve">
|
<data name="GuideWindowTitle" xml:space="preserve">
|
||||||
<value>Добро пожаловать в Snap Hutao, путешественник ~</value>
|
<value>Добро пожаловать в Snap Hutao, путешественник ~</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -929,6 +932,9 @@
|
|||||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||||
<value>未开启长路径功能,无法设置注册表键值</value>
|
<value>未开启长路径功能,无法设置注册表键值</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ServiceGameRegisteryInteropPowershellNotFound" xml:space="preserve">
|
||||||
|
<value>找不到 PowerShell 的安装目录</value>
|
||||||
|
</data>
|
||||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||||
<value>无法读取游戏配置文件 {0},可能是文件不存在</value>
|
<value>无法读取游戏配置文件 {0},可能是文件不存在</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2426,6 +2432,12 @@
|
|||||||
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
||||||
<value>设置游戏路径时,请选择游戏本体(YuanShen.exe 或 GenshinImpact.exe)而不是启动器(launcher.exe)</value>
|
<value>设置游戏路径时,请选择游戏本体(YuanShen.exe 或 GenshinImpact.exe)而不是启动器(launcher.exe)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellDescription" xml:space="preserve">
|
||||||
|
<value>胡桃使用 PowerShell 更改注册表中的信息以修改游戏内账号</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve">
|
||||||
|
<value>PowerShell 路径</value>
|
||||||
|
</data>
|
||||||
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
||||||
<value>Shell 体验</value>
|
<value>Shell 体验</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -60,45 +60,45 @@
|
|||||||
: and then encoded with base64 encoding.
|
: 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: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:element name="root" msdata:IsDataSet="true">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:choice maxOccurs="unbounded">
|
<xsd:choice maxOccurs="unbounded">
|
||||||
<xsd:element name="metadata">
|
<xsd:element name="metadata">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
<xsd:attribute name="type" type="xsd:string"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="assembly">
|
<xsd:element name="assembly">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
<xsd:attribute name="alias" type="xsd:string"/>
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
<xsd:attribute name="name" type="xsd:string"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="data">
|
<xsd:element name="data">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
<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="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||||
<xsd:attribute ref="xml:space" />
|
<xsd:attribute ref="xml:space"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
<xsd:element name="resheader">
|
<xsd:element name="resheader">
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
<xsd:sequence>
|
<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:sequence>
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
@@ -186,6 +186,9 @@
|
|||||||
<data name="FilePickerImportCommit" xml:space="preserve">
|
<data name="FilePickerImportCommit" xml:space="preserve">
|
||||||
<value>匯入</value>
|
<value>匯入</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FilePickerPowerShellCommit" xml:space="preserve">
|
||||||
|
<value>選擇 PowerShell</value>
|
||||||
|
</data>
|
||||||
<data name="GuideWindowTitle" xml:space="preserve">
|
<data name="GuideWindowTitle" xml:space="preserve">
|
||||||
<value>歡迎使用胡桃</value>
|
<value>歡迎使用胡桃</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -929,6 +932,9 @@
|
|||||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||||
<value>未開啓長路徑功能,無法設定注冊表鍵值</value>
|
<value>未開啓長路徑功能,無法設定注冊表鍵值</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ServiceGameRegisteryInteropPowershellNotFound" xml:space="preserve">
|
||||||
|
<value>找不到 PowerShell 的安裝目錄</value>
|
||||||
|
</data>
|
||||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||||
<value>無法讀取遊戲設定檔 {0},可能是檔案不存在</value>
|
<value>無法讀取遊戲設定檔 {0},可能是檔案不存在</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2426,6 +2432,12 @@
|
|||||||
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
<data name="ViewPageSettingSetGamePathHint" xml:space="preserve">
|
||||||
<value>設置游戲路徑時,請選擇游戲本體(YuanShen.exe 或 GenshinImpact.exe) 而不是啓動器(launcher.exe)</value>
|
<value>設置游戲路徑時,請選擇游戲本體(YuanShen.exe 或 GenshinImpact.exe) 而不是啓動器(launcher.exe)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellDescription" xml:space="preserve">
|
||||||
|
<value>胡桃使用 PowerShell 更改註冊表中的信息以修改遊戲內賬號</value>
|
||||||
|
</data>
|
||||||
|
<data name="ViewPageSettingSetPowerShellPathHeader" xml:space="preserve">
|
||||||
|
<value>PowerShell 路徑</value>
|
||||||
|
</data>
|
||||||
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
<data name="ViewPageSettingShellExperienceHeader" xml:space="preserve">
|
||||||
<value>Shell 體驗</value>
|
<value>Shell 體驗</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Snap.Hutao.Service.Abstraction;
|
|||||||
using Snap.Hutao.Web.Hoyolab;
|
using Snap.Hutao.Web.Hoyolab;
|
||||||
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
|
||||||
using Snap.Hutao.Web.Response;
|
using Snap.Hutao.Web.Response;
|
||||||
|
using System.Globalization;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
using Snap.Hutao.Core.Windowing;
|
using Snap.Hutao.Core.Windowing;
|
||||||
using Snap.Hutao.Model;
|
using Snap.Hutao.Model;
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
using Snap.Hutao.Web.Hoyolab;
|
using Snap.Hutao.Web.Hoyolab;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service;
|
namespace Snap.Hutao.Service;
|
||||||
|
|
||||||
@@ -14,12 +16,42 @@ namespace Snap.Hutao.Service;
|
|||||||
[Injection(InjectAs.Singleton)]
|
[Injection(InjectAs.Singleton)]
|
||||||
internal sealed partial class AppOptions : DbStoreOptions
|
internal sealed partial class AppOptions : DbStoreOptions
|
||||||
{
|
{
|
||||||
|
private string? powerShellPath;
|
||||||
private bool? isEmptyHistoryWishVisible;
|
private bool? isEmptyHistoryWishVisible;
|
||||||
private BackdropType? backdropType;
|
private BackdropType? backdropType;
|
||||||
private CultureInfo? currentCulture;
|
private CultureInfo? currentCulture;
|
||||||
private Region? region;
|
private Region? region;
|
||||||
private string? geetestCustomCompositeUrl;
|
private string? geetestCustomCompositeUrl;
|
||||||
|
|
||||||
|
public string PowerShellPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return GetOption(ref powerShellPath, SettingEntry.PowerShellPath, GetDefaultPowerShellLocationOrEmpty);
|
||||||
|
|
||||||
|
static string GetDefaultPowerShellLocationOrEmpty()
|
||||||
|
{
|
||||||
|
string? paths = Environment.GetEnvironmentVariable("Path");
|
||||||
|
if (!string.IsNullOrEmpty(paths))
|
||||||
|
{
|
||||||
|
foreach (StringSegment path in new StringTokenizer(paths, [';']))
|
||||||
|
{
|
||||||
|
if (path is { HasValue: true, Length: > 0 })
|
||||||
|
{
|
||||||
|
if (path.Value.Contains("WindowsPowerShell", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Path.Combine(path.Value, "powershell.exe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set => SetOption(ref powerShellPath, SettingEntry.PowerShellPath, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsEmptyHistoryWishVisible
|
public bool IsEmptyHistoryWishVisible
|
||||||
{
|
{
|
||||||
get => GetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible);
|
get => GetOption(ref isEmptyHistoryWishVisible, SettingEntry.IsEmptyHistoryWishVisible);
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
|||||||
|
|
||||||
List<DailyNoteEntry> entryList = await dailyNoteDbService.GetDailyNoteEntryIncludeUserListAsync().ConfigureAwait(false);
|
List<DailyNoteEntry> entryList = await dailyNoteDbService.GetDailyNoteEntryIncludeUserListAsync().ConfigureAwait(false);
|
||||||
entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); });
|
entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); });
|
||||||
entries = entryList.ToObservableCollection();
|
entries = new(entryList);
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
@@ -147,7 +147,7 @@ internal sealed partial class DailyNoteService : IDailyNoteService, IRecipient<U
|
|||||||
// 发送通知必须早于数据库更新,否则会导致通知重复
|
// 发送通知必须早于数据库更新,否则会导致通知重复
|
||||||
await dailyNoteNotificationOperation.SendAsync(entry).ConfigureAwait(false);
|
await dailyNoteNotificationOperation.SendAsync(entry).ConfigureAwait(false);
|
||||||
await dailyNoteDbService.UpdateDailyNoteEntryAsync(entry).ConfigureAwait(false);
|
await dailyNoteDbService.UpdateDailyNoteEntryAsync(entry).ConfigureAwait(false);
|
||||||
await dailyNoteWebhookOperation.TryPostDailyNoteToWebhookAsync(entry.Uid, dailyNote).ConfigureAwait(false);
|
await dailyNoteWebhookOperation.TryPostDailyNoteToWebhookAsync(dailyNote).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||||
using Snap.Hutao.Web.Hoyolab;
|
|
||||||
using Snap.Hutao.Web.Request.Builder;
|
using Snap.Hutao.Web.Request.Builder;
|
||||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -19,7 +18,7 @@ internal sealed partial class DailyNoteWebhookOperation
|
|||||||
private readonly DailyNoteOptions dailyNoteOptions;
|
private readonly DailyNoteOptions dailyNoteOptions;
|
||||||
private readonly HttpClient httpClient;
|
private readonly HttpClient httpClient;
|
||||||
|
|
||||||
public async ValueTask TryPostDailyNoteToWebhookAsync(PlayerUid playerUid, WebDailyNote dailyNote, CancellationToken token = default)
|
public async ValueTask TryPostDailyNoteToWebhookAsync(WebDailyNote dailyNote, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
string? targetUrl = dailyNoteOptions.WebhookUrl;
|
string? targetUrl = dailyNoteOptions.WebhookUrl;
|
||||||
if (string.IsNullOrEmpty(targetUrl) || !Uri.TryCreate(targetUrl, UriKind.Absolute, out Uri? targetUri))
|
if (string.IsNullOrEmpty(targetUrl) || !Uri.TryCreate(targetUrl, UriKind.Absolute, out Uri? targetUri))
|
||||||
@@ -29,7 +28,6 @@ internal sealed partial class DailyNoteWebhookOperation
|
|||||||
|
|
||||||
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
|
||||||
.SetRequestUri(targetUri)
|
.SetRequestUri(targetUri)
|
||||||
.SetHeader("x-uid", $"{playerUid}")
|
|
||||||
.PostJson(dailyNote);
|
.PostJson(dailyNote);
|
||||||
|
|
||||||
await builder.TryCatchSendAsync(httpClient, logger, token).ConfigureAwait(false);
|
await builder.TryCatchSendAsync(httpClient, logger, token).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
using Snap.Hutao.Core.ExceptionService;
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
using Snap.Hutao.Factory.ContentDialog;
|
using Snap.Hutao.Factory.ContentDialog;
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Model.Entity.Primitive;
|
|
||||||
using Snap.Hutao.View.Dialog;
|
using Snap.Hutao.View.Dialog;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
@@ -17,6 +16,7 @@ internal sealed partial class GameAccountService : IGameAccountService
|
|||||||
private readonly IContentDialogFactory contentDialogFactory;
|
private readonly IContentDialogFactory contentDialogFactory;
|
||||||
private readonly IGameDbService gameDbService;
|
private readonly IGameDbService gameDbService;
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
private readonly AppOptions appOptions;
|
||||||
|
|
||||||
private ObservableCollection<GameAccount>? gameAccounts;
|
private ObservableCollection<GameAccount>? gameAccounts;
|
||||||
|
|
||||||
@@ -25,56 +25,77 @@ internal sealed partial class GameAccountService : IGameAccountService
|
|||||||
get => gameAccounts ??= gameDbService.GetGameAccountCollection();
|
get => gameAccounts ??= gameDbService.GetGameAccountCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType schemeType)
|
public async ValueTask<GameAccount?> DetectGameAccountAsync()
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(gameAccounts);
|
ArgumentNullException.ThrowIfNull(gameAccounts);
|
||||||
|
|
||||||
string? registrySdk = RegistryInterop.Get(schemeType);
|
string? registrySdk = RegistryInterop.Get();
|
||||||
if (string.IsNullOrEmpty(registrySdk))
|
if (!string.IsNullOrEmpty(registrySdk))
|
||||||
{
|
{
|
||||||
return default;
|
GameAccount? account = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
account = gameAccounts.SingleOrDefault(a => a.MihoyoSDK == registrySdk);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.UserdataCorrupted(SH.ServiceGameDetectGameAccountMultiMatched, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account is null)
|
||||||
|
{
|
||||||
|
LaunchGameAccountNameDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGameAccountNameDialog>().ConfigureAwait(false);
|
||||||
|
(bool isOk, string name) = await dialog.GetInputNameAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (isOk)
|
||||||
|
{
|
||||||
|
account = GameAccount.From(name, registrySdk);
|
||||||
|
|
||||||
|
// sync database
|
||||||
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
|
await gameDbService.AddGameAccountAsync(account).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// sync cache
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
gameAccounts.Add(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameAccount? account = SingleGameAccountOrDefault(gameAccounts, registrySdk);
|
return default;
|
||||||
if (account is null)
|
}
|
||||||
|
|
||||||
|
public GameAccount? DetectCurrentGameAccount()
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(gameAccounts);
|
||||||
|
|
||||||
|
string? registrySdk = RegistryInterop.Get();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(registrySdk))
|
||||||
{
|
{
|
||||||
LaunchGameAccountNameDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGameAccountNameDialog>().ConfigureAwait(false);
|
try
|
||||||
(bool isOk, string name) = await dialog.GetInputNameAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (isOk)
|
|
||||||
{
|
{
|
||||||
account = GameAccount.From(name, registrySdk, schemeType);
|
return gameAccounts.SingleOrDefault(a => a.MihoyoSDK == registrySdk);
|
||||||
|
}
|
||||||
// sync database
|
catch (InvalidOperationException ex)
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
{
|
||||||
await gameDbService.AddGameAccountAsync(account).ConfigureAwait(false);
|
ThrowHelper.UserdataCorrupted(SH.ServiceGameDetectGameAccountMultiMatched, ex);
|
||||||
|
|
||||||
// sync cache
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
|
||||||
gameAccounts.Add(account);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return account;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
public GameAccount? DetectCurrentGameAccount(SchemeType schemeType)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(gameAccounts);
|
|
||||||
|
|
||||||
string? registrySdk = RegistryInterop.Get(schemeType);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(registrySdk))
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SingleGameAccountOrDefault(gameAccounts, registrySdk);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SetGameAccount(GameAccount account)
|
public bool SetGameAccount(GameAccount account)
|
||||||
{
|
{
|
||||||
return RegistryInterop.Set(account);
|
if (string.IsNullOrEmpty(appOptions.PowerShellPath))
|
||||||
|
{
|
||||||
|
ThrowHelper.RuntimeEnvironment(SH.ServiceGameRegisteryInteropPowershellNotFound, default!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegistryInterop.Set(account, appOptions.PowerShellPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AttachGameAccountToUid(GameAccount gameAccount, string uid)
|
public void AttachGameAccountToUid(GameAccount gameAccount, string uid)
|
||||||
@@ -85,12 +106,12 @@ internal sealed partial class GameAccountService : IGameAccountService
|
|||||||
|
|
||||||
public async ValueTask ModifyGameAccountAsync(GameAccount gameAccount)
|
public async ValueTask ModifyGameAccountAsync(GameAccount gameAccount)
|
||||||
{
|
{
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
LaunchGameAccountNameDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGameAccountNameDialog>().ConfigureAwait(false);
|
LaunchGameAccountNameDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGameAccountNameDialog>().ConfigureAwait(false);
|
||||||
(bool isOk, string name) = await dialog.GetInputNameAsync().ConfigureAwait(false);
|
(bool isOk, string name) = await dialog.GetInputNameAsync().ConfigureAwait(true);
|
||||||
|
|
||||||
if (isOk)
|
if (isOk)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
|
||||||
gameAccount.UpdateName(name);
|
gameAccount.UpdateName(name);
|
||||||
|
|
||||||
// sync database
|
// sync database
|
||||||
@@ -101,24 +122,11 @@ internal sealed partial class GameAccountService : IGameAccountService
|
|||||||
|
|
||||||
public async ValueTask RemoveGameAccountAsync(GameAccount gameAccount)
|
public async ValueTask RemoveGameAccountAsync(GameAccount gameAccount)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(gameAccounts);
|
|
||||||
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
ArgumentNullException.ThrowIfNull(gameAccounts);
|
||||||
gameAccounts.Remove(gameAccount);
|
gameAccounts.Remove(gameAccount);
|
||||||
|
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
await gameDbService.RemoveGameAccountByIdAsync(gameAccount.InnerId).ConfigureAwait(false);
|
await gameDbService.RemoveGameAccountByIdAsync(gameAccount.InnerId).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameAccount? SingleGameAccountOrDefault(ObservableCollection<GameAccount> gameAccounts, string registrySdk)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return gameAccounts.SingleOrDefault(a => a.MihoyoSDK == registrySdk);
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
throw ThrowHelper.UserdataCorrupted(SH.ServiceGameDetectGameAccountMultiMatched, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Model.Entity.Primitive;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game.Account;
|
namespace Snap.Hutao.Service.Game.Account;
|
||||||
@@ -13,9 +12,9 @@ internal interface IGameAccountService
|
|||||||
|
|
||||||
void AttachGameAccountToUid(GameAccount gameAccount, string uid);
|
void AttachGameAccountToUid(GameAccount gameAccount, string uid);
|
||||||
|
|
||||||
GameAccount? DetectCurrentGameAccount(SchemeType schemeType);
|
GameAccount? DetectCurrentGameAccount();
|
||||||
|
|
||||||
ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType schemeType);
|
ValueTask<GameAccount?> DetectGameAccountAsync();
|
||||||
|
|
||||||
ValueTask ModifyGameAccountAsync(GameAccount gameAccount);
|
ValueTask ModifyGameAccountAsync(GameAccount gameAccount);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Model.Entity.Primitive;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -15,21 +16,52 @@ namespace Snap.Hutao.Service.Game.Account;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class RegistryInterop
|
internal static class RegistryInterop
|
||||||
{
|
{
|
||||||
private const string ChineseKeyName = @"HKEY_CURRENT_USER\Software\miHoYo\原神";
|
private const string GenshinPath = @"Software\miHoYo\原神";
|
||||||
private const string OverseaKeyName = @"HKEY_CURRENT_USER\Software\miHoYo\Genshin Impact";
|
private const string GenshinKey = $@"HKEY_CURRENT_USER\{GenshinPath}";
|
||||||
private const string SdkChineseValueName = "MIHOYOSDK_ADL_PROD_CN_h3123967166";
|
private const string SdkChineseKey = "MIHOYOSDK_ADL_PROD_CN_h3123967166";
|
||||||
private const string SdkOverseaValueName = "MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810";
|
|
||||||
|
|
||||||
public static bool Set(GameAccount? account)
|
/// <summary>
|
||||||
|
/// 设置键值
|
||||||
|
/// 需要支持
|
||||||
|
/// https://learn.microsoft.com/zh-cn/windows/win32/fileio/maximum-file-path-limitation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="account">账户</param>
|
||||||
|
/// <param name="powerShellPath">PowerShell 路径</param>
|
||||||
|
/// <returns>账号是否设置</returns>
|
||||||
|
public static bool Set(GameAccount? account, string powerShellPath)
|
||||||
{
|
{
|
||||||
if (account is not null)
|
if (account is not null)
|
||||||
{
|
{
|
||||||
// 存回注册表的字节需要 '\0' 结尾
|
// 存回注册表的字节需要 '\0' 结尾
|
||||||
byte[] target = [.. Encoding.UTF8.GetBytes(account.MihoyoSDK), 0];
|
Encoding.UTF8.GetByteCount(account.MihoyoSDK);
|
||||||
(string keyName, string valueName) = GetKeyValueName(account.Type);
|
byte[] tempBytes = Encoding.UTF8.GetBytes(account.MihoyoSDK);
|
||||||
Registry.SetValue(keyName, valueName, target);
|
byte[] target = new byte[tempBytes.Length + 1];
|
||||||
|
tempBytes.CopyTo(target, 0);
|
||||||
|
|
||||||
if (Get(account.Type) == account.MihoyoSDK)
|
string base64 = Convert.ToBase64String(target);
|
||||||
|
string path = $"HKCU:{GenshinPath}";
|
||||||
|
string command = $"""
|
||||||
|
-Command "$value = [Convert]::FromBase64String('{base64}'); Set-ItemProperty -Path '{path}' -Name '{SdkChineseKey}' -Value $value -Force;"
|
||||||
|
""";
|
||||||
|
|
||||||
|
ProcessStartInfo startInfo = new()
|
||||||
|
{
|
||||||
|
Arguments = command,
|
||||||
|
WorkingDirectory = Path.GetDirectoryName(powerShellPath),
|
||||||
|
CreateNoWindow = true,
|
||||||
|
FileName = powerShellPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.Diagnostics.Process.Start(startInfo)?.WaitForExit();
|
||||||
|
}
|
||||||
|
catch (Win32Exception ex)
|
||||||
|
{
|
||||||
|
ThrowHelper.RuntimeEnvironment(SH.ServiceGameRegisteryInteropLongPathsDisabled, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Get() == account.MihoyoSDK)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -38,31 +70,24 @@ internal static class RegistryInterop
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe string? Get(SchemeType scheme)
|
/// <summary>
|
||||||
|
/// 在注册表中获取账号信息
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>当前注册表中的信息</returns>
|
||||||
|
public static unsafe string? Get()
|
||||||
{
|
{
|
||||||
(string keyName, string valueName) = GetKeyValueName(scheme);
|
object? sdk = Registry.GetValue(GenshinKey, SdkChineseKey, Array.Empty<byte>());
|
||||||
object? sdk = Registry.GetValue(keyName, valueName, Array.Empty<byte>());
|
|
||||||
|
|
||||||
if (sdk is not byte[] bytes)
|
if (sdk is byte[] bytes)
|
||||||
{
|
{
|
||||||
return null;
|
fixed (byte* pByte = bytes)
|
||||||
|
{
|
||||||
|
// 从注册表获取的字节数组带有 '\0' 结尾,需要舍去
|
||||||
|
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pByte);
|
||||||
|
return Encoding.UTF8.GetString(span);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* pByte = bytes)
|
return null;
|
||||||
{
|
|
||||||
// 从注册表获取的字节数组带有 '\0' 结尾,需要舍去
|
|
||||||
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pByte);
|
|
||||||
return Encoding.UTF8.GetString(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (string KeyName, string ValueName) GetKeyValueName(SchemeType scheme)
|
|
||||||
{
|
|
||||||
return scheme switch
|
|
||||||
{
|
|
||||||
SchemeType.ChineseOfficial => (ChineseKeyName, SdkChineseValueName),
|
|
||||||
SchemeType.Oversea => (OverseaKeyName, SdkOverseaValueName),
|
|
||||||
_ => throw ThrowHelper.NotSupported($"Invalid account SchemeType: {scheme}"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,21 @@ internal readonly struct ChannelOptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly string? ConfigFilePath;
|
public readonly string? ConfigFilePath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的多通道
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channel">通道</param>
|
||||||
|
/// <param name="subChannel">子通道</param>
|
||||||
|
/// <param name="isOversea">是否为国际服</param>
|
||||||
|
/// <param name="configFilePath">配置文件路径</param>
|
||||||
|
public ChannelOptions(string? channel, string? subChannel, bool isOversea, string? configFilePath = null)
|
||||||
|
{
|
||||||
|
_ = Enum.TryParse(channel, out Channel);
|
||||||
|
_ = Enum.TryParse(subChannel, out SubChannel);
|
||||||
|
IsOversea = isOversea;
|
||||||
|
ConfigFilePath = configFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
public ChannelOptions(ChannelType channel, SubChannelType subChannel, bool isOversea)
|
public ChannelOptions(ChannelType channel, SubChannelType subChannel, bool isOversea)
|
||||||
{
|
{
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
@@ -41,33 +56,24 @@ internal readonly struct ChannelOptions
|
|||||||
IsOversea = isOversea;
|
IsOversea = isOversea;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelOptions(string? channel, string? subChannel, bool isOversea)
|
/// <summary>
|
||||||
{
|
/// 配置文件未找到
|
||||||
_ = Enum.TryParse(channel, out Channel);
|
/// </summary>
|
||||||
_ = Enum.TryParse(subChannel, out SubChannel);
|
/// <param name="isOversea">是否为国际服</param>
|
||||||
IsOversea = isOversea;
|
/// <param name="configFilePath">配置文件期望路径</param>
|
||||||
}
|
/// <returns>选项</returns>
|
||||||
|
|
||||||
private ChannelOptions(bool isOversea, string? configFilePath)
|
|
||||||
{
|
|
||||||
IsOversea = isOversea;
|
|
||||||
ConfigFilePath = configFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ChannelOptions FileNotFound(bool isOversea, string configFilePath)
|
public static ChannelOptions FileNotFound(bool isOversea, string configFilePath)
|
||||||
{
|
{
|
||||||
return new(isOversea, configFilePath);
|
return new(null, null, isOversea, configFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $$"""
|
return $"[ChannelType:{Channel}] [SubChannel:{SubChannel}] [IsOversea: {IsOversea}]";
|
||||||
{ ChannelType: {{Channel}}, SubChannel: {{SubChannel}}, IsOversea: {{IsOversea}}}
|
|
||||||
""";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT DELETE, used in HashSet
|
// DO NOT DELETE used in HashSet
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
return HashCode.Combine(Channel, SubChannel, IsOversea);
|
return HashCode.Combine(Channel, SubChannel, IsOversea);
|
||||||
|
|||||||
@@ -17,12 +17,9 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer
|
|||||||
|
|
||||||
public ChannelOptions GetChannelOptions()
|
public ChannelOptions GetChannelOptions()
|
||||||
{
|
{
|
||||||
if (!launchOptions.TryGetGamePathAndFilePathByName(ConfigFileName, out string gamePath, out string? configPath))
|
string gamePath = launchOptions.GamePath;
|
||||||
{
|
string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFileName);
|
||||||
throw ThrowHelper.InvalidOperation($"Invalid game path: {gamePath}");
|
bool isOversea = string.Equals(Path.GetFileName(gamePath), GenshinImpactFileName, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
|
||||||
|
|
||||||
bool isOversea = LaunchScheme.ExecutableIsOversea(Path.GetFileName(gamePath));
|
|
||||||
|
|
||||||
if (!File.Exists(configPath))
|
if (!File.Exists(configPath))
|
||||||
{
|
{
|
||||||
@@ -41,10 +38,10 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer
|
|||||||
|
|
||||||
public bool SetChannelOptions(LaunchScheme scheme)
|
public bool SetChannelOptions(LaunchScheme scheme)
|
||||||
{
|
{
|
||||||
if (!launchOptions.TryGetGamePathAndFilePathByName(ConfigFileName, out string gamePath, out string? configPath))
|
string gamePath = launchOptions.GamePath;
|
||||||
{
|
string? directory = Path.GetDirectoryName(gamePath);
|
||||||
return false;
|
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||||
}
|
string configPath = Path.Combine(directory, ConfigFileName);
|
||||||
|
|
||||||
List<IniElement> elements = default!;
|
List<IniElement> elements = default!;
|
||||||
try
|
try
|
||||||
@@ -73,16 +70,14 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer
|
|||||||
{
|
{
|
||||||
if (element is IniParameter parameter)
|
if (element is IniParameter parameter)
|
||||||
{
|
{
|
||||||
if (parameter.Key is ChannelOptions.ChannelName)
|
if (parameter.Key == "channel")
|
||||||
{
|
{
|
||||||
changed = parameter.Set(scheme.Channel.ToString("D")) || changed;
|
changed = parameter.Set(scheme.Channel.ToString("D")) || changed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameter.Key is ChannelOptions.SubChannelName)
|
if (parameter.Key == "sub_channel")
|
||||||
{
|
{
|
||||||
changed = parameter.Set(scheme.SubChannel.ToString("D")) || changed;
|
changed = parameter.Set(scheme.SubChannel.ToString("D")) || changed;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,38 @@ namespace Snap.Hutao.Service.Game;
|
|||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal static class GameConstants
|
internal static class GameConstants
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 设置文件
|
||||||
|
/// </summary>
|
||||||
public const string ConfigFileName = "config.ini";
|
public const string ConfigFileName = "config.ini";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 国服文件名
|
||||||
|
/// </summary>
|
||||||
public const string YuanShenFileName = "YuanShen.exe";
|
public const string YuanShenFileName = "YuanShen.exe";
|
||||||
public const string YuanShenFileNameUpper = "YUANSHEN.EXE";
|
|
||||||
|
/// <summary>
|
||||||
|
/// 外服文件名
|
||||||
|
/// </summary>
|
||||||
public const string GenshinImpactFileName = "GenshinImpact.exe";
|
public const string GenshinImpactFileName = "GenshinImpact.exe";
|
||||||
public const string GenshinImpactFileNameUpper = "GENSHINIMPACT.EXE";
|
|
||||||
|
/// <summary>
|
||||||
|
/// 国服数据文件夹
|
||||||
|
/// </summary>
|
||||||
public const string YuanShenData = "YuanShen_Data";
|
public const string YuanShenData = "YuanShen_Data";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 国际服数据文件夹
|
||||||
|
/// </summary>
|
||||||
public const string GenshinImpactData = "GenshinImpact_Data";
|
public const string GenshinImpactData = "GenshinImpact_Data";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 国服进程名
|
||||||
|
/// </summary>
|
||||||
public const string YuanShenProcessName = "YuanShen";
|
public const string YuanShenProcessName = "YuanShen";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 外服进程名
|
||||||
|
/// </summary>
|
||||||
public const string GenshinImpactProcessName = "GenshinImpact";
|
public const string GenshinImpactProcessName = "GenshinImpact";
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Model.Entity.Primitive;
|
|
||||||
using Snap.Hutao.Service.Game.Account;
|
using Snap.Hutao.Service.Game.Account;
|
||||||
using Snap.Hutao.Service.Game.Configuration;
|
using Snap.Hutao.Service.Game.Configuration;
|
||||||
using Snap.Hutao.Service.Game.Package;
|
using Snap.Hutao.Service.Game.Package;
|
||||||
@@ -52,15 +51,15 @@ internal sealed partial class GameServiceFacade : IGameServiceFacade
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType scheme)
|
public ValueTask<GameAccount?> DetectGameAccountAsync()
|
||||||
{
|
{
|
||||||
return gameAccountService.DetectGameAccountAsync(scheme);
|
return gameAccountService.DetectGameAccountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public GameAccount? DetectCurrentGameAccount(SchemeType scheme)
|
public GameAccount? DetectCurrentGameAccount()
|
||||||
{
|
{
|
||||||
return gameAccountService.DetectCurrentGameAccount(scheme);
|
return gameAccountService.DetectCurrentGameAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Model.Entity;
|
|
||||||
using Snap.Hutao.Service.Game.Scheme;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game;
|
|
||||||
|
|
||||||
internal static class GameServiceFacadeExtension
|
|
||||||
{
|
|
||||||
public static GameAccount? DetectCurrentGameAccount(this IGameServiceFacade gameServiceFacade, LaunchScheme scheme)
|
|
||||||
{
|
|
||||||
return gameServiceFacade.DetectCurrentGameAccount(scheme.GetSchemeType());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ValueTask<GameAccount?> DetectGameAccountAsync(this IGameServiceFacade gameServiceFacade, LaunchScheme scheme)
|
|
||||||
{
|
|
||||||
return gameServiceFacade.DetectGameAccountAsync(scheme.GetSchemeType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Model.Entity.Primitive;
|
|
||||||
using Snap.Hutao.Service.Game.Configuration;
|
using Snap.Hutao.Service.Game.Configuration;
|
||||||
using Snap.Hutao.Service.Game.Package;
|
using Snap.Hutao.Service.Game.Package;
|
||||||
using Snap.Hutao.Service.Game.Scheme;
|
using Snap.Hutao.Service.Game.Scheme;
|
||||||
@@ -29,7 +28,7 @@ internal interface IGameServiceFacade
|
|||||||
/// <param name="uid">uid</param>
|
/// <param name="uid">uid</param>
|
||||||
void AttachGameAccountToUid(GameAccount gameAccount, string uid);
|
void AttachGameAccountToUid(GameAccount gameAccount, string uid);
|
||||||
|
|
||||||
ValueTask<GameAccount?> DetectGameAccountAsync(SchemeType scheme);
|
ValueTask<GameAccount?> DetectGameAccountAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步获取游戏路径
|
/// 异步获取游戏路径
|
||||||
@@ -87,5 +86,9 @@ internal interface IGameServiceFacade
|
|||||||
/// <returns>是否更改了ini文件</returns>
|
/// <returns>是否更改了ini文件</returns>
|
||||||
bool SetChannelOptions(LaunchScheme scheme);
|
bool SetChannelOptions(LaunchScheme scheme);
|
||||||
|
|
||||||
GameAccount? DetectCurrentGameAccount(SchemeType scheme);
|
/// <summary>
|
||||||
|
/// 检测账号
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>账号</returns>
|
||||||
|
GameAccount? DetectCurrentGameAccount();
|
||||||
}
|
}
|
||||||
@@ -9,25 +9,12 @@ namespace Snap.Hutao.Service.Game;
|
|||||||
|
|
||||||
internal static class LaunchOptionsExtension
|
internal static class LaunchOptionsExtension
|
||||||
{
|
{
|
||||||
public static bool TryGetGamePathAndGameDirectory(this LaunchOptions options, out string gamePath, [NotNullWhen(true)] out string? gameDirectory)
|
public static bool TryGetGameFolderAndFileName(this LaunchOptions options, [NotNullWhen(true)] out string? gameFolder, [NotNullWhen(true)] out string? gameFileName)
|
||||||
{
|
|
||||||
gamePath = options.GamePath;
|
|
||||||
|
|
||||||
gameDirectory = Path.GetDirectoryName(gamePath);
|
|
||||||
if (string.IsNullOrEmpty(gameDirectory))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryGetGameDirectoryAndGameFileName(this LaunchOptions options, [NotNullWhen(true)] out string? gameDirectory, [NotNullWhen(true)] out string? gameFileName)
|
|
||||||
{
|
{
|
||||||
string gamePath = options.GamePath;
|
string gamePath = options.GamePath;
|
||||||
|
|
||||||
gameDirectory = Path.GetDirectoryName(gamePath);
|
gameFolder = Path.GetDirectoryName(gamePath);
|
||||||
if (string.IsNullOrEmpty(gameDirectory))
|
if (string.IsNullOrEmpty(gameFolder))
|
||||||
{
|
{
|
||||||
gameFileName = default;
|
gameFileName = default;
|
||||||
return false;
|
return false;
|
||||||
@@ -55,18 +42,6 @@ internal static class LaunchOptionsExtension
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetGamePathAndFilePathByName(this LaunchOptions options, string fileName, out string gamePath, [NotNullWhen(true)] out string? filePath)
|
|
||||||
{
|
|
||||||
if (options.TryGetGamePathAndGameDirectory(out gamePath, out string? gameDirectory))
|
|
||||||
{
|
|
||||||
filePath = Path.Combine(gameDirectory, fileName);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ImmutableList<GamePathEntry> GetGamePathEntries(this LaunchOptions options, out GamePathEntry? entry)
|
public static ImmutableList<GamePathEntry> GetGamePathEntries(this LaunchOptions options, out GamePathEntry? entry)
|
||||||
{
|
{
|
||||||
string gamePath = options.GamePath;
|
string gamePath = options.GamePath;
|
||||||
|
|||||||
@@ -4,13 +4,20 @@
|
|||||||
namespace Snap.Hutao.Service.Game.Locator;
|
namespace Snap.Hutao.Service.Game.Locator;
|
||||||
|
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
[Injection(InjectAs.Singleton, typeof(IGameLocatorFactory))]
|
[Injection(InjectAs.Transient, typeof(IGameLocatorFactory))]
|
||||||
internal sealed partial class GameLocatorFactory : IGameLocatorFactory
|
internal sealed partial class GameLocatorFactory : IGameLocatorFactory
|
||||||
{
|
{
|
||||||
|
[SuppressMessage("", "SH301")]
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
public IGameLocator Create(GameLocationSource source)
|
public IGameLocator Create(GameLocationSource source)
|
||||||
{
|
{
|
||||||
return serviceProvider.GetRequiredKeyedService<IGameLocator>(source);
|
return source switch
|
||||||
|
{
|
||||||
|
GameLocationSource.Registry => serviceProvider.GetRequiredService<RegistryLauncherLocator>(),
|
||||||
|
GameLocationSource.UnityLog => serviceProvider.GetRequiredService<UnityLogGameLocator>(),
|
||||||
|
GameLocationSource.Manual => serviceProvider.GetRequiredService<ManualGameLocator>(),
|
||||||
|
_ => throw Must.NeverHappen(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game.Locator;
|
|
||||||
|
|
||||||
internal static class GameLocatorFactoryExtensions
|
|
||||||
{
|
|
||||||
public static ValueTask<ValueResult<bool, string>> LocateAsync(this IGameLocatorFactory factory, GameLocationSource source)
|
|
||||||
{
|
|
||||||
return factory.Create(source).LocateGamePathAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ namespace Snap.Hutao.Service.Game.Locator;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
[Injection(InjectAs.Transient, typeof(IGameLocator), Key = GameLocationSource.Manual)]
|
[Injection(InjectAs.Transient)]
|
||||||
internal sealed partial class ManualGameLocator : IGameLocator
|
internal sealed partial class ManualGameLocator : IGameLocator
|
||||||
{
|
{
|
||||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||||
@@ -26,7 +26,7 @@ internal sealed partial class ManualGameLocator : IGameLocator
|
|||||||
if (isPickerOk)
|
if (isPickerOk)
|
||||||
{
|
{
|
||||||
string fileName = System.IO.Path.GetFileName(file);
|
string fileName = System.IO.Path.GetFileName(file);
|
||||||
if (fileName.ToUpperInvariant() is GameConstants.YuanShenFileNameUpper or GameConstants.GenshinImpactFileNameUpper)
|
if (fileName is GameConstants.YuanShenFileName or GameConstants.GenshinImpactFileName)
|
||||||
{
|
{
|
||||||
return ValueTask.FromResult<ValueResult<bool, string>>(new(true, file));
|
return ValueTask.FromResult<ValueResult<bool, string>>(new(true, file));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ namespace Snap.Hutao.Service.Game.Locator;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
[Injection(InjectAs.Transient, typeof(IGameLocator), Key = GameLocationSource.Registry)]
|
[Injection(InjectAs.Transient)]
|
||||||
internal sealed partial class RegistryLauncherLocator : IGameLocator
|
internal sealed partial class RegistryLauncherLocator : IGameLocator
|
||||||
{
|
{
|
||||||
private const string RegistryKeyName = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神";
|
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -30,37 +29,50 @@ internal sealed partial class RegistryLauncherLocator : IGameLocator
|
|||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
string? path = Path.GetDirectoryName(result.Value);
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
|
||||||
string configPath = Path.Combine(path, GameConstants.ConfigFileName);
|
|
||||||
|
|
||||||
string? escapedPath;
|
|
||||||
using (FileStream stream = File.OpenRead(configPath))
|
|
||||||
{
|
{
|
||||||
IEnumerable<IniElement> elements = IniSerializer.Deserialize(stream);
|
string? path = Path.GetDirectoryName(result.Value);
|
||||||
escapedPath = elements
|
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||||
.OfType<IniParameter>()
|
string configPath = Path.Combine(path, GameConstants.ConfigFileName);
|
||||||
.FirstOrDefault(p => p.Key == "game_install_path")?.Value;
|
string? escapedPath;
|
||||||
}
|
using (FileStream stream = File.OpenRead(configPath))
|
||||||
|
{
|
||||||
|
IEnumerable<IniElement> elements = IniSerializer.Deserialize(stream);
|
||||||
|
escapedPath = elements
|
||||||
|
.OfType<IniParameter>()
|
||||||
|
.FirstOrDefault(p => p.Key == "game_install_path")?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(escapedPath))
|
if (escapedPath is not null)
|
||||||
{
|
{
|
||||||
string gamePath = Path.Combine(Unescape(escapedPath), GameConstants.YuanShenFileName);
|
string gamePath = Path.Combine(Unescape(escapedPath), GameConstants.YuanShenFileName);
|
||||||
return new(true, gamePath);
|
return new(true, gamePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(false, string.Empty);
|
return new(false, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ValueResult<bool, string> LocateInternal(string valueName)
|
private static ValueResult<bool, string> LocateInternal(string key)
|
||||||
{
|
{
|
||||||
if (Registry.GetValue(RegistryKeyName, valueName, null) is string path)
|
using (RegistryKey? uninstallKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\原神"))
|
||||||
{
|
{
|
||||||
return new(true, path);
|
if (uninstallKey is not null)
|
||||||
|
{
|
||||||
|
if (uninstallKey.GetValue(key) is string path)
|
||||||
|
{
|
||||||
|
return new(true, path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new(false, default!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new(false, default!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(false, default!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Unescape(string str)
|
private static string Unescape(string str)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.Game.Locator;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
[Injection(InjectAs.Transient, typeof(IGameLocator), Key = GameLocationSource.UnityLog)]
|
[Injection(InjectAs.Transient)]
|
||||||
internal sealed partial class UnityLogGameLocator : IGameLocator
|
internal sealed partial class UnityLogGameLocator : IGameLocator
|
||||||
{
|
{
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ internal sealed partial class GamePackageService : IGamePackageService
|
|||||||
|
|
||||||
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress)
|
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress)
|
||||||
{
|
{
|
||||||
if (!launchOptions.TryGetGameDirectoryAndGameFileName(out string? gameFolder, out string? gameFileName))
|
if (!launchOptions.TryGetGameFolderAndFileName(out string? gameFolder, out string? gameFileName))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,8 @@ internal sealed partial class GamePackageService : IGamePackageService
|
|||||||
|
|
||||||
if (!launchScheme.ExecutableMatches(gameFileName))
|
if (!launchScheme.ExecutableMatches(gameFileName))
|
||||||
{
|
{
|
||||||
// We can't start the game when we failed to convert game
|
// We can't start the game
|
||||||
|
// when we failed to convert game
|
||||||
if (!await packageConverter.EnsureGameResourceAsync(launchScheme, resource, gameFolder, progress).ConfigureAwait(false))
|
if (!await packageConverter.EnsureGameResourceAsync(launchScheme, resource, gameFolder, progress).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -66,13 +67,6 @@ internal sealed partial class GamePackageService : IGamePackageService
|
|||||||
|
|
||||||
private static bool CheckDirectoryPermissions(string folder)
|
private static bool CheckDirectoryPermissions(string folder)
|
||||||
{
|
{
|
||||||
// Program Files has special permissions limitation.
|
|
||||||
string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
|
||||||
if (folder.StartsWith(programFiles, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string tempFilePath = Path.Combine(folder, $"{Guid.NewGuid():N}.tmp");
|
string tempFilePath = Path.Combine(folder, $"{Guid.NewGuid():N}.tmp");
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ namespace Snap.Hutao.Service.Game.Package;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[DebuggerDisplay("Action:{Type} Target:{Target} Cache:{Cache}")]
|
[DebuggerDisplay("Action:{Type} Target:{Target} Cache:{Cache}")]
|
||||||
internal readonly struct PackageItemOperationInfo
|
internal readonly struct ItemOperationInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 操作的类型
|
/// 操作的类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly PackageItemOperationType Type;
|
public readonly ItemOperationType Type;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 目标文件
|
/// 目标文件
|
||||||
@@ -33,7 +33,7 @@ internal readonly struct PackageItemOperationInfo
|
|||||||
/// <param name="type">操作类型</param>
|
/// <param name="type">操作类型</param>
|
||||||
/// <param name="remote">远程</param>
|
/// <param name="remote">远程</param>
|
||||||
/// <param name="local">本地</param>
|
/// <param name="local">本地</param>
|
||||||
public PackageItemOperationInfo(PackageItemOperationType type, VersionItem remote, VersionItem local)
|
public ItemOperationInfo(ItemOperationType type, VersionItem remote, VersionItem local)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
Remote = remote;
|
Remote = remote;
|
||||||
@@ -7,7 +7,7 @@ namespace Snap.Hutao.Service.Game.Package;
|
|||||||
/// 包文件操作的类型
|
/// 包文件操作的类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal enum PackageItemOperationType
|
internal enum ItemOperationType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 需要备份
|
/// 需要备份
|
||||||
@@ -6,7 +6,7 @@ using static Snap.Hutao.Service.Game.GameConstants;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Service.Game.Package;
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
internal readonly struct PackageConverterFileSystemContext
|
internal readonly struct PackageConvertContext
|
||||||
{
|
{
|
||||||
public readonly string GameFolder;
|
public readonly string GameFolder;
|
||||||
public readonly string ServerCacheFolder;
|
public readonly string ServerCacheFolder;
|
||||||
@@ -22,7 +22,7 @@ internal readonly struct PackageConverterFileSystemContext
|
|||||||
public readonly string ScatteredFilesUrl;
|
public readonly string ScatteredFilesUrl;
|
||||||
public readonly string PkgVersionUrl;
|
public readonly string PkgVersionUrl;
|
||||||
|
|
||||||
public PackageConverterFileSystemContext(bool isTargetOversea, string dataFolder, string gameFolder, string scatteredFilesUrl)
|
public PackageConvertContext(bool isTargetOversea, string dataFolder, string gameFolder, string scatteredFilesUrl)
|
||||||
{
|
{
|
||||||
GameFolder = gameFolder;
|
GameFolder = gameFolder;
|
||||||
ServerCacheFolder = Path.Combine(dataFolder, "ServerCache");
|
ServerCacheFolder = Path.Combine(dataFolder, "ServerCache");
|
||||||
@@ -37,8 +37,7 @@ internal readonly struct PackageConverterFileSystemContext
|
|||||||
? (YuanShenData, GenshinImpactData)
|
? (YuanShenData, GenshinImpactData)
|
||||||
: (GenshinImpactData, YuanShenData);
|
: (GenshinImpactData, YuanShenData);
|
||||||
|
|
||||||
FromDataFolder = Path.Combine(GameFolder, FromDataFolderName);
|
(FromDataFolder, ToDataFolder) = (Path.Combine(GameFolder, FromDataFolderName), Path.Combine(GameFolder, ToDataFolderName));
|
||||||
ToDataFolder = Path.Combine(GameFolder, ToDataFolderName);
|
|
||||||
|
|
||||||
ScatteredFilesUrl = scatteredFilesUrl;
|
ScatteredFilesUrl = scatteredFilesUrl;
|
||||||
PkgVersionUrl = $"{scatteredFilesUrl}/pkg_version";
|
PkgVersionUrl = $"{scatteredFilesUrl}/pkg_version";
|
||||||
@@ -15,7 +15,6 @@ using System.IO.Compression;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using static Snap.Hutao.Service.Game.GameConstants;
|
using static Snap.Hutao.Service.Game.GameConstants;
|
||||||
using RelativePathVersionItemDictionary = System.Collections.Generic.Dictionary<string, Snap.Hutao.Service.Game.Package.VersionItem>;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game.Package;
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
@@ -59,15 +58,15 @@ internal sealed partial class PackageConverter
|
|||||||
string scatteredFilesUrl = gameResource.Game.Latest.DecompressedPath;
|
string scatteredFilesUrl = gameResource.Game.Latest.DecompressedPath;
|
||||||
string pkgVersionUrl = $"{scatteredFilesUrl}/{PackageVersion}";
|
string pkgVersionUrl = $"{scatteredFilesUrl}/{PackageVersion}";
|
||||||
|
|
||||||
PackageConverterFileSystemContext context = new(targetScheme.IsOversea, runtimeOptions.DataFolder, gameFolder, scatteredFilesUrl);
|
PackageConvertContext context = new(targetScheme.IsOversea, runtimeOptions.DataFolder, gameFolder, scatteredFilesUrl);
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
progress.Report(new(SH.ServiceGamePackageRequestPackageVerion));
|
progress.Report(new(SH.ServiceGamePackageRequestPackageVerion));
|
||||||
RelativePathVersionItemDictionary remoteItems = await GetRemoteItemsAsync(pkgVersionUrl).ConfigureAwait(false);
|
Dictionary<string, VersionItem> remoteItems = await GetRemoteItemsAsync(pkgVersionUrl).ConfigureAwait(false);
|
||||||
RelativePathVersionItemDictionary localItems = await GetLocalItemsAsync(gameFolder).ConfigureAwait(false);
|
Dictionary<string, VersionItem> localItems = await GetLocalItemsAsync(gameFolder).ConfigureAwait(false);
|
||||||
|
|
||||||
// Step 2
|
// Step 2
|
||||||
List<PackageItemOperationInfo> diffOperations = GetItemOperationInfos(remoteItems, localItems).ToList();
|
List<ItemOperationInfo> diffOperations = GetItemOperationInfos(remoteItems, localItems).ToList();
|
||||||
diffOperations.SortBy(i => i.Type);
|
diffOperations.SortBy(i => i.Type);
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
@@ -117,16 +116,16 @@ internal sealed partial class PackageConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<PackageItemOperationInfo> GetItemOperationInfos(RelativePathVersionItemDictionary remote, RelativePathVersionItemDictionary local)
|
private static IEnumerable<ItemOperationInfo> GetItemOperationInfos(Dictionary<string, VersionItem> remote, Dictionary<string, VersionItem> local)
|
||||||
{
|
{
|
||||||
foreach ((string remoteName, VersionItem remoteItem) in remote)
|
foreach ((string remoteName, VersionItem remoteItem) in remote)
|
||||||
{
|
{
|
||||||
if (local.TryGetValue(remoteName, out VersionItem? localItem))
|
if (local.TryGetValue(remoteName, out VersionItem? localItem))
|
||||||
{
|
{
|
||||||
if (!(remoteItem.FileSize == localItem.FileSize && remoteItem.Md5.Equals(localItem.Md5, StringComparison.OrdinalIgnoreCase)))
|
if (!remoteItem.Md5.Equals(localItem.Md5, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// 本地发现了同名且不同 MD5 的项,需要替换为服务器上的项
|
// 本地发现了同名且不同 MD5 的项,需要替换为服务器上的项
|
||||||
yield return new(PackageItemOperationType.Replace, remoteItem, localItem);
|
yield return new(ItemOperationType.Replace, remoteItem, localItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 同名同MD5,跳过
|
// 同名同MD5,跳过
|
||||||
@@ -135,22 +134,22 @@ internal sealed partial class PackageConverter
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 本地没有发现同名项
|
// 本地没有发现同名项
|
||||||
yield return new(PackageItemOperationType.Add, remoteItem, remoteItem);
|
yield return new(ItemOperationType.Add, remoteItem, remoteItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ((_, VersionItem localItem) in local)
|
foreach ((_, VersionItem localItem) in local)
|
||||||
{
|
{
|
||||||
yield return new(PackageItemOperationType.Backup, localItem, localItem);
|
yield return new(ItemOperationType.Backup, localItem, localItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GeneratedRegex("^(?:YuanShen_Data|GenshinImpact_Data)(?=/)")]
|
[GeneratedRegex("^(?:YuanShen_Data|GenshinImpact_Data)(?=/)")]
|
||||||
private static partial Regex DataFolderRegex();
|
private static partial Regex DataFolderRegex();
|
||||||
|
|
||||||
private async ValueTask<RelativePathVersionItemDictionary> GetVersionItemsAsync(Stream stream)
|
private async ValueTask<Dictionary<string, VersionItem>> GetVersionItemsAsync(Stream stream)
|
||||||
{
|
{
|
||||||
RelativePathVersionItemDictionary results = [];
|
Dictionary<string, VersionItem> results = [];
|
||||||
using (StreamReader reader = new(stream))
|
using (StreamReader reader = new(stream))
|
||||||
{
|
{
|
||||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is { Length: > 0 } row)
|
while (await reader.ReadLineAsync().ConfigureAwait(false) is { Length: > 0 } row)
|
||||||
@@ -165,7 +164,7 @@ internal sealed partial class PackageConverter
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<RelativePathVersionItemDictionary> GetRemoteItemsAsync(string pkgVersionUrl)
|
private async ValueTask<Dictionary<string, VersionItem>> GetRemoteItemsAsync(string pkgVersionUrl)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -180,7 +179,7 @@ internal sealed partial class PackageConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<RelativePathVersionItemDictionary> GetLocalItemsAsync(string gameFolder)
|
private async ValueTask<Dictionary<string, VersionItem>> GetLocalItemsAsync(string gameFolder)
|
||||||
{
|
{
|
||||||
using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, PackageVersion)))
|
using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, PackageVersion)))
|
||||||
{
|
{
|
||||||
@@ -188,23 +187,23 @@ internal sealed partial class PackageConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask PrepareCacheFilesAsync(List<PackageItemOperationInfo> operations, PackageConverterFileSystemContext context, IProgress<PackageReplaceStatus> progress)
|
private async ValueTask PrepareCacheFilesAsync(List<ItemOperationInfo> operations, PackageConvertContext context, IProgress<PackageReplaceStatus> progress)
|
||||||
{
|
{
|
||||||
foreach (PackageItemOperationInfo info in operations)
|
foreach (ItemOperationInfo info in operations)
|
||||||
{
|
{
|
||||||
switch (info.Type)
|
switch (info.Type)
|
||||||
{
|
{
|
||||||
case PackageItemOperationType.Backup:
|
case ItemOperationType.Backup:
|
||||||
continue;
|
continue;
|
||||||
case PackageItemOperationType.Replace:
|
case ItemOperationType.Replace:
|
||||||
case PackageItemOperationType.Add:
|
case ItemOperationType.Add:
|
||||||
await SkipOrDownloadAsync(info, context, progress).ConfigureAwait(false);
|
await SkipOrDownloadAsync(info, context, progress).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask SkipOrDownloadAsync(PackageItemOperationInfo info, PackageConverterFileSystemContext context, IProgress<PackageReplaceStatus> progress)
|
private async ValueTask SkipOrDownloadAsync(ItemOperationInfo info, PackageConvertContext context, IProgress<PackageReplaceStatus> progress)
|
||||||
{
|
{
|
||||||
// 还原正确的远程地址
|
// 还原正确的远程地址
|
||||||
string remoteName = string.Format(CultureInfo.CurrentCulture, info.Remote.RelativePath, context.ToDataFolderName);
|
string remoteName = string.Format(CultureInfo.CurrentCulture, info.Remote.RelativePath, context.ToDataFolderName);
|
||||||
@@ -258,16 +257,16 @@ internal sealed partial class PackageConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<bool> ReplaceGameResourceAsync(List<PackageItemOperationInfo> operations, PackageConverterFileSystemContext context, IProgress<PackageReplaceStatus> progress)
|
private async ValueTask<bool> ReplaceGameResourceAsync(List<ItemOperationInfo> operations, PackageConvertContext context, IProgress<PackageReplaceStatus> progress)
|
||||||
{
|
{
|
||||||
// 执行下载与移动操作
|
// 执行下载与移动操作
|
||||||
foreach (PackageItemOperationInfo info in operations)
|
foreach (ItemOperationInfo info in operations)
|
||||||
{
|
{
|
||||||
(bool moveToBackup, bool moveToTarget) = info.Type switch
|
(bool moveToBackup, bool moveToTarget) = info.Type switch
|
||||||
{
|
{
|
||||||
PackageItemOperationType.Backup => (true, false),
|
ItemOperationType.Backup => (true, false),
|
||||||
PackageItemOperationType.Replace => (true, true),
|
ItemOperationType.Replace => (true, true),
|
||||||
PackageItemOperationType.Add => (false, true),
|
ItemOperationType.Add => (false, true),
|
||||||
_ => (false, false),
|
_ => (false, false),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -322,7 +321,7 @@ internal sealed partial class PackageConverter
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask ReplacePackageVersionFilesAsync(PackageConverterFileSystemContext context)
|
private async ValueTask ReplacePackageVersionFilesAsync(PackageConvertContext context)
|
||||||
{
|
{
|
||||||
foreach (string versionFilePath in Directory.EnumerateFiles(context.GameFolder, "*pkg_version"))
|
foreach (string versionFilePath in Directory.EnumerateFiles(context.GameFolder, "*pkg_version"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Common;
|
using CommunityToolkit.Common;
|
||||||
|
using Snap.Hutao.Core.Abstraction;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game.Package;
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包更新状态
|
/// 包更新状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class PackageReplaceStatus
|
internal sealed class PackageReplaceStatus : ICloneable<PackageReplaceStatus>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的包更新状态
|
/// 构造一个新的包更新状态
|
||||||
@@ -33,6 +34,10 @@ internal sealed class PackageReplaceStatus
|
|||||||
Description = $"{Converters.ToFileSizeString(bytesRead)}/{Converters.ToFileSizeString(totalBytes)}";
|
Description = $"{Converters.ToFileSizeString(bytesRead)}/{Converters.ToFileSizeString(totalBytes)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PackageReplaceStatus()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public string Name { get; set; } = default!;
|
public string Name { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,4 +54,19 @@ internal sealed class PackageReplaceStatus
|
|||||||
/// 是否有进度
|
/// 是否有进度
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsIndeterminate { get => Percent < 0; }
|
public bool IsIndeterminate { get => Percent < 0; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 克隆
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>克隆的实例</returns>
|
||||||
|
public PackageReplaceStatus Clone()
|
||||||
|
{
|
||||||
|
// 进度需要在主线程上创建
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Description = Description,
|
||||||
|
Percent = Percent,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Service.Game.PathAbstraction;
|
|||||||
[Injection(InjectAs.Singleton, typeof(IGamePathService))]
|
[Injection(InjectAs.Singleton, typeof(IGamePathService))]
|
||||||
internal sealed partial class GamePathService : IGamePathService
|
internal sealed partial class GamePathService : IGamePathService
|
||||||
{
|
{
|
||||||
private readonly IGameLocatorFactory gameLocatorFactory;
|
private readonly IServiceProvider serviceProvider;
|
||||||
private readonly LaunchOptions launchOptions;
|
private readonly LaunchOptions launchOptions;
|
||||||
|
|
||||||
public async ValueTask<ValueResult<bool, string>> SilentGetGamePathAsync()
|
public async ValueTask<ValueResult<bool, string>> SilentGetGamePathAsync()
|
||||||
@@ -17,16 +17,24 @@ internal sealed partial class GamePathService : IGamePathService
|
|||||||
// Cannot find in setting
|
// Cannot find in setting
|
||||||
if (string.IsNullOrEmpty(launchOptions.GamePath))
|
if (string.IsNullOrEmpty(launchOptions.GamePath))
|
||||||
{
|
{
|
||||||
|
IGameLocatorFactory locatorFactory = serviceProvider.GetRequiredService<IGameLocatorFactory>();
|
||||||
|
|
||||||
bool isOk;
|
bool isOk;
|
||||||
string path;
|
string path;
|
||||||
|
|
||||||
// Try locate by unity log
|
// Try locate by unity log
|
||||||
(isOk, path) = await gameLocatorFactory.LocateAsync(GameLocationSource.UnityLog).ConfigureAwait(false);
|
(isOk, path) = await locatorFactory
|
||||||
|
.Create(GameLocationSource.UnityLog)
|
||||||
|
.LocateGamePathAsync()
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (!isOk)
|
if (!isOk)
|
||||||
{
|
{
|
||||||
// Try locate by registry
|
// Try locate by registry
|
||||||
(isOk, path) = await gameLocatorFactory.LocateAsync(GameLocationSource.Registry).ConfigureAwait(false);
|
(isOk, path) = await locatorFactory
|
||||||
|
.Create(GameLocationSource.Registry)
|
||||||
|
.LocateGamePathAsync()
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOk)
|
if (isOk)
|
||||||
@@ -40,11 +48,13 @@ internal sealed partial class GamePathService : IGamePathService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(launchOptions.GamePath))
|
if (!string.IsNullOrEmpty(launchOptions.GamePath))
|
||||||
|
{
|
||||||
|
return new(true, launchOptions.GamePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return new(false, default!);
|
return new(false, default!);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(true, launchOptions.GamePath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Core;
|
using Snap.Hutao.Core;
|
||||||
using Snap.Hutao.Factory.Progress;
|
|
||||||
using Snap.Hutao.Service.Discord;
|
using Snap.Hutao.Service.Discord;
|
||||||
using Snap.Hutao.Service.Game.Scheme;
|
using Snap.Hutao.Service.Game.Scheme;
|
||||||
using Snap.Hutao.Service.Game.Unlocker;
|
using Snap.Hutao.Service.Game.Unlocker;
|
||||||
@@ -19,7 +18,6 @@ namespace Snap.Hutao.Service.Game.Process;
|
|||||||
internal sealed partial class GameProcessService : IGameProcessService
|
internal sealed partial class GameProcessService : IGameProcessService
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
private readonly IProgressFactory progressFactory;
|
|
||||||
private readonly IDiscordService discordService;
|
private readonly IDiscordService discordService;
|
||||||
private readonly RuntimeOptions runtimeOptions;
|
private readonly RuntimeOptions runtimeOptions;
|
||||||
private readonly LaunchOptions launchOptions;
|
private readonly LaunchOptions launchOptions;
|
||||||
@@ -111,13 +109,13 @@ internal sealed partial class GameProcessService : IGameProcessService
|
|||||||
// https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html
|
// https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html
|
||||||
// https://docs.unity3d.com/2017.4/Documentation/Manual/CommandLineArguments.html
|
// https://docs.unity3d.com/2017.4/Documentation/Manual/CommandLineArguments.html
|
||||||
commandLine = new CommandLineBuilder()
|
commandLine = new CommandLineBuilder()
|
||||||
.AppendIf(launchOptions.IsBorderless, "-popupwindow")
|
.AppendIf("-popupwindow", launchOptions.IsBorderless)
|
||||||
.AppendIf(launchOptions.IsExclusive, "-window-mode", "exclusive")
|
.AppendIf("-window-mode", launchOptions.IsExclusive, "exclusive")
|
||||||
.Append("-screen-fullscreen", launchOptions.IsFullScreen ? 1 : 0)
|
.Append("-screen-fullscreen", launchOptions.IsFullScreen ? 1 : 0)
|
||||||
.AppendIf(launchOptions.IsScreenWidthEnabled, "-screen-width", launchOptions.ScreenWidth)
|
.AppendIf("-screen-width", launchOptions.IsScreenWidthEnabled, launchOptions.ScreenWidth)
|
||||||
.AppendIf(launchOptions.IsScreenHeightEnabled, "-screen-height", launchOptions.ScreenHeight)
|
.AppendIf("-screen-height", launchOptions.IsScreenHeightEnabled, launchOptions.ScreenHeight)
|
||||||
.AppendIf(launchOptions.IsMonitorEnabled, "-monitor", launchOptions.Monitor.Value)
|
.AppendIf("-monitor", launchOptions.IsMonitorEnabled, launchOptions.Monitor.Value)
|
||||||
.AppendIf(launchOptions.IsUseCloudThirdPartyMobile, "-platform_type CLOUD_THIRD_PARTY_MOBILE")
|
.AppendIf("-platform_type CLOUD_THIRD_PARTY_MOBILE", launchOptions.IsUseCloudThirdPartyMobile)
|
||||||
.ToString();
|
.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +138,7 @@ internal sealed partial class GameProcessService : IGameProcessService
|
|||||||
IGameFpsUnlocker unlocker = serviceProvider.CreateInstance<GameFpsUnlocker>(game);
|
IGameFpsUnlocker unlocker = serviceProvider.CreateInstance<GameFpsUnlocker>(game);
|
||||||
#pragma warning restore CA1859
|
#pragma warning restore CA1859
|
||||||
UnlockTimingOptions options = new(100, 20000, 3000);
|
UnlockTimingOptions options = new(100, 20000, 3000);
|
||||||
IProgress<UnlockerStatus> lockerProgress = progressFactory.CreateForMainThread<UnlockerStatus>(unlockStatus => progress.Report(LaunchStatus.FromUnlockStatus(unlockStatus)));
|
Progress<UnlockerStatus> lockerProgress = new(unlockStatus => progress.Report(LaunchStatus.FromUnlockStatus(unlockStatus)));
|
||||||
return unlocker.UnlockAsync(options, lockerProgress, token);
|
return unlocker.UnlockAsync(options, lockerProgress, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,11 +59,11 @@ internal class LaunchScheme : IEquatable<ChannelOptions>
|
|||||||
|
|
||||||
public static bool ExecutableIsOversea(string gameFileName)
|
public static bool ExecutableIsOversea(string gameFileName)
|
||||||
{
|
{
|
||||||
return gameFileName.ToUpperInvariant() switch
|
return gameFileName switch
|
||||||
{
|
{
|
||||||
GameConstants.GenshinImpactFileNameUpper => true,
|
GameConstants.GenshinImpactFileName => true,
|
||||||
GameConstants.YuanShenFileNameUpper => false,
|
GameConstants.YuanShenFileName => false,
|
||||||
_ => throw Requires.Fail("Invalid game executable file name:{0}", gameFileName),
|
_ => throw Requires.Fail("无效的游戏可执行文件名称:{0}", gameFileName),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Model.Entity.Primitive;
|
|
||||||
using Snap.Hutao.Model.Intrinsic;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game.Scheme;
|
|
||||||
|
|
||||||
internal static class LaunchSchemeExtension
|
|
||||||
{
|
|
||||||
public static SchemeType GetSchemeType(this LaunchScheme scheme)
|
|
||||||
{
|
|
||||||
return (scheme.Channel, scheme.IsOversea) switch
|
|
||||||
{
|
|
||||||
(ChannelType.Bili, false) => SchemeType.ChineseBilibili,
|
|
||||||
(_, false) => SchemeType.ChineseOfficial,
|
|
||||||
(_, true) => SchemeType.Oversea,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -228,7 +228,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
|||||||
|
|
||||||
nuint rip = localMemoryUserAssemblyAddress + (uint)offset;
|
nuint rip = localMemoryUserAssemblyAddress + (uint)offset;
|
||||||
rip += 5U;
|
rip += 5U;
|
||||||
rip += (nuint)(*(int*)(rip + 2U) + 6);
|
rip += (nuint)(*(int*)(rip + 2) + 6);
|
||||||
|
|
||||||
nuint address = userAssembly.Address + (rip - localMemoryUserAssemblyAddress);
|
nuint address = userAssembly.Address + (rip - localMemoryUserAssemblyAddress);
|
||||||
|
|
||||||
@@ -236,8 +236,6 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
|||||||
SpinWait.SpinUntil(() => UnsafeReadProcessMemory(gameProcess, address, out ptr) && ptr != 0);
|
SpinWait.SpinUntil(() => UnsafeReadProcessMemory(gameProcess, address, out ptr) && ptr != 0);
|
||||||
|
|
||||||
rip = ptr - unityPlayer.Address + localMemoryUnityPlayerAddress;
|
rip = ptr - unityPlayer.Address + localMemoryUnityPlayerAddress;
|
||||||
|
|
||||||
// CALL or JMP
|
|
||||||
while (*(byte*)rip == 0xE8 || *(byte*)rip == 0xE9)
|
while (*(byte*)rip == 0xE8 || *(byte*)rip == 0xE9)
|
||||||
{
|
{
|
||||||
rip += (nuint)(*(int*)(rip + 1) + 5);
|
rip += (nuint)(*(int*)(rip + 1) + 5);
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ internal interface IUpdateService
|
|||||||
{
|
{
|
||||||
ValueTask<bool> CheckForUpdateAndDownloadAsync(IProgress<UpdateStatus> progress, CancellationToken token = default);
|
ValueTask<bool> CheckForUpdateAndDownloadAsync(IProgress<UpdateStatus> progress, CancellationToken token = default);
|
||||||
|
|
||||||
ValueTask LaunchUpdaterAsync();
|
void LaunchInstaller();
|
||||||
}
|
}
|
||||||
@@ -4,14 +4,12 @@
|
|||||||
using Snap.Hutao.Core;
|
using Snap.Hutao.Core;
|
||||||
using Snap.Hutao.Core.IO.Hashing;
|
using Snap.Hutao.Core.IO.Hashing;
|
||||||
using Snap.Hutao.Core.IO.Http.Sharding;
|
using Snap.Hutao.Core.IO.Http.Sharding;
|
||||||
using Snap.Hutao.Core.Setting;
|
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
using Snap.Hutao.Web.Hutao;
|
using Snap.Hutao.Web.Hutao;
|
||||||
using Snap.Hutao.Web.Hutao.Response;
|
using Snap.Hutao.Web.Hutao.Response;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Update;
|
namespace Snap.Hutao.Service.Update;
|
||||||
|
|
||||||
@@ -19,8 +17,6 @@ namespace Snap.Hutao.Service.Update;
|
|||||||
[Injection(InjectAs.Singleton, typeof(IUpdateService))]
|
[Injection(InjectAs.Singleton, typeof(IUpdateService))]
|
||||||
internal sealed partial class UpdateService : IUpdateService
|
internal sealed partial class UpdateService : IUpdateService
|
||||||
{
|
{
|
||||||
private const string UpdaterFilename = "Snap.Hutao.Deployment.exe";
|
|
||||||
|
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
public async ValueTask<bool> CheckForUpdateAndDownloadAsync(IProgress<UpdateStatus> progress, CancellationToken token = default)
|
public async ValueTask<bool> CheckForUpdateAndDownloadAsync(IProgress<UpdateStatus> progress, CancellationToken token = default)
|
||||||
@@ -41,22 +37,19 @@ internal sealed partial class UpdateService : IUpdateService
|
|||||||
HutaoVersionInformation versionInformation = response.Data;
|
HutaoVersionInformation versionInformation = response.Data;
|
||||||
string msixPath = GetUpdatePackagePath();
|
string msixPath = GetUpdatePackagePath();
|
||||||
|
|
||||||
if (!LocalSetting.Get(SettingKeys.OverrideUpdateVersionComparison, false))
|
if (scope.ServiceProvider.GetRequiredService<RuntimeOptions>().Version >= versionInformation.Version)
|
||||||
{
|
{
|
||||||
if (scope.ServiceProvider.GetRequiredService<RuntimeOptions>().Version >= versionInformation.Version)
|
if (File.Exists(msixPath))
|
||||||
{
|
{
|
||||||
if (File.Exists(msixPath))
|
File.Delete(msixPath);
|
||||||
{
|
|
||||||
File.Delete(msixPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.Report(new(versionInformation.Version.ToString(), 0, 0));
|
progress.Report(new(versionInformation.Version.ToString(), 0, 0));
|
||||||
|
|
||||||
if (versionInformation.Sha256 is not { Length: > 0 } sha256)
|
if (versionInformation.Sha256 is not { } sha256)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -70,27 +63,12 @@ internal sealed partial class UpdateService : IUpdateService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask LaunchUpdaterAsync()
|
public void LaunchInstaller()
|
||||||
{
|
{
|
||||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
|
||||||
string updaterTargetPath = runtimeOptions.GetDataFolderUpdateCacheFolderFile(UpdaterFilename);
|
|
||||||
|
|
||||||
Uri updaterSourceUri = $"ms-appx:///{UpdaterFilename}".ToUri();
|
|
||||||
StorageFile updaterFile = await StorageFile.GetFileFromApplicationUriAsync(updaterSourceUri);
|
|
||||||
await updaterFile.OverwriteCopyAsync(updaterTargetPath).ConfigureAwait(false);
|
|
||||||
|
|
||||||
string commandLine = new CommandLineBuilder()
|
|
||||||
.Append("--package-path", GetUpdatePackagePath(runtimeOptions))
|
|
||||||
.Append("--family-name", runtimeOptions.FamilyName)
|
|
||||||
.Append("--update-behavior", true)
|
|
||||||
.ToString();
|
|
||||||
|
|
||||||
Process.Start(new ProcessStartInfo()
|
Process.Start(new ProcessStartInfo()
|
||||||
{
|
{
|
||||||
Arguments = commandLine,
|
|
||||||
WindowStyle = ProcessWindowStyle.Minimized,
|
|
||||||
FileName = updaterTargetPath,
|
|
||||||
UseShellExecute = true,
|
UseShellExecute = true,
|
||||||
|
FileName = GetUpdatePackagePath(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,10 +78,12 @@ internal sealed partial class UpdateService : IUpdateService
|
|||||||
return string.Equals(localHash, remoteHash, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(localHash, remoteHash, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetUpdatePackagePath(RuntimeOptions? runtimeOptions = default)
|
private string GetUpdatePackagePath()
|
||||||
{
|
{
|
||||||
runtimeOptions ??= serviceProvider.GetRequiredService<RuntimeOptions>();
|
string dataFolder = serviceProvider.GetRequiredService<RuntimeOptions>().DataFolder;
|
||||||
return runtimeOptions.GetDataFolderUpdateCacheFolderFile("Snap.Hutao.msix");
|
string directory = Path.Combine(dataFolder, "UpdateCache");
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
return Path.Combine(directory, "Snap.Hutao.msix");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<bool> DownloadUpdatePackageAsync(HutaoVersionInformation versionInformation, string filePath, IProgress<UpdateStatus> progress, CancellationToken token = default)
|
private async ValueTask<bool> DownloadUpdatePackageAsync(HutaoVersionInformation versionInformation, string filePath, IProgress<UpdateStatus> progress, CancellationToken token = default)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Snap.Hutao.Service.User;
|
|||||||
|
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
[Injection(InjectAs.Singleton, typeof(IUserCollectionService))]
|
[Injection(InjectAs.Singleton, typeof(IUserCollectionService))]
|
||||||
internal sealed partial class UserCollectionService : IUserCollectionService, IDisposable
|
internal sealed partial class UserCollectionService : IUserCollectionService
|
||||||
{
|
{
|
||||||
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
||||||
private readonly IUserInitializationService userInitializationService;
|
private readonly IUserInitializationService userInitializationService;
|
||||||
@@ -22,7 +22,7 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID
|
|||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
private readonly IMessenger messenger;
|
private readonly IMessenger messenger;
|
||||||
|
|
||||||
private readonly SemaphoreSlim throttler = new(1);
|
private readonly Throttler throttler = new();
|
||||||
|
|
||||||
private ObservableCollection<BindingUser>? userCollection;
|
private ObservableCollection<BindingUser>? userCollection;
|
||||||
private Dictionary<string, BindingUser>? midUserMap;
|
private Dictionary<string, BindingUser>? midUserMap;
|
||||||
@@ -38,9 +38,7 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID
|
|||||||
|
|
||||||
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
||||||
{
|
{
|
||||||
// Force run in background thread, otherwise will cause reentrance
|
using (await throttler.ThrottleAsync().ConfigureAwait(false))
|
||||||
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
|
||||||
using (await throttler.EnterAsync().ConfigureAwait(false))
|
|
||||||
{
|
{
|
||||||
if (userCollection is null)
|
if (userCollection is null)
|
||||||
{
|
{
|
||||||
@@ -133,7 +131,17 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
return uidUserGameRoleMap.GetValueOrDefault(uid);
|
try
|
||||||
|
{
|
||||||
|
return uidUserGameRoleMap[uid];
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// Sequence contains more than one matching element
|
||||||
|
// TODO: return a specialize UserGameRole to indicate error
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user)
|
public bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user)
|
||||||
@@ -178,9 +186,4 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID
|
|||||||
ArgumentNullException.ThrowIfNull(newUser.UserInfo);
|
ArgumentNullException.ThrowIfNull(newUser.UserInfo);
|
||||||
return new(UserOptionResult.Added, newUser.UserInfo.Uid);
|
return new(UserOptionResult.Added, newUser.UserInfo.Uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
throttler.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -204,10 +204,8 @@
|
|||||||
<AdditionalFiles Include="stylecop.json" />
|
<AdditionalFiles Include="stylecop.json" />
|
||||||
<AdditionalFiles Include="Resource\Localization\SH.resx" />
|
<AdditionalFiles Include="Resource\Localization\SH.resx" />
|
||||||
<AdditionalFiles Include="Resource\Localization\SH.en.resx" />
|
<AdditionalFiles Include="Resource\Localization\SH.en.resx" />
|
||||||
<AdditionalFiles Include="Resource\Localization\SH.id.resx" />
|
|
||||||
<AdditionalFiles Include="Resource\Localization\SH.ja.resx" />
|
<AdditionalFiles Include="Resource\Localization\SH.ja.resx" />
|
||||||
<AdditionalFiles Include="Resource\Localization\SH.ko.resx" />
|
<AdditionalFiles Include="Resource\Localization\SH.ko.resx" />
|
||||||
<AdditionalFiles Include="Resource\Localization\SH.ru.resx" />
|
|
||||||
<AdditionalFiles Include="Resource\Localization\SH.zh-Hant.resx" />
|
<AdditionalFiles Include="Resource\Localization\SH.zh-Hant.resx" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -303,19 +301,11 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.1" />
|
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.5.0" />
|
<PackageReference Include="Snap.Discord.GameSDK" Version="1.5.0" />
|
||||||
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.9.0">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.0.3">
|
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
VerticalAlignment="Bottom">
|
VerticalAlignment="Bottom">
|
||||||
<ComboBox
|
<ComboBox
|
||||||
DisplayMemberPath="Name"
|
DisplayMemberPath="Name"
|
||||||
ItemsSource="{Binding GameAccountsView}"
|
ItemsSource="{Binding GameAccounts}"
|
||||||
PlaceholderText="{shcm:ResourceString Name=ViewCardLaunchGameSelectAccountPlaceholder}"
|
PlaceholderText="{shcm:ResourceString Name=ViewCardLaunchGameSelectAccountPlaceholder}"
|
||||||
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
||||||
</shc:SizeRestrictedContentControl>
|
</shc:SizeRestrictedContentControl>
|
||||||
|
|||||||
@@ -76,15 +76,16 @@ internal partial class WebViewer : UserControl, IRecipient<UserChangedMessage>
|
|||||||
|
|
||||||
private async ValueTask InitializeAsync()
|
private async ValueTask InitializeAsync()
|
||||||
{
|
{
|
||||||
if (!isInitializingOrInitialized)
|
if (isInitializingOrInitialized)
|
||||||
{
|
{
|
||||||
isInitializingOrInitialized = true;
|
return;
|
||||||
|
|
||||||
await WebView.EnsureCoreWebView2Async();
|
|
||||||
WebView.CoreWebView2.DisableDevToolsForReleaseBuild();
|
|
||||||
WebView.CoreWebView2.DocumentTitleChanged += documentTitleChangedEventHander;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isInitializingOrInitialized = true;
|
||||||
|
|
||||||
|
await WebView.EnsureCoreWebView2Async();
|
||||||
|
WebView.CoreWebView2.DisableDevToolsForReleaseBuild();
|
||||||
|
WebView.CoreWebView2.DocumentTitleChanged += documentTitleChangedEventHander;
|
||||||
RefreshWebview2Content();
|
RefreshWebview2Content();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,9 +128,6 @@ internal partial class WebViewer : UserControl, IRecipient<UserChangedMessage>
|
|||||||
string source = SourceProvider.GetSource(userAndUid);
|
string source = SourceProvider.GetSource(userAndUid);
|
||||||
if (!string.IsNullOrEmpty(source))
|
if (!string.IsNullOrEmpty(source))
|
||||||
{
|
{
|
||||||
CoreWebView2Navigator navigator = new(coreWebView2);
|
|
||||||
await navigator.NavigateAsync("about:blank").ConfigureAwait(true);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await coreWebView2.Profile.ClearBrowsingDataAsync();
|
await coreWebView2.Profile.ClearBrowsingDataAsync();
|
||||||
@@ -140,6 +138,9 @@ internal partial class WebViewer : UserControl, IRecipient<UserChangedMessage>
|
|||||||
await coreWebView2.DeleteCookiesAsync(userAndUid.IsOversea).ConfigureAwait(true);
|
await coreWebView2.DeleteCookiesAsync(userAndUid.IsOversea).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoreWebView2Navigator navigator = new(coreWebView2);
|
||||||
|
await navigator.NavigateAsync("about:blank").ConfigureAwait(true);
|
||||||
|
|
||||||
coreWebView2
|
coreWebView2
|
||||||
.SetCookie(user.CookieToken, user.LToken, userAndUid.IsOversea)
|
.SetCookie(user.CookieToken, user.LToken, userAndUid.IsOversea)
|
||||||
.SetMobileUserAgent(userAndUid.IsOversea);
|
.SetMobileUserAgent(userAndUid.IsOversea);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.WinUI.Converters;
|
using CommunityToolkit.WinUI.Converters;
|
||||||
|
using Snap.Hutao.Control;
|
||||||
|
|
||||||
namespace Snap.Hutao.View.Converter;
|
namespace Snap.Hutao.View.Converter;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||||
xmlns:shccs="using:Snap.Hutao.Control.Collection.Selector"
|
xmlns:shccs="using:Snap.Hutao.Control.Collection.Selector"
|
||||||
xmlns:shch="using:Snap.Hutao.Control.Helper"
|
xmlns:shch="using:Snap.Hutao.Control.Helper"
|
||||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
|
||||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||||
xmlns:shvg="using:Snap.Hutao.ViewModel.Game"
|
xmlns:shvg="using:Snap.Hutao.ViewModel.Game"
|
||||||
@@ -198,7 +197,7 @@
|
|||||||
<Border Style="{StaticResource BorderCardStyle}">
|
<Border Style="{StaticResource BorderCardStyle}">
|
||||||
<ListView
|
<ListView
|
||||||
ItemTemplate="{StaticResource GameAccountListTemplate}"
|
ItemTemplate="{StaticResource GameAccountListTemplate}"
|
||||||
ItemsSource="{Binding GameAccountsView}"
|
ItemsSource="{Binding GameAccounts}"
|
||||||
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -347,20 +346,10 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid Visibility="{Binding GamePathSelectedAndValid, Converter={StaticResource BoolToVisibilityRevertConverter}}">
|
<Grid Visibility="{Binding GamePathSelectedAndValid, Converter={StaticResource BoolToVisibilityRevertConverter}}">
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Margin="128,0"
|
MaxWidth="600"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Spacing="3">
|
Spacing="3">
|
||||||
<shci:CachedImage
|
|
||||||
Width="120"
|
|
||||||
Height="120"
|
|
||||||
EnableLazyLoading="False"
|
|
||||||
Source="{StaticResource UI_EmotionIcon445}"/>
|
|
||||||
<TextBlock
|
|
||||||
Margin="0,5,0,21"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
|
||||||
Text="{shcm:ResourceString Name=ViewPageLaunchGameSelectGamePath}"/>
|
|
||||||
<Border Style="{ThemeResource BorderCardStyle}">
|
<Border Style="{ThemeResource BorderCardStyle}">
|
||||||
<ListView
|
<ListView
|
||||||
ItemTemplate="{StaticResource GamePathEntryListTemplate}"
|
ItemTemplate="{StaticResource GamePathEntryListTemplate}"
|
||||||
@@ -375,7 +364,9 @@
|
|||||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||||
IsClickEnabled="True">
|
IsClickEnabled="True">
|
||||||
<cwc:SettingsCard.Description>
|
<cwc:SettingsCard.Description>
|
||||||
<TextBlock Foreground="{ThemeResource SystemErrorTextColor}" Text="{shcm:ResourceString Name=ViewPageSettingSetGamePathHint}"/>
|
<StackPanel>
|
||||||
|
<TextBlock Foreground="{ThemeResource SystemErrorTextColor}" Text="{shcm:ResourceString Name=ViewPageSettingSetGamePathHint}"/>
|
||||||
|
</StackPanel>
|
||||||
</cwc:SettingsCard.Description>
|
</cwc:SettingsCard.Description>
|
||||||
</cwc:SettingsCard>
|
</cwc:SettingsCard>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -23,10 +23,7 @@
|
|||||||
|
|
||||||
<ScrollViewer shch:ScrollViewerHelper.LeftPanelMaxWidth="800" Style="{StaticResource TwoPanelScrollViewerStyle}">
|
<ScrollViewer shch:ScrollViewerHelper.LeftPanelMaxWidth="800" Style="{StaticResource TwoPanelScrollViewerStyle}">
|
||||||
<shch:ScrollViewerHelper.RightPanel>
|
<shch:ScrollViewerHelper.RightPanel>
|
||||||
<StackPanel
|
<StackPanel Width="360" Margin="0,16,16,16">
|
||||||
Width="360"
|
|
||||||
Margin="0,16,16,16"
|
|
||||||
Spacing="{StaticResource SettingsCardSpacing}">
|
|
||||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||||
<Grid Style="{ThemeResource GridCardStyle}">
|
<Grid Style="{ThemeResource GridCardStyle}">
|
||||||
<Border
|
<Border
|
||||||
@@ -86,27 +83,6 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<cwc:SettingsExpander
|
|
||||||
Header="{shcm:ResourceString Name=ViewPageSettingElevatedModeHeader}"
|
|
||||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
|
||||||
IsExpanded="True">
|
|
||||||
<cwc:SettingsExpander.Items>
|
|
||||||
<cwc:SettingsCard
|
|
||||||
Command="{Binding RestartAsElevatedCommand}"
|
|
||||||
Description="{shcm:ResourceString Name=ViewPageSettingElevatedModeDescription}"
|
|
||||||
Header="{shcm:ResourceString Name=ViewPageSettingElevatedModeRestartAction}"
|
|
||||||
IsClickEnabled="True"
|
|
||||||
IsEnabled="{Binding HutaoOptions.IsElevated, Converter={StaticResource BoolNegationConverter}}"/>
|
|
||||||
<cwc:SettingsCard
|
|
||||||
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCreateDesktopShortcutAction}"
|
|
||||||
Command="{Binding CreateDesktopShortcutCommand}"
|
|
||||||
Description="{shcm:ResourceString Name=ViewPageSettingCreateDesktopShortcutDescription}"
|
|
||||||
Header="{shcm:ResourceString Name=ViewPageSettingCreateDesktopShortcutHeader}"
|
|
||||||
IsClickEnabled="True"/>
|
|
||||||
</cwc:SettingsExpander.Items>
|
|
||||||
</cwc:SettingsExpander>
|
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</shch:ScrollViewerHelper.RightPanel>
|
</shch:ScrollViewerHelper.RightPanel>
|
||||||
<Grid Padding="16" HorizontalAlignment="Left">
|
<Grid Padding="16" HorizontalAlignment="Left">
|
||||||
@@ -160,17 +136,17 @@
|
|||||||
Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}"
|
Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}"
|
||||||
Description="{shcm:ResourceString Name=ViewPageSettingHutaoPassportLicensedDeveloperDescription}"
|
Description="{shcm:ResourceString Name=ViewPageSettingHutaoPassportLicensedDeveloperDescription}"
|
||||||
Header="{shcm:ResourceString Name=ViewPageSettingHutaoPassportLicensedDeveloperHeader}"
|
Header="{shcm:ResourceString Name=ViewPageSettingHutaoPassportLicensedDeveloperHeader}"
|
||||||
Visibility="{Binding UserOptions.IsLicensedDeveloper, Converter={StaticResource BoolToVisibilityConverter}}">
|
Visibility="{Binding UserOptions.IsLicensedDeveloper, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||||
|
<cwc:SettingsCard
|
||||||
|
Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}"
|
||||||
|
Description="{shcm:ResourceString Name=ViewPageSettingHutaoPassportMaintainerDescription}"
|
||||||
|
Header="{shcm:ResourceString Name=ViewPageSettingHutaoPassportMaintainerHeader}"
|
||||||
|
Visibility="{Binding UserOptions.IsMaintainer, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
<Button
|
<Button
|
||||||
Command="{Binding OpenTestPageCommand}"
|
Command="{Binding OpenTestPageCommand}"
|
||||||
Content="TEST"
|
Content="TEST"
|
||||||
Style="{ThemeResource SettingButtonStyle}"/>
|
Style="{ThemeResource SettingButtonStyle}"/>
|
||||||
</cwc:SettingsCard>
|
</cwc:SettingsCard>
|
||||||
<cwc:SettingsCard
|
|
||||||
Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}"
|
|
||||||
Description="{shcm:ResourceString Name=ViewPageSettingHutaoPassportMaintainerDescription}"
|
|
||||||
Header="{shcm:ResourceString Name=ViewPageSettingHutaoPassportMaintainerHeader}"
|
|
||||||
Visibility="{Binding UserOptions.IsMaintainer, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
|
||||||
<cwc:SettingsCard Description="{Binding UserOptions.GachaLogExpireAtSlim}" Header="{shcm:ResourceString Name=ViewPageSettingHutaoPassportGachaLogExpiredAtHeader}"/>
|
<cwc:SettingsCard Description="{Binding UserOptions.GachaLogExpireAtSlim}" Header="{shcm:ResourceString Name=ViewPageSettingHutaoPassportGachaLogExpiredAtHeader}"/>
|
||||||
<cwc:SettingsCard
|
<cwc:SettingsCard
|
||||||
Command="{Binding Passport.OpenRedeemWebsiteCommand}"
|
Command="{Binding Passport.OpenRedeemWebsiteCommand}"
|
||||||
@@ -205,6 +181,15 @@
|
|||||||
HeaderIcon="{shcm:FontIcon Glyph=}"
|
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||||
IsClickEnabled="True"/>
|
IsClickEnabled="True"/>
|
||||||
|
|
||||||
|
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingShellExperienceHeader}"/>
|
||||||
|
<cwc:SettingsCard
|
||||||
|
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCreateDesktopShortcutAction}"
|
||||||
|
Command="{Binding CreateDesktopShortcutCommand}"
|
||||||
|
Description="{shcm:ResourceString Name=ViewPageSettingCreateDesktopShortcutDescription}"
|
||||||
|
Header="{shcm:ResourceString Name=ViewPageSettingCreateDesktopShortcutHeader}"
|
||||||
|
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||||
|
IsClickEnabled="True"/>
|
||||||
|
|
||||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingApperanceHeader}"/>
|
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingApperanceHeader}"/>
|
||||||
<cwc:SettingsCard
|
<cwc:SettingsCard
|
||||||
Description="{shcm:ResourceString Name=ViewPageSettingApperanceLanguageDescription}"
|
Description="{shcm:ResourceString Name=ViewPageSettingApperanceLanguageDescription}"
|
||||||
@@ -234,47 +219,35 @@
|
|||||||
Description="{shcm:ResourceString Name=ViewPageSettingKeyShortcutAutoClickingDescription}"
|
Description="{shcm:ResourceString Name=ViewPageSettingKeyShortcutAutoClickingDescription}"
|
||||||
Header="{shcm:ResourceString Name=ViewPageSettingKeyShortcutAutoClickingHeader}"
|
Header="{shcm:ResourceString Name=ViewPageSettingKeyShortcutAutoClickingHeader}"
|
||||||
HeaderIcon="{shcm:FontIcon Glyph=}">
|
HeaderIcon="{shcm:FontIcon Glyph=}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Button
|
<cwc:UniformGrid
|
||||||
MinWidth="32"
|
Margin="16,-12"
|
||||||
MinHeight="32"
|
ColumnSpacing="16"
|
||||||
Padding="0"
|
Columns="2"
|
||||||
VerticalAlignment="Center"
|
Orientation="Horizontal"
|
||||||
Content=""
|
RowSpacing="0">
|
||||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
<CheckBox
|
||||||
Style="{ThemeResource SettingButtonStyle}">
|
MinWidth="64"
|
||||||
<Button.Flyout>
|
VerticalAlignment="Center"
|
||||||
<Flyout FlyoutPresenterStyle="{ThemeResource FlyoutPresenterPadding16And10Style}">
|
Content="Win"
|
||||||
<cwc:UniformGrid
|
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasWindows, Mode=TwoWay}"/>
|
||||||
ColumnSpacing="16"
|
<CheckBox
|
||||||
Columns="2"
|
MinWidth="64"
|
||||||
Orientation="Horizontal"
|
VerticalAlignment="Center"
|
||||||
RowSpacing="0">
|
Content="Ctrl"
|
||||||
<CheckBox
|
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasControl, Mode=TwoWay}"/>
|
||||||
MinWidth="64"
|
<CheckBox
|
||||||
VerticalAlignment="Center"
|
MinWidth="64"
|
||||||
Content="Win"
|
VerticalAlignment="Center"
|
||||||
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasWindows, Mode=TwoWay}"/>
|
Content="Shift"
|
||||||
<CheckBox
|
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasShift, Mode=TwoWay}"/>
|
||||||
MinWidth="64"
|
<CheckBox
|
||||||
VerticalAlignment="Center"
|
MinWidth="64"
|
||||||
Content="Ctrl"
|
VerticalAlignment="Center"
|
||||||
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasControl, Mode=TwoWay}"/>
|
Content="Alt"
|
||||||
<CheckBox
|
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasAlt, Mode=TwoWay}"/>
|
||||||
MinWidth="64"
|
</cwc:UniformGrid>
|
||||||
VerticalAlignment="Center"
|
<shc:SizeRestrictedContentControl>
|
||||||
Content="Shift"
|
|
||||||
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasShift, Mode=TwoWay}"/>
|
|
||||||
<CheckBox
|
|
||||||
MinWidth="64"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Content="Alt"
|
|
||||||
IsChecked="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.ModifierHasAlt, Mode=TwoWay}"/>
|
|
||||||
</cwc:UniformGrid>
|
|
||||||
</Flyout>
|
|
||||||
</Button.Flyout>
|
|
||||||
</Button>
|
|
||||||
<shc:SizeRestrictedContentControl VerticalAlignment="Center">
|
|
||||||
<ComboBox
|
<ComboBox
|
||||||
MinWidth="120"
|
MinWidth="120"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -335,6 +308,20 @@
|
|||||||
</cwc:SettingsCard>
|
</cwc:SettingsCard>
|
||||||
|
|
||||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingGameHeader}"/>
|
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingGameHeader}"/>
|
||||||
|
<cwc:SettingsCard
|
||||||
|
ActionIcon="{shcm:FontIcon Glyph=}"
|
||||||
|
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingSetGamePathAction}"
|
||||||
|
Command="{Binding SetPowerShellPathCommand}"
|
||||||
|
Header="{shcm:ResourceString Name=ViewPageSettingSetPowerShellPathHeader}"
|
||||||
|
HeaderIcon="{shcm:FontIcon Glyph=}"
|
||||||
|
IsClickEnabled="True">
|
||||||
|
<cwc:SettingsCard.Description>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{shcm:ResourceString Name=ViewPageSettingSetPowerShellDescription}"/>
|
||||||
|
<TextBlock Text="{Binding AppOptions.PowerShellPath}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</cwc:SettingsCard.Description>
|
||||||
|
</cwc:SettingsCard>
|
||||||
<cwc:SettingsCard
|
<cwc:SettingsCard
|
||||||
ActionIcon="{shcm:FontIcon Glyph=}"
|
ActionIcon="{shcm:FontIcon Glyph=}"
|
||||||
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingDeleteCacheAction}"
|
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingDeleteCacheAction}"
|
||||||
|
|||||||
@@ -78,17 +78,15 @@
|
|||||||
</cwc:SettingsCard>
|
</cwc:SettingsCard>
|
||||||
|
|
||||||
<cwc:SettingsCard Header="Reset Guide State">
|
<cwc:SettingsCard Header="Reset Guide State">
|
||||||
<Button
|
<StackPanel Orientation="Horizontal">
|
||||||
Command="{Binding ResetGuideStateCommand}"
|
<Button Command="{Binding ResetGuideStateCommand}" Content="Reset (No restart)"/>
|
||||||
Content="Reset (No restart)"
|
</StackPanel>
|
||||||
Style="{ThemeResource SettingButtonStyle}"/>
|
|
||||||
</cwc:SettingsCard>
|
</cwc:SettingsCard>
|
||||||
|
|
||||||
<cwc:SettingsCard Header="Resize MainWindow">
|
<cwc:SettingsCard Header="Resize MainWindow">
|
||||||
<Button
|
<StackPanel Orientation="Horizontal">
|
||||||
Command="{Binding ResetMainWindowSizeCommand}"
|
<Button Command="{Binding ResetMainWindowSizeCommand}" Content="Reset"/>
|
||||||
Content="Reset"
|
</StackPanel>
|
||||||
Style="{ThemeResource SettingButtonStyle}"/>
|
|
||||||
</cwc:SettingsCard>
|
</cwc:SettingsCard>
|
||||||
|
|
||||||
<cwc:SettingsCard Header="Suppress Metadata Initialization">
|
<cwc:SettingsCard Header="Suppress Metadata Initialization">
|
||||||
@@ -99,10 +97,6 @@
|
|||||||
<ToggleSwitch IsOn="{Binding OverrideElevationRequirement, Mode=TwoWay}"/>
|
<ToggleSwitch IsOn="{Binding OverrideElevationRequirement, Mode=TwoWay}"/>
|
||||||
</cwc:SettingsCard>
|
</cwc:SettingsCard>
|
||||||
|
|
||||||
<cwc:SettingsCard Header="Override Update Version Comparison">
|
|
||||||
<ToggleSwitch IsOn="{Binding OverrideUpdateVersionComparison, Mode=TwoWay}"/>
|
|
||||||
</cwc:SettingsCard>
|
|
||||||
|
|
||||||
<cwc:SettingsCard
|
<cwc:SettingsCard
|
||||||
Command="{Binding CompensationGachaLogServiceTimeCommand}"
|
Command="{Binding CompensationGachaLogServiceTimeCommand}"
|
||||||
Header="Compensation GachaLog Service Time For 15 Days"
|
Header="Compensation GachaLog Service Time For 15 Days"
|
||||||
|
|||||||
@@ -4,19 +4,10 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
|
||||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
|
||||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
|
||||||
Height="44"
|
Height="44"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
d:DataContext="{d:DesignInstance shv:TitleViewModel}"
|
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<mxi:Interaction.Behaviors>
|
|
||||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
|
||||||
</mxi:Interaction.Behaviors>
|
|
||||||
|
|
||||||
<Grid x:Name="DragableGrid">
|
<Grid x:Name="DragableGrid">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition/>
|
<ColumnDefinition/>
|
||||||
@@ -28,7 +19,7 @@
|
|||||||
Margin="4,0,0,0"
|
Margin="4,0,0,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Text="{Binding Title}"
|
Text="{x:Bind Title}"
|
||||||
TextWrapping="NoWrap"/>
|
TextWrapping="NoWrap"/>
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
@@ -39,12 +30,12 @@
|
|||||||
<StackPanel
|
<StackPanel
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="6"
|
Spacing="6"
|
||||||
Visibility="{Binding RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityConverter}}">
|
Visibility="{x:Bind RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsChecked="{Binding HotKeyOptions.IsMouseClickRepeatForeverOn, Mode=OneWay}"
|
IsChecked="{x:Bind HotKeyOptions.IsMouseClickRepeatForeverOn, Mode=OneWay}"
|
||||||
IsHitTestVisible="False"
|
IsHitTestVisible="False"
|
||||||
Visibility="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.IsEnabled, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
Visibility="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.IsEnabled, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||||
<Grid ColumnSpacing="8">
|
<Grid ColumnSpacing="8">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition/>
|
<ColumnDefinition/>
|
||||||
@@ -58,7 +49,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Text="{Binding HotKeyOptions.MouseClickRepeatForeverKeyCombination.DisplayName, Mode=OneWay}"/>
|
Text="{x:Bind HotKeyOptions.MouseClickRepeatForeverKeyCombination.DisplayName, Mode=OneWay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -68,7 +59,7 @@
|
|||||||
Padding="12,0"
|
Padding="12,0"
|
||||||
ColumnSpacing="12"
|
ColumnSpacing="12"
|
||||||
Style="{ThemeResource GridCardStyle}"
|
Style="{ThemeResource GridCardStyle}"
|
||||||
Visibility="{Binding UpdateStatus, Converter={StaticResource EmptyObjectToVisibilityConverter}, Mode=OneWay}">
|
Visibility="{x:Bind UpdateStatus, Converter={StaticResource EmptyObjectToVisibilityConverter}, Mode=OneWay}">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition/>
|
<ColumnDefinition/>
|
||||||
<ColumnDefinition/>
|
<ColumnDefinition/>
|
||||||
@@ -80,17 +71,17 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||||
Maximum="{Binding UpdateStatus.TotalBytes, Mode=OneWay}"
|
Maximum="{x:Bind UpdateStatus.TotalBytes, Mode=OneWay}"
|
||||||
Opacity="{ThemeResource LargeBackgroundProgressBarOpacity}"
|
Opacity="{ThemeResource LargeBackgroundProgressBarOpacity}"
|
||||||
Value="{Binding UpdateStatus.BytesRead, Mode=OneWay}"/>
|
Value="{x:Bind UpdateStatus.BytesRead, Mode=OneWay}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{Binding UpdateStatus.ProgressDescription, Mode=OneWay}"/>
|
Text="{x:Bind UpdateStatus.ProgressDescription, Mode=OneWay}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{Binding UpdateStatus.VersionDescription, Mode=OneWay}"/>
|
Text="{x:Bind UpdateStatus.VersionDescription, Mode=OneWay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Snap.Hutao.ViewModel;
|
using Snap.Hutao.Core;
|
||||||
|
using Snap.Hutao.Core.Windowing.HotKey;
|
||||||
|
using Snap.Hutao.Factory.ContentDialog;
|
||||||
|
using Snap.Hutao.Factory.Progress;
|
||||||
|
using Snap.Hutao.Service.Abstraction;
|
||||||
|
using Snap.Hutao.Service.Update;
|
||||||
|
|
||||||
namespace Snap.Hutao.View;
|
namespace Snap.Hutao.View;
|
||||||
|
|
||||||
@@ -11,16 +17,78 @@ namespace Snap.Hutao.View;
|
|||||||
/// 标题视图
|
/// 标题视图
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
|
[INotifyPropertyChanged]
|
||||||
internal sealed partial class TitleView : UserControl
|
internal sealed partial class TitleView : UserControl
|
||||||
{
|
{
|
||||||
|
private CancellationTokenSource checkUpdateTaskCancellationTokenSource = new();
|
||||||
|
private UpdateStatus? updateStatus;
|
||||||
|
|
||||||
public TitleView()
|
public TitleView()
|
||||||
{
|
{
|
||||||
DataContext = Ioc.Default.GetRequiredService<TitleViewModel>();
|
Loaded += OnTitleViewLoaded;
|
||||||
|
Unloaded += OnTitleViewUnloaded;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
[SuppressMessage("", "IDE0027")]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
return SH.FormatAppDevNameAndVersion(RuntimeOptions.Version);
|
||||||
|
#else
|
||||||
|
return SH.FormatAppNameAndVersion(RuntimeOptions.Version);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public FrameworkElement DragArea
|
public FrameworkElement DragArea
|
||||||
{
|
{
|
||||||
get => DragableGrid;
|
get => DragableGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RuntimeOptions RuntimeOptions { get; } = Ioc.Default.GetRequiredService<RuntimeOptions>();
|
||||||
|
|
||||||
|
public HotKeyOptions HotKeyOptions { get; } = Ioc.Default.GetRequiredService<HotKeyOptions>();
|
||||||
|
|
||||||
|
public UpdateStatus? UpdateStatus { get => updateStatus; set => SetProperty(ref updateStatus, value); }
|
||||||
|
|
||||||
|
private void OnTitleViewLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DoCheckUpdateAsync(checkUpdateTaskCancellationTokenSource.Token).SafeForget();
|
||||||
|
Loaded -= OnTitleViewLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTitleViewUnloaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
checkUpdateTaskCancellationTokenSource.Cancel();
|
||||||
|
Unloaded -= OnTitleViewUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask DoCheckUpdateAsync(CancellationToken token)
|
||||||
|
{
|
||||||
|
IServiceProvider serviceProvider = Ioc.Default;
|
||||||
|
IUpdateService updateService = serviceProvider.GetRequiredService<IUpdateService>();
|
||||||
|
|
||||||
|
IProgressFactory progressFactory = serviceProvider.GetRequiredService<IProgressFactory>();
|
||||||
|
IProgress<UpdateStatus> progress = progressFactory.CreateForMainThread<UpdateStatus>(status => UpdateStatus = status);
|
||||||
|
if (await updateService.CheckForUpdateAndDownloadAsync(progress, token).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
ContentDialogResult result = await serviceProvider
|
||||||
|
.GetRequiredService<IContentDialogFactory>()
|
||||||
|
.CreateForConfirmCancelAsync(
|
||||||
|
SH.FormatViewTitileUpdatePackageReadyTitle(UpdateStatus?.Version),
|
||||||
|
SH.ViewTitileUpdatePackageReadyContent,
|
||||||
|
ContentDialogButton.Primary)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
if (result == ContentDialogResult.Primary)
|
||||||
|
{
|
||||||
|
updateService.LaunchInstaller();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await serviceProvider.GetRequiredService<ITaskContext>().SwitchToMainThreadAsync();
|
||||||
|
UpdateStatus = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Model.Entity;
|
|
||||||
using Snap.Hutao.Model.Entity.Primitive;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Game;
|
|
||||||
|
|
||||||
internal sealed class GameAccountFilter
|
|
||||||
{
|
|
||||||
private readonly SchemeType? type;
|
|
||||||
|
|
||||||
public GameAccountFilter(SchemeType? type)
|
|
||||||
{
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Filter(object? item)
|
|
||||||
{
|
|
||||||
if (type is null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item is GameAccount account && account.Type == type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
|
||||||
using Snap.Hutao.Service.Game;
|
|
||||||
using Snap.Hutao.Service.Game.Configuration;
|
|
||||||
using Snap.Hutao.Service.Game.Scheme;
|
|
||||||
using Snap.Hutao.Service.Notification;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Game;
|
|
||||||
|
|
||||||
internal static class LaunchGameShared
|
|
||||||
{
|
|
||||||
public static LaunchScheme? GetCurrentLaunchSchemeFromConfigFile(IGameServiceFacade gameService, IInfoBarService infoBarService)
|
|
||||||
{
|
|
||||||
ChannelOptions options = gameService.GetChannelOptions();
|
|
||||||
if (string.IsNullOrEmpty(options.ConfigFilePath))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return KnownLaunchSchemes.Get().Single(scheme => scheme.Equals(options));
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
if (!IgnoredInvalidChannelOptions.Contains(options))
|
|
||||||
{
|
|
||||||
// 后台收集
|
|
||||||
throw ThrowHelper.NotSupported($"不支持的 MultiChannel: {options}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
infoBarService.Warning(SH.FormatViewModelLaunchGameMultiChannelReadFail(options.ConfigFilePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.WinUI.Collections;
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Snap.Hutao.Control.Extension;
|
using Snap.Hutao.Control.Extension;
|
||||||
using Snap.Hutao.Core;
|
using Snap.Hutao.Core;
|
||||||
using Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
using Snap.Hutao.Factory.ContentDialog;
|
using Snap.Hutao.Factory.ContentDialog;
|
||||||
using Snap.Hutao.Factory.Progress;
|
using Snap.Hutao.Factory.Progress;
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Service;
|
using Snap.Hutao.Service;
|
||||||
using Snap.Hutao.Service.Game;
|
using Snap.Hutao.Service.Game;
|
||||||
|
using Snap.Hutao.Service.Game.Configuration;
|
||||||
using Snap.Hutao.Service.Game.Locator;
|
using Snap.Hutao.Service.Game.Locator;
|
||||||
using Snap.Hutao.Service.Game.Package;
|
using Snap.Hutao.Service.Game.Package;
|
||||||
using Snap.Hutao.Service.Game.PathAbstraction;
|
using Snap.Hutao.Service.Game.PathAbstraction;
|
||||||
@@ -23,7 +23,6 @@ using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Windows.Win32.Foundation;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Game;
|
namespace Snap.Hutao.ViewModel.Game;
|
||||||
|
|
||||||
@@ -43,7 +42,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
private readonly IContentDialogFactory contentDialogFactory;
|
private readonly IContentDialogFactory contentDialogFactory;
|
||||||
private readonly LaunchStatusOptions launchStatusOptions;
|
private readonly LaunchStatusOptions launchStatusOptions;
|
||||||
private readonly IGameLocatorFactory gameLocatorFactory;
|
private readonly IGameLocatorFactory gameLocatorFactory;
|
||||||
private readonly ILogger<LaunchGameViewModel> logger;
|
|
||||||
private readonly IProgressFactory progressFactory;
|
private readonly IProgressFactory progressFactory;
|
||||||
private readonly IInfoBarService infoBarService;
|
private readonly IInfoBarService infoBarService;
|
||||||
private readonly ResourceClient resourceClient;
|
private readonly ResourceClient resourceClient;
|
||||||
@@ -56,13 +54,46 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
private readonly AppOptions appOptions;
|
private readonly AppOptions appOptions;
|
||||||
|
|
||||||
private LaunchScheme? selectedScheme;
|
private LaunchScheme? selectedScheme;
|
||||||
private AdvancedCollectionView? gameAccountsView;
|
private ObservableCollection<GameAccount>? gameAccounts;
|
||||||
private GameAccount? selectedGameAccount;
|
private GameAccount? selectedGameAccount;
|
||||||
private GameResource? gameResource;
|
private GameResource? gameResource;
|
||||||
private bool gamePathSelectedAndValid;
|
private bool gamePathSelectedAndValid;
|
||||||
private ImmutableList<GamePathEntry> gamePathEntries;
|
private ImmutableList<GamePathEntry> gamePathEntries;
|
||||||
private GamePathEntry? selectedGamePathEntry;
|
private GamePathEntry? selectedGamePathEntry;
|
||||||
private GameAccountFilter? gameAccountFilter;
|
|
||||||
|
public List<LaunchScheme> KnownSchemes { get; } = KnownLaunchSchemes.Get();
|
||||||
|
|
||||||
|
public LaunchScheme? SelectedScheme
|
||||||
|
{
|
||||||
|
get => selectedScheme;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref selectedScheme, value, UpdateGameResourceAsync);
|
||||||
|
|
||||||
|
async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme)
|
||||||
|
{
|
||||||
|
if (scheme is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
|
Web.Response.Response<GameResource> response = await resourceClient
|
||||||
|
.GetResourceAsync(scheme)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (response.IsOk())
|
||||||
|
{
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
GameResource = response.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<GameAccount>? GameAccounts { get => gameAccounts; set => SetProperty(ref gameAccounts, value); }
|
||||||
|
|
||||||
|
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
|
||||||
|
|
||||||
public LaunchOptions LaunchOptions { get => launchOptions; }
|
public LaunchOptions LaunchOptions { get => launchOptions; }
|
||||||
|
|
||||||
@@ -72,22 +103,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
|
|
||||||
public AppOptions AppOptions { get => appOptions; }
|
public AppOptions AppOptions { get => appOptions; }
|
||||||
|
|
||||||
public List<LaunchScheme> KnownSchemes { get; } = KnownLaunchSchemes.Get();
|
|
||||||
|
|
||||||
[AlsoAsyncSets(nameof(GameResource), nameof(GameAccountsView))]
|
|
||||||
public LaunchScheme? SelectedScheme
|
|
||||||
{
|
|
||||||
get => selectedScheme;
|
|
||||||
set => SetSelectedSchemeAsync(value).SafeForget();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
|
|
||||||
|
|
||||||
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
|
|
||||||
|
|
||||||
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
|
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
|
||||||
|
|
||||||
[AlsoAsyncSets(nameof(SelectedScheme), nameof(GameAccountsView))]
|
|
||||||
public bool GamePathSelectedAndValid
|
public bool GamePathSelectedAndValid
|
||||||
{
|
{
|
||||||
get => gamePathSelectedAndValid;
|
get => gamePathSelectedAndValid;
|
||||||
@@ -95,100 +112,112 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref gamePathSelectedAndValid, value) && value)
|
if (SetProperty(ref gamePathSelectedAndValid, value) && value)
|
||||||
{
|
{
|
||||||
RefreshUIAsync().SafeForget();
|
InitializeUICoreAsync().SafeForget();
|
||||||
}
|
|
||||||
|
|
||||||
async ValueTask RefreshUIAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
|
|
||||||
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
|
||||||
await SetSelectedSchemeAsync(scheme).ConfigureAwait(true);
|
|
||||||
TrySetGameAccountByDesiredUid();
|
|
||||||
|
|
||||||
// Try set to the current account.
|
|
||||||
if (SelectedScheme is not null)
|
|
||||||
{
|
|
||||||
// The GameAccount is gaurenteed to be in the view, bacause the scheme is synced
|
|
||||||
SelectedGameAccount ??= gameService.DetectCurrentGameAccount(SelectedScheme);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
infoBarService.Warning(SH.ViewModelLaunchGameSchemeNotSelected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (UserdataCorruptedException ex)
|
|
||||||
{
|
|
||||||
infoBarService.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrySetGameAccountByDesiredUid()
|
|
||||||
{
|
|
||||||
// Sync uid, almost never hit, so we are not so care about performance
|
|
||||||
if (memoryCache.TryRemove(DesiredUid, out object? value) && value is string uid)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(GameAccountsView);
|
|
||||||
|
|
||||||
// Exists in the source collection
|
|
||||||
if (GameAccountsView.SourceCollection.Cast<GameAccount>().FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount)
|
|
||||||
{
|
|
||||||
SelectedGameAccount = GameAccountsView.Cast<GameAccount>().FirstOrDefault(g => g.AttachUid == uid);
|
|
||||||
|
|
||||||
// But not exists in the view for current scheme
|
|
||||||
if (SelectedGameAccount is null)
|
|
||||||
{
|
|
||||||
infoBarService.Warning(SH.FormatViewModelLaunchGameUnableToSwitchUidAttachedGameAccount(uid, sourceAccount.Name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); }
|
public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); }
|
||||||
|
|
||||||
[AlsoSets(nameof(GamePathSelectedAndValid))]
|
|
||||||
public GamePathEntry? SelectedGamePathEntry
|
public GamePathEntry? SelectedGamePathEntry
|
||||||
{
|
{
|
||||||
get => selectedGamePathEntry;
|
get => selectedGamePathEntry;
|
||||||
set
|
set => UpdateSelectedGamePathEntry(value, true);
|
||||||
{
|
|
||||||
if (SetProperty(ref selectedGamePathEntry, value, nameof(SelectedGamePathEntry)))
|
|
||||||
{
|
|
||||||
if (IsViewDisposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
launchOptions.GamePath = value?.Path ?? string.Empty;
|
|
||||||
GamePathSelectedAndValid = File.Exists(launchOptions.GamePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ValueTask<bool> InitializeUIAsync()
|
protected override ValueTask<bool> InitializeUIAsync()
|
||||||
{
|
{
|
||||||
SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions();
|
GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
|
||||||
|
SelectedGamePathEntry = entry;
|
||||||
return ValueTask.FromResult(true);
|
return ValueTask.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ValueTask InitializeUICoreAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
ChannelOptions options = gameService.GetChannelOptions();
|
||||||
|
if (string.IsNullOrEmpty(options.ConfigFilePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SelectedScheme = KnownSchemes.Single(scheme => scheme.Equals(options));
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
if (!IgnoredInvalidChannelOptions.Contains(options))
|
||||||
|
{
|
||||||
|
// 后台收集
|
||||||
|
throw new NotSupportedException($"不支持的 MultiChannel: {options}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
infoBarService.Warning(SH.FormatViewModelLaunchGameMultiChannelReadFail(options.ConfigFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
|
||||||
|
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
GameAccounts = accounts;
|
||||||
|
|
||||||
|
// Sync uid
|
||||||
|
if (memoryCache.TryRemove(DesiredUid, out object? value) && value is string uid)
|
||||||
|
{
|
||||||
|
SelectedGameAccount = GameAccounts.FirstOrDefault(g => g.AttachUid == uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try set to the current account.
|
||||||
|
SelectedGameAccount ??= gameService.DetectCurrentGameAccount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UserdataCorruptedException ex)
|
||||||
|
{
|
||||||
|
infoBarService.Error(ex);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSelectedGamePathEntry(GamePathEntry? value, bool setBack)
|
||||||
|
{
|
||||||
|
if (SetProperty(ref selectedGamePathEntry, value, nameof(SelectedGamePathEntry)) && setBack)
|
||||||
|
{
|
||||||
|
if (IsViewDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
launchOptions.GamePath = value?.Path ?? string.Empty;
|
||||||
|
GamePathSelectedAndValid = File.Exists(launchOptions.GamePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Command("SetGamePathCommand")]
|
[Command("SetGamePathCommand")]
|
||||||
private async Task SetGamePathAsync()
|
private async Task SetGamePathAsync()
|
||||||
{
|
{
|
||||||
(bool isOk, string path) = await gameLocatorFactory.LocateAsync(GameLocationSource.Manual).ConfigureAwait(false);
|
IGameLocator locator = gameLocatorFactory.Create(GameLocationSource.Manual);
|
||||||
|
|
||||||
|
(bool isOk, string path) = await locator.LocateGamePathAsync().ConfigureAwait(false);
|
||||||
if (!isOk)
|
if (!isOk)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
GamePathEntries = launchOptions.UpdateGamePathAndRefreshEntries(path);
|
try
|
||||||
|
{
|
||||||
|
GamePathEntries = launchOptions.UpdateGamePathAndRefreshEntries(path);
|
||||||
|
}
|
||||||
|
catch (SqliteException ex)
|
||||||
|
{
|
||||||
|
// 文件夹权限不足,无法写入数据库
|
||||||
|
infoBarService.Error(ex, SH.ViewModelSettingSetGamePathDatabaseFailedTitle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("ResetGamePathCommand")]
|
[Command("ResetGamePathCommand")]
|
||||||
@@ -225,20 +254,24 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
// Always ensure game resources
|
// Always ensure game resources
|
||||||
if (!await gameService.EnsureGameResourceAsync(SelectedScheme, convertProgress).ConfigureAwait(false))
|
if (!await gameService.EnsureGameResourceAsync(SelectedScheme, convertProgress).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
infoBarService.Warning(SH.ViewModelLaunchGameEnsureGameResourceFail, dialog.State.Name);
|
infoBarService.Warning(SH.ViewModelLaunchGameEnsureGameResourceFail);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions();
|
GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
|
||||||
|
UpdateSelectedGamePathEntry(entry, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SelectedGameAccount is not null && !gameService.SetGameAccount(SelectedGameAccount))
|
if (SelectedGameAccount is not null)
|
||||||
{
|
{
|
||||||
infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail);
|
if (!gameService.SetGameAccount(SelectedGameAccount))
|
||||||
return;
|
{
|
||||||
|
infoBarService.Warning(SH.ViewModelLaunchGameSwitchGameAccountFail);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IProgress<LaunchStatus> launchProgress = progressFactory.CreateForMainThread<LaunchStatus>(status => launchStatusOptions.LaunchStatus = status);
|
IProgress<LaunchStatus> launchProgress = progressFactory.CreateForMainThread<LaunchStatus>(status => launchStatusOptions.LaunchStatus = status);
|
||||||
@@ -246,13 +279,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (ex is Win32Exception win32Exception && win32Exception.HResult == HRESULT.E_FAIL)
|
|
||||||
{
|
|
||||||
// User canceled the operation. ignore
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogCritical(ex, "Launch failed");
|
|
||||||
infoBarService.Error(ex);
|
infoBarService.Error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,14 +288,11 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (SelectedScheme is null)
|
GameAccount? account = await gameService.DetectGameAccountAsync().ConfigureAwait(false);
|
||||||
{
|
|
||||||
infoBarService.Error(SH.ViewModelLaunchGameSchemeNotSelected);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user canceled the operation, the return is null
|
// If user canceled the operation, the return is null,
|
||||||
if (await gameService.DetectGameAccountAsync(SelectedScheme).ConfigureAwait(false) is { } account)
|
// and thus we should not set SelectedAccount
|
||||||
|
if (account is not null)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
SelectedGameAccount = account;
|
SelectedGameAccount = account;
|
||||||
@@ -327,54 +350,4 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
await Windows.System.Launcher.LaunchFolderPathAsync(screenshot);
|
await Windows.System.Launcher.LaunchFolderPathAsync(screenshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask SetSelectedSchemeAsync(LaunchScheme? value)
|
|
||||||
{
|
|
||||||
if (SetProperty(ref selectedScheme, value, nameof(SelectedScheme)))
|
|
||||||
{
|
|
||||||
UpdateGameResourceAsync(value).SafeForget();
|
|
||||||
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
// Clear the selected game account to prevent setting
|
|
||||||
// incorrect CN/OS account when scheme not match
|
|
||||||
SelectedGameAccount = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme)
|
|
||||||
{
|
|
||||||
if (scheme is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
|
||||||
Web.Response.Response<GameResource> response = await resourceClient
|
|
||||||
.GetResourceAsync(scheme)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (response.IsOk())
|
|
||||||
{
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
|
||||||
GameResource = response.Data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async ValueTask UpdateGameAccountsViewAsync()
|
|
||||||
{
|
|
||||||
gameAccountFilter = new(SelectedScheme?.GetSchemeType());
|
|
||||||
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
|
|
||||||
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
|
||||||
GameAccountsView = new(accounts, true)
|
|
||||||
{
|
|
||||||
Filter = gameAccountFilter.Filter,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SyncGamePathEntriesAndSelectedGamePathEntryFromLaunchOptions()
|
|
||||||
{
|
|
||||||
GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
|
|
||||||
SelectedGamePathEntry = entry;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.WinUI.Collections;
|
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
using Snap.Hutao.Factory.Progress;
|
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Service.Game;
|
using Snap.Hutao.Service.Game;
|
||||||
using Snap.Hutao.Service.Game.Scheme;
|
|
||||||
using Snap.Hutao.Service.Notification;
|
using Snap.Hutao.Service.Notification;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using Windows.Win32.Foundation;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Game;
|
namespace Snap.Hutao.ViewModel.Game;
|
||||||
|
|
||||||
@@ -20,17 +16,17 @@ namespace Snap.Hutao.ViewModel.Game;
|
|||||||
[ConstructorGenerated(CallBaseConstructor = true)]
|
[ConstructorGenerated(CallBaseConstructor = true)]
|
||||||
internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSlim<View.Page.LaunchGamePage>
|
internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSlim<View.Page.LaunchGamePage>
|
||||||
{
|
{
|
||||||
private readonly LaunchStatusOptions launchStatusOptions;
|
|
||||||
private readonly IProgressFactory progressFactory;
|
|
||||||
private readonly IInfoBarService infoBarService;
|
|
||||||
private readonly IGameServiceFacade gameService;
|
private readonly IGameServiceFacade gameService;
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
|
private readonly IInfoBarService infoBarService;
|
||||||
|
|
||||||
private AdvancedCollectionView? gameAccountsView;
|
private ObservableCollection<GameAccount>? gameAccounts;
|
||||||
private GameAccount? selectedGameAccount;
|
private GameAccount? selectedGameAccount;
|
||||||
private GameAccountFilter? gameAccountFilter;
|
|
||||||
|
|
||||||
public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
|
/// <summary>
|
||||||
|
/// 游戏账号集合
|
||||||
|
/// </summary>
|
||||||
|
public ObservableCollection<GameAccount>? GameAccounts { get => gameAccounts; set => SetProperty(ref gameAccounts, value); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选中的账号
|
/// 选中的账号
|
||||||
@@ -40,29 +36,19 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override async Task OpenUIAsync()
|
protected override async Task OpenUIAsync()
|
||||||
{
|
{
|
||||||
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
|
|
||||||
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
|
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
GameAccounts = accounts;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (scheme is not null)
|
// Try set to the current account.
|
||||||
{
|
SelectedGameAccount ??= gameService.DetectCurrentGameAccount();
|
||||||
// Try set to the current account.
|
|
||||||
SelectedGameAccount ??= gameService.DetectCurrentGameAccount(scheme);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (UserdataCorruptedException ex)
|
catch (UserdataCorruptedException ex)
|
||||||
{
|
{
|
||||||
infoBarService.Error(ex);
|
infoBarService.Error(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
gameAccountFilter = new(scheme?.GetSchemeType());
|
|
||||||
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
|
||||||
GameAccountsView = new(accounts, true)
|
|
||||||
{
|
|
||||||
Filter = gameAccountFilter.Filter,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("LaunchCommand")]
|
[Command("LaunchCommand")]
|
||||||
@@ -81,17 +67,10 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IProgress<LaunchStatus> launchProgress = progressFactory.CreateForMainThread<LaunchStatus>(status => launchStatusOptions.LaunchStatus = status);
|
await gameService.LaunchAsync(new Progress<LaunchStatus>()).ConfigureAwait(false);
|
||||||
await gameService.LaunchAsync(launchProgress).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (ex is Win32Exception win32Exception && win32Exception.HResult == HRESULT.E_FAIL)
|
|
||||||
{
|
|
||||||
// User canceled the operation. ignore
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
infoBarService.Error(ex);
|
infoBarService.Error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.Windows.AppLifecycle;
|
using Microsoft.Windows.AppLifecycle;
|
||||||
using Snap.Hutao.Core;
|
using Snap.Hutao.Core;
|
||||||
|
using Snap.Hutao.Core.IO;
|
||||||
using Snap.Hutao.Core.IO.DataTransfer;
|
using Snap.Hutao.Core.IO.DataTransfer;
|
||||||
using Snap.Hutao.Core.Setting;
|
using Snap.Hutao.Core.Setting;
|
||||||
using Snap.Hutao.Core.Shell;
|
using Snap.Hutao.Core.Shell;
|
||||||
@@ -15,6 +16,7 @@ using Snap.Hutao.Model;
|
|||||||
using Snap.Hutao.Service;
|
using Snap.Hutao.Service;
|
||||||
using Snap.Hutao.Service.GachaLog.QueryProvider;
|
using Snap.Hutao.Service.GachaLog.QueryProvider;
|
||||||
using Snap.Hutao.Service.Game;
|
using Snap.Hutao.Service.Game;
|
||||||
|
using Snap.Hutao.Service.Game.Locator;
|
||||||
using Snap.Hutao.Service.Hutao;
|
using Snap.Hutao.Service.Hutao;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
using Snap.Hutao.Service.Notification;
|
using Snap.Hutao.Service.Notification;
|
||||||
@@ -24,7 +26,6 @@ using Snap.Hutao.ViewModel.Guide;
|
|||||||
using Snap.Hutao.Web.Hoyolab;
|
using Snap.Hutao.Web.Hoyolab;
|
||||||
using Snap.Hutao.Web.Hutao;
|
using Snap.Hutao.Web.Hutao;
|
||||||
using Snap.Hutao.Web.Response;
|
using Snap.Hutao.Web.Response;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -46,6 +47,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
|||||||
private readonly HutaoInfrastructureClient hutaoInfrastructureClient;
|
private readonly HutaoInfrastructureClient hutaoInfrastructureClient;
|
||||||
private readonly HutaoPassportViewModel hutaoPassportViewModel;
|
private readonly HutaoPassportViewModel hutaoPassportViewModel;
|
||||||
private readonly IContentDialogFactory contentDialogFactory;
|
private readonly IContentDialogFactory contentDialogFactory;
|
||||||
|
private readonly IGameLocatorFactory gameLocatorFactory;
|
||||||
private readonly INavigationService navigationService;
|
private readonly INavigationService navigationService;
|
||||||
private readonly IClipboardProvider clipboardInterop;
|
private readonly IClipboardProvider clipboardInterop;
|
||||||
private readonly IShellLinkInterop shellLinkInterop;
|
private readonly IShellLinkInterop shellLinkInterop;
|
||||||
@@ -172,6 +174,18 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
|||||||
await Launcher.LaunchUriAsync(new("ms-windows-store://pdp/?productid=9PH4NXJ2JN52"));
|
await Launcher.LaunchUriAsync(new("ms-windows-store://pdp/?productid=9PH4NXJ2JN52"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command("SetPowerShellPathCommand")]
|
||||||
|
private async Task SetPowerShellPathAsync()
|
||||||
|
{
|
||||||
|
(bool isOk, ValueFile file) = fileSystemPickerInteraction.PickFile(SH.FilePickerPowerShellCommit, [("PowerShell", "powershell.exe;pwsh.exe")]);
|
||||||
|
|
||||||
|
if (isOk && Path.GetFileNameWithoutExtension(file).EqualsAny(["POWERSHELL", "PWSH"], StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
AppOptions.PowerShellPath = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Command("DeleteGameWebCacheCommand")]
|
[Command("DeleteGameWebCacheCommand")]
|
||||||
private void DeleteGameWebCache()
|
private void DeleteGameWebCache()
|
||||||
{
|
{
|
||||||
@@ -280,17 +294,4 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
|||||||
infoBarService.Warning(SH.ViewModelSettingCreateDesktopShortcutFailed);
|
infoBarService.Warning(SH.ViewModelSettingCreateDesktopShortcutFailed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("RestartAsElevatedCommand")]
|
|
||||||
private void RestartAsElevated()
|
|
||||||
{
|
|
||||||
Process.Start(new ProcessStartInfo()
|
|
||||||
{
|
|
||||||
FileName = $"shell:AppsFolder\\{runtimeOptions.FamilyName}!App",
|
|
||||||
UseShellExecute = true,
|
|
||||||
Verb = "runas",
|
|
||||||
});
|
|
||||||
|
|
||||||
Process.GetCurrentProcess().Kill();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -39,13 +39,6 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
|
|||||||
set => LocalSetting.Set(SettingKeys.OverrideElevationRequirement, value);
|
set => LocalSetting.Set(SettingKeys.OverrideElevationRequirement, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("", "CA1822")]
|
|
||||||
public bool OverrideUpdateVersionComparison
|
|
||||||
{
|
|
||||||
get => LocalSetting.Get(SettingKeys.OverrideUpdateVersionComparison, false);
|
|
||||||
set => LocalSetting.Set(SettingKeys.OverrideUpdateVersionComparison, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Command("ResetGuideStateCommand")]
|
[Command("ResetGuideStateCommand")]
|
||||||
private static void ResetGuideState()
|
private static void ResetGuideState()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Snap.Hutao.Core;
|
|
||||||
using Snap.Hutao.Core.Windowing.HotKey;
|
|
||||||
using Snap.Hutao.Factory.ContentDialog;
|
|
||||||
using Snap.Hutao.Factory.Progress;
|
|
||||||
using Snap.Hutao.Service.Abstraction;
|
|
||||||
using Snap.Hutao.Service.Update;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel;
|
|
||||||
|
|
||||||
[ConstructorGenerated]
|
|
||||||
[Injection(InjectAs.Singleton)]
|
|
||||||
internal sealed partial class TitleViewModel : Abstraction.ViewModel
|
|
||||||
{
|
|
||||||
private readonly IContentDialogFactory contentDialogFactory;
|
|
||||||
private readonly IProgressFactory progressFactory;
|
|
||||||
private readonly RuntimeOptions runtimeOptions;
|
|
||||||
private readonly HotKeyOptions hotKeyOptions;
|
|
||||||
private readonly IUpdateService updateService;
|
|
||||||
private readonly ITaskContext taskContext;
|
|
||||||
|
|
||||||
private UpdateStatus? updateStatus;
|
|
||||||
|
|
||||||
public RuntimeOptions RuntimeOptions { get => runtimeOptions; }
|
|
||||||
|
|
||||||
public HotKeyOptions HotKeyOptions { get => hotKeyOptions; }
|
|
||||||
|
|
||||||
public string Title
|
|
||||||
{
|
|
||||||
[SuppressMessage("", "IDE0027")]
|
|
||||||
get
|
|
||||||
{
|
|
||||||
string name = new StringBuilder()
|
|
||||||
.Append("App")
|
|
||||||
.AppendIf(runtimeOptions.IsElevated, "Elevated")
|
|
||||||
#if DEBUG
|
|
||||||
.Append("Dev")
|
|
||||||
#endif
|
|
||||||
.Append("NameAndVersion")
|
|
||||||
.ToString();
|
|
||||||
|
|
||||||
string? format = SH.GetString(CultureInfo.CurrentCulture, name);
|
|
||||||
ArgumentException.ThrowIfNullOrEmpty(format);
|
|
||||||
return string.Format(CultureInfo.CurrentCulture, format, runtimeOptions.Version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UpdateStatus? UpdateStatus { get => updateStatus; set => SetProperty(ref updateStatus, value); }
|
|
||||||
|
|
||||||
protected override async ValueTask<bool> InitializeUIAsync()
|
|
||||||
{
|
|
||||||
await DoCheckUpdateAsync().ConfigureAwait(false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask DoCheckUpdateAsync()
|
|
||||||
{
|
|
||||||
IProgress<UpdateStatus> progress = progressFactory.CreateForMainThread<UpdateStatus>(status => UpdateStatus = status);
|
|
||||||
if (await updateService.CheckForUpdateAndDownloadAsync(progress).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
ContentDialogResult result = await contentDialogFactory
|
|
||||||
.CreateForConfirmCancelAsync(
|
|
||||||
SH.FormatViewTitileUpdatePackageReadyTitle(UpdateStatus?.Version),
|
|
||||||
SH.ViewTitileUpdatePackageReadyContent,
|
|
||||||
ContentDialogButton.Primary)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
if (result == ContentDialogResult.Primary)
|
|
||||||
{
|
|
||||||
await updateService.LaunchUpdaterAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
|
||||||
UpdateStatus = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@ using Snap.Hutao.Web.Hoyolab.Bbs.User;
|
|||||||
using Snap.Hutao.Web.Hoyolab.DataSigning;
|
using Snap.Hutao.Web.Hoyolab.DataSigning;
|
||||||
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
|
||||||
using Snap.Hutao.Web.Response;
|
using Snap.Hutao.Web.Response;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
|
||||||
|
|
||||||
internal sealed class ArchonQuest
|
|
||||||
{
|
|
||||||
[JsonPropertyName("status")]
|
|
||||||
public ArchonQuestStatus Status { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 第X章 第Y幕
|
|
||||||
/// </summary>
|
|
||||||
[JsonPropertyName("chapter_num")]
|
|
||||||
public string ChapterNum { get; set; } = default!;
|
|
||||||
|
|
||||||
[JsonPropertyName("chapter_title")]
|
|
||||||
public string ChapterTitle { get; set; } = default!;
|
|
||||||
|
|
||||||
[JsonPropertyName("id")]
|
|
||||||
public uint Id { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
|
||||||
|
|
||||||
internal sealed class ArchonQuestProgress
|
|
||||||
{
|
|
||||||
[JsonPropertyName("list")]
|
|
||||||
public List<ArchonQuest> List { get; set; } = default!;
|
|
||||||
|
|
||||||
[JsonPropertyName("is_open_archon_quest")]
|
|
||||||
public bool IsOpenArchonQuest { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("is_finish_all_mainline")]
|
|
||||||
public bool IsFinishAllMainline { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("is_finish_all_interchapter")]
|
|
||||||
public bool IsFinishAllInterchapter { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("wiki_url")]
|
|
||||||
public string WikiUrl { get; set; } = default!;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
|
||||||
|
|
||||||
[Localization]
|
|
||||||
internal enum ArchonQuestStatus
|
|
||||||
{
|
|
||||||
[LocalizationKey("WebDailyNoteArchonQuestStatusFinished")]
|
|
||||||
StatusFinished,
|
|
||||||
|
|
||||||
[LocalizationKey("WebDailyNoteArchonQuestStatusOngoing")]
|
|
||||||
StatusOngoing,
|
|
||||||
|
|
||||||
[LocalizationKey("WebDailyNoteArchonQuestStatusNotOpen")]
|
|
||||||
StatusNotOpen,
|
|
||||||
}
|
|
||||||
@@ -7,16 +7,20 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
|
|||||||
/// 实时便笺
|
/// 实时便笺
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[SuppressMessage("", "SA1124")]
|
|
||||||
internal sealed class DailyNote : DailyNoteCommon
|
internal sealed class DailyNote : DailyNoteCommon
|
||||||
{
|
{
|
||||||
#region Binding
|
/// <summary>
|
||||||
|
/// 格式化的树脂显示
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string ResinFormatted
|
public string ResinFormatted
|
||||||
{
|
{
|
||||||
get => $"{CurrentResin}/{MaxResin}";
|
get => $"{CurrentResin}/{MaxResin}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 格式化的树脂恢复时间
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string ResinRecoveryTargetTime
|
public string ResinRecoveryTargetTime
|
||||||
{
|
{
|
||||||
@@ -41,12 +45,18 @@ internal sealed class DailyNote : DailyNoteCommon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 格式化任务
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string TaskFormatted
|
public string TaskFormatted
|
||||||
{
|
{
|
||||||
get => $"{FinishedTaskNum}/{TotalTaskNum}";
|
get => $"{FinishedTaskNum}/{TotalTaskNum}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每日委托奖励字符串
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string ExtraTaskRewardDescription
|
public string ExtraTaskRewardDescription
|
||||||
{
|
{
|
||||||
@@ -60,24 +70,53 @@ internal sealed class DailyNote : DailyNoteCommon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 剩余周本折扣次数
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("remain_resin_discount_num")]
|
||||||
|
public int RemainResinDiscountNum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 周本树脂减免使用次数
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int ResinDiscountUsedNum
|
public int ResinDiscountUsedNum
|
||||||
{
|
{
|
||||||
get => ResinDiscountNumLimit - RemainResinDiscountNum;
|
get => ResinDiscountNumLimit - RemainResinDiscountNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
/// <summary>
|
||||||
|
/// 周本折扣总次数
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("resin_discount_num_limit")]
|
||||||
|
public int ResinDiscountNumLimit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 格式化周本
|
||||||
|
/// </summary>
|
||||||
public string ResinDiscountFormatted
|
public string ResinDiscountFormatted
|
||||||
{
|
{
|
||||||
get => $"{ResinDiscountUsedNum}/{ResinDiscountNumLimit}";
|
get => $"{ResinDiscountUsedNum}/{ResinDiscountNumLimit}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 洞天宝钱恢复时间 <see cref="string"/>类型的秒数
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("home_coin_recovery_time")]
|
||||||
|
public int HomeCoinRecoveryTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 格式化洞天宝钱
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string HomeCoinFormatted
|
public string HomeCoinFormatted
|
||||||
{
|
{
|
||||||
get => MaxHomeCoin == 0 ? SH.WebDailyNoteHomeLocked : $"{CurrentHomeCoin}/{MaxHomeCoin}";
|
get => MaxHomeCoin == 0 ? SH.WebDailyNoteHomeLocked : $"{CurrentHomeCoin}/{MaxHomeCoin}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 格式化的洞天宝钱恢复时间
|
||||||
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string HomeCoinRecoveryTargetTimeFormatted
|
public string HomeCoinRecoveryTargetTimeFormatted
|
||||||
{
|
{
|
||||||
@@ -95,25 +134,6 @@ internal sealed class DailyNote : DailyNoteCommon
|
|||||||
return SH.FormatWebDailyNoteHomeCoinRecoveryFormat(day, reach);
|
return SH.FormatWebDailyNoteHomeCoinRecoveryFormat(day, reach);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 剩余周本折扣次数
|
|
||||||
/// </summary>
|
|
||||||
[JsonPropertyName("remain_resin_discount_num")]
|
|
||||||
public int RemainResinDiscountNum { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 周本折扣总次数
|
|
||||||
/// </summary>
|
|
||||||
[JsonPropertyName("resin_discount_num_limit")]
|
|
||||||
public int ResinDiscountNumLimit { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 洞天宝钱恢复时间 <see cref="string"/>类型的秒数
|
|
||||||
/// </summary>
|
|
||||||
[JsonPropertyName("home_coin_recovery_time")]
|
|
||||||
public int HomeCoinRecoveryTime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 日历链接
|
/// 日历链接
|
||||||
@@ -129,7 +149,4 @@ internal sealed class DailyNote : DailyNoteCommon
|
|||||||
|
|
||||||
[JsonPropertyName("daily_task")]
|
[JsonPropertyName("daily_task")]
|
||||||
public DailyTask DailyTask { get; set; } = default!;
|
public DailyTask DailyTask { get; set; } = default!;
|
||||||
|
|
||||||
[JsonPropertyName("archon_quest_progress")]
|
|
||||||
public ArchonQuestProgress ArchonQuestProgress { get; set; } = default!;
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user