Compare commits

..

2 Commits

Author SHA1 Message Date
DismissedLight
98c003ae77 Merge branch 'develop' into feat/1239 2024-01-02 13:18:32 +08:00
qhy040404
d26611ccf7 impl #1239 2024-01-01 20:13:11 +08:00
35 changed files with 448 additions and 1023 deletions

View File

@@ -22,7 +22,7 @@ body:
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
required: true
- label: 我的问题不是[已完成](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E5%AE%8C%E6%88%90)的问题也不是一个别人已发布的**重复的**问题
- 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)的问题也不是一个别人已发布的**重复的**问题
required: true
- type: input
@@ -51,7 +51,7 @@ body:
description: |
在胡桃工具箱的设置界面,你可以找到并复制你的设备 ID
如果你的问题涉及程序崩溃,请填写该项,这将有助于我们定位问题
如果你的程序已经无法启动,请下载并运行[此工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将显示你的设备 ID
如果你的程序已经无法启动,请下载并运行[此PowerShell脚本](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1),它将显示你的设备 ID
validations:
required: false
@@ -87,7 +87,7 @@ body:
label: 发生了什么?
description: |
详细的描述问题发生前后的行为,以便我们解决问题。**如果你的问题涉及程序崩溃,你应当检查 Windows 事件查看器,并将相关的 `.Net 错误`详情附上**
如果你无法找到该日志,请下载并运行[此工具](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe),它将转储问题日志至工具运行目录中的 `Snap.Hutao Error Log.txt`
如果你无法找到该日志,请下载并运行[此PowerShell脚本](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/dump_log_script/dump_log_zh.ps1),它将输出错误日志
validations:
required: true

View File

@@ -22,7 +22,7 @@ body:
- label: I and tried **search feature** in Snap Hutao document site, and no associated article
required: true
- label: My issue is not a [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
- 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
required: true
- type: input
@@ -51,7 +51,7 @@ body:
description: |
In Snap Hutao's settings page, you can find and copy your device ID
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
If your program cannot startup, please download and run [this tool](https://github.com/DGP-Automation/ISSUE_TEMPLATES/releases/download/diagnosis_tools/Snap.Hutao.DiagTools.exe), it will shows your device ID.
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.
validations:
required: false
@@ -87,7 +87,7 @@ body:
label: What Happened?
description: |
Describe your issue in detail to help us identify the issue. **If your issue is about program crash, you should check Windows Event Viewer, and attach associated `.Net Error` details here**If your program cannot startup, please download and run [this PowerShell script](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1), it will shows your device ID.
If you cannot find it, please download and run [this 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.
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.
validations:
required: true

View File

@@ -12,26 +12,50 @@ body:
value: |
## 创建版本
- [ ] 同步一次 [Crowdin](https://crowdin.com/project/snap-hutao) 翻译
- [ ] 发布 RC 版本Optional
- [ ] 合并入主分支
- [ ] 整理更新内容,等待翻译
- [ ] 打包
- [ ] 提交微软商店
- [ ] 包含更新日志
- [ ] 在 [Snap.Hutao.Docs@next-patch](https://github.com/DGP-Studio/Snap.Hutao.Docs/tree/next-patch) 分支更新文档并直接开 PR
- [ ] 更新日志
- [ ] 功能文档更新
## 发布版本 [半自动]
- [ ] 在 GitHub 个人设置中更新 [Publish-Automate Beta PAT](https://github.com/settings/tokens?type=beta),有效期需小于预计发版需要天数
- [ ] 将更新的 PAT 更新至 Publish-Automate 库的 [Actions Secrets](https://github.com/DGP-Studio/Publish-Automate/settings/secrets/actions) 中
***
***
- [ ] 主分支合并入 release 分支
- [ ] 等待 Release 自动发布
- [ ] 检查极狐是否同步完成 Release
- [ ] 通知用户
- [ ] 运行 [Auto Publish Action](https://github.com/DGP-Studio/Publish-Automate/actions/workflows/auto-publish.yml)
- [ ] 在 https://store.rg-adguard.net/ 下载新版本安装包
- [ ] Store URL: https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52
- [ ] 命名格式为 `Snap.Hutao x.x.x.msix`
- [ ] Merge 文档 PR
- [ ] 发布 Release
- [ ] 更新日志格式(以 1.6.2 版本为例)
```jsx
## Update log
https://hut.ao/en/statements/update-log.html#_1-6-2
## 更新日志
[此处从文档复制]
## What's Changed
**Full Changelog**: https://github.com/DGP-Studio/Snap.Hutao/compare/1.6.0...1.6.2
```
- [ ] 通知用户
- type: checkboxes
id: checklist-final
attributes:
label: Final Check
description: Understand what you are doing
description: Understand what you are doing
options:
- label: I understand that I will get banned from repository if I don't have permission to use this template
required: true
required: true

View File

@@ -1,73 +0,0 @@
stages:
- fetch
- release
- refresh
Fetch:
stage: fetch
rules:
- if: $CI_COMMIT_TAG
tags:
- us3
script:
- apt-get update -qy
- apt-get install -y curl jq
- RELEASE_INFO=$(curl -sSL "https://api.github.com/repos/$CI_PROJECT_PATH/releases/latest")
- ASSET_URL=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name | endswith(".msix")) | .browser_download_url')
- SHA256SUMS_URL=$(echo "$RELEASE_INFO" | jq -r '.assets[] | select(.name == "SHA256SUMS") | .browser_download_url')
- curl -LJO "$ASSET_URL"
- curl -LJO "$SHA256SUMS_URL"
- FILE_NAME=$(basename "$ASSET_URL")
- SHA256SUMS_NAME=$(basename "$SHA256SUMS_URL")
- echo "File name at script stage is $FILE_NAME"
- echo "SHA256SUMS name at script stage is $SHA256SUMS_NAME"
- echo "THIS_FILE_NAME=$FILE_NAME" >> next.env
- echo "THIS_SHA256SUMS_NAME=$SHA256SUMS_NAME" >> next.env
after_script:
- echo "Current Job ID is $CI_JOB_ID"
- echo "THIS_JOB_ID=$CI_JOB_ID" >> next.env
artifacts:
paths:
- "*.msix"
- "SHA256SUMS"
expire_in: 180 days
reports:
dotenv: next.env
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: $CI_COMMIT_TAG
needs:
- job: Fetch
artifacts: true
variables:
TAG: '$CI_COMMIT_TAG'
script:
- echo "Create Release $TAG"
- echo "$THIS_JOB_ID"
- echo "$THIS_FILE_NAME"
release:
name: '$TAG'
tag_name: '$TAG'
ref: '$TAG'
description: 'Release $TAG by CI'
assets:
links:
- name: "$THIS_FILE_NAME"
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/$THIS_FILE_NAME?inline=false"
link_type: package
- name: "$THIS_SHA256SUMS_NAME"
url: "https://$CI_SERVER_SHELL_SSH_HOST/$CI_PROJECT_PATH/-/jobs/$THIS_JOB_ID/artifacts/raw/$THIS_SHA256SUMS_NAME?inline=false"
link_type: other
Refresh:
stage: refresh
rules:
- if: $CI_COMMIT_TAG
needs:
- job: release
script:
- apt-get install -y curl
- curl -X PATCH $PURGE_URL

View File

@@ -1,4 +1,4 @@
![HutaoRepoBanner2-20231222](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/2d178de1-95bc-44a1-a95e-20c5f11a8628)
![Banner2-large](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/742129f4-f903-4d16-bf1e-3cbfad873ee4)
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新 功能相结合,它提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
@@ -7,29 +7,7 @@ Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed
## 下载使用 / Download
![](https://ci.appveyor.com/api/projects/status/n4s40t9llru4si9y?svg=true) [![GitHub Release](https://img.shields.io/github/release/DGP-Studio/Snap.Hutao?style=flat)](https://github.com/DGP-Studio/Snap.Hutao/releases/latest) [![Github All Releases](https://img.shields.io/github/downloads/DGP-Studio/Snap.Hutao/total.svg?style=flat)]()
---
#### 使用安装器安装 / Install with Snap.Hutao.Depolyment Installer
Snap.Hutao.Depolyment 是一个由 DGP-Studio 重新包装的 Windows 应用安装器,适用于缺少专业计算机知识的一般用户,可以在安装时同时解决缺少必要系统环境的问题。
Snap.Hutao.Depolyment is a Windows application installer repackaged by DGP-Studio for the users who lacks computer knowledge and can solve the problem of missing necessary system environment at the same time as the installation.
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao.Deployment/releases/latest)
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao.Deployment/-/releases)
#### 使用 MSIX 包安装 / Install with MSIX Package
直接使用 Snap Hutao MSIX 安装包,使用 Windows 内置的 App Installer 即可安装。如在安装中出现问题,请查阅我们的[常见问题](https://hut.ao/zh/advanced/FAQ.html)文档
Install with Snap Hutao MSIX package, can be installed with Windows built-in App Installer. If you faced any issue, please check our [FAQ](https://hut.ao/en/advanced/FAQ.html) document.
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao/releases/latest)
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao/-/releases)
[<img src="https://get.microsoft.com/images/zh-cn%20light.svg" width="30%" height="30%">](https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52)
## 贡献 / Contribute

View File

@@ -11,12 +11,6 @@ namespace Snap.Hutao.Test.IncomingFeature;
[TestClass]
public class GameRegistryContentTest
{
private static readonly JsonSerializerOptions RegistryContentSerializerOptions = new()
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
[TestMethod]
[SupportedOSPlatform("windows")]
public void GetRegistryContent()
@@ -39,29 +33,28 @@ public class GameRegistryContentTest
data[valueName] = gameKey.GetValueKind(valueName) switch
{
RegistryValueKind.DWord => (int)gameKey.GetValue(valueName)!,
RegistryValueKind.Binary => GetStringOrObject((byte[])gameKey.GetValue(valueName)!),
RegistryValueKind.Binary => GetString((byte[])gameKey.GetValue(valueName)!),
_ => throw new NotImplementedException()
};
}
JsonSerializerOptions options = new()
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
Console.WriteLine($"Subkey: {subkey}");
Console.WriteLine(JsonSerializer.Serialize(data, RegistryContentSerializerOptions));
Console.WriteLine(JsonSerializer.Serialize(data, options));
}
}
private static unsafe object GetStringOrObject(byte[] bytes)
private static unsafe string GetString(byte[] bytes)
{
fixed (byte* pByte = bytes)
{
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pByte);
string temp = Encoding.UTF8.GetString(span);
if (temp.AsSpan()[0] is '{' or '[')
{
return JsonSerializer.Deserialize<JsonElement>(temp);
}
return temp;
return Encoding.UTF8.GetString(span);
}
}
}

View File

@@ -4,6 +4,8 @@
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Control.Image;

View File

@@ -13,9 +13,4 @@ internal static class RuntimeOptionsExtension
Directory.CreateDirectory(directory);
return Path.Combine(directory, fileName);
}
public static string GetDataFolderServerCacheFolder(this RuntimeOptions options)
{
return Path.Combine(options.DataFolder, "ServerCache");
}
}

View File

@@ -1,18 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Threading;
internal delegate bool SpinWaitPredicate<T>(ref readonly T state);
internal static class SpinWaitPolyfill
{
public static unsafe void SpinUntil<T>(ref T state, delegate*<ref readonly T, bool> condition)
{
SpinWait spinner = default;
while (!condition(ref state))
{
spinner.SpinOnce();
}
}
}

View File

@@ -8,45 +8,123 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
internal sealed partial class SettingEntry
{
/// <summary>
/// 游戏路径
/// </summary>
public const string GamePath = "GamePath";
public const string GamePathEntries = "GamePathEntries";
public const string Culture = "Culture";
public const string SystemBackdropType = "SystemBackdropType";
public const string AnnouncementRegion = "AnnouncementRegion";
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
public const string DailyNoteSilentWhenPlayingGame = "DailyNote.SilentWhenPlayingGame";
public const string DailyNoteWebhookUrl = "DailyNote.WebhookUrl";
public const string IsAdvancedLaunchOptionsEnabled = "IsAdvancedLaunchOptionsEnabled";
public const string LaunchIsLaunchOptionsEnabled = "Launch.IsLaunchOptionsEnabled";
public const string LaunchIsExclusive = "Launch.IsExclusive";
public const string LaunchIsFullScreen = "Launch.IsFullScreen";
public const string LaunchIsBorderless = "Launch.IsBorderless";
public const string LaunchScreenWidth = "Launch.ScreenWidth";
public const string LaunchIsScreenWidthEnabled = "Launch.IsScreenWidthEnabled";
public const string LaunchScreenHeight = "Launch.ScreenHeight";
public const string LaunchIsScreenHeightEnabled = "Launch.IsScreenHeightEnabled";
public const string LaunchUnlockFps = "Launch.UnlockFps";
public const string LaunchTargetFps = "Launch.TargetFps";
public const string LaunchMonitor = "Launch.Monitor";
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";
public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile";
public const string LaunchIsWindowsHDREnabled = "Launch.IsWindowsHDREnabled";
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
[Obsolete("不再支持多开")]
public const string MultipleInstances = "Launch.MultipleInstances";
[Obsolete("不再使用 PowerShell")]
public const string PowerShellPath = "PowerShellPath";
/// <summary>
/// 空的历史记录卡池是否可见
/// </summary>
public const string IsEmptyHistoryWishVisible = "IsEmptyHistoryWishVisible";
/// <summary>
/// 窗口背景类型
/// </summary>
public const string SystemBackdropType = "SystemBackdropType";
/// <summary>
/// 启用高级功能
/// </summary>
public const string IsAdvancedLaunchOptionsEnabled = "IsAdvancedLaunchOptionsEnabled";
/// <summary>
/// 实时便笺刷新时间
/// </summary>
public const string DailyNoteRefreshSeconds = "DailyNote.RefreshSeconds";
/// <summary>
/// 实时便笺提醒式通知
/// </summary>
public const string DailyNoteReminderNotify = "DailyNote.ReminderNotify";
/// <summary>
/// 实时便笺免打扰模式
/// </summary>
public const string DailyNoteSilentWhenPlayingGame = "DailyNote.SilentWhenPlayingGame";
/// <summary>
/// 实时便笺 WebhookUrl
/// </summary>
public const string DailyNoteWebhookUrl = "DailyNote.WebhookUrl";
/// <summary>
/// 启动游戏 总开关
/// </summary>
public const string LaunchIsLaunchOptionsEnabled = "Launch.IsLaunchOptionsEnabled";
/// <summary>
/// 启动游戏 独占全屏
/// </summary>
public const string LaunchIsExclusive = "Launch.IsExclusive";
/// <summary>
/// 启动游戏 全屏
/// </summary>
public const string LaunchIsFullScreen = "Launch.IsFullScreen";
/// <summary>
/// 启动游戏 无边框
/// </summary>
public const string LaunchIsBorderless = "Launch.IsBorderless";
/// <summary>
/// 启动游戏 宽度
/// </summary>
public const string LaunchScreenWidth = "Launch.ScreenWidth";
public const string LaunchIsScreenWidthEnabled = "Launch.IsScreenWidthEnabled";
/// <summary>
/// 启动游戏 高度
/// </summary>
public const string LaunchScreenHeight = "Launch.ScreenHeight";
public const string LaunchIsScreenHeightEnabled = "Launch.IsScreenHeightEnabled";
/// <summary>
/// 启动游戏 解锁帧率
/// </summary>
public const string LaunchUnlockFps = "Launch.UnlockFps";
/// <summary>
/// 启动游戏 目标帧率
/// </summary>
public const string LaunchTargetFps = "Launch.TargetFps";
/// <summary>
/// 启动游戏 显示器编号
/// </summary>
public const string LaunchMonitor = "Launch.Monitor";
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";
public const string LaunchIsUseCloudThirdPartyMobile = "Launch.IsUseCloudThirdPartyMobile";
public const string LaunchUseStarwardPlayTimeStatistics = "Launch.UseStarwardPlayTimeStatistics";
public const string LaunchSetDiscordActivityWhenPlaying = "Launch.SetDiscordActivityWhenPlaying";
/// <summary>
/// 启动游戏 多倍启动
/// </summary>
[Obsolete("不再支持多开")]
public const string MultipleInstances = "Launch.MultipleInstances";
/// <summary>
/// 语言
/// </summary>
public const string Culture = "Culture";
/// <summary>
/// 自定义极验接口
/// </summary>
public const string GeetestCustomCompositeUrl = "GeetestCustomCompositeUrl";
public const string AnnouncementRegion = "AnnouncementRegion";
}

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.2.0" />
Version="1.9.1.0" />
<Properties>
<DisplayName>Snap Hutao</DisplayName>

View File

@@ -13,7 +13,7 @@
<Identity
Name="60568DGPStudio.SnapHutaoDev"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.9.2.0" />
Version="1.9.1.0" />
<Properties>
<DisplayName>Snap Hutao Dev</DisplayName>

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -120,12 +120,6 @@
<data name="AppDevNameAndVersion" xml:space="preserve">
<value>Snap Hutao Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>Snap Hutao Dev {0} [Administrator]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>Snap Hutao {0} [Administrator]</value>
</data>
<data name="AppName" xml:space="preserve">
<value>Snap Hutao</value>
</data>
@@ -1553,9 +1547,6 @@
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
<value>Switch game account failed</value>
</data>
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
<value>Cannot select account [{1}] for Uid [{0}], the account does not belong to current server</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>Operation completed</value>
</data>
@@ -2147,9 +2138,6 @@
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>Pre-download</value>
</data>
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
<value>Select Game Path</value>
</data>
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
<value>This account has not been bound to the Real-time Note notification UID.</value>
</data>
@@ -2282,15 +2270,6 @@
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
<value>Device IP</value>
</data>
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
<value>Administrator mode will change some features' availability and behaviors</value>
</data>
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
<value>Administrator Mode</value>
</data>
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
<value>Restart with Administrator Mode</value>
</data>
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
<value>Show or hide wish event with no wish history</value>
</data>
@@ -2750,15 +2729,6 @@
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>Copied to clipboard</value>
</data>
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
<value>Completed</value>
</data>
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
<value>Locked</value>
</data>
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
<value>In Progress</value>
</data>
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
<value>Finished</value>
</data>

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -120,12 +120,6 @@
<data name="AppDevNameAndVersion" xml:space="preserve">
<value>Snap Hutao Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>Snap Hutao Dev {0} [Administrator]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>Snap Hutao {0} [Administrator]</value>
</data>
<data name="AppName" xml:space="preserve">
<value>Snap Hutao</value>
</data>
@@ -996,7 +990,7 @@
<value>Versi UIGF tidak didukung</value>
</data>
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
<value>Versi terbau {0} sudah tersedia.</value>
<value>发现新版本 {0}</value>
</data>
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
<value>Beberapa pengguna tercatat sebagai dipilih</value>
@@ -1553,9 +1547,6 @@
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
<value>Gagal ganti akun game</value>
</data>
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
<value>Tidak bisa memilih akun [{1}] dengan UID [{0}], Akun itu tidak berada pada server tersebut</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>Tindakan selesai</value>
</data>
@@ -2100,7 +2091,7 @@
<value>Mengatur Status Aktivitas Discord Saya Ketika Saya Sedang Bermain Game</value>
</data>
<data name="ViewPageLaunchGameDiscordActivityHeader" xml:space="preserve">
<value>Aktifitas Discord</value>
<value>Discord Activity</value>
</data>
<data name="ViewPageLaunchGameFileHeader" xml:space="preserve">
<value>Berkas</value>
@@ -2147,9 +2138,6 @@
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>Prapengunduhan</value>
</data>
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
<value>Pilih Path Game</value>
</data>
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
<value>Akun ini belum terikat ke UID notifikasi Catatan Realtime.</value>
</data>
@@ -2282,15 +2270,6 @@
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
<value>ID Perangkat</value>
</data>
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
<value>Mode administrator akan mengubah ketersediaan dan perilaku beberapa fitur</value>
</data>
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
<value>Mode Admin</value>
</data>
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
<value>Restart menggunakan Mode Administrator</value>
</data>
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
<value>Tampilkan atau sembunyikan acara keinginan tanpa riwayat Wish</value>
</data>
@@ -2634,10 +2613,10 @@
<value>Mengunggah Data</value>
</data>
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
<value>Pasang sekarang?</value>
<value>是否立即安装?</value>
</data>
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
<value>Snap Hutao versi {0} sudah siap</value>
<value>胡桃 {0} 版本已准备就绪</value>
</data>
<data name="ViewTitleAutoClicking" xml:space="preserve">
<value>Auto Click</value>
@@ -2750,15 +2729,6 @@
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>Disalin ke clipboard</value>
</data>
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
<value>Selesai</value>
</data>
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
<value>Terkunci</value>
</data>
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
<value>Dalam proses</value>
</data>
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
<value>Selesai</value>
</data>
@@ -2904,7 +2874,7 @@
<value>Server Internasional Server Asia</value>
</data>
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
<value>Server International: Server TW/HK/MU</value>
<value>国际服 港澳台服</value>
</data>
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
<value>Server Internasional Server Eropa</value>

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -120,12 +120,6 @@
<data name="AppDevNameAndVersion" xml:space="preserve">
<value>胡桃 Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>胡桃Dev {0} [管理者]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>胡桃 {0} [管理者]</value>
</data>
<data name="AppName" xml:space="preserve">
<value>胡桃</value>
</data>
@@ -996,7 +990,7 @@
<value>サポートされていないUIGFバージョン</value>
</data>
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
<value>新しいバージョン {0} が利用できます</value>
<value>发现新版本 {0}</value>
</data>
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
<value>ユーザー情報を複数選択しています。</value>
@@ -1553,9 +1547,6 @@
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
<value>アカウントの切り替えができませんでした</value>
</data>
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
<value>UID [{0}] に対するアカウント [{1}] を選択できませんでした。このアカウントは現在のサーバーに属していません。</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>操作は完了しました</value>
</data>
@@ -1656,7 +1647,7 @@
<value>重要</value>
</data>
<data name="ViewPageAnnouncementViewDetails" xml:space="preserve">
<value>詳細を表示</value>
<value>查看详情</value>
</data>
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
<value>聖遺物スコア</value>
@@ -2147,9 +2138,6 @@
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>事前ダウンロード</value>
</data>
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
<value>ゲームのフォルダを選択</value>
</data>
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
<value>このアカウントはリアルタイムノート通知 UID として連携されていません。</value>
</data>
@@ -2282,15 +2270,6 @@
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
<value>デバイスのIP</value>
</data>
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
<value>管理者モードは一部の機能の使用可否と動作に影響します。</value>
</data>
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
<value>管理者モード</value>
</data>
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
<value>管理者モードで再起動</value>
</data>
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
<value>祈願履歴のないイベント限定祈願を祈願履歴に表示するかを変更します。</value>
</data>
@@ -2328,10 +2307,10 @@
<value>CAPTCHA</value>
</data>
<data name="ViewPageSettingHomeAnnouncementRegionDescription" xml:space="preserve">
<value>お知らせを受け取りたいサーバーを選択してください</value>
<value>选择想要获取公告的游戏服务器</value>
</data>
<data name="ViewPageSettingHomeAnnouncementRegionHeader" xml:space="preserve">
<value>お知らせのサーバー</value>
<value>公告所属服务器</value>
</data>
<data name="ViewpageSettingHomeCardDescription" xml:space="preserve">
<value>ダッシュボードを整理する</value>
@@ -2634,10 +2613,10 @@
<value>データをアップロード</value>
</data>
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
<value>今すぐインストールしますか?</value>
<value>是否立即安装?</value>
</data>
<data name="ViewTitileUpdatePackageReadyTitle" xml:space="preserve">
<value>胡桃 {0} の準備が完了</value>
<value>胡桃 {0} 版本已准备就绪</value>
</data>
<data name="ViewTitleAutoClicking" xml:space="preserve">
<value>オートクリック</value>
@@ -2750,15 +2729,6 @@
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>クリップボードにコピーしました。</value>
</data>
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
<value>すべて完了</value>
</data>
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
<value>未解放</value>
</data>
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
<value>進行中</value>
</data>
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
<value>完了しました。</value>
</data>
@@ -2889,28 +2859,28 @@
<value>ダウンロードリンクのコピーに成功しました</value>
</data>
<data name="WebHoyolabInvalidRegion" xml:space="preserve">
<value>無効なサーバー</value>
<value>无效的服务器</value>
</data>
<data name="WebHoyolabInvalidUid" xml:space="preserve">
<value>無効なUIDです</value>
</data>
<data name="WebHoyolabRegionCNGF01" xml:space="preserve">
<value>中国サーバー: 公式</value>
<value>国服 官方服</value>
</data>
<data name="WebHoyolabRegionCNQD01" xml:space="preserve">
<value>中国サーバー: ビリビリ(bilibili)</value>
<value>国服 渠道服</value>
</data>
<data name="WebHoyolabRegionOSASIA" xml:space="preserve">
<value>国内サーバー: アジア</value>
<value>国际服 亚服</value>
</data>
<data name="WebHoyolabRegionOSCHT" xml:space="preserve">
<value>海外サーバー: 台湾/香港/モンゴル</value>
<value>国际服 港澳台服</value>
</data>
<data name="WebHoyolabRegionOSEURO" xml:space="preserve">
<value>海外サーバー: ヨーロッパ</value>
<value>国际服 欧服</value>
</data>
<data name="WebHoyolabRegionOSUSA" xml:space="preserve">
<value>海外サーバー: 北アメリカ</value>
<value>国际服 美服</value>
</data>
<data name="WebHutaoServiceUnAvailable" xml:space="preserve">
<value>胡桃サーバがメンテナンス中です</value>

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -120,12 +120,6 @@
<data name="AppDevNameAndVersion" xml:space="preserve">
<value>호두 Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>胡桃Dev {0} [管理员]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>胡桃 {0} [管理员]</value>
</data>
<data name="AppName" xml:space="preserve">
<value>호두</value>
</data>
@@ -1553,9 +1547,6 @@
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
<value>계정 전환 살패</value>
</data>
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
<value>无法选择UID [{0}] 对应的账号 [{1}],该账号不属于当前服务器</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>操作完成</value>
</data>
@@ -2147,9 +2138,6 @@
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>사전 다운로드</value>
</data>
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
<value>选择游戏路径</value>
</data>
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
<value>该账号尚未绑定实时便笺通知 UID</value>
</data>
@@ -2282,15 +2270,6 @@
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
<value>设备 IP</value>
</data>
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
<value>管理员模式会影响部分功能的可用性与行为</value>
</data>
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
<value>管理员模式</value>
</data>
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
<value>以管理员身份重启</value>
</data>
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
<value>기원 기록 페이지에 기록되지 않은 오래된 기원 이벤트를 표시하거나 숨깁니다</value>
</data>
@@ -2750,15 +2729,6 @@
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>已复制到剪贴板</value>
</data>
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
<value>全部完成</value>
</data>
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
<value>尚未开启</value>
</data>
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
<value>进行中</value>
</data>
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
<value>已完成</value>
</data>

View File

@@ -121,7 +121,7 @@
<value>胡桃 Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>胡桃 Dev {0} [管理员]</value>
<value>胡桃Dev {0} [管理员]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>胡桃 {0} [管理员]</value>
@@ -1271,12 +1271,6 @@
<data name="ViewDialogQRCodeTitle" xml:space="preserve">
<value>使用米游社扫描二维码</value>
</data>
<data name="ViewDialogReconfirmTextHeader" xml:space="preserve">
<value>请输入你正在启用的功能标题</value>
</data>
<data name="ViewDialogReconfirmTitle" xml:space="preserve">
<value>你正在启用一个危险功能</value>
</data>
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
<value>该操作是不可逆的,所有用户登录状态会丢失</value>
</data>
@@ -1580,12 +1574,6 @@
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
<value>创建桌面快捷方式失败</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderContent" xml:space="preserve">
<value>后续转换会重新下载所需的文件,确定要删除吗?</value>
</data>
<data name="ViewModelSettingDeleteServerCacheFolderTitle" xml:space="preserve">
<value>删除转换服务器游戏客户端缓存</value>
</data>
<data name="ViewModelSettingFolderSizeDescription" xml:space="preserve">
<value>已使用磁盘空间:{0}</value>
</data>
@@ -2204,12 +2192,6 @@
<data name="ViewPageLaunchGameUnlockFpsOn" xml:space="preserve">
<value>启用</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRDescription" xml:space="preserve">
<value>充分利用支持高动态范围的显示器获得更亮、更生动、更精细的画面</value>
</data>
<data name="ViewPageLaunchGameWindowsHDRHeader" xml:space="preserve">
<value>Windows HDR</value>
</data>
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>请输入你的 HoYoLab Uid</value>
</data>
@@ -2427,7 +2409,7 @@
<value>在完整阅读原神和胡桃工具箱用户协议后,我选择启用「启动游戏-高级功能」</value>
</data>
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader" xml:space="preserve">
<value>高级功能</value>
<value>启动高级功能</value>
</data>
<data name="ViewPageSettingKeyShortcutAutoClickingDescription" xml:space="preserve">
<value>更改自动连点功能的快捷键</value>
@@ -2582,12 +2564,6 @@
<data name="ViewSettingAllocConsoleHeader" xml:space="preserve">
<value>调试控制台</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderDescription" xml:space="preserve">
<value>在启动游戏中转换服务器后会产生对应的游戏客户端文件用作缓存</value>
</data>
<data name="ViewSettingDeleteServerCacheFolderHeader" xml:space="preserve">
<value>删除转换服务器缓存</value>
</data>
<data name="ViewSettingFolderViewOpenFolderAction" xml:space="preserve">
<value>打开文件夹</value>
</data>
@@ -2771,9 +2747,6 @@
<data name="WebAnnouncementTimeHoursEndFormat" xml:space="preserve">
<value>{0} 小时后结束</value>
</data>
<data name="WebBridgeShareCopyToClipboardFailed" xml:space="preserve">
<value>打开剪贴板失败</value>
</data>
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>已复制到剪贴板</value>
</data>

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -120,17 +120,11 @@
<data name="AppDevNameAndVersion" xml:space="preserve">
<value>Snap Hutao Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>Ху Тао Dev {0} [Администратор]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>Ху Тао {0} [Администратор]</value>
</data>
<data name="AppName" xml:space="preserve">
<value>Ху Тао</value>
<value>Snap Hutao</value>
</data>
<data name="AppNameAndVersion" xml:space="preserve">
<value>Ху Тао {0}</value>
<value>Snap Hutao {0}</value>
</data>
<data name="ContentDialogCancelCloseButtonText" xml:space="preserve">
<value>Отмена</value>
@@ -495,19 +489,19 @@
<value>Волна 3: Волна появится только после убийства всех врагов в предыдущей волне.</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave4" xml:space="preserve">
<value>Четвертая волна: Победите всех монстров, прежде чем появится следующая волна</value>
<value>第四波:击败所有怪物,下一波才会出现</value>
</data>
<data name="ModelNameValueDefaultDescription" xml:space="preserve">
<value>Обновите данные витрины персонажей</value>
<value>请更新角色橱窗数据</value>
</data>
<data name="ModelNameValueDefaultName" xml:space="preserve">
<value>Нет данных</value>
<value>暂无数据</value>
</data>
<data name="ModelWeaponAffixFormat" xml:space="preserve">
<value>Уровень совершенствования {0}</value>
<value>精炼 {0}</value>
</data>
<data name="MustSelectUserAndUid" xml:space="preserve">
<value>Необходимо войти в учетную запись miHoYo/HoYoLAB и выбрать пользователя с персонажем</value>
<value>必须登录 米游社/HoYoLAB 并选择一个用户与角色</value>
</data>
<data name="ServerGachaLogServiceDeleteEntrySucceed" xml:space="preserve">
<value>删除了 Uid{0} 的 {1} 条祈愿记录</value>
@@ -1553,9 +1547,6 @@
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
<value>切换账号失败</value>
</data>
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
<value>无法选择UID [{0}] 对应的账号 [{1}],该账号不属于当前服务器</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>操作完成</value>
</data>
@@ -2147,9 +2138,6 @@
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>预下载</value>
</data>
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
<value>选择游戏路径</value>
</data>
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
<value>该账号尚未绑定实时便笺通知 UID</value>
</data>
@@ -2282,15 +2270,6 @@
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
<value>设备 IP</value>
</data>
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
<value>管理员模式会影响部分功能的可用性与行为</value>
</data>
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
<value>管理员模式</value>
</data>
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
<value>以管理员身份重启</value>
</data>
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
<value>在祈愿记录页面显示或隐藏无记录的历史祈愿活动</value>
</data>
@@ -2750,15 +2729,6 @@
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>已复制到剪贴板</value>
</data>
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
<value>全部完成</value>
</data>
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
<value>尚未开启</value>
</data>
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
<value>进行中</value>
</data>
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
<value>已完成</value>
</data>

View File

@@ -60,45 +60,45 @@
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
@@ -120,12 +120,6 @@
<data name="AppDevNameAndVersion" xml:space="preserve">
<value>胡桃 Dev {0}</value>
</data>
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
<value>胡桃Dev {0} [管理员]</value>
</data>
<data name="AppElevatedNameAndVersion" xml:space="preserve">
<value>胡桃 {0} [管理员]</value>
</data>
<data name="AppName" xml:space="preserve">
<value>胡桃</value>
</data>
@@ -148,7 +142,7 @@
<value>無效的 Uri</value>
</data>
<data name="ControlImageCompositionImageHttpRequest" xml:space="preserve">
<value>获取HTTP{0}</value>
<value>HTTP GET {0}</value>
</data>
<data name="ControlImageCompositionImageSystemException" xml:space="preserve">
<value>應用 CompositionImage 的源時發生異常</value>
@@ -298,7 +292,7 @@
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelIntrinsicBodyTypeBoy" xml:space="preserve">
<value>少</value>
<value>少</value>
</data>
<data name="ModelIntrinsicBodyTypeGirl" xml:space="preserve">
<value>少女</value>
@@ -310,7 +304,7 @@
<value>蘿莉</value>
</data>
<data name="ModelIntrinsicBodyTypeMale" xml:space="preserve">
<value>成年男子</value>
<value>成</value>
</data>
<data name="ModelIntrinsicElementNameElec" xml:space="preserve">
<value>雷</value>
@@ -379,7 +373,7 @@
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelMetadataAvatarPlayerName" xml:space="preserve">
<value>旅</value>
<value>旅行者</value>
</data>
<data name="ModelMetadataFetterInfoBirthdayFormat" xml:space="preserve">
<value>{0} 月 {1} 日</value>
@@ -486,7 +480,7 @@
<value>第一波:擊敗所有怪物,下一波才會出現</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave1Additional" xml:space="preserve">
<value>第一波加:增援第一波怪物</value>
<value>第一波加:增援第一波怪物</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave2" xml:space="preserve">
<value>第二波:擊敗所有怪物,下一波才會出現</value>
@@ -846,7 +840,7 @@
<value>找不到原神內置瀏覽器緩存路徑:\n{0}</value>
</data>
<data name="ServiceGachaLogUrlProviderCacheUrlNotFound" xml:space="preserve">
<value>找到可用的 Url</value>
<value>找到可用的 Url</value>
</data>
<data name="ServiceGachaLogUrlProviderManualInputInvalid" xml:space="preserve">
<value>提供的 Url 無效</value>
@@ -996,7 +990,7 @@
<value>不支援的 UIGF 版本</value>
</data>
<data name="ServiceUpdateStatusVersionDescription" xml:space="preserve">
<value>發現新版本 {0}</value>
<value>发现新版本 {0}</value>
</data>
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
<value>已选中多条用户记录</value>
@@ -1553,9 +1547,6 @@
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
<value>切換帳號失敗</value>
</data>
<data name="ViewModelLaunchGameUnableToSwitchUidAttachedGameAccount" xml:space="preserve">
<value>无法选择UID [{0}] 对应的账号 [{1}],该账号不属于当前服务器</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>操作完成</value>
</data>
@@ -2147,9 +2138,6 @@
<data name="ViewPageLaunchGameResourcePreDownloadHeader" xml:space="preserve">
<value>預下載</value>
</data>
<data name="ViewPageLaunchGameSelectGamePath" xml:space="preserve">
<value>选择游戏路径</value>
</data>
<data name="ViewPageLaunchGameSwitchAccountAttachUidNull" xml:space="preserve">
<value>該用戶尚未綁定即時便箋通知 UID</value>
</data>
@@ -2282,15 +2270,6 @@
<data name="ViewPageSettingDeviceIpHeader" xml:space="preserve">
<value>設備 IP</value>
</data>
<data name="ViewPageSettingElevatedModeDescription" xml:space="preserve">
<value>管理员模式会影响部分功能的可用性与行为</value>
</data>
<data name="ViewPageSettingElevatedModeHeader" xml:space="preserve">
<value>管理员模式</value>
</data>
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
<value>以管理员身份重启</value>
</data>
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
<value>在祈願紀錄頁面顯示或隱藏無記錄的歷史祈願活動</value>
</data>
@@ -2750,15 +2729,6 @@
<data name="WebBridgeShareCopyToClipboardSuccess" xml:space="preserve">
<value>已複製到剪貼簿</value>
</data>
<data name="WebDailyNoteArchonQuestStatusFinished" xml:space="preserve">
<value>全部完成</value>
</data>
<data name="WebDailyNoteArchonQuestStatusNotOpen" xml:space="preserve">
<value>尚未开启</value>
</data>
<data name="WebDailyNoteArchonQuestStatusOngoing" xml:space="preserve">
<value>进行中</value>
</data>
<data name="WebDailyNoteAttendanceRewardStatusFinishedNonReward" xml:space="preserve">
<value>已完成</value>
</data>

View File

@@ -1,10 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Discord.GameSDK;
using Snap.Discord.GameSDK.ABI;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.Unicode;
namespace Snap.Hutao.Service.Discord;
@@ -18,94 +18,75 @@ internal static class DiscordController
private static readonly CancellationTokenSource StopTokenSource = new();
private static readonly object SyncRoot = new();
private static long currentClientId;
private static unsafe IDiscordCore* discordCorePtr;
private static Snap.Discord.GameSDK.Discord? discordManager;
private static bool isInitialized;
public static async ValueTask<DiscordResult> SetDefaultActivityAsync(DateTimeOffset startTime)
public static async ValueTask<Result> SetDefaultActivityAsync(DateTimeOffset startTime)
{
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
return SetDefaultActivity(startTime);
ResetManagerOrIgnore(HutaoAppId);
static unsafe DiscordResult SetDefaultActivity(in DateTimeOffset startTime)
if (discordManager is null)
{
ResetManagerOrIgnore(HutaoAppId);
if (discordCorePtr is null)
{
return DiscordResult.Ok;
}
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
DiscordActivity activity = default;
activity.timestamps.start = startTime.ToUnixTimeSeconds();
SetString(activity.assets.large_image, 128, "icon"u8);
SetString(activity.assets.large_text, 128, SH.AppName);
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
return Result.Ok;
}
ActivityManager activityManager = discordManager.GetActivityManager();
Activity activity = default;
activity.Timestamps.Start = startTime.ToUnixTimeSeconds();
activity.Assets.LargeImage = "icon";
activity.Assets.LargeText = SH.AppName;
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
}
public static async ValueTask<DiscordResult> SetPlayingYuanShenAsync()
public static async ValueTask<Result> SetPlayingYuanShenAsync()
{
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
return SetPlayingYuanShen();
ResetManagerOrIgnore(YuanshenId);
static unsafe DiscordResult SetPlayingYuanShen()
if (discordManager is null)
{
ResetManagerOrIgnore(YuanshenId);
if (discordCorePtr is null)
{
return DiscordResult.Ok;
}
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
DiscordActivity activity = default;
SetString(activity.state, 128, SH.FormatServiceDiscordGameLaunchedBy(SH.AppName));
SetString(activity.details, 128, SH.ServiceDiscordGameActivityDetails);
activity.timestamps.start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
SetString(activity.assets.large_image, 128, "icon"u8);
SetString(activity.assets.large_text, 128, "原神"u8);
SetString(activity.assets.small_image, 128, "app"u8);
SetString(activity.assets.small_text, 128, SH.AppName);
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
return Result.Ok;
}
ActivityManager activityManager = discordManager.GetActivityManager();
Activity activity = default;
activity.State = SH.FormatServiceDiscordGameLaunchedBy(SH.AppName);
activity.Details = SH.ServiceDiscordGameActivityDetails;
activity.Timestamps.Start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
activity.Assets.LargeImage = "icon";
activity.Assets.LargeText = "原神";
activity.Assets.SmallImage = "app";
activity.Assets.SmallText = SH.AppName;
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
}
public static async ValueTask<DiscordResult> SetPlayingGenshinImpactAsync()
public static async ValueTask<Result> SetPlayingGenshinImpactAsync()
{
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
return SetPlayingGenshinImpact();
ResetManagerOrIgnore(GenshinImpactId);
static unsafe DiscordResult SetPlayingGenshinImpact()
if (discordManager is null)
{
ResetManagerOrIgnore(GenshinImpactId);
if (discordCorePtr is null)
{
return DiscordResult.Ok;
}
IDiscordActivityManager* activityManagerPtr = discordCorePtr->get_activity_manager(discordCorePtr);
DiscordActivity activity = default;
SetString(activity.state, 128, SH.FormatServiceDiscordGameLaunchedBy(SH.AppName));
SetString(activity.details, 128, SH.ServiceDiscordGameActivityDetails);
activity.timestamps.start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
SetString(activity.assets.large_image, 128, "icon"u8);
SetString(activity.assets.large_text, 128, "Genshin Impact"u8);
SetString(activity.assets.small_image, 128, "app"u8);
SetString(activity.assets.small_text, 128, SH.AppName);
return new DiscordUpdateActivityAsyncAction(activityManagerPtr).WaitUpdateActivity(activity);
return Result.Ok;
}
ActivityManager activityManager = discordManager.GetActivityManager();
Activity activity = default;
activity.State = SH.FormatServiceDiscordGameLaunchedBy(SH.AppName);
activity.Details = SH.ServiceDiscordGameActivityDetails;
activity.Timestamps.Start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
activity.Assets.LargeImage = "icon";
activity.Assets.LargeText = "Genshin Impact";
activity.Assets.SmallImage = "app";
activity.Assets.SmallText = SH.AppName;
return await activityManager.UpdateActivityAsync(activity).ConfigureAwait(false);
}
public static unsafe void Stop()
public static void Stop()
{
if (!isInitialized)
{
@@ -117,7 +98,7 @@ internal static class DiscordController
StopTokenSource.Cancel();
try
{
discordCorePtr = default;
discordManager?.Dispose();
}
catch (SEHException)
{
@@ -127,30 +108,23 @@ internal static class DiscordController
private static unsafe void ResetManagerOrIgnore(long clientId)
{
if (currentClientId == clientId)
if (discordManager?.ClientId == clientId)
{
return;
}
// Actually requires a discord client to be running on Windows platform.
// If not, the following creation code will throw.
if (System.Diagnostics.Process.GetProcessesByName("Discord").Length <= 0)
if (System.Diagnostics.Process.GetProcessesByName("Discord").Length == 0)
{
return;
}
lock (SyncRoot)
{
DiscordCreateParams @params = default;
Methods.DiscordCreateParamsSetDefault(&@params);
@params.client_id = clientId;
@params.flags = (uint)DiscordCreateFlags.Default;
IDiscordCore* ptr = default;
Methods.DiscordCreate(3, &@params, &ptr);
currentClientId = clientId;
discordCorePtr = ptr;
discordCorePtr->set_log_hook(discordCorePtr, DiscordLogLevel.Debug, default, &DebugWriteDiscordMessage);
discordManager?.Dispose();
discordManager = new(clientId, CreateFlags.NoRequireDiscord);
discordManager.SetLogHook(Snap.Discord.GameSDK.LogLevel.Debug, SetLogHookHandler.Create(&DebugWriteDiscordMessage));
}
if (isInitialized)
@@ -161,10 +135,10 @@ internal static class DiscordController
DiscordRunCallbacksAsync(StopTokenSource.Token).SafeForget();
isInitialized = true;
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
static unsafe void DebugWriteDiscordMessage(void* state, DiscordLogLevel logLevel, sbyte* ptr)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
static unsafe void DebugWriteDiscordMessage(Snap.Discord.GameSDK.LogLevel logLevel, byte* ptr)
{
ReadOnlySpan<byte> utf8 = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)ptr);
ReadOnlySpan<byte> utf8 = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(ptr);
string message = System.Text.Encoding.UTF8.GetString(utf8);
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK]:[{logLevel}]:{message}");
}
@@ -172,7 +146,7 @@ internal static class DiscordController
private static async ValueTask DiscordRunCallbacksAsync(CancellationToken cancellationToken)
{
using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(500)))
using (PeriodicTimer timer = new(TimeSpan.FromMilliseconds(1000)))
{
try
{
@@ -187,10 +161,15 @@ internal static class DiscordController
{
try
{
DiscordResult result = DiscordCoreRunRunCallbacks();
if (result is not DiscordResult.Ok)
discordManager?.RunCallbacks();
}
catch (ResultException ex)
{
// If result is Ok
// Maybe the connection is reset.
if (ex.Result is not Result.Ok)
{
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK ERROR]:{result:D} {result}");
System.Diagnostics.Debug.WriteLine($"[Discord.GameSDK ERROR]:{ex.Result:D} {ex.Result}");
}
}
catch (SEHException ex)
@@ -206,70 +185,5 @@ internal static class DiscordController
{
}
}
unsafe DiscordResult DiscordCoreRunRunCallbacks()
{
if (discordCorePtr is not null)
{
return discordCorePtr->run_callbacks(discordCorePtr);
}
return DiscordResult.Ok;
}
}
private static unsafe void SetString(sbyte* reference, int length, string source)
{
Span<sbyte> sbytes = new(reference, length);
sbytes.Clear();
Utf8.FromUtf16(source.AsSpan(), MemoryMarshal.Cast<sbyte, byte>(sbytes), out _, out _);
}
private static unsafe void SetString(sbyte* reference, int length, in ReadOnlySpan<byte> source)
{
Span<sbyte> sbytes = new(reference, length);
sbytes.Clear();
source.CopyTo(MemoryMarshal.Cast<sbyte, byte>(sbytes));
}
private struct DiscordAsyncAction
{
public DiscordResult Result;
public bool IsCompleted;
}
private unsafe struct DiscordUpdateActivityAsyncAction
{
private readonly IDiscordActivityManager* activityManagerPtr;
private DiscordAsyncAction discordAsyncAction;
public DiscordUpdateActivityAsyncAction(IDiscordActivityManager* activityManagerPtr)
{
this.activityManagerPtr = activityManagerPtr;
}
public DiscordResult WaitUpdateActivity(DiscordActivity activity)
{
fixed (DiscordAsyncAction* actionPtr = &discordAsyncAction)
{
activityManagerPtr->update_activity(activityManagerPtr, &activity, actionPtr, &HandleResult);
}
SpinWaitPolyfill.SpinUntil(ref discordAsyncAction, &CheckActionCompleted);
return discordAsyncAction.Result;
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void HandleResult(void* state, DiscordResult result)
{
DiscordAsyncAction* action = (DiscordAsyncAction*)state;
action->Result = result;
action->IsCompleted = true;
}
private static bool CheckActionCompleted(ref readonly DiscordAsyncAction state)
{
return state.IsCompleted;
}
}
}

View File

@@ -20,8 +20,6 @@ internal static class RegistryInterop
private const string SdkChineseValueName = "MIHOYOSDK_ADL_PROD_CN_h3123967166";
private const string SdkOverseaValueName = "MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810";
private const string WindowsHDROnValueName = "WINDOWS_HDR_ON_h3132281285";
public static bool Set(GameAccount? account)
{
if (account is not null)
@@ -58,12 +56,6 @@ internal static class RegistryInterop
}
}
public static void SetWindowsHDR(bool isOversea)
{
string keyName = isOversea ? OverseaKeyName : ChineseKeyName;
Registry.SetValue(keyName, WindowsHDROnValueName, 1);
}
private static (string KeyName, string ValueName) GetKeyValueName(SchemeType scheme)
{
return scheme switch

View File

@@ -42,7 +42,6 @@ internal sealed class LaunchOptions : DbStoreOptions
private NameValue<int>? monitor;
private bool? isMonitorEnabled;
private bool? isUseCloudThirdPartyMobile;
private bool? isWindowsHDREnabled;
private AspectRatio? selectedAspectRatio;
private bool? useStarwardPlayTimeStatistics;
private bool? setDiscordActivityWhenPlaying;
@@ -199,12 +198,6 @@ internal sealed class LaunchOptions : DbStoreOptions
set => SetOption(ref isUseCloudThirdPartyMobile, SettingEntry.LaunchIsUseCloudThirdPartyMobile, value);
}
public bool IsWindowsHDREnabled
{
get => GetOption(ref isWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, false);
set => SetOption(ref isWindowsHDREnabled, SettingEntry.LaunchIsWindowsHDREnabled, value);
}
public List<AspectRatio> AspectRatios { get; } =
[
new(2560, 1440),

View File

@@ -4,7 +4,6 @@
using Snap.Hutao.Core;
using Snap.Hutao.Factory.Progress;
using Snap.Hutao.Service.Discord;
using Snap.Hutao.Service.Game.Account;
using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Service.Game.Unlocker;
using System.IO;
@@ -62,11 +61,6 @@ internal sealed partial class GameProcessService : IGameProcessService
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileName);
if (launchOptions.IsWindowsHDREnabled)
{
RegistryInterop.SetWindowsHDR(isOversea);
}
progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing));
using (System.Diagnostics.Process game = InitializeGameProcess(gamePath))
{

View File

@@ -173,7 +173,6 @@
<None Remove="View\Dialog\HutaoPassportUnregisterDialog.xaml" />
<None Remove="View\Dialog\LaunchGameAccountNameDialog.xaml" />
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
<None Remove="View\Dialog\ReconfirmDialog.xaml" />
<None Remove="View\Dialog\UserDialog.xaml" />
<None Remove="View\Dialog\UserQRCodeDialog.xaml" />
<None Remove="View\Guide\GuideView.xaml" />
@@ -312,7 +311,7 @@
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231115000" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.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>
@@ -344,11 +343,6 @@
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\ReconfirmDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\UserQRCodeDialog.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -1,21 +0,0 @@
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.ReconfirmDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
Title="{shcm:ResourceString Name=ViewDialogReconfirmTitle}"
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
DefaultButton="Close"
IsPrimaryButtonEnabled="False"
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<TextBox
Margin="0,0,0,8"
VerticalAlignment="Top"
Header="{shcm:ResourceString Name=ViewDialogReconfirmTextHeader}"
Text="{x:Bind Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</ContentDialog>

View File

@@ -1,35 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.View.Dialog;
[DependencyProperty("Text", typeof(string), default(string), nameof(OnTextChanged))]
internal sealed partial class ReconfirmDialog : ContentDialog
{
private readonly ITaskContext taskContext;
public ReconfirmDialog(IServiceProvider serviceProvider)
{
InitializeComponent();
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
}
public string ConfirmText { get; private set; } = default!;
public async ValueTask<bool> ConfirmAsync(string confirmText)
{
await taskContext.SwitchToMainThreadAsync();
ConfirmText = confirmText;
return await ShowAsync() is ContentDialogResult.Primary;
}
private static void OnTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
ReconfirmDialog dialog = (ReconfirmDialog)sender;
dialog.IsPrimaryButtonEnabled = string.Equals(dialog.Text, dialog.ConfirmText, StringComparison.Ordinal);
}
}

View File

@@ -12,6 +12,7 @@ using Snap.Hutao.Web.Response;
using System.Collections.Specialized;
using System.IO;
using System.Web;
using Windows.Foundation;
namespace Snap.Hutao.View.Dialog;

View File

@@ -199,12 +199,6 @@
ItemsSource="{Binding GameAccountsView}"
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
</Border>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGameWindowsHDRDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameWindowsHDRHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE7F7;}">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsWindowsHDREnabled, Mode=TwoWay}"/>
</cwc:SettingsCard>
<!-- 进程 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameProcessHeader}"/>

View File

@@ -374,14 +374,9 @@
Description="{shcm:ResourceString Name=ViewPageSettingSetDataFolderDescription}"
Header="{shcm:ResourceString Name=ViewPageSettingSetDataFolderHeader}"
IsClickEnabled="True"/>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
Command="{Binding DeleteServerCacheFolderCommand}"
Description="{shcm:ResourceString Name=ViewSettingDeleteServerCacheFolderDescription}"
Header="{shcm:ResourceString Name=ViewSettingDeleteServerCacheFolderHeader}"
IsClickEnabled="True"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
<cwc:SettingsExpander
Description="{Binding CacheFolderView.Size}"
Header="{shcm:ResourceString Name=ViewPageSettingCacheFolderHeader}"
@@ -413,6 +408,7 @@
HeaderIcon="{cw:FontIcon Glyph=&#xE756;}">
<ToggleSwitch Width="120" IsOn="{Binding IsAllocConsoleDebugModeEnabled, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard
Header="{shcm:ResourceString Name=ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE730;}"
@@ -428,7 +424,7 @@
</cwc:SettingsCard.Description>
<StackPanel Orientation="Horizontal">
<shvc:Elevation Visibility="{Binding HutaoOptions.IsElevated, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
<ToggleSwitch Width="120" IsOn="{Binding IsAdvancedLaunchOptionsEnabled, Mode=TwoWay}"/>
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsAdvancedLaunchOptionsEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
<cwc:SettingsCard
@@ -447,4 +443,4 @@
</StackPanel>
</Grid>
</ScrollViewer>
</shc:ScopedPage>
</shc:ScopedPage>

View File

@@ -122,56 +122,11 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
public IPInformation? IPInformation { get => ipInformation; private set => SetProperty(ref ipInformation, value); }
[SuppressMessage("", "CA1822")]
public bool IsAllocConsoleDebugModeEnabled
{
get => LocalSetting.Get(SettingKeys.IsAllocConsoleDebugModeEnabled, false);
set
{
ConfirmSetIsAllocConsoleDebugModeEnabledAsync(value).SafeForget();
async ValueTask ConfirmSetIsAllocConsoleDebugModeEnabledAsync(bool value)
{
if (value)
{
ReconfirmDialog dialog = await contentDialogFactory.CreateInstanceAsync<ReconfirmDialog>().ConfigureAwait(false);
if (await dialog.ConfirmAsync(SH.ViewSettingAllocConsoleHeader).ConfigureAwait(true))
{
LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, true);
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
return;
}
}
LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, false);
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
}
}
}
public bool IsAdvancedLaunchOptionsEnabled
{
get => launchOptions.IsAdvancedLaunchOptionsEnabled;
set
{
ConfirmSetIsAdvancedLaunchOptionsEnabledAsync(value).SafeForget();
async ValueTask ConfirmSetIsAdvancedLaunchOptionsEnabledAsync(bool value)
{
if (value)
{
ReconfirmDialog dialog = await contentDialogFactory.CreateInstanceAsync<ReconfirmDialog>().ConfigureAwait(false);
if (await dialog.ConfirmAsync(SH.ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader).ConfigureAwait(true))
{
launchOptions.IsAdvancedLaunchOptionsEnabled = true;
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
return;
}
}
launchOptions.IsAdvancedLaunchOptionsEnabled = false;
OnPropertyChanged(nameof(IsAllocConsoleDebugModeEnabled));
}
}
set => LocalSetting.Set(SettingKeys.IsAllocConsoleDebugModeEnabled, value);
}
protected override async ValueTask<bool> InitializeUIAsync()
@@ -268,26 +223,6 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
}
}
[Command("DeleteServerCacheFolderCommand")]
private async Task DeleteServerCacheFolderAsync()
{
ContentDialogResult result = await contentDialogFactory.CreateForConfirmCancelAsync(
SH.ViewModelSettingDeleteServerCacheFolderTitle,
SH.ViewModelSettingDeleteServerCacheFolderContent)
.ConfigureAwait(false);
if (result is ContentDialogResult.Primary)
{
string cacheFolder = runtimeOptions.GetDataFolderServerCacheFolder();
if (Directory.Exists(cacheFolder))
{
Directory.Delete(cacheFolder, true);
}
infoBarService.Information(SH.ViewModelSettingActionComplete);
}
}
[Command("CopyDeviceIdCommand")]
private void CopyDeviceId()
{

View File

@@ -1,41 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Service.Notification;
using System.Net.Http;
namespace Snap.Hutao.Web.Bridge;
internal sealed class BridgeShareContext
{
private readonly CoreWebView2 coreWebView2;
private readonly ITaskContext taskContext;
private readonly HttpClient httpClient;
private readonly IInfoBarService infoBarService;
private readonly IClipboardProvider clipboardProvider;
private readonly JsonSerializerOptions jsonSerializerOptions;
public BridgeShareContext(CoreWebView2 coreWebView2, ITaskContext taskContext, HttpClient httpClient, IInfoBarService infoBarService, IClipboardProvider clipboardProvider, JsonSerializerOptions jsonSerializerOptions)
{
this.httpClient = httpClient;
this.taskContext = taskContext;
this.infoBarService = infoBarService;
this.clipboardProvider = clipboardProvider;
this.coreWebView2 = coreWebView2;
this.jsonSerializerOptions = jsonSerializerOptions;
}
public CoreWebView2 CoreWebView2 { get => coreWebView2; }
public ITaskContext TaskContext { get => taskContext; }
public HttpClient HttpClient { get => httpClient; }
public IInfoBarService InfoBarService { get => infoBarService; }
public IClipboardProvider ClipboardProvider { get => clipboardProvider; }
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
}

View File

@@ -1,108 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Web.Bridge.Model;
using System.IO;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
namespace Snap.Hutao.Web.Bridge;
internal sealed partial class BridgeShareImplmentation
{
public static async ValueTask<IJsBridgeResult?> ShareAsync(JsParam<SharePayload> param, BridgeShareContext context)
{
SharePayload payload = param.Payload;
switch (payload.Type)
{
case "image":
{
ShareContent content = payload.Content;
if (content.ImageUrl is { Length: > 0 } imageUrl)
{
await ShareFromImageUrlAsync(context, imageUrl).ConfigureAwait(false);
}
else if (content.ImageBase64 is { } imageBase64)
{
await ShareFromImageBase64Async(context, imageBase64).ConfigureAwait(false);
}
break;
}
case "screenshot":
{
await context.TaskContext.SwitchToMainThreadAsync();
// https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot
string jsonParameters = """{ "format": "png", "captureBeyondViewport": true }""";
string resultJson = await context.CoreWebView2.CallDevToolsProtocolMethodAsync("Page.captureScreenshot", jsonParameters);
CaptureScreenshotResult? result = JsonSerializer.Deserialize<CaptureScreenshotResult>(resultJson, context.JsonSerializerOptions);
ArgumentNullException.ThrowIfNull(result);
await ShareFromRawPixelDataAsync(context, result.Data).ConfigureAwait(false);
break;
}
}
return new JsResult<Dictionary<string, string>>()
{
Data = new()
{
["type"] = param.Payload.Type,
},
};
}
private static async ValueTask ShareFromImageUrlAsync(BridgeShareContext context, string imageUrl)
{
using (Stream stream = await context.HttpClient.GetStreamAsync(imageUrl).ConfigureAwait(false))
{
await ShareCoreAsync(context, stream, static (stream, web) => web.CopyToAsync(stream.AsStreamForWrite())).ConfigureAwait(false);
}
}
private static ValueTask ShareFromImageBase64Async(BridgeShareContext context, string base64ImageData)
{
return ShareFromRawPixelDataAsync(context, Convert.FromBase64String(base64ImageData));
}
private static ValueTask ShareFromRawPixelDataAsync(BridgeShareContext context, byte[] rawPixelData)
{
return ShareCoreAsync(context, rawPixelData, static (stream, bytes) => stream.AsStreamForWrite().WriteAsync(bytes).AsTask());
}
private static async ValueTask ShareCoreAsync<TData>(BridgeShareContext context, TData data, Func<InMemoryRandomAccessStream, TData, Task> asyncWriteData)
{
using (InMemoryRandomAccessStream rawPixelDataStream = new())
{
await asyncWriteData(rawPixelDataStream, data).ConfigureAwait(false);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(rawPixelDataStream);
using (InMemoryRandomAccessStream stream = new())
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetSoftwareBitmap(await decoder.GetSoftwareBitmapAsync());
await encoder.FlushAsync();
await context.TaskContext.SwitchToMainThreadAsync();
if (context.ClipboardProvider.SetBitmap(stream))
{
context.InfoBarService.Success(SH.WebBridgeShareCopyToClipboardSuccess);
}
else
{
context.InfoBarService.Error(SH.WebBridgeShareCopyToClipboardFailed);
}
}
}
}
private sealed class CaptureScreenshotResult
{
[JsonPropertyName("data")]
public byte[] Data { get; set; } = default!;
}
}

View File

@@ -3,9 +3,7 @@
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Bridge.Model;
@@ -14,7 +12,6 @@ using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.DataSigning;
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
using Snap.Hutao.Web.Response;
using System.Net.Http;
using System.Text;
using Windows.Foundation;
@@ -27,7 +24,7 @@ namespace Snap.Hutao.Web.Bridge;
[SuppressMessage("", "CA1001")]
internal class MiHoYoJSBridge
{
private const string InitializeJsInterfaceScript = """
private const string InitializeJsInterfaceScript2 = """
window.MiHoYoJSInterface = {
postMessage: function(arg) { chrome.webview.postMessage(arg) },
closePage: function() { this.postMessage('{"method":"closePage"}') },
@@ -42,6 +39,7 @@ internal class MiHoYoJSBridge
private const string ConvertMouseToTouchScript = """
function mouseListener (e, event) {
console.log(event);
let touch = new Touch({
identifier: Date.now(),
target: e.target,
@@ -59,6 +57,7 @@ internal class MiHoYoJSBridge
targetTouches: [touch],
changedTouches: [touch],
});
console.log(touchEvent);
e.target.dispatchEvent(touchEvent);
}
@@ -81,7 +80,7 @@ internal class MiHoYoJSBridge
""";
private readonly SemaphoreSlim webMessageSemaphore = new(1);
private readonly Guid bridgeId = Guid.NewGuid();
private readonly Guid interfaceId = Guid.NewGuid();
private readonly UserAndUid userAndUid;
private readonly IServiceProvider serviceProvider;
@@ -123,6 +122,11 @@ internal class MiHoYoJSBridge
coreWebView2 = default!;
}
/// <summary>
/// 关闭
/// </summary>
/// <param name="param">参数</param>
/// <returns>响应</returns>
protected virtual async ValueTask<IJsBridgeResult?> ClosePageAsync(JsParam param)
{
await taskContext.SwitchToMainThreadAsync();
@@ -138,11 +142,21 @@ internal class MiHoYoJSBridge
return null;
}
/// <summary>
/// 调整分享设置
/// </summary>
/// <param name="param">参数</param>
/// <returns>响应</returns>
protected virtual IJsBridgeResult? ConfigureShare(JsParam param)
{
return null;
}
/// <summary>
/// 获取ActionTicket
/// </summary>
/// <param name="jsParam">参数</param>
/// <returns>响应</returns>
protected virtual async ValueTask<IJsBridgeResult?> GetActionTicketAsync(JsParam<ActionTypePayload> jsParam)
{
return await serviceProvider
@@ -151,6 +165,11 @@ internal class MiHoYoJSBridge
.ConfigureAwait(false);
}
/// <summary>
/// 异步获取账户信息
/// </summary>
/// <param name="param">参数</param>
/// <returns>响应</returns>
protected virtual JsResult<Dictionary<string, string>> GetCookieInfo(JsParam param)
{
ArgumentNullException.ThrowIfNull(userAndUid.User.LToken);
@@ -166,6 +185,11 @@ internal class MiHoYoJSBridge
};
}
/// <summary>
/// 获取CookieToken
/// </summary>
/// <param name="param">参数</param>
/// <returns>响应</returns>
protected virtual async ValueTask<JsResult<Dictionary<string, string>>> GetCookieTokenAsync(JsParam<CookieTokenPayload> param)
{
IUserService userService = serviceProvider.GetRequiredService<IUserService>();
@@ -181,6 +205,11 @@ internal class MiHoYoJSBridge
return new() { Data = new() { [Cookie.COOKIE_TOKEN] = userAndUid.User.CookieToken[Cookie.COOKIE_TOKEN] } };
}
/// <summary>
/// 获取当前语言和时区
/// </summary>
/// <param name="param">param</param>
/// <returns>语言与时区</returns>
protected virtual JsResult<Dictionary<string, string>> GetCurrentLocale(JsParam<PushPagePayload> param)
{
MetadataOptions metadataOptions = serviceProvider.GetRequiredService<MetadataOptions>();
@@ -195,6 +224,11 @@ internal class MiHoYoJSBridge
};
}
/// <summary>
/// 获取1代动态密钥
/// </summary>
/// <param name="param">参数</param>
/// <returns>响应</returns>
protected virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV1(JsParam param)
{
DataSignOptions options = DataSignOptions.CreateForGeneration1(SaltType.LK2, true);
@@ -207,6 +241,11 @@ internal class MiHoYoJSBridge
};
}
/// <summary>
/// 获取2代动态密钥
/// </summary>
/// <param name="param">参数</param>
/// <returns>响应</returns>
protected virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV2(JsParam<DynamicSecrect2Playload> param)
{
DataSignOptions options = DataSignOptions.CreateForGeneration2(SaltType.X4, false, param.Payload.Body, param.Payload.GetQueryParam());
@@ -219,6 +258,11 @@ internal class MiHoYoJSBridge
};
}
/// <summary>
/// 获取Http请求头
/// </summary>
/// <param name="param">参数</param>
/// <returns>Http请求头</returns>
protected virtual JsResult<Dictionary<string, string>> GetHttpRequestHeader(JsParam param)
{
Dictionary<string, string> headers = new()
@@ -252,11 +296,21 @@ internal class MiHoYoJSBridge
{
}
/// <summary>
/// 获取状态栏高度
/// </summary>
/// <param name="param">参数</param>
/// <returns>结果</returns>
protected virtual JsResult<Dictionary<string, object>> GetStatusBarHeight(JsParam param)
{
return new() { Data = new() { ["statusBarHeight"] = 0 } };
}
/// <summary>
/// 获取用户基本信息
/// </summary>
/// <param name="param">参数</param>
/// <returns>响应</returns>
protected virtual async ValueTask<JsResult<Dictionary<string, object>>> GetUserInfoAsync(JsParam param)
{
Response<UserFullInfoWrapper> response = await serviceProvider
@@ -310,19 +364,15 @@ internal class MiHoYoJSBridge
return null;
}
protected virtual async ValueTask<IJsBridgeResult?> ShareAsync(JsParam<SharePayload> param)
protected virtual IJsBridgeResult? Share(JsParam<SharePayload> param)
{
using (IServiceScope scope = serviceProvider.CreateScope())
return new JsResult<Dictionary<string, string>>()
{
JsonSerializerOptions jsonSerializerOptions = scope.ServiceProvider.GetRequiredService<JsonSerializerOptions>();
HttpClient httpClient = scope.ServiceProvider.GetRequiredService<HttpClient>();
IClipboardProvider clipboardProvider = scope.ServiceProvider.GetRequiredService<IClipboardProvider>();
IInfoBarService infoBarService = scope.ServiceProvider.GetRequiredService<IInfoBarService>();
BridgeShareContext context = new(coreWebView2, taskContext, httpClient, infoBarService, clipboardProvider, jsonSerializerOptions);
return await BridgeShareImplmentation.ShareAsync(param, context).ConfigureAwait(false);
}
Data = new()
{
["type"] = param.Payload.Type,
},
};
}
protected virtual ValueTask<IJsBridgeResult?> ShowAlertDialogAsync(JsParam param)
@@ -392,7 +442,7 @@ internal class MiHoYoJSBridge
.Append(')')
.ToString();
logger?.LogInformation("[{Id}][ExecuteScript: {callback}]\n{payload}", bridgeId, callback, payload);
logger?.LogInformation("[{Id}][ExecuteScript: {callback}]\n{payload}", interfaceId, callback, payload);
await taskContext.SwitchToMainThreadAsync();
if (coreWebView2 is null || coreWebView2.IsDisposed())
@@ -406,7 +456,7 @@ internal class MiHoYoJSBridge
private async void OnWebMessageReceived(CoreWebView2 webView2, CoreWebView2WebMessageReceivedEventArgs args)
{
string message = args.TryGetWebMessageAsString();
logger.LogInformation("[{Id}][OnRawMessage]\n{message}", bridgeId, message);
logger.LogInformation("[{Id}][OnRawMessage]\n{message}", interfaceId, message);
JsParam? param = JsonSerializer.Deserialize<JsParam>(message);
ArgumentNullException.ThrowIfNull(param);
@@ -455,7 +505,7 @@ internal class MiHoYoJSBridge
"hideLoading" => null,
"login" => null,
"pushPage" => await PushPageAsync(param).ConfigureAwait(false),
"share" => await ShareAsync(param).ConfigureAwait(false),
"share" => Share(param),
"showLoading" => null,
_ => LogUnhandledMessage("Unhandled Message Type: {Method}", param.Method),
};
@@ -480,7 +530,7 @@ internal class MiHoYoJSBridge
ReadOnlySpan<char> uriHostSpan = uriHost.AsSpan();
if (uriHostSpan.EndsWith("mihoyo.com") || uriHostSpan.EndsWith("hoyolab.com"))
{
coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript).AsTask().SafeForget(logger);
coreWebView2.ExecuteScriptAsync(InitializeJsInterfaceScript2).AsTask().SafeForget(logger);
}
}
}

View File

@@ -3,15 +3,8 @@
namespace Snap.Hutao.Web.Bridge.Model;
[SuppressMessage("", "SA1124")]
internal sealed class ShareContent
{
[JsonPropertyName("preview")]
public bool Preview { get; set; }
[JsonPropertyName("image_url")]
public string? ImageUrl { get; set; }
[JsonPropertyName("image_base64")]
public string? ImageBase64 { get; set; }
}
}

View File

@@ -3,4 +3,6 @@
namespace Snap.Hutao.Web.Request.Builder.Abstraction;
internal interface IBuilder;
internal interface IBuilder
{
}