mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
80 Commits
feat/culti
...
revert/qr_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a845dff6ee | ||
|
|
ee99d0b665 | ||
|
|
72b62aa9c6 | ||
|
|
6b031e1866 | ||
|
|
59c03c7f3b | ||
|
|
c03a96b44f | ||
|
|
d5a97903d3 | ||
|
|
7f998dc87f | ||
|
|
2367c4759d | ||
|
|
043e3f07d8 | ||
|
|
cd075c4dab | ||
|
|
65252f1f69 | ||
|
|
f5dd5f4c1d | ||
|
|
27ce55f3f7 | ||
|
|
311941bb89 | ||
|
|
07e2489cab | ||
|
|
699ac60aaf | ||
|
|
dc7bc7e35d | ||
|
|
bf67fcf3a2 | ||
|
|
5093246571 | ||
|
|
94fe192581 | ||
|
|
c679032387 | ||
|
|
d119b056c7 | ||
|
|
ab95ce8ce8 | ||
|
|
0ede5b158f | ||
|
|
6c9a98c2c9 | ||
|
|
a725fc0e9e | ||
|
|
5251dd9343 | ||
|
|
0b71053bf9 | ||
|
|
a91499171d | ||
|
|
e041def070 | ||
|
|
2e81ddf3f4 | ||
|
|
4d988e5500 | ||
|
|
36aff679c0 | ||
|
|
d490393108 | ||
|
|
1633f2c668 | ||
|
|
241122eadc | ||
|
|
339bb9292b | ||
|
|
4e27d40b76 | ||
|
|
4f42a299d1 | ||
|
|
80aad5f09e | ||
|
|
35370c392b | ||
|
|
115dae8966 | ||
|
|
795e9730b9 | ||
|
|
7dac0d6f73 | ||
|
|
7c42b7e8ed | ||
|
|
3c766f55b5 | ||
|
|
38a07caeab | ||
|
|
5fb55ea645 | ||
|
|
aba568d8c6 | ||
|
|
116bd58a81 | ||
|
|
46cc5f7f1e | ||
|
|
3e5ca2440b | ||
|
|
081a60f3f8 | ||
|
|
4a5ddd872d | ||
|
|
81e5159615 | ||
|
|
d5eec58157 | ||
|
|
0242d3d0ca | ||
|
|
3fcb4caaf1 | ||
|
|
ae2ddee6c4 | ||
|
|
089938c1c0 | ||
|
|
c515f70e95 | ||
|
|
317b68b1ce | ||
|
|
7b5a6ccae0 | ||
|
|
c4ff359995 | ||
|
|
70399f7a7d | ||
|
|
3d1a365079 | ||
|
|
2b6d4af5a3 | ||
|
|
80450660e4 | ||
|
|
28b09d56f3 | ||
|
|
995860463f | ||
|
|
474d06457c | ||
|
|
7534729794 | ||
|
|
167d66aa67 | ||
|
|
e20a9f3ff4 | ||
|
|
868232d98f | ||
|
|
4c7e02cd5c | ||
|
|
d8e0d74be6 | ||
|
|
655f97353c | ||
|
|
db3a611c6e |
30
README.md
30
README.md
@@ -1,42 +1,36 @@
|
||||

|
||||

|
||||
|
||||
|
||||
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新 功能相结合,它提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
|
||||
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新功能相结合,提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
|
||||
|
||||
Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed for modern Windows platform to improve the gaming experience for desktop players. By combining existing official resources with new features designed by the development team, it provides a complete and useful set of tools without the need to rely on mobile devices. Snap Hutao does not take any destructive modification to the game client to ensure the security of the toolkit.
|
||||
|
||||
## 下载使用 / Download
|
||||
## 安装 / Installation
|
||||
|
||||
 [](https://github.com/DGP-Studio/Snap.Hutao/releases/latest) []()
|
||||
|
||||
---
|
||||
|
||||
#### 使用安装器安装 / Install with Snap.Hutao.Depolyment Installer
|
||||
你可以按照[快速开始](https://hut.ao/zh/quick-start.html)文档中提供的流程安装并设置 Snap Hutao。
|
||||
|
||||
Snap.Hutao.Depolyment 是一个由 DGP-Studio 重新包装的 Windows 应用安装器,适用于缺少专业计算机知识的一般用户,可以在安装时同时解决缺少必要系统环境的问题。
|
||||
You can follow the instructions in the [Quick Start](https://hut.ao/en/quick-start.html) document to install and set up Snap Hutao.
|
||||
|
||||
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.
|
||||
## 本地化翻译 / Localization
|
||||
|
||||
[从 GitHub 发布页获取 / Download from GitHub release](https://github.com/DGP-Studio/Snap.Hutao.Deployment/releases/latest)
|
||||
].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json)].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json) ].data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15670597-565845.json)
|
||||
|
||||
[从极狐Lab 发布页获取 / Download from Jihu Gitlab release](https://jihulab.com/DGP-Studio/Snap.Hutao.Deployment/-/releases)
|
||||
Snap Hutao 使用 [Crowdin](https://translate.hut.ao/) 作为客户端文本翻译平台,在该平台上你可以为你熟悉的语言提交翻译文本。我们感谢每一个为 Snap Hutao 做出贡献的社区成员,并且欢迎更多的朋友能参与到这个项目中。
|
||||
|
||||
#### 使用 MSIX 包安装 / Install with MSIX Package
|
||||
Snap Hutao uses [Crowdin](https://translate.hut.ao/) as a client text translation platform where you can submit translated text for languages you are familiar with. We are grateful to every community member who has contributed to Snap Hutao and welcome more friends to participate in this project.
|
||||
|
||||
直接使用 Snap Hutao MSIX 安装包,使用 Windows 内置的 App Installer 即可安装。如在安装中出现问题,请查阅我们的[常见问题](https://hut.ao/zh/advanced/FAQ.html)文档
|
||||
## 社区 / Community
|
||||
|
||||
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)
|
||||
[](https://discord.gg/CcH5XtDtvR) [](https://qm.qq.com/q/WJKykrY9W)
|
||||
|
||||
## 贡献 / Contribute
|
||||
|
||||
* [向我们提交 PR / Make Pull Requests](https://github.com/DGP-Studio/Snap.Hutao/pulls)
|
||||
* [在 Crowdin 上进行本地化 / Translate Project on Crowdin](https://translate.hut.ao/)
|
||||
* [向我们提交 PR / Make Pull Requests](https://hut.ao/development/contribute.html)
|
||||
* [为我们更新文档 / Enhance our Document](https://github.com/DGP-Studio/Snap.Hutao.Docs)
|
||||
* [帮助我们测试程序 / Test Binary Package](https://hut.ao/development/contribute.html)
|
||||
|
||||
## 特别感谢 / Special Thanks
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| >=1.6.0 | :white_check_mark: |
|
||||
| <1.6.0 | :x: |
|
||||
| >=1.9.0 | :white_check_mark: |
|
||||
| <1.9.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
BIN
res/Banner3-large-cn.psd
Normal file
BIN
res/Banner3-large-cn.psd
Normal file
Binary file not shown.
BIN
res/Banner3-large.psd
Normal file
BIN
res/Banner3-large.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/abyss.psd
Normal file
BIN
res/Store/chs/abyss.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/achievement.psd
Normal file
BIN
res/Store/chs/achievement.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/character-data.psd
Normal file
BIN
res/Store/chs/character-data.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/lancher.psd
Normal file
BIN
res/Store/chs/lancher.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/realtime-notes.psd
Normal file
BIN
res/Store/chs/realtime-notes.psd
Normal file
Binary file not shown.
BIN
res/Store/chs/wish.psd
Normal file
BIN
res/Store/chs/wish.psd
Normal file
Binary file not shown.
BIN
res/Store/en/abyss.psd
Normal file
BIN
res/Store/en/abyss.psd
Normal file
Binary file not shown.
BIN
res/Store/en/achievement.psd
Normal file
BIN
res/Store/en/achievement.psd
Normal file
Binary file not shown.
BIN
res/Store/en/character-data.psd
Normal file
BIN
res/Store/en/character-data.psd
Normal file
Binary file not shown.
BIN
res/Store/en/lancher.psd
Normal file
BIN
res/Store/en/lancher.psd
Normal file
Binary file not shown.
BIN
res/Store/en/realtime-notes.psd
Normal file
BIN
res/Store/en/realtime-notes.psd
Normal file
Binary file not shown.
BIN
res/Store/en/wish.psd
Normal file
BIN
res/Store/en/wish.psd
Normal file
Binary file not shown.
@@ -110,7 +110,6 @@ dotnet_diagnostic.SA1642.severity = none
|
||||
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
dotnet_diagnostic.IDE0060.severity = none
|
||||
dotnet_diagnostic.IDE0290.severity = none
|
||||
|
||||
# SA1208: System using directives should be placed before other using directives
|
||||
dotnet_diagnostic.SA1208.severity = none
|
||||
@@ -321,7 +320,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
|
||||
|
||||
# CA2251: 使用 “string.Equals”
|
||||
dotnet_diagnostic.CA2251.severity = suggestion
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
|
||||
csharp_style_prefer_primary_constructors = false:none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace Snap.Hutao.Test.PlatformExtensions;
|
||||
@@ -11,6 +12,7 @@ public sealed class DependencyInjectionTest
|
||||
.AddSingleton<IService, ServiceB>()
|
||||
.AddScoped<IScopedService, ServiceA>()
|
||||
.AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
|
||||
.AddLogging(builder => builder.AddConsole())
|
||||
.BuildServiceProvider();
|
||||
|
||||
[TestMethod]
|
||||
@@ -41,6 +43,13 @@ public sealed class DependencyInjectionTest
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LoggerWithInterfaceTypeCanBeResolved()
|
||||
{
|
||||
Assert.IsNotNull(services.GetService<ILogger<IScopedService>>());
|
||||
Assert.IsNotNull(services.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(IScopedService)));
|
||||
}
|
||||
|
||||
private interface IService
|
||||
{
|
||||
Guid Id { get; }
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal class ButtonBaseBuilder<TButton> : IButtonBaseBuilder<TButton>
|
||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase, new()
|
||||
{
|
||||
public TButton Button { get; } = new();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction.Extension;
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal static class ButtonBaseBuilderExtension
|
||||
{
|
||||
public static TBuilder SetContent<TBuilder, TButton>(this TBuilder builder, object? content)
|
||||
where TBuilder : IButtonBaseBuilder<TButton>
|
||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
||||
{
|
||||
builder.Configure(builder => builder.Button.Content = content);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static TBuilder SetCommand<TBuilder, TButton>(this TBuilder builder, ICommand command)
|
||||
where TBuilder : IButtonBaseBuilder<TButton>
|
||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
||||
{
|
||||
builder.Configure(builder => builder.Button.Command = command);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal sealed class ButtonBuilder : ButtonBaseBuilder<Button>;
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal static class ButtonBuilderExtension
|
||||
{
|
||||
public static ButtonBuilder SetContent(this ButtonBuilder builder, object? content)
|
||||
{
|
||||
return builder.SetContent<ButtonBuilder, Button>(content);
|
||||
}
|
||||
|
||||
public static ButtonBuilder SetCommand(this ButtonBuilder builder, ICommand command)
|
||||
{
|
||||
return builder.SetCommand<ButtonBuilder, Button>(command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Control.Builder.ButtonBase;
|
||||
|
||||
internal interface IButtonBaseBuilder<TButton> : IBuilder
|
||||
where TButton : Microsoft.UI.Xaml.Controls.Primitives.ButtonBase
|
||||
{
|
||||
TButton Button { get; }
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Web.Request.Builder;
|
||||
namespace Snap.Hutao.Core.Abstraction.Extension;
|
||||
|
||||
internal static class BuilderExtension
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
namespace Snap.Hutao.Core.Abstraction;
|
||||
|
||||
internal interface IBuilder;
|
||||
@@ -3,13 +3,16 @@
|
||||
|
||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
using Snap.Hutao.Web.Request.Builder;
|
||||
using Snap.Hutao.Web.Request.Builder.Abstraction;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core.Caching;
|
||||
|
||||
@@ -36,6 +39,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
private readonly ConcurrentDictionary<string, Task> concurrentTasks = new();
|
||||
|
||||
private readonly IHttpClientFactory httpClientFactory;
|
||||
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<ImageCache> logger;
|
||||
|
||||
@@ -104,12 +108,12 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
{
|
||||
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
|
||||
{
|
||||
logger.LogDebug("Begin downloading image file from '{Uri}' to '{File}'", uri, filePath);
|
||||
logger.LogColorizedInformation("Begin to download file from '{Uri}' to '{File}'", (uri, ConsoleColor.Cyan), (filePath, ConsoleColor.Cyan));
|
||||
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
||||
}
|
||||
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
||||
{
|
||||
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", uri);
|
||||
logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", (uri, ConsoleColor.Cyan));
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -132,10 +136,7 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
|
||||
private static string GetCacheFileName(Uri uri)
|
||||
{
|
||||
string url = uri.ToString();
|
||||
byte[] chars = Encoding.UTF8.GetBytes(url);
|
||||
byte[] hash = SHA1.HashData(chars);
|
||||
return System.Convert.ToHexString(hash);
|
||||
return Hash.SHA1HexString(uri.ToString());
|
||||
}
|
||||
|
||||
private static bool IsFileInvalid(string file, bool treatNullFileAsInvalid = true)
|
||||
@@ -172,38 +173,49 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
|
||||
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
while (retryCount < 3)
|
||||
{
|
||||
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
if (message.RequestMessage is { RequestUri: { } target } && target != uri)
|
||||
{
|
||||
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
|
||||
}
|
||||
HttpRequestMessageBuilder requestMessageBuilder = httpRequestMessageBuilderFactory
|
||||
.Create()
|
||||
.SetRequestUri(uri)
|
||||
|
||||
if (message.IsSuccessStatusCode)
|
||||
// These headers are only available for our own api
|
||||
.SetStaticResourceControlHeadersIf(uri.Host.Contains("api.snapgenshin.com", StringComparison.OrdinalIgnoreCase))
|
||||
.Get();
|
||||
|
||||
using (HttpRequestMessage requestMessage = requestMessageBuilder.HttpRequestMessage)
|
||||
{
|
||||
using (HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
using (Stream httpStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
if (responseMessage.RequestMessage is { RequestUri: { } target } && target != uri)
|
||||
{
|
||||
using (FileStream fileStream = File.Create(baseFile))
|
||||
logger.LogDebug("The Request '{Source}' has been redirected to '{Target}'", uri, target);
|
||||
}
|
||||
|
||||
if (responseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
using (Stream httpStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
return;
|
||||
using (FileStream fileStream = File.Create(baseFile))
|
||||
{
|
||||
await httpStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (message.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
switch (responseMessage.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
{
|
||||
retryCount++;
|
||||
TimeSpan delay = responseMessage.Headers.RetryAfter?.Delta ?? retryCountToDelay[retryCount];
|
||||
logger.LogInformation("Retry download '{Uri}' after {Delay}.", uri, delay);
|
||||
await Task.Delay(delay).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ internal sealed partial class ScopedDbCurrent<TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
if (serviceProvider.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ internal sealed partial class ScopedDbCurrent<TEntityOnly, TEntity, TMessage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceProvider.IsDisposedSlow())
|
||||
if (serviceProvider.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,13 @@ internal static class DependencyInjection
|
||||
ServiceProvider serviceProvider = new ServiceCollection()
|
||||
|
||||
// Microsoft extension
|
||||
.AddLogging(builder => builder.AddDebug().AddConsoleWindow())
|
||||
.AddLogging(builder =>
|
||||
{
|
||||
builder
|
||||
.SetMinimumLevel(LogLevel.Trace)
|
||||
.AddDebug()
|
||||
.AddConsoleWindow();
|
||||
})
|
||||
.AddMemoryCache()
|
||||
|
||||
// Hutao extensions
|
||||
|
||||
@@ -18,13 +18,22 @@ internal static class ServiceProviderExtension
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDisposedSlow(this IServiceProvider? serviceProvider)
|
||||
public static bool IsDisposed(this IServiceProvider? serviceProvider)
|
||||
{
|
||||
if (serviceProvider is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (serviceProvider is ServiceProvider serviceProviderImpl)
|
||||
{
|
||||
return GetPrivateDisposed(serviceProviderImpl);
|
||||
}
|
||||
|
||||
return serviceProvider.GetType().GetField("_disposed")?.GetValue(serviceProvider) is true;
|
||||
}
|
||||
|
||||
// private bool _disposed;
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_disposed")]
|
||||
private static extern ref bool GetPrivateDisposed(ServiceProvider serviceProvider);
|
||||
}
|
||||
@@ -40,15 +40,23 @@ internal sealed class HutaoException : Exception
|
||||
}
|
||||
}
|
||||
|
||||
public static HutaoException ServiceTypeCastFailed<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
{
|
||||
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
|
||||
throw new HutaoException(HutaoExceptionKind.ServiceTypeCastFailed, message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static HutaoException GachaStatisticsInvalidItemId(uint id, Exception? innerException = default)
|
||||
{
|
||||
string message = SH.FormatServiceGachaStatisticsFactoryItemIdInvalid(id);
|
||||
throw new HutaoException(HutaoExceptionKind.GachaStatisticsInvalidItemId, message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static InvalidCastException InvalidCast<TFrom, TTo>(string name, Exception? innerException = default)
|
||||
{
|
||||
string message = $"This instance of '{typeof(TFrom).FullName}' '{name}' doesn't implement '{typeof(TTo).FullName}'";
|
||||
throw new InvalidCastException(message, innerException);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
public static OperationCanceledException OperationCanceled(string message, Exception? innerException = default)
|
||||
{
|
||||
return new OperationCanceledException(message, innerException);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ internal enum HutaoExceptionKind
|
||||
None,
|
||||
|
||||
// Foundation
|
||||
ServiceTypeCastFailed,
|
||||
ImageCacheInvalidUri,
|
||||
|
||||
// IO
|
||||
@@ -18,4 +17,5 @@ internal enum HutaoExceptionKind
|
||||
// Service
|
||||
GachaStatisticsInvalidItemId,
|
||||
GameFpsUnlockingFailed,
|
||||
GameConfigInvalidChannelOptions,
|
||||
}
|
||||
20
src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/Hash.cs
Normal file
20
src/Snap.Hutao/Snap.Hutao/Core/IO/Hashing/Hash.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.Core.IO.Hashing;
|
||||
|
||||
internal static class Hash
|
||||
{
|
||||
public static string SHA1HexString(string input)
|
||||
{
|
||||
return HashCore(BitConverter.ToString, SHA1.HashData, Encoding.UTF8.GetBytes, input);
|
||||
}
|
||||
|
||||
private static TResult HashCore<TInput, TResult>(Func<byte[], TResult> resultConverter, Func<byte[], byte[]> hashMethod, Func<TInput, byte[]> bytesConverter, TInput input)
|
||||
{
|
||||
return resultConverter(hashMethod(bytesConverter(input)));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -77,34 +78,36 @@ internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
using (HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
Memory<byte> buffer = new byte[bufferSize];
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent())
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
Memory<byte> buffer = memoryOwner.Memory;
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
break;
|
||||
}
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
|
||||
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
totalBytesRead += bytesRead;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
@@ -51,26 +52,30 @@ internal class StreamCopyWorker<TStatus>
|
||||
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
Memory<byte> buffer = new byte[bufferSize];
|
||||
|
||||
do
|
||||
using (IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(bufferSize))
|
||||
{
|
||||
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
break;
|
||||
}
|
||||
Memory<byte> buffer = memoryOwner.Memory;
|
||||
|
||||
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
|
||||
do
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
bytesRead = await source.ReadAsync(buffer).ConfigureAwait(false);
|
||||
if (bytesRead is 0)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
break;
|
||||
}
|
||||
|
||||
await destination.WriteAsync(buffer[..bytesRead]).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 1000)
|
||||
{
|
||||
progress.Report(statusFactory(totalBytesRead));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
while (bytesRead > 0);
|
||||
}
|
||||
while (bytesRead > 0);
|
||||
}
|
||||
}
|
||||
@@ -116,15 +116,15 @@ internal sealed partial class Activation : IActivation
|
||||
|
||||
// If it's the first time launch, we show the guide window anyway.
|
||||
// Otherwise, we check if there's any unfulfilled resource category present.
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) >= GuideState.StaticResourceBegin)
|
||||
{
|
||||
if (StaticResource.IsAnyUnfulfilledCategoryPresent())
|
||||
{
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
UnsafeLocalSetting.Set(SettingKeys.Major1Minor10Revision0GuideState, GuideState.StaticResourceBegin);
|
||||
}
|
||||
}
|
||||
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language) < GuideState.Completed)
|
||||
if (UnsafeLocalSetting.Get(SettingKeys.Major1Minor10Revision0GuideState, GuideState.Language) < GuideState.Completed)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
serviceProvider.GetRequiredService<GuideWindow>();
|
||||
|
||||
@@ -9,7 +9,11 @@ internal static class LoggerFactoryExtension
|
||||
{
|
||||
builder.Services.AddSingleton<ConsoleWindowLifeTime>();
|
||||
|
||||
builder.AddSimpleConsole();
|
||||
builder.AddSimpleConsole(options =>
|
||||
{
|
||||
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff ";
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,15 @@ internal static class RuntimeOptionsExtension
|
||||
|
||||
public static string GetDataFolderServerCacheFolder(this RuntimeOptions options)
|
||||
{
|
||||
return Path.Combine(options.DataFolder, "ServerCache");
|
||||
string directory = Path.Combine(options.DataFolder, "ServerCache");
|
||||
Directory.CreateDirectory(directory);
|
||||
return directory;
|
||||
}
|
||||
|
||||
public static string GetDataFolderBackgroundFolder(this RuntimeOptions options)
|
||||
{
|
||||
return Path.Combine(options.DataFolder, "Background");
|
||||
string directory = Path.Combine(options.DataFolder, "Background");
|
||||
Directory.CreateDirectory(directory);
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ internal static class SettingKeys
|
||||
#region Application
|
||||
public const string LaunchTimes = "LaunchTimes";
|
||||
public const string DataFolderPath = "DataFolderPath";
|
||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||
public const string Major1Minor10Revision0GuideState = "Major1Minor10Revision0GuideState1";
|
||||
public const string StaticResourceImageQuality = "StaticResourceImageQuality";
|
||||
public const string StaticResourceImageArchive = "StaticResourceImageArchive";
|
||||
public const string HotKeyMouseClickRepeatForever = "HotKeyMouseClickRepeatForever";
|
||||
public const string IsAllocConsoleDebugModeEnabled = "IsAllocConsoleDebugModeEnabled2";
|
||||
#endregion
|
||||
@@ -60,6 +62,10 @@ internal static class SettingKeys
|
||||
#endregion
|
||||
|
||||
#region Obsolete
|
||||
|
||||
[Obsolete("重置新手引导状态")]
|
||||
public const string Major1Minor7Revision0GuideState = "Major1Minor7Revision0GuideState";
|
||||
|
||||
[Obsolete("重置调试控制台开关")]
|
||||
public const string IsAllocConsoleDebugModeEnabledLegacy1 = "IsAllocConsoleDebugModeEnabled";
|
||||
#endregion
|
||||
|
||||
@@ -200,6 +200,13 @@ internal static partial class EnumerableExtension
|
||||
return list;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static List<TSource> SortBy<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
|
||||
{
|
||||
list.Sort((left, right) => comparer.Compare(keySelector(left), keySelector(right)));
|
||||
return list;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static List<TSource> SortByDescending<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector)
|
||||
where TKey : IComparable
|
||||
@@ -207,4 +214,11 @@ internal static partial class EnumerableExtension
|
||||
list.Sort((left, right) => keySelector(right).CompareTo(keySelector(left)));
|
||||
return list;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static List<TSource> SortByDescending<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
|
||||
{
|
||||
list.Sort((left, right) => comparer.Compare(keySelector(right), keySelector(left)));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:shvg="using:Snap.Hutao.View.Guide"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid x:Name="RootGrid" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
|
||||
@@ -14,10 +14,10 @@ namespace Snap.Hutao;
|
||||
internal sealed partial class GuideWindow : Window, IWindowOptionsSource, IMinMaxInfoHandler
|
||||
{
|
||||
private const int MinWidth = 1000;
|
||||
private const int MinHeight = 600;
|
||||
private const int MinHeight = 650;
|
||||
|
||||
private const int MaxWidth = 1200;
|
||||
private const int MaxHeight = 750;
|
||||
private const int MaxHeight = 800;
|
||||
|
||||
private readonly WindowOptions windowOptions;
|
||||
|
||||
|
||||
@@ -27,4 +27,27 @@ internal sealed partial class IdentifyMonitorWindow : Window
|
||||
}
|
||||
|
||||
public string Monitor { get; private set; }
|
||||
|
||||
public static async ValueTask IdentifyAllMonitorsAsync(int secondsDelay)
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1));
|
||||
}
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
await Delay.FromSeconds(secondsDelay).ConfigureAwait(true);
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutao"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.9.8.0" />
|
||||
Version="1.9.9.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao</DisplayName>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutaoDev"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.9.8.0" />
|
||||
Version="1.9.9.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao Dev</DisplayName>
|
||||
|
||||
@@ -2345,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>Enter your HoYoLab UID</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>You are using an embedded webview to login to your MiHoYo passport account, the client will fetch the Cookie data when you click on I'm Signed in button. All network communication initialed in this webview occurs only between your computer and MiHoYo official servers.</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>I'm logged in</value>
|
||||
</data>
|
||||
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>Simpan</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>未找到结果</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>Invalid Url</value>
|
||||
</data>
|
||||
@@ -2342,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>Masukkan UID HoYoLab Anda</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>Saya sudah masuk</value>
|
||||
</data>
|
||||
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>保存</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>未找到结果</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>無効なURL</value>
|
||||
</data>
|
||||
@@ -2342,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>HoYoLab UIDを入力してください</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>ログインしました</value>
|
||||
</data>
|
||||
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>저장</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>未找到结果</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>잘못된 Uri</value>
|
||||
</data>
|
||||
@@ -2342,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>HoYoLab Uid를 입력하세요</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>로그인됨</value>
|
||||
</data>
|
||||
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>Salvar</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>未找到结果</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>Uri inválido</value>
|
||||
</data>
|
||||
@@ -2342,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>Digite seu UID do HoYoLab</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>Estou conectado</value>
|
||||
</data>
|
||||
|
||||
@@ -1334,6 +1334,12 @@
|
||||
<data name="ViewDialogLaunchGameAccountTitle" xml:space="preserve">
|
||||
<value>为账号命名</value>
|
||||
</data>
|
||||
<data name="ViewDialogLaunchGameConfigurationFixDialogHint" xml:space="preserve">
|
||||
<value>请选择当前游戏路径对应的游戏服务器</value>
|
||||
</data>
|
||||
<data name="ViewDialogLaunchGameConfigurationFixDialogTitle" xml:space="preserve">
|
||||
<value>正在修复配置文件</value>
|
||||
</data>
|
||||
<data name="ViewDialogLaunchGamePackageConvertHint" xml:space="preserve">
|
||||
<value>转换可能需要花费一段时间,请勿关闭胡桃</value>
|
||||
</data>
|
||||
@@ -1376,6 +1382,9 @@
|
||||
<data name="ViewGachaLogHeader" xml:space="preserve">
|
||||
<value>祈愿记录</value>
|
||||
</data>
|
||||
<data name="ViewGuideStaticResourceDownloadSize" xml:space="preserve">
|
||||
<value>预计下载大小:{0}</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepAgreementIHaveReadText" xml:space="preserve">
|
||||
<value>我已阅读并同意</value>
|
||||
</data>
|
||||
@@ -1391,6 +1400,12 @@
|
||||
<data name="ViewGuideStepAgreementTermOfService" xml:space="preserve">
|
||||
<value>用户使用协议与法律声明</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepCommonSetting" xml:space="preserve">
|
||||
<value>基础设置</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepCommonSettingHint" xml:space="preserve">
|
||||
<value>稍后可以在设置中修改</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepDocument" xml:space="preserve">
|
||||
<value>文档</value>
|
||||
</data>
|
||||
@@ -1401,7 +1416,7 @@
|
||||
<value>安装完成后重启胡桃以查看是否正常生效</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve">
|
||||
<value>如果上方的图标中存在乱码,请前往</value>
|
||||
<value>如果上方的图标中存在乱码或方块字,请前往</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentFontDescription2" xml:space="preserve">
|
||||
<value>下载并自行安装图标字体</value>
|
||||
@@ -1418,6 +1433,24 @@
|
||||
<data name="ViewGuideStepStaticResource" xml:space="preserve">
|
||||
<value>资源</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSetting" xml:space="preserve">
|
||||
<value>图像资源设置</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingHint" xml:space="preserve">
|
||||
<value>* 除非你卸载并重新安装胡桃,否则你将无法更改这些设置</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingMinimumHeader" xml:space="preserve">
|
||||
<value>图片资源包体</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingMinimumOff" xml:space="preserve">
|
||||
<value>完整包体</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingMinimumOn" xml:space="preserve">
|
||||
<value>精简包体</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepStaticResourceSettingQualityHeader" xml:space="preserve">
|
||||
<value>图片资源质量</value>
|
||||
</data>
|
||||
<data name="ViewHutaoDatabaseHeader" xml:space="preserve">
|
||||
<value>深渊统计</value>
|
||||
</data>
|
||||
@@ -1607,6 +1640,12 @@
|
||||
<data name="ViewModelGuideActionStaticResourceBegin" xml:space="preserve">
|
||||
<value>下载资源文件中,请稍候</value>
|
||||
</data>
|
||||
<data name="ViewModelGuideStaticResourceQualityHigh" xml:space="preserve">
|
||||
<value>高质量</value>
|
||||
</data>
|
||||
<data name="ViewModelGuideStaticResourceQualityRaw" xml:space="preserve">
|
||||
<value>原图</value>
|
||||
</data>
|
||||
<data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve">
|
||||
<value>请输入正确的邮箱</value>
|
||||
</data>
|
||||
@@ -1625,6 +1664,12 @@
|
||||
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
|
||||
<value>切换服务器失败</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameFixConfigurationFileButtonText" xml:space="preserve">
|
||||
<value>修复配置文件</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameFixConfigurationFileSuccess" xml:space="preserve">
|
||||
<value>修复完成</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
|
||||
<value>识别显示器</value>
|
||||
</data>
|
||||
@@ -1637,6 +1682,9 @@
|
||||
<data name="ViewModelLaunchGameSchemeNotSelected" xml:space="preserve">
|
||||
<value>尚未选择任何服务器</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameSetGamePathButtonText" xml:space="preserve">
|
||||
<value>设置游戏目录</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
|
||||
<value>切换账号失败</value>
|
||||
</data>
|
||||
@@ -2345,6 +2393,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>请输入你的 HoYoLab Uid</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>我已登录</value>
|
||||
</data>
|
||||
@@ -2831,6 +2882,15 @@
|
||||
<data name="ViewSpiralAbyssUploadRecord" xml:space="preserve">
|
||||
<value>上传数据</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageDownloadContent" xml:space="preserve">
|
||||
<value>是否立即下载</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageDownloadFailedMessage" xml:space="preserve">
|
||||
<value>下载更新失败</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageDownloadTitle" xml:space="preserve">
|
||||
<value>胡桃 {0} 版本已发布</value>
|
||||
</data>
|
||||
<data name="ViewTitileUpdatePackageReadyContent" xml:space="preserve">
|
||||
<value>是否立即安装?</value>
|
||||
</data>
|
||||
@@ -2840,6 +2900,9 @@
|
||||
<data name="ViewTitleAutoClicking" xml:space="preserve">
|
||||
<value>自动连点</value>
|
||||
</data>
|
||||
<data name="ViewTitleUpdatePackageInstallingContent" xml:space="preserve">
|
||||
<value>正在安装更新</value>
|
||||
</data>
|
||||
<data name="ViewToolHeader" xml:space="preserve">
|
||||
<value>工具</value>
|
||||
</data>
|
||||
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>Сохранить</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>未找到结果</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>Ошибка ссылки</value>
|
||||
</data>
|
||||
@@ -2342,6 +2345,9 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>请输入你的 HoYoLab Uid</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>我已登录</value>
|
||||
</data>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<value>胡桃 Dev {0}</value>
|
||||
</data>
|
||||
<data name="AppElevatedDevNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃Dev {0} [管理员]</value>
|
||||
<value>胡桃Dev {0} [系統管理员]</value>
|
||||
</data>
|
||||
<data name="AppElevatedNameAndVersion" xml:space="preserve">
|
||||
<value>胡桃 {0} [系統管理員]</value>
|
||||
@@ -144,6 +144,9 @@
|
||||
<data name="ContentDialogSavePrimaryButtonText" xml:space="preserve">
|
||||
<value>儲存</value>
|
||||
</data>
|
||||
<data name="ControlAutoSuggestBoxNotFoundValue" xml:space="preserve">
|
||||
<value>未找到结果</value>
|
||||
</data>
|
||||
<data name="ControlImageCachedImageInvalidResourceUri" xml:space="preserve">
|
||||
<value>無效的 Uri</value>
|
||||
</data>
|
||||
@@ -166,7 +169,7 @@
|
||||
<value>用户數據已損壞:{0}</value>
|
||||
</data>
|
||||
<data name="CoreIOPickerExtensionPickerExceptionInfoBarMessage" xml:space="preserve">
|
||||
<value>請勿在管理員模式下使用此功能 {0}</value>
|
||||
<value>請勿在系統管理員模式下使用此功能 {0}</value>
|
||||
</data>
|
||||
<data name="CoreIOPickerExtensionPickerExceptionInfoBarTitle" xml:space="preserve">
|
||||
<value>無法打開文件選擇器</value>
|
||||
@@ -274,7 +277,7 @@
|
||||
<value>尚未重新整理</value>
|
||||
</data>
|
||||
<data name="ModelEntityDailyNoteRefreshTimeFormat" xml:space="preserve">
|
||||
<value>重新整理于 {0:MM.dd HH:mm:ss}</value>
|
||||
<value>重新整理於 {0:MM.dd HH:mm:ss}</value>
|
||||
</data>
|
||||
<data name="ModelEntitySpiralAbyssScheduleFormat" xml:space="preserve">
|
||||
<value>第 {0} 期</value>
|
||||
@@ -540,28 +543,28 @@
|
||||
<value>必须登入 米遊社/HoYoLAB 並選定一個用戶與角色</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceDeleteEntrySucceed" xml:space="preserve">
|
||||
<value>刪除了 Uid:{0} 的 {1} 筆祈願記錄</value>
|
||||
<value>刪除了 UID:{0} 的 {1} 筆祈願記錄</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceInsufficientRecordSlot" xml:space="preserve">
|
||||
<value>胡桃雲保存的祈願記錄存檔數已達當前帳號上限</value>
|
||||
<value>胡桃雲儲存的祈願記錄存檔數已達當前帳號上限</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceInsufficientTime" xml:space="preserve">
|
||||
<value>未開通祈願紀錄上傳服務或已到期</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceInvalidGachaLogData" xml:space="preserve">
|
||||
<value>祈願數據存在無效的物品,無法保存至胡桃雲</value>
|
||||
<value>祈願數據存在無效的物品,無法儲存至胡桃雲</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceServerDatabaseError" xml:space="preserve">
|
||||
<value>數據異常,無法保存至雲端,請勿跨帳號上傳或嘗試删除雲端數據後重試</value>
|
||||
<value>數據異常,無法儲存至雲端,請勿跨帳號上傳或嘗試删除雲端數據後重試</value>
|
||||
</data>
|
||||
<data name="ServerGachaLogServiceUploadEntrySucceed" xml:space="preserve">
|
||||
<value>上傳了 Uid:{0} 的 {1} 筆祈願記錄,儲存了 {2} 筆</value>
|
||||
<value>上傳了 UID:{0} 的 {1} 筆祈願記錄,儲存了 {2} 筆</value>
|
||||
</data>
|
||||
<data name="ServerPassportLoginRequired" xml:space="preserve">
|
||||
<value>請先登錄或注冊胡桃帳號</value>
|
||||
<value>請先登入或注冊胡桃帳號</value>
|
||||
</data>
|
||||
<data name="ServerPassportLoginSucceed" xml:space="preserve">
|
||||
<value>登錄成功</value>
|
||||
<value>登入成功</value>
|
||||
</data>
|
||||
<data name="ServerPassportRegisterSucceed" xml:space="preserve">
|
||||
<value>註冊成功</value>
|
||||
@@ -594,7 +597,7 @@
|
||||
<value>驗證失敗</value>
|
||||
</data>
|
||||
<data name="ServerPassportVerifyRequestNotCurrentUser" xml:space="preserve">
|
||||
<value>驗證請求失敗,不是當前登錄的帳號</value>
|
||||
<value>驗證請求失敗,不是當前登入的帳號</value>
|
||||
</data>
|
||||
<data name="ServerPassportVerifyRequestSuccess" xml:space="preserve">
|
||||
<value>驗證碼已發送至郵箱</value>
|
||||
@@ -606,7 +609,7 @@
|
||||
<value>驗證請求過快,請 1 分鐘後再試</value>
|
||||
</data>
|
||||
<data name="ServerRecordBannedUid" xml:space="preserve">
|
||||
<value>上傳深淵記錄失敗,當前 Uid 已被胡桃數據庫封禁</value>
|
||||
<value>上傳深淵記錄失敗,當前 UID 已被胡桃數據庫封禁</value>
|
||||
</data>
|
||||
<data name="ServerRecordComputingStatistics" xml:space="preserve">
|
||||
<value>上傳深淵記錄失敗,正在計算統計數據</value>
|
||||
@@ -621,19 +624,19 @@
|
||||
<value>上傳深淵記錄失敗,存在無效的數據</value>
|
||||
</data>
|
||||
<data name="ServerRecordInvalidUid" xml:space="preserve">
|
||||
<value>無效的 Uid</value>
|
||||
<value>無效的 UID</value>
|
||||
</data>
|
||||
<data name="ServerRecordNotCurrentSchedule" xml:space="preserve">
|
||||
<value>上傳深淵記錄失敗,不是本期數據</value>
|
||||
</data>
|
||||
<data name="ServerRecordPreviousRequestNotCompleted" xml:space="preserve">
|
||||
<value>上傳深淵記錄失敗,當前 Uid 的紀錄仍在處理中,請勿重複操作</value>
|
||||
<value>上傳深淵記錄失敗,當前 UID 的紀錄仍在處理中,請勿重複操作</value>
|
||||
</data>
|
||||
<data name="ServerRecordUploadSuccessAndGachaLogServiceTimeExtended" xml:space="preserve">
|
||||
<value>上傳深淵記錄成功,獲贈祈願記錄上傳服務時長</value>
|
||||
</data>
|
||||
<data name="ServerRecordUploadSuccessButNoPassport" xml:space="preserve">
|
||||
<value>上傳深淵記錄成功,但未登錄胡桃通行證,無法獲贈祈願記錄上傳服務時長</value>
|
||||
<value>上傳深淵記錄成功,但未登入胡桃通行證,無法獲贈祈願記錄上傳服務時長</value>
|
||||
</data>
|
||||
<data name="ServerRecordUploadSuccessButNoSuchUser" xml:space="preserve">
|
||||
<value>上傳深淵記錄成功,但無法找到使用者,無法獲贈祈願記錄上傳服務時長</value>
|
||||
@@ -762,19 +765,19 @@
|
||||
<comment>Need EXACT same string in game</comment>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryCalculatorNotRefreshed" xml:space="preserve">
|
||||
<value>養成計算:尚未刷新</value>
|
||||
<value>養成計算:尚未重新整理</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryCalculatorRefreshTimeFormat" xml:space="preserve">
|
||||
<value>養成計算:{0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordNotRefreshed" xml:space="preserve">
|
||||
<value>原神戰績:尚未更新</value>
|
||||
<value>原神戰績:尚未重新整理</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryGameRecordRefreshTimeFormat" xml:space="preserve">
|
||||
<value>原神戰績:{0:MM-dd HH:mm}</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryShowcaseNotRefreshed" xml:space="preserve">
|
||||
<value>角色櫥窗:尚未刷新</value>
|
||||
<value>角色櫥窗:尚未重新整理</value>
|
||||
</data>
|
||||
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
|
||||
<value>角色櫥窗:{0:MM-dd HH:mm}</value>
|
||||
@@ -795,7 +798,7 @@
|
||||
<value>無背景圖片</value>
|
||||
</data>
|
||||
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
|
||||
<value>保存養成計劃狀態失敗</value>
|
||||
<value>儲存養成計劃狀態失敗</value>
|
||||
</data>
|
||||
<data name="ServiceCultivationProjectCurrentUserdataCourrpted2" xml:space="preserve">
|
||||
<value>存在多個選中的養成計劃</value>
|
||||
@@ -849,7 +852,7 @@
|
||||
<value>準備完成</value>
|
||||
</data>
|
||||
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
|
||||
<value>參量質變儀已準備完成</value>
|
||||
<value>參數質變儀已準備完成</value>
|
||||
</data>
|
||||
<data name="ServiceDiscordActivityElevationRequiredHint" xml:space="preserve">
|
||||
<value>權限不足,將無法為您設定 Discord Activity 狀態</value>
|
||||
@@ -879,7 +882,7 @@
|
||||
<value>神鑄賦形</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogHutaoCloudEndIdFetchFailed" xml:space="preserve">
|
||||
<value>獲取祈願紀錄失敗</value>
|
||||
<value>獲取雲端祈願紀錄失敗</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogHutaoCloudServiceNotAllowed" xml:space="preserve">
|
||||
<value>祈願記錄上傳服務不可用</value>
|
||||
@@ -897,19 +900,19 @@
|
||||
<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>
|
||||
<value>提供的 URL 無效</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogUrlProviderStokenUnsupported" xml:space="preserve">
|
||||
<value>HoYoLAB 賬號不支持使用 SToken 刷新祈願記錄</value>
|
||||
<value>HoYoLAB 賬號不支持使用 SToken 重新整理祈願記錄</value>
|
||||
</data>
|
||||
<data name="ServiceGachaLogUrlProviderUrlLanguageNotMatchCurrentLocale" xml:space="preserve">
|
||||
<value>Url 中的語言:{0} 與胡桃的語言:{1} 不對應,請切換到對應語言重試</value>
|
||||
<value>URL 中的語言:{0} 與胡桃的語言:{1} 不對應,請切換到對應語言重試</value>
|
||||
</data>
|
||||
<data name="ServiceGachaStatisticsFactoryItemIdInvalid" xml:space="preserve">
|
||||
<value>不支持的 Item Id: {0}</value>
|
||||
<value>不支持的 Item Id:{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGachaUIGFImportLanguageNotMatch" xml:space="preserve">
|
||||
<value>UIGF 文件的語言:{0} 與胡桃的語言:{1} 不對應,請切換到對應語言重試</value>
|
||||
@@ -921,7 +924,7 @@
|
||||
<value>文件系統權限不足,無法轉換伺服器</value>
|
||||
</data>
|
||||
<data name="ServiceGameEnsureGameResourceQueryResourceInformation" xml:space="preserve">
|
||||
<value>下载游戏资源索引</value>
|
||||
<value>下載遊戲資源索引</value>
|
||||
</data>
|
||||
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
|
||||
<value>遊戲檔案操作失敗:{0}</value>
|
||||
@@ -936,7 +939,7 @@
|
||||
<value>請選擇遊戲路徑</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchExecutionGameResourceQueryIndexFailed" xml:space="preserve">
|
||||
<value>下载游戏资源索引失败: {0}</value>
|
||||
<value>下載遊戲資源索引失敗: {0}</value>
|
||||
</data>
|
||||
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
|
||||
<value>遊戲進程已退出</value>
|
||||
@@ -996,13 +999,13 @@
|
||||
<value>無法找到遊戲本體路徑,請前往設定修改</value>
|
||||
</data>
|
||||
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
|
||||
<value>未開啓長路徑功能,無法設定注冊表鍵值</value>
|
||||
<value>未開啟長路徑功能,無法設定註冊表鍵值</value>
|
||||
</data>
|
||||
<data name="ServiceGameSetMultiChannelConfigFileNotFound" xml:space="preserve">
|
||||
<value>無法讀取遊戲設定檔 {0},可能是檔案不存在</value>
|
||||
</data>
|
||||
<data name="ServiceGameSetMultiChannelUnauthorizedAccess" xml:space="preserve">
|
||||
<value>無法讀取或保存配置文件,請用管理員模式重試</value>
|
||||
<value>無法讀取或儲存配置文件,請用系統管理員模式重試</value>
|
||||
</data>
|
||||
<data name="ServiceGameUnlockerFindModuleNoModuleFound" xml:space="preserve">
|
||||
<value>在尋找必要的模組時遇到問題:無法讀取任何模組,可能是保護驅動已經載入完成,請重試</value>
|
||||
@@ -1047,13 +1050,13 @@
|
||||
<value>獲取簽到次數失敗</value>
|
||||
</data>
|
||||
<data name="ServiceSignInRewardListRequestFailed" xml:space="preserve">
|
||||
<value>獲取獎勵列表失敗</value>
|
||||
<value>獲取獎勵清單失敗</value>
|
||||
</data>
|
||||
<data name="ServiceSignInRiskVerificationFailed" xml:space="preserve">
|
||||
<value>驗證失敗,請前往HoYoLab原神簽到頁面自行領取獎勵</value>
|
||||
<value>驗證失敗,請前往HoYoLAB原神簽到頁面自行領取獎勵</value>
|
||||
</data>
|
||||
<data name="ServiceSignInSuccessRewardFormat" xml:space="preserve">
|
||||
<value>遷到成功,{0}×{1}</value>
|
||||
<value>簽到成功,{0}×{1}</value>
|
||||
</data>
|
||||
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
|
||||
<value>不支援的 UIGF 版本</value>
|
||||
@@ -1065,13 +1068,13 @@
|
||||
<value>已选中多条用户记录</value>
|
||||
</data>
|
||||
<data name="ServiceUserCurrentUpdateAndSaveFailed" xml:space="preserve">
|
||||
<value>用戶 {0} 狀態保存失敗</value>
|
||||
<value>用戶 {0} 狀態儲存失敗</value>
|
||||
</data>
|
||||
<data name="ServiceUserProcessCookieNoMid" xml:space="preserve">
|
||||
<value>Mid 必須包含在輸入的 Cookie 中</value>
|
||||
<value>輸入的 Cookie 中必須包含 Mid</value>
|
||||
</data>
|
||||
<data name="ServiceUserProcessCookieNoSToken" xml:space="preserve">
|
||||
<value>輸入的 Cookie 中必須包含 Stoken 字段</value>
|
||||
<value>輸入的 Cookie 中必須包含 SToken</value>
|
||||
</data>
|
||||
<data name="ServiceUserProcessCookieRequestUserInfoFailed" xml:space="preserve">
|
||||
<value>輸入的 Cookie 無法獲取用戶信息</value>
|
||||
@@ -1104,7 +1107,7 @@
|
||||
<value>突破後</value>
|
||||
</data>
|
||||
<data name="ViewControlElevationText" xml:space="preserve">
|
||||
<value>需要管理者權限</value>
|
||||
<value>需要系統管理員權限</value>
|
||||
</data>
|
||||
<data name="ViewControlLoadingText" xml:space="preserve">
|
||||
<value>加載中,請等候</value>
|
||||
@@ -1233,10 +1236,10 @@
|
||||
<value>參數質變儀提醒</value>
|
||||
</data>
|
||||
<data name="ViewDialogDailyNoteWebhookUrlInputPlaceholder" xml:space="preserve">
|
||||
<value>請輸入 Url</value>
|
||||
<value>請輸入 URL</value>
|
||||
</data>
|
||||
<data name="ViewDialogDailyNoteWebhookUrlTitle" xml:space="preserve">
|
||||
<value>即時便箋 Webhook Url</value>
|
||||
<value>即時便箋 Webhook URL</value>
|
||||
</data>
|
||||
<data name="ViewDialogFeedbackEnableLoopbackContent" xml:space="preserve">
|
||||
<value>解除限制後需使用其他工具恢復限制</value>
|
||||
@@ -1257,13 +1260,13 @@
|
||||
<value>獲取祈願物品中</value>
|
||||
</data>
|
||||
<data name="ViewDialogGachaLogUrlInputPlaceholder" xml:space="preserve">
|
||||
<value>請輸入 Url</value>
|
||||
<value>請輸入 URL</value>
|
||||
</data>
|
||||
<data name="ViewDialogGachaLogUrlTitle" xml:space="preserve">
|
||||
<value>手動輸入祈願記錄 Url</value>
|
||||
<value>手動輸入祈願記錄 URL</value>
|
||||
</data>
|
||||
<data name="ViewDialogGeetestCustomUrlCompositInputHint" xml:space="preserve">
|
||||
<value>請輸入請求接口的 Url 複合模板</value>
|
||||
<value>請輸入請求接口的 URL 複合模板</value>
|
||||
</data>
|
||||
<data name="ViewDialogGeetestCustomUrlReturnDataDescription1" xml:space="preserve">
|
||||
<value>接口需要返回形如上方所示的 Json 數據,多餘的數據會被忽略</value>
|
||||
@@ -1293,7 +1296,7 @@
|
||||
<value>登入胡桃通行證</value>
|
||||
</data>
|
||||
<data name="ViewDialogHutaoPassportRegisterTitle" xml:space="preserve">
|
||||
<value>建立胡桃通行證用戶</value>
|
||||
<value>註冊胡桃通行證</value>
|
||||
</data>
|
||||
<data name="ViewDialogHutaoPassportResetPasswordTitle" xml:space="preserve">
|
||||
<value>重設胡桃通行證用戶密碼</value>
|
||||
@@ -1347,7 +1350,7 @@
|
||||
<value>你正在啟用一個危險功能</value>
|
||||
</data>
|
||||
<data name="ViewDialogSettingDeleteUserDataContent" xml:space="preserve">
|
||||
<value>該操作是不可逆,所有用戶登錄狀態會遺失</value>
|
||||
<value>該操作是不可逆,所有用戶登入狀態會遺失</value>
|
||||
</data>
|
||||
<data name="ViewDialogSettingDeleteUserDataTitle" xml:space="preserve">
|
||||
<value>是否永久刪除用戶數據</value>
|
||||
@@ -1395,7 +1398,7 @@
|
||||
<value>環境</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentAfterInstallDescription" xml:space="preserve">
|
||||
<value>安裝完成後重啟胡桃以查看是否正常生效</value>
|
||||
<value>安裝完成後重新啟動胡桃以查看是否正常生效</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve">
|
||||
<value>如果上方的圖標中存在亂碼,請前往</value>
|
||||
@@ -1407,7 +1410,7 @@
|
||||
<value>若未檢測到 WebView2 Runtime信息,可以前往</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepEnvironmentWebView2Description2" xml:space="preserve">
|
||||
<value>下載並自行安裝Runtime</value>
|
||||
<value>下載並自行安裝運行時</value>
|
||||
</data>
|
||||
<data name="ViewGuideStepLanguage" xml:space="preserve">
|
||||
<value>語言</value>
|
||||
@@ -1425,7 +1428,7 @@
|
||||
<value>啟動遊戲</value>
|
||||
</data>
|
||||
<data name="ViewListViewDragElevatedHint" xml:space="preserve">
|
||||
<value>管理員模式下無法拖動排序</value>
|
||||
<value>系統管理員模式下無法拖動排序</value>
|
||||
</data>
|
||||
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
|
||||
<value>存檔 [{0}] 添加成功</value>
|
||||
@@ -1506,7 +1509,7 @@
|
||||
<value>不能新增名稱無效的計劃</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteConfigWebhookUrlComplete" xml:space="preserve">
|
||||
<value>即時便箋 Webhook Url 配置成功</value>
|
||||
<value>即時便箋 Webhook URL 配置成功</value>
|
||||
</data>
|
||||
<data name="ViewModelDailyNoteHoyolabVerificationUnsupported" xml:space="preserve">
|
||||
<value>HoYoLAB 賬號不支持驗證實时便箋</value>
|
||||
@@ -1572,7 +1575,7 @@
|
||||
<value>獲取祈願紀錄失敗</value>
|
||||
</data>
|
||||
<data name="ViewModelGachaLogRefreshOperationCancel" xml:space="preserve">
|
||||
<value>祈願紀錄刷新操作被異常取消</value>
|
||||
<value>祈願紀錄重新整理操作被異常取消</value>
|
||||
</data>
|
||||
<data name="ViewModelGachaLogRemoveArchiveDescription" xml:space="preserve">
|
||||
<value>該操作是不可逆的,該存檔和其內的所有祈願紀錄會丟失</value>
|
||||
@@ -1629,7 +1632,7 @@
|
||||
<value>無法讀取遊戲設定檔案: {0},可能是檔案不存在或權限不足</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGamePathInvalid" xml:space="preserve">
|
||||
<value>游戲程式路徑不正確,前往設定更改遊戲路徑</value>
|
||||
<value>遊戲程式路徑不正確,前往設定更改遊戲路徑</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameSchemeNotSelected" xml:space="preserve">
|
||||
<value>還未選擇任何伺服器</value>
|
||||
@@ -1644,7 +1647,7 @@
|
||||
<value>操作完成</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingClearWebCacheFail" xml:space="preserve">
|
||||
<value>清除失敗,文件目錄權限不足,請使用管理員模式重試</value>
|
||||
<value>清除失敗,文件目錄權限不足,請使用系統管理員模式重試</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingClearWebCachePathInvalid" xml:space="preserve">
|
||||
<value>清除失敗,找不到目錄:{0}</value>
|
||||
@@ -1668,10 +1671,10 @@
|
||||
<value>已使用磁碟空間:{0}</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingGeetestCustomUrlSucceed" xml:space="preserve">
|
||||
<value>無感驗證復合 Url 配置成功</value>
|
||||
<value>無感驗證復合 URL 配置成功</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve">
|
||||
<value>設置數據目錄成功,重啓以應用更改</value>
|
||||
<value>設置數據目錄成功,重新啟動以應用更改</value>
|
||||
</data>
|
||||
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
|
||||
<value>儲存遊戲路徑失敗</value>
|
||||
@@ -1737,7 +1740,7 @@
|
||||
<value>刪除當前存檔</value>
|
||||
</data>
|
||||
<data name="ViewPageAchievementSearchPlaceholder" xml:space="preserve">
|
||||
<value>搜索成就名稱,描述,版本或編號</value>
|
||||
<value>搜尋成就名稱,描述,版本或編號</value>
|
||||
</data>
|
||||
<data name="ViewPageAchievementSortIncompletedItemsFirst" xml:space="preserve">
|
||||
<value>優先未完成</value>
|
||||
@@ -1797,7 +1800,7 @@
|
||||
<value>同步角色天賦外的大部分信息</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyRefreshTimeToggle" xml:space="preserve">
|
||||
<value>刷新時間</value>
|
||||
<value>重新整理時間</value>
|
||||
</data>
|
||||
<data name="ViewPageAvatarPropertyScore" xml:space="preserve">
|
||||
<value>評分</value>
|
||||
@@ -1809,13 +1812,13 @@
|
||||
<value>養成計算</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationAddProject" xml:space="preserve">
|
||||
<value>新建計劃</value>
|
||||
<value>新增計劃</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationAddProjectAction" xml:space="preserve">
|
||||
<value>新增</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationAddProjectContinue" xml:space="preserve">
|
||||
<value>新建養成計劃以繼續</value>
|
||||
<value>新增養成計劃以繼續</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationAddProjectDescription" xml:space="preserve">
|
||||
<value>稍後可以前往其他頁面添加養成計劃條目</value>
|
||||
@@ -1863,7 +1866,7 @@
|
||||
<value>歷練點獲取詳情</value>
|
||||
</data>
|
||||
<data name="ViewPageDailyNoteConfigWebhookDescription" xml:space="preserve">
|
||||
<value>在即時便箋重整之後推送到指定的 Webhook</value>
|
||||
<value>在即時便箋重新整理之後推送到指定的 Webhook</value>
|
||||
</data>
|
||||
<data name="ViewPageDailyNoteConfigWebhookHeader" xml:space="preserve">
|
||||
<value>配置 Webhook</value>
|
||||
@@ -1896,13 +1899,13 @@
|
||||
<value>本周已消耗減半次數</value>
|
||||
</data>
|
||||
<data name="ViewPageDailyNoteSettingAutoRefresh" xml:space="preserve">
|
||||
<value>自動刷新</value>
|
||||
<value>自動重新整理</value>
|
||||
</data>
|
||||
<data name="ViewPageDailyNoteSettingAutoRefreshDescription" xml:space="preserve">
|
||||
<value>間隔選定的時間後刷新添加的實時便箋</value>
|
||||
<value>間隔選定的時間後重新整理添加的實時便箋</value>
|
||||
</data>
|
||||
<data name="ViewPageDailyNoteSettingRefreshElevatedHint" xml:space="preserve">
|
||||
<value>這些選項僅允許在非管理員模式下更改</value>
|
||||
<value>這些選項僅允許在非系統管理員模式下更改</value>
|
||||
</data>
|
||||
<data name="ViewPageDailyNoteSettingRefreshHeader" xml:space="preserve">
|
||||
<value>重新整理</value>
|
||||
@@ -1959,7 +1962,7 @@
|
||||
<value>胡桃服務</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
|
||||
<value>全量式重整</value>
|
||||
<value>全量式重新整理</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogExportAction" xml:space="preserve">
|
||||
<value>匯出</value>
|
||||
@@ -2019,22 +2022,22 @@
|
||||
<value>獲取</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRefreshByManualInput" xml:space="preserve">
|
||||
<value>手動輸入 Url</value>
|
||||
<value>手動輸入 URL</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRefreshByManualInputDescription" xml:space="preserve">
|
||||
<value>使用由你提供的 Url 刷新祈願記錄</value>
|
||||
<value>使用由你提供的 URL 重新整理祈願記錄</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRefreshBySToken" xml:space="preserve">
|
||||
<value>SToken 重整</value>
|
||||
<value>SToken 重新整理</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRefreshBySTokenDescription" xml:space="preserve">
|
||||
<value>使用當前用戶的 Cookie 信息刷新祈願紀錄</value>
|
||||
<value>使用當前用戶的 Cookie 信息重新整理祈願紀錄</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRefreshByWebCache" xml:space="preserve">
|
||||
<value>網頁緩存刷新</value>
|
||||
<value>網頁緩存重新整理</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRefreshByWebCacheDescription" xml:space="preserve">
|
||||
<value>使用遊戲內瀏覽器的網頁快取刷新祈願紀錄</value>
|
||||
<value>使用遊戲內瀏覽器的網頁快取重新整理祈願紀錄</value>
|
||||
</data>
|
||||
<data name="ViewPageGachaLogRemoveArchiveAction" xml:space="preserve">
|
||||
<value>刪除當前存檔</value>
|
||||
@@ -2118,7 +2121,7 @@
|
||||
<value>上傳記錄總數</value>
|
||||
</data>
|
||||
<data name="ViewPageHutaoDatabaseOverviewRefreshTime" xml:space="preserve">
|
||||
<value>數據刷新時間</value>
|
||||
<value>數據重新整理時間</value>
|
||||
</data>
|
||||
<data name="ViewPageHutaoDatabaseOverviewSpiralAbyss" xml:space="preserve">
|
||||
<value>深淵數據統計</value>
|
||||
@@ -2151,7 +2154,7 @@
|
||||
<value>至少需要八個字元</value>
|
||||
</data>
|
||||
<data name="ViewPageHutaoPassportRegisterHeader" xml:space="preserve">
|
||||
<value>建立帳號</value>
|
||||
<value>註冊</value>
|
||||
</data>
|
||||
<data name="ViewPageHutaoPassportResetPasswordHeader" xml:space="preserve">
|
||||
<value>重設密碼</value>
|
||||
@@ -2193,7 +2196,7 @@
|
||||
<value>啟用內置觸摸布局,不會響應鍵鼠輸入</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceExclusiveDescription" xml:space="preserve">
|
||||
<value>與游戲内瀏覽器不兼容,切屏等操作也能使游戲閃退</value>
|
||||
<value>與遊戲内瀏覽器不兼容,切屏等操作也能使遊戲閃退</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameAppearanceExclusiveHeader" xml:space="preserve">
|
||||
<value>獨占全屏</value>
|
||||
@@ -2235,7 +2238,7 @@
|
||||
<value>一般</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameConfigurationSaveHint" xml:space="preserve">
|
||||
<value>所有選項盡會在啓動游戲成功後保存</value>
|
||||
<value>所有選項盡會在啟動遊戲成功後儲存</value>
|
||||
</data>
|
||||
<data name="ViewPageLaunchGameDiscordActivityDescription" xml:space="preserve">
|
||||
<value>在我遊戲時設定 Discord Activity 狀態</value>
|
||||
@@ -2342,11 +2345,14 @@
|
||||
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
|
||||
<value>請輸入您的 HoYoLAB UID</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserDescription" xml:space="preserve">
|
||||
<value>你正在通过由我们提供的内嵌网页视图登录 米哈游通行证,我们会在你点击 我已登录 按钮后,读取你的 Cookie 信息,由此视图发起的网络通信只发生于你的计算机与米哈游服务器之间</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
|
||||
<value>我已登錄</value>
|
||||
<value>我已登入</value>
|
||||
</data>
|
||||
<data name="ViewPageLoginMihoyoUserTitle" xml:space="preserve">
|
||||
<value>在下方登錄 MiHoYo 通行證賬號</value>
|
||||
<value>在下方登入 miHoYo 通行證賬號</value>
|
||||
</data>
|
||||
<data name="ViewPageOpenScreenshotFolderAction" xml:space="preserve">
|
||||
<value>開啟截圖資料夾</value>
|
||||
@@ -2376,7 +2382,7 @@
|
||||
<value>圖片版權訊息</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
|
||||
<value>更改視窗的背景圖片來源,重啟胡桃以盡快生效</value>
|
||||
<value>更改視窗的背景圖片來源,重新啟動胡桃以盡快生效</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
|
||||
<value>背景圖片</value>
|
||||
@@ -2424,7 +2430,7 @@
|
||||
<value>刪除</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingDeleteCacheDescription" xml:space="preserve">
|
||||
<value>若祈願紀錄緩存刷新頻繁提示驗證密鑰過期,可以嘗試此操作</value>
|
||||
<value>若祈願紀錄緩存重新整理頻繁提示驗證密鑰過期,可以嘗試此操作</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingDeleteCacheHeader" xml:space="preserve">
|
||||
<value>刪除游戲内網頁緩存</value>
|
||||
@@ -2451,7 +2457,7 @@
|
||||
<value>系統管理員模式</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingElevatedModeRestartAction" xml:space="preserve">
|
||||
<value>以系統管理員身分重啟動</value>
|
||||
<value>以系統管理員身分重新啟動</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingEmptyHistoryVisibleDescription" xml:space="preserve">
|
||||
<value>在祈願紀錄頁面顯示或隱藏無記錄的歷史祈願活動</value>
|
||||
@@ -2466,7 +2472,7 @@
|
||||
<value>顯示</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingFeaturesDangerousHint" xml:space="preserve">
|
||||
<value>您解鎖了含有違反原神服務條款風險的「啟動遊戲-高級功能」,將自行承擔任何不良後果。</value>
|
||||
<value>您解鎖了含有違反原神服務條款風險的「啟動遊戲-進階功能」,將自行承擔任何不良後果。</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingFeedbackNavigate" xml:space="preserve">
|
||||
<value>前往反饋</value>
|
||||
@@ -2532,7 +2538,7 @@
|
||||
<value>胡桃雲服務到期時間</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHutaoPassportHeader" xml:space="preserve">
|
||||
<value>胡桃雲通行證帳號</value>
|
||||
<value>胡桃通行證帳號</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHutaoPassportLicensedDeveloperDescription" xml:space="preserve">
|
||||
<value>您可以無限制使用任何基於胡桃雲服務的功能</value>
|
||||
@@ -2559,7 +2565,7 @@
|
||||
<value>使用兌換碼</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHutaoPassportRegisterAction" xml:space="preserve">
|
||||
<value>建立帳號</value>
|
||||
<value>註冊</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingHutaoPassportResetPasswordAction" xml:space="preserve">
|
||||
<value>重設密碼</value>
|
||||
@@ -2568,10 +2574,10 @@
|
||||
<value>刪除帳號</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledDescription" xml:space="preserve">
|
||||
<value>在完整閱讀原神和胡桃工具箱使用者協定後,我選擇啟用「啟動遊戲 - 高級功能」</value>
|
||||
<value>在完整閱讀原神和胡桃工具箱使用者協定後,我選擇啟用「啟動遊戲 - 進階功能」</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader" xml:space="preserve">
|
||||
<value>啟動高級功能</value>
|
||||
<value>進階功能</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingKeyShortcutAutoClickingDescription" xml:space="preserve">
|
||||
<value>更改自動連續點按功能的快速鍵</value>
|
||||
@@ -2601,10 +2607,10 @@
|
||||
<value>重設圖片資源</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsDescription" xml:space="preserve">
|
||||
<value>在啟動遊戲頁面的程序部分加入解鎖幀率限制選項</value>
|
||||
<value>在啟動遊戲頁面的程序部分加入解鎖 FPS 限制選項</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingsAdvancedOptionsLaunchUnlockFpsHeader" xml:space="preserve">
|
||||
<value>啟動遊戲-解鎖幀率限制</value>
|
||||
<value>啟動遊戲-解鎖 FPS 限制</value>
|
||||
</data>
|
||||
<data name="ViewPageSettingSetDataFolderDescription" xml:space="preserve">
|
||||
<value>更改目錄后需要手動移動目錄内的數據,否則會重新創建用戶數據</value>
|
||||
@@ -2742,7 +2748,7 @@
|
||||
<value>登入失敗,請重新登入</value>
|
||||
</data>
|
||||
<data name="ViewServiceHutaoUserLoginOrRegisterHint" xml:space="preserve">
|
||||
<value>立即登入或建立帳號</value>
|
||||
<value>立即登入或註冊</value>
|
||||
</data>
|
||||
<data name="ViewSettingAllocConsoleDescription" xml:space="preserve">
|
||||
<value>控制胡桃啟動時是否開啟主控台,重新啟動後生效</value>
|
||||
@@ -2811,7 +2817,7 @@
|
||||
<value>重新整理數據</value>
|
||||
</data>
|
||||
<data name="ViewSpiralAbyssRefreshDescription" xml:space="preserve">
|
||||
<value>同步 HoYo-LAB 的深淵挑戰記錄</value>
|
||||
<value>同步 HoYoLAB 的深淵挑戰記錄</value>
|
||||
</data>
|
||||
<data name="ViewSpiralAbyssReveal" xml:space="preserve">
|
||||
<value>出戰次數</value>
|
||||
@@ -2880,10 +2886,10 @@
|
||||
<value>尚未登入</value>
|
||||
</data>
|
||||
<data name="ViewUserRefreshCookieTokenSuccess" xml:space="preserve">
|
||||
<value>更新 CookieToken 成功</value>
|
||||
<value>重新整理 CookieToken 成功</value>
|
||||
</data>
|
||||
<data name="ViewUserRefreshCookieTokenWarning" xml:space="preserve">
|
||||
<value>更新 CookieToken 失敗</value>
|
||||
<value>重新整理 CookieToken 失敗</value>
|
||||
</data>
|
||||
<data name="ViewUserRemoveAction" xml:space="preserve">
|
||||
<value>移除用戶</value>
|
||||
@@ -3030,7 +3036,7 @@
|
||||
<value>尚未獲得</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteTransformerNotObtainedDetail" xml:space="preserve">
|
||||
<value>尚未獲得參量質變儀</value>
|
||||
<value>尚未獲得參數質變儀</value>
|
||||
</data>
|
||||
<data name="WebDailyNoteTransformerNotReached" xml:space="preserve">
|
||||
<value>冷卻中</value>
|
||||
@@ -3123,7 +3129,7 @@
|
||||
<value>狀態:{0} | 信息:{1}</value>
|
||||
</data>
|
||||
<data name="WebResponseRefreshCookieHintFormat" xml:space="preserve">
|
||||
<value>請更新 Cookie,原始消息:{0}</value>
|
||||
<value>請重新整理 Cookie,原始消息:{0}</value>
|
||||
</data>
|
||||
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
|
||||
<value>[{0}] 中的 [{1}] 網路請求異常,請稍後再試</value>
|
||||
|
||||
@@ -88,7 +88,6 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
|
||||
if (currentBackgroundPathSet is not { Count: > 0 })
|
||||
{
|
||||
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
|
||||
Directory.CreateDirectory(backgroundFolder);
|
||||
|
||||
currentBackgroundPathSet = Directory
|
||||
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
|
||||
|
||||
@@ -126,10 +126,7 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
return resultItems
|
||||
.OrderByDescending(i => i.TotalCount)
|
||||
.ThenByDescending(i => i.Count)
|
||||
.ToObservableCollection();
|
||||
return resultItems.SortBy(item => item.Inner.Id, MaterialIdComparer.Shared).ToObservableCollection();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.Cultivation;
|
||||
|
||||
namespace Snap.Hutao.Service.Cultivation;
|
||||
|
||||
internal sealed class MaterialIdComparer : IComparer<MaterialId>
|
||||
{
|
||||
private static readonly Lazy<MaterialIdComparer> LazyShared = new(() => new());
|
||||
|
||||
public static MaterialIdComparer Shared { get => LazyShared.Value; }
|
||||
|
||||
public int Compare(MaterialId x, MaterialId y)
|
||||
{
|
||||
return Transform(x).CompareTo(Transform(y));
|
||||
}
|
||||
|
||||
private static uint Transform(MaterialId value)
|
||||
{
|
||||
return value.Value switch
|
||||
{
|
||||
// 摩拉
|
||||
202U => 0U,
|
||||
|
||||
// 经验
|
||||
104001U => 1U,
|
||||
104002U => 2U,
|
||||
104003U => 3U,
|
||||
|
||||
// 魔矿
|
||||
104011U => 4U,
|
||||
104012U => 5U,
|
||||
104013U => 6U,
|
||||
|
||||
_ => value,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ namespace Snap.Hutao.Service.Game.Configuration;
|
||||
[Injection(InjectAs.Singleton, typeof(IGameChannelOptionsService))]
|
||||
internal sealed partial class GameChannelOptionsService : IGameChannelOptionsService
|
||||
{
|
||||
private readonly IGameConfigurationFileService gameConfigurationFileService;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
|
||||
public ChannelOptions GetChannelOptions()
|
||||
@@ -22,6 +23,13 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer
|
||||
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName);
|
||||
|
||||
if (!File.Exists(gameFileSystem.GameConfigFilePath))
|
||||
{
|
||||
// Try restore the configuration file if it does not exist
|
||||
// The configuration file may be deleted by a incompatible launcher
|
||||
gameConfigurationFileService.Restore(gameFileSystem.GameConfigFilePath);
|
||||
}
|
||||
|
||||
if (!File.Exists(gameFileSystem.GameConfigFilePath))
|
||||
{
|
||||
return ChannelOptions.ConfigurationFileNotFound(gameFileSystem.GameConfigFilePath);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Configuration;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IGameConfigurationFileService))]
|
||||
internal sealed partial class GameConfigurationFileService : IGameConfigurationFileService
|
||||
{
|
||||
private const string ConfigurationFileName = "config.ini";
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
|
||||
public void Backup(string source)
|
||||
{
|
||||
if (File.Exists(source))
|
||||
{
|
||||
string serverCacheFolder = runtimeOptions.GetDataFolderServerCacheFolder();
|
||||
File.Copy(source, Path.Combine(serverCacheFolder, ConfigurationFileName), true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Restore(string destination)
|
||||
{
|
||||
string serverCacheFolder = runtimeOptions.GetDataFolderServerCacheFolder();
|
||||
string source = Path.Combine(serverCacheFolder, ConfigurationFileName);
|
||||
if (File.Exists(source))
|
||||
{
|
||||
File.Copy(source, destination, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Configuration;
|
||||
|
||||
internal interface IGameConfigurationFileService
|
||||
{
|
||||
void Backup(string source);
|
||||
|
||||
void Restore(string destination);
|
||||
}
|
||||
@@ -36,4 +36,6 @@ internal sealed class GameFileSystem
|
||||
public string GameConfigFilePath { get => gameConfigFilePath ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); }
|
||||
|
||||
public string PCGameSDKFilePath { get => pcGameSDKFilePath ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); }
|
||||
|
||||
public string ScreenShotDirectory { get => Path.Combine(GameDirectory, "ScreenShot"); }
|
||||
}
|
||||
@@ -178,7 +178,16 @@ internal sealed class LaunchOptions : DbStoreOptions
|
||||
[AllowNull]
|
||||
public NameValue<int> Monitor
|
||||
{
|
||||
get => GetOption(ref monitor, SettingEntry.LaunchMonitor, index => Monitors[int.Parse(index, CultureInfo.InvariantCulture) - 1], Monitors[0]);
|
||||
get
|
||||
{
|
||||
return GetOption(ref monitor, SettingEntry.LaunchMonitor, index => Monitors[RestrictIndex(Monitors, index)], Monitors[0]);
|
||||
|
||||
static int RestrictIndex(List<NameValue<int>> monitors, string index)
|
||||
{
|
||||
return Math.Clamp(int.Parse(index, CultureInfo.InvariantCulture) - 1, 0, monitors.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value is not null)
|
||||
|
||||
@@ -7,6 +7,7 @@ using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Service.Game.Configuration;
|
||||
using Snap.Hutao.Service.Game.Package;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||
@@ -42,6 +43,9 @@ internal sealed class LaunchExecutionEnsureGameResourceHandler : ILaunchExecutio
|
||||
return;
|
||||
}
|
||||
|
||||
// Backup config file, in order to prevent a incompatible launcher to delete it.
|
||||
context.ServiceProvider.GetRequiredService<IGameConfigurationFileService>().Backup(gameFileSystem.GameConfigFilePath);
|
||||
|
||||
await context.TaskContext.SwitchToMainThreadAsync();
|
||||
context.UpdateGamePathEntry();
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ internal sealed partial class PackageConverter
|
||||
string scatteredFilesUrl = gameResource.Game.Latest.DecompressedPath;
|
||||
string pkgVersionUrl = $"{scatteredFilesUrl}/{PackageVersion}";
|
||||
|
||||
PackageConverterFileSystemContext context = new(targetScheme.IsOversea, runtimeOptions.DataFolder, gameFolder, scatteredFilesUrl);
|
||||
PackageConverterFileSystemContext context = new(targetScheme.IsOversea, runtimeOptions.GetDataFolderServerCacheFolder(), gameFolder, scatteredFilesUrl);
|
||||
|
||||
// Step 1
|
||||
progress.Report(new(SH.ServiceGamePackageRequestPackageVerion));
|
||||
|
||||
@@ -22,10 +22,10 @@ internal readonly struct PackageConverterFileSystemContext
|
||||
public readonly string ScatteredFilesUrl;
|
||||
public readonly string PkgVersionUrl;
|
||||
|
||||
public PackageConverterFileSystemContext(bool isTargetOversea, string dataFolder, string gameFolder, string scatteredFilesUrl)
|
||||
public PackageConverterFileSystemContext(bool isTargetOversea, string serverCacheFolder, string gameFolder, string scatteredFilesUrl)
|
||||
{
|
||||
GameFolder = gameFolder;
|
||||
ServerCacheFolder = Path.Combine(dataFolder, "ServerCache");
|
||||
ServerCacheFolder = serverCacheFolder;
|
||||
|
||||
string serverCacheOversea = Path.Combine(ServerCacheFolder, "Oversea");
|
||||
string serverCacheChinese = Path.Combine(ServerCacheFolder, "Chinese");
|
||||
|
||||
@@ -5,6 +5,7 @@ using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Item;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.Cultivation;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
|
||||
@@ -67,7 +68,7 @@ internal static class MetadataServiceContextExtension
|
||||
#pragma warning disable SH002
|
||||
public static IEnumerable<Material> EnumerateInventoryMaterial(this IMetadataListMaterialSource context)
|
||||
{
|
||||
return context.Materials.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value);
|
||||
return context.Materials.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id, MaterialIdComparer.Shared);
|
||||
}
|
||||
|
||||
public static Avatar GetAvatar(this IMetadataDictionaryIdAvatarSource context, AvatarId id)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
|
||||
namespace Snap.Hutao.Service.Notification;
|
||||
|
||||
internal interface IInfoBarOptionsBuilder : IBuilder
|
||||
{
|
||||
public InfoBarOptions Options { get; }
|
||||
}
|
||||
@@ -11,5 +11,5 @@ internal interface IInfoBarService
|
||||
{
|
||||
ObservableCollection<InfoBar> Collection { get; }
|
||||
|
||||
void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay);
|
||||
void PrepareInfoBarAndShow(Action<IInfoBarOptionsBuilder> configure);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
|
||||
namespace Snap.Hutao.Service.Notification;
|
||||
|
||||
internal sealed class InfoBarOptions
|
||||
{
|
||||
public InfoBarSeverity Severity { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Message { get; set; }
|
||||
|
||||
public object? Content { get; set; }
|
||||
|
||||
public ButtonBase? ActionButton { get; set; }
|
||||
|
||||
public int MilliSecondsDelay { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Notification;
|
||||
|
||||
internal sealed class InfoBarOptionsBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
public InfoBarOptions Options { get; } = new();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Snap.Hutao.Control.Builder.ButtonBase;
|
||||
using Snap.Hutao.Core.Abstraction.Extension;
|
||||
|
||||
namespace Snap.Hutao.Service.Notification;
|
||||
|
||||
internal static class InfoBarOptionsBuilderExtension
|
||||
{
|
||||
public static TBuilder SetSeverity<TBuilder>(this TBuilder builder, InfoBarSeverity severity)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
builder.Configure(builder => builder.Options.Severity = severity);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static TBuilder SetTitle<TBuilder>(this TBuilder builder, string? title)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
builder.Configure(builder => builder.Options.Title = title);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IInfoBarOptionsBuilder SetMessage<TBuilder>(this TBuilder builder, string? message)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
builder.Configure(builder => builder.Options.Message = message);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IInfoBarOptionsBuilder SetContent<TBuilder>(this TBuilder builder, object? content)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
builder.Configure(builder => builder.Options.Content = content);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IInfoBarOptionsBuilder SetActionButton<TBuilder, TButton>(this TBuilder builder, Action<ButtonBaseBuilder<TButton>> configureButton)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
where TButton : ButtonBase, new()
|
||||
{
|
||||
ButtonBaseBuilder<TButton> buttonBaseBuilder = new ButtonBaseBuilder<TButton>().Configure(configureButton);
|
||||
builder.Configure(builder => builder.Options.ActionButton = buttonBaseBuilder.Button);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IInfoBarOptionsBuilder SetActionButton<TBuilder>(this TBuilder builder, Action<ButtonBuilder> configureButton)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
ButtonBuilder buttonBaseBuilder = new ButtonBuilder().Configure(configureButton);
|
||||
builder.Configure(builder => builder.Options.ActionButton = buttonBaseBuilder.Button);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IInfoBarOptionsBuilder SetDelay<TBuilder>(this TBuilder builder, int milliSeconds)
|
||||
where TBuilder : IInfoBarOptionsBuilder
|
||||
{
|
||||
builder.Configure(builder => builder.Options.MilliSecondsDelay = milliSeconds);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Snap.Hutao.Core.Abstraction.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
@@ -34,26 +35,30 @@ internal sealed class InfoBarService : IInfoBarService
|
||||
get => collection ??= [];
|
||||
}
|
||||
|
||||
public void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay)
|
||||
public void PrepareInfoBarAndShow(Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
if (collection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PrepareInfoBarAndShowCoreAsync(severity, title, message, delay).SafeForget(logger);
|
||||
PrepareInfoBarAndShowCoreAsync(configure).SafeForget(logger);
|
||||
}
|
||||
|
||||
private async ValueTask PrepareInfoBarAndShowCoreAsync(InfoBarSeverity severity, string? title, string? message, int delay)
|
||||
private async ValueTask PrepareInfoBarAndShowCoreAsync(Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
IInfoBarOptionsBuilder builder = new InfoBarOptionsBuilder().Configure(configure);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
InfoBar infoBar = new()
|
||||
{
|
||||
Severity = severity,
|
||||
Title = title,
|
||||
Message = message,
|
||||
Severity = builder.Options.Severity,
|
||||
Title = builder.Options.Title,
|
||||
Message = builder.Options.Message,
|
||||
Content = builder.Options.Content,
|
||||
IsOpen = true,
|
||||
ActionButton = builder.Options.ActionButton,
|
||||
Transitions = [new AddDeleteThemeTransition()],
|
||||
};
|
||||
|
||||
@@ -61,9 +66,9 @@ internal sealed class InfoBarService : IInfoBarService
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
collection.Add(infoBar);
|
||||
|
||||
if (delay > 0)
|
||||
if (builder.Options.MilliSecondsDelay > 0)
|
||||
{
|
||||
await Delay.FromMilliSeconds(delay).ConfigureAwait(true);
|
||||
await Delay.FromMilliSeconds(builder.Options.MilliSecondsDelay).ConfigureAwait(true);
|
||||
collection.Remove(infoBar);
|
||||
infoBar.IsOpen = false;
|
||||
}
|
||||
|
||||
@@ -2,58 +2,100 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Builder.ButtonBase;
|
||||
using Snap.Hutao.Core.Abstraction.Extension;
|
||||
|
||||
namespace Snap.Hutao.Service.Notification;
|
||||
|
||||
internal static class InfoBarServiceExtension
|
||||
{
|
||||
public static void Information(this IInfoBarService infoBarService, string message, int delay = 5000)
|
||||
public static void Information(this IInfoBarService infoBarService, string message, int milliSeconds = 5000)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, null, message, delay);
|
||||
infoBarService.Information(builder => builder.SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Information(this IInfoBarService infoBarService, string title, string message, int delay = 5000)
|
||||
public static void Information(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 5000)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay);
|
||||
infoBarService.Information(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Success(this IInfoBarService infoBarService, string message, int delay = 5000)
|
||||
public static void Information(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 5000)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, null, message, delay);
|
||||
infoBarService.Information(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Success(this IInfoBarService infoBarService, string title, string message, int delay = 5000)
|
||||
public static void Information(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay);
|
||||
infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Informational).Configure(configure));
|
||||
}
|
||||
|
||||
public static void Warning(this IInfoBarService infoBarService, string message, int delay = 30000)
|
||||
public static void Success(this IInfoBarService infoBarService, string message, int milliSeconds = 5000)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, null, message, delay);
|
||||
infoBarService.Success(builder => builder.SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Warning(this IInfoBarService infoBarService, string title, string message, int delay = 30000)
|
||||
public static void Success(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 5000)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay);
|
||||
infoBarService.Success(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, string message, int delay = 0)
|
||||
public static void Success(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, null, message, delay);
|
||||
infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Success).Configure(configure));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, string title, string message, int delay = 0)
|
||||
public static void Warning(this IInfoBarService infoBarService, string message, int milliSeconds = 30000)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, title, message, delay);
|
||||
infoBarService.Warning(builder => builder.SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Exception ex, int delay = 0)
|
||||
public static void Warning(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 30000)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, ex.Message, delay);
|
||||
infoBarService.Warning(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Exception ex, string title, int delay = 0)
|
||||
public static void Warning(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 30000)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay);
|
||||
infoBarService.Warning(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Warning(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Warning).Configure(configure));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, string message, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, string title, string message, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetTitle(title).SetMessage(message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetTitle(title).SetMessage(message).SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Exception ex, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage(ex.Message).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Exception ex, string subtitle, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage($"{subtitle}\n{ex.Message}").SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Exception ex, string subtitle, string buttonContent, ICommand buttonCommand, int milliSeconds = 0)
|
||||
{
|
||||
infoBarService.Error(builder => builder.SetTitle(ex.GetType().Name).SetMessage($"{subtitle}\n{ex.Message}").SetActionButton(buttonBuilder => buttonBuilder.SetContent(buttonContent).SetCommand(buttonCommand)).SetDelay(milliSeconds));
|
||||
}
|
||||
|
||||
public static void Error(this IInfoBarService infoBarService, Action<IInfoBarOptionsBuilder> configure)
|
||||
{
|
||||
infoBarService.PrepareInfoBarAndShow(builder => builder.SetSeverity(InfoBarSeverity.Error).Configure(configure));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
using Snap.Hutao.Core.IO.Http.Sharding;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Hutao.Response;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Snap.Hutao.Service.Update;
|
||||
|
||||
internal sealed class CheckUpdateResult
|
||||
{
|
||||
public CheckUpdateResultKind Kind { get; set; }
|
||||
|
||||
public HutaoVersionInformation? HutaoVersionInformation { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Update;
|
||||
|
||||
internal enum CheckUpdateResultKind
|
||||
{
|
||||
None = 0,
|
||||
VersionApiInvalidResponse = 1,
|
||||
VersionApiInvalidSha256 = 2,
|
||||
AlreayUpdated = 3,
|
||||
|
||||
NeedDownload = 4,
|
||||
NeedInstall = 5,
|
||||
}
|
||||
@@ -7,7 +7,9 @@ namespace Snap.Hutao.Service.Abstraction;
|
||||
|
||||
internal interface IUpdateService
|
||||
{
|
||||
ValueTask<bool> CheckForUpdateAndDownloadAsync(IProgress<UpdateStatus> progress, CancellationToken token = default);
|
||||
ValueTask<CheckUpdateResult> CheckUpdateAsync(IProgress<UpdateStatus> progress, CancellationToken token = default);
|
||||
|
||||
ValueTask<bool> LaunchUpdaterAsync();
|
||||
ValueTask<bool> DownloadUpdateAsync(CheckUpdateResult checkUpdateResult, IProgress<UpdateStatus> progress, CancellationToken token = default);
|
||||
|
||||
ValueTask<LaunchUpdaterResult> LaunchUpdaterAsync();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.IO.Hashing;
|
||||
using Snap.Hutao.Core.IO.Http.Sharding;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Web.Hutao;
|
||||
using Snap.Hutao.Web.Hutao.Response;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Snap.Hutao.Service.Update;
|
||||
|
||||
internal sealed class LaunchUpdaterResult
|
||||
{
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
public Process? Process { get; set; }
|
||||
}
|
||||
@@ -24,54 +24,71 @@ internal sealed partial class UpdateService : IUpdateService
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
public async ValueTask<bool> CheckForUpdateAndDownloadAsync(IProgress<UpdateStatus> progress, CancellationToken token = default)
|
||||
public async ValueTask<CheckUpdateResult> CheckUpdateAsync(IProgress<UpdateStatus> progress, CancellationToken token = default)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
ITaskContext taskContext = scope.ServiceProvider.GetRequiredService<ITaskContext>();
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
|
||||
HutaoInfrastructureClient infrastructureClient = serviceProvider.GetRequiredService<HutaoInfrastructureClient>();
|
||||
HutaoInfrastructureClient infrastructureClient = scope.ServiceProvider.GetRequiredService<HutaoInfrastructureClient>();
|
||||
HutaoResponse<HutaoVersionInformation> response = await infrastructureClient.GetHutaoVersionInfomationAsync(token).ConfigureAwait(false);
|
||||
|
||||
CheckUpdateResult checkUpdateResult = new();
|
||||
|
||||
if (!response.IsOk())
|
||||
{
|
||||
return false;
|
||||
checkUpdateResult.Kind = CheckUpdateResultKind.VersionApiInvalidResponse;
|
||||
return checkUpdateResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
checkUpdateResult.Kind = CheckUpdateResultKind.NeedDownload;
|
||||
checkUpdateResult.HutaoVersionInformation = response.Data;
|
||||
}
|
||||
|
||||
HutaoVersionInformation versionInformation = response.Data;
|
||||
string msixPath = GetUpdatePackagePath();
|
||||
|
||||
if (!LocalSetting.Get(SettingKeys.OverrideUpdateVersionComparison, false))
|
||||
{
|
||||
if (scope.ServiceProvider.GetRequiredService<RuntimeOptions>().Version >= versionInformation.Version)
|
||||
// Launched in an updated version
|
||||
if (scope.ServiceProvider.GetRequiredService<RuntimeOptions>().Version >= checkUpdateResult.HutaoVersionInformation.Version)
|
||||
{
|
||||
if (File.Exists(msixPath))
|
||||
{
|
||||
File.Delete(msixPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
checkUpdateResult.Kind = CheckUpdateResultKind.AlreayUpdated;
|
||||
return checkUpdateResult;
|
||||
}
|
||||
}
|
||||
|
||||
progress.Report(new(versionInformation.Version.ToString(), 0, 0));
|
||||
progress.Report(new(checkUpdateResult.HutaoVersionInformation.Version.ToString(), 0, 0));
|
||||
|
||||
if (versionInformation.Sha256 is not { Length: > 0 } sha256)
|
||||
if (checkUpdateResult.HutaoVersionInformation.Sha256 is not { Length: > 0 } sha256)
|
||||
{
|
||||
return false;
|
||||
checkUpdateResult.Kind = CheckUpdateResultKind.VersionApiInvalidSha256;
|
||||
return checkUpdateResult;
|
||||
}
|
||||
|
||||
if (File.Exists(msixPath) && await CheckUpdateCacheSHA256Async(msixPath, sha256, token).ConfigureAwait(false))
|
||||
{
|
||||
return true;
|
||||
checkUpdateResult.Kind = CheckUpdateResultKind.NeedInstall;
|
||||
return checkUpdateResult;
|
||||
}
|
||||
|
||||
return await DownloadUpdatePackageAsync(versionInformation, msixPath, progress, token).ConfigureAwait(false);
|
||||
return checkUpdateResult;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<bool> LaunchUpdaterAsync()
|
||||
public ValueTask<bool> DownloadUpdateAsync(CheckUpdateResult checkUpdateResult, IProgress<UpdateStatus> progress, CancellationToken token = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(checkUpdateResult.HutaoVersionInformation);
|
||||
return DownloadUpdatePackageAsync(checkUpdateResult.HutaoVersionInformation, GetUpdatePackagePath(), progress, token);
|
||||
}
|
||||
|
||||
public async ValueTask<LaunchUpdaterResult> LaunchUpdaterAsync()
|
||||
{
|
||||
RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService<RuntimeOptions>();
|
||||
string updaterTargetPath = runtimeOptions.GetDataFolderUpdateCacheFolderFile(UpdaterFilename);
|
||||
@@ -88,19 +105,19 @@ internal sealed partial class UpdateService : IUpdateService
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo()
|
||||
Process? process = Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
Arguments = commandLine,
|
||||
FileName = updaterTargetPath,
|
||||
UseShellExecute = true,
|
||||
});
|
||||
|
||||
return true;
|
||||
return new() { IsSuccess = true, Process = process };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
serviceProvider.GetRequiredService<IInfoBarService>().Error(ex);
|
||||
return false;
|
||||
return new() { IsSuccess = false };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ namespace Snap.Hutao.Service.User;
|
||||
|
||||
internal interface IUserDbService
|
||||
{
|
||||
ValueTask AddUserAsync(Model.Entity.User user);
|
||||
|
||||
ValueTask DeleteUserByIdAsync(Guid id);
|
||||
|
||||
ValueTask RemoveUsersAsync();
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Snap.Hutao.Service.User;
|
||||
[Injection(InjectAs.Singleton, typeof(IUserCollectionService))]
|
||||
internal sealed partial class UserCollectionService : IUserCollectionService, IDisposable
|
||||
{
|
||||
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
||||
private readonly ScopedDbCurrent<BindingUser, EntityUser, UserChangedMessage> dbCurrent;
|
||||
private readonly IUserInitializationService userInitializationService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IUserDbService userDbService;
|
||||
@@ -163,7 +163,7 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID
|
||||
|
||||
// Sync cache
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
userCollection.Add(newUser);
|
||||
userCollection.Add(newUser); // Database synced in the collection
|
||||
if (newUser.Entity.Mid is not null)
|
||||
{
|
||||
midUserMap?.Add(newUser.Entity.Mid, newUser);
|
||||
@@ -178,9 +178,6 @@ internal sealed partial class UserCollectionService : IUserCollectionService, ID
|
||||
}
|
||||
}
|
||||
|
||||
// Sync database
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
await userDbService.AddUserAsync(newUser.Entity).ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(newUser.UserInfo);
|
||||
return new(UserOptionResult.Added, newUser.UserInfo.Uid);
|
||||
}
|
||||
|
||||
@@ -40,15 +40,6 @@ internal sealed partial class UserDbService : IUserDbService
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask AddUserAsync(Model.Entity.User user)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
await appDbContext.Users.AddAndSaveAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask RemoveUsersAsync()
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
<SelfContained>true</SelfContained>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSdkUndockedRegFreeWinRTInitialize>false</WindowsAppSdkUndockedRegFreeWinRTInitialize>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -176,6 +178,7 @@
|
||||
<None Remove="View\Dialog\HutaoPassportResetPasswordDialog.xaml" />
|
||||
<None Remove="View\Dialog\HutaoPassportUnregisterDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGameAccountNameDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGameConfigurationFixDialog.xaml" />
|
||||
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
|
||||
<None Remove="View\Dialog\ReconfirmDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserDialog.xaml" />
|
||||
@@ -302,8 +305,8 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.TokenizingTextBox" Version="8.0.240109" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.0.240109" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -318,14 +321,14 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.8.8" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240311000" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240404000" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<PackageReference Include="Snap.Discord.GameSDK" Version="1.6.0" />
|
||||
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.15.3">
|
||||
<PackageReference Include="Snap.Hutao.Deployment.Runtime" Version="1.16.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.0.6">
|
||||
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -347,7 +350,9 @@
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
<Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Control\Theme\SegmentedOverride.xaml">
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.LaunchGameConfigurationFixDialog"
|
||||
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:shccs="using:Snap.Hutao.Control.Collection.Selector"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shvd="using:Snap.Hutao.View.Dialog"
|
||||
Title="{shcm:ResourceString Name=ViewDialogLaunchGameConfigurationFixDialogTitle}"
|
||||
d:DataContext="{d:DesignInstance shvd:LaunchGameConfigurationFixDialog}"
|
||||
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
|
||||
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<shccs:ComboBox2
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
DisplayMemberPath="DisplayName"
|
||||
EnableMemberPath="IsNotCompatOnly"
|
||||
Header="{shcm:ResourceString Name=ViewDialogLaunchGameConfigurationFixDialogHint}"
|
||||
ItemsSource="{x:Bind KnownSchemes}"
|
||||
SelectedItem="{x:Bind SelectedScheme, Mode=TwoWay}"
|
||||
Style="{StaticResource DefaultComboBoxStyle}"/>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
[DependencyProperty("KnownSchemes", typeof(IEnumerable<LaunchScheme>))]
|
||||
[DependencyProperty("SelectedScheme", typeof(LaunchScheme))]
|
||||
internal sealed partial class LaunchGameConfigurationFixDialog : ContentDialog
|
||||
{
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public LaunchGameConfigurationFixDialog(IServiceProvider serviceProvider)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
|
||||
}
|
||||
|
||||
public async ValueTask<ValueResult<bool, LaunchScheme>> GetLaunchSchemeAsync()
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
return new(result is ContentDialogResult.Primary, SelectedScheme);
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<cwc:SwitchPresenter Value="{Binding State, Mode=OneWay}">
|
||||
<cwc:SwitchPresenter ContentTransitions="{ThemeResource EntranceThemeTransitions}" Value="{Binding State, Mode=OneWay}">
|
||||
<cwc:Case Value="{shcm:UInt32 Value=0}">
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<GridView
|
||||
@@ -125,10 +125,11 @@
|
||||
<StackPanel
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
VerticalAlignment="Center"
|
||||
Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock
|
||||
Margin="1,0,0,5"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="Segoe Fluent Icons"/>
|
||||
<StackPanel
|
||||
Margin="0,8"
|
||||
@@ -155,7 +156,10 @@
|
||||
<Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentFontDescription2}"/>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentAfterInstallDescription}"/>
|
||||
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
|
||||
<TextBlock
|
||||
Margin="1,32,0,5"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{Binding RuntimeOptions.WebView2Version}"/>
|
||||
<TextBlock>
|
||||
<Run Text="{shcm:ResourceString Name=ViewGuideStepEnvironmentWebView2Description1}"/>
|
||||
@@ -169,6 +173,78 @@
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case Value="{shcm:UInt32 Value=3}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingHomeAnnouncementRegionHeader}"/>
|
||||
<ListView
|
||||
MinWidth="320"
|
||||
Margin="0,8,0,0"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding AppOptions.LazyRegions.Value}"
|
||||
SelectedItem="{Binding SelectedRegion, Mode=TwoWay}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
Opacity="0.7"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewGuideStepCommonSettingHint}"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case Value="{shcm:UInt32 Value=4}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingQualityHeader}"/>
|
||||
<ListView
|
||||
MinWidth="320"
|
||||
Margin="0,8,0,32"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding StaticResourceOptions.ImageQualities}"
|
||||
SelectedItem="{Binding StaticResourceOptions.ImageQuality, Mode=TwoWay}"/>
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingMinimumHeader}"/>
|
||||
<ListView
|
||||
MinWidth="320"
|
||||
Margin="0,8,0,32"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding StaticResourceOptions.ImageArchives}"
|
||||
SelectedItem="{Binding StaticResourceOptions.ImageArchive, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Margin="0,16,0,0" Text="{Binding StaticResourceOptions.SizeInformationText, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
HorizontalTextAlignment="Center"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{shcm:ResourceString Name=ViewGuideStepStaticResourceSettingHint}"/>
|
||||
</Grid>
|
||||
</cwc:Case>
|
||||
<cwc:Case Value="{shcm:UInt32 Value=5}">
|
||||
<StackPanel Margin="32,0" HorizontalAlignment="Left">
|
||||
<TextBlock
|
||||
Margin="1,16,0,5"
|
||||
@@ -190,12 +266,23 @@
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<cwc:Segmented
|
||||
Margin="16"
|
||||
HorizontalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
SelectedIndex="{Binding State, Mode=TwoWay}">
|
||||
<!--
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepLanguage}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepDocument}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepEnvironment}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepCommonSetting}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResourceSetting}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Content="{shcm:ResourceString Name=ViewGuideStepStaticResource}" Icon="{shcm:FontIcon Glyph=}"/>
|
||||
-->
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
<cwc:SegmentedItem Icon="{shcm:FontIcon Glyph=}"/>
|
||||
</cwc:Segmented>
|
||||
<Button
|
||||
Command="{Binding NextOrCompleteCommand}"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.ViewModel.Guide;
|
||||
|
||||
namespace Snap.Hutao.View.Guide;
|
||||
@@ -14,6 +15,6 @@ internal sealed partial class GuideView : UserControl
|
||||
public GuideView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<GuideViewModel>();
|
||||
DataContext = this.ServiceProvider().GetRequiredService<GuideViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,22 @@
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{shcm:ResourceString Name=ViewPageLoginMihoyoUserTitle}"/>
|
||||
<Button
|
||||
Margin="16"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Bind HandleCurrentCookieCommand}"
|
||||
Content="{shcm:ResourceString Name=ViewPageLoginMihoyoUserLoggedInAction}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Margin="12">
|
||||
<TextBlock Text="{shcm:ResourceString Name=ViewPageLoginMihoyoUserTitle}"/>
|
||||
<TextBlock Text="{shcm:ResourceString Name=ViewPageLoginMihoyoUserDescription}"/>
|
||||
</StackPanel>
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Margin="12"
|
||||
Command="{x:Bind HandleCurrentCookieCommand}"
|
||||
Content="{shcm:ResourceString Name=ViewPageLoginMihoyoUserLoggedInAction}"/>
|
||||
</Grid>
|
||||
<WebView2 x:Name="WebView" Grid.Row="2"/>
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
|
||||
@@ -13,16 +13,22 @@
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{shcm:ResourceString Name=ViewPageLoginMihoyoUserTitle}"/>
|
||||
<Button
|
||||
Margin="16"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Bind HandleCurrentCookieCommand}"
|
||||
Content="{shcm:ResourceString Name=ViewPageLoginMihoyoUserLoggedInAction}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Margin="12">
|
||||
<TextBlock Text="{shcm:ResourceString Name=ViewPageLoginMihoyoUserTitle}"/>
|
||||
<TextBlock Text="{shcm:ResourceString Name=ViewPageLoginMihoyoUserDescription}"/>
|
||||
</StackPanel>
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Margin="12"
|
||||
Command="{x:Bind HandleCurrentCookieCommand}"
|
||||
Content="{shcm:ResourceString Name=ViewPageLoginMihoyoUserLoggedInAction}"/>
|
||||
</Grid>
|
||||
<WebView2 x:Name="WebView" Grid.Row="2"/>
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
|
||||
@@ -115,10 +115,7 @@
|
||||
Padding="16"
|
||||
Spacing="16">
|
||||
|
||||
<Border
|
||||
x:Name="ScrollViwerTopPanel"
|
||||
Margin="0,0,0,16"
|
||||
cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border x:Name="ScrollViwerTopPanel" cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}" Visibility="Visible">
|
||||
<Grid MaxHeight="176" Style="{ThemeResource GridCardStyle}">
|
||||
@@ -197,7 +194,7 @@
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border Margin="0,-16,0,0" cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
|
||||
<Border Padding="16" Style="{ThemeResource AcrylicBorderCardStyle}">
|
||||
<StackPanel Spacing="{ThemeResource SettingsCardSpacing}">
|
||||
<TextBlock Style="{StaticResource SettingsCardHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingCardHutaoPassportHeader}"/>
|
||||
|
||||
@@ -420,12 +420,14 @@
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding Selected.Name}"/>
|
||||
Text="{Binding Selected.Name}"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock
|
||||
Margin="24,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding Selected.FetterInfo.Title}"/>
|
||||
Text="{Binding Selected.FetterInfo.Title}"
|
||||
TextWrapping="NoWrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Snap.Hutao.ViewModel.Abstraction;
|
||||
/// 视图模型抽象类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[SuppressMessage("", "SA1124")]
|
||||
internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
{
|
||||
private bool isInitialized;
|
||||
@@ -28,9 +29,15 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
[Command("OpenUICommand")]
|
||||
protected virtual async Task OpenUIAsync()
|
||||
{
|
||||
// Set value on UI thread
|
||||
IsInitialized = await InitializeUIAsync().ConfigureAwait(true);
|
||||
Initialization.TrySetResult(IsInitialized);
|
||||
try
|
||||
{
|
||||
// ConfigureAwait(true) sets value on UI thread
|
||||
IsInitialized = await InitializeUIAsync().ConfigureAwait(true);
|
||||
Initialization.TrySetResult(IsInitialized);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual ValueTask<bool> InitializeUIAsync()
|
||||
@@ -46,6 +53,8 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
return disposable;
|
||||
}
|
||||
|
||||
#region SetProperty
|
||||
|
||||
protected new bool SetProperty<T>([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
return !IsViewDisposed && base.SetProperty(ref field, newValue, propertyName);
|
||||
@@ -99,12 +108,13 @@ internal abstract partial class ViewModel : ObservableObject, IViewModel
|
||||
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void ThrowIfViewDisposed()
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
ThrowHelper.OperationCanceled(SH.ViewModelViewDisposedOperationCancel);
|
||||
HutaoException.OperationCanceled(SH.ViewModelViewDisposedOperationCancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -33,19 +34,18 @@ internal static class AchievementFinishPercent
|
||||
|
||||
if (achievements.SourceCollection is not List<AchievementView> list)
|
||||
{
|
||||
// Fast path
|
||||
throw Must.NeverHappen("AchievementViewModel.Achievements.SourceCollection 应为 List<AchievementView>");
|
||||
throw HutaoException.InvalidCast<IEnumerable<AchievementView>, List<AchievementView>>("AchievementViewModel.Achievements.SourceCollection");
|
||||
}
|
||||
|
||||
Dictionary<AchievementGoalId, AchievementGoalStatistics> counter = achievementGoals.ToDictionary(x => x.Id, AchievementGoalStatistics.From);
|
||||
|
||||
foreach (ref readonly AchievementView achievement in CollectionsMarshal.AsSpan(list))
|
||||
foreach (ref readonly AchievementView achievementView in CollectionsMarshal.AsSpan(list))
|
||||
{
|
||||
ref AchievementGoalStatistics goalStat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
|
||||
ref AchievementGoalStatistics goalStat = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievementView.Inner.Goal);
|
||||
|
||||
goalStat.TotalCount += 1;
|
||||
totalCount += 1;
|
||||
if (achievement.IsChecked)
|
||||
if (achievementView.IsChecked)
|
||||
{
|
||||
goalStat.Finished += 1;
|
||||
totalFinished += 1;
|
||||
|
||||
@@ -15,123 +15,99 @@ using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就导入器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementImporter
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IClipboardProvider clipboardInterop;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly AchievementImporterDependencies dependencies;
|
||||
|
||||
/// <summary>
|
||||
/// 从剪贴板导入
|
||||
/// </summary>
|
||||
/// <returns>是否导入成功</returns>
|
||||
public async ValueTask<bool> FromClipboardAsync()
|
||||
{
|
||||
if (achievementService.CurrentArchive is { } archive)
|
||||
if (dependencies.AchievementService.CurrentArchive is not { } archive)
|
||||
{
|
||||
if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is { } uiaf)
|
||||
{
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (await TryCatchGetUIAFFromClipboardAsync().ConfigureAwait(false) is not { } uiaf)
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件导入
|
||||
/// </summary>
|
||||
/// <returns>是否导入成功</returns>
|
||||
public async ValueTask<bool> FromFileAsync()
|
||||
{
|
||||
if (achievementService.CurrentArchive is { } archive)
|
||||
if (dependencies.AchievementService.CurrentArchive is not { } archive)
|
||||
{
|
||||
ValueResult<bool, ValueFile> pickerResult = fileSystemPickerInteraction.PickFile(
|
||||
SH.ServiceAchievementUIAFImportPickerTitile,
|
||||
[(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]);
|
||||
|
||||
if (pickerResult.TryGetValue(out ValueFile file))
|
||||
{
|
||||
ValueResult<bool, UIAF?> uiafResult = await file.DeserializeFromJsonAsync<UIAF>(options).ConfigureAwait(false);
|
||||
|
||||
if (uiafResult.TryGetValue(out UIAF? uiaf))
|
||||
{
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage2);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
ValueResult<bool, ValueFile> pickerResult = dependencies.FileSystemPickerInteraction.PickFile(
|
||||
SH.ServiceAchievementUIAFImportPickerTitile,
|
||||
[(SH.ServiceAchievementUIAFImportPickerFilterText, "*.json")]);
|
||||
|
||||
if (!pickerResult.TryGetValue(out ValueFile file))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ValueResult<bool, UIAF?> uiafResult = await file.DeserializeFromJsonAsync<UIAF>(dependencies.JsonSerializerOptions).ConfigureAwait(false);
|
||||
|
||||
if (!uiafResult.TryGetValue(out UIAF? uiaf))
|
||||
{
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await TryImportAsync(archive, uiaf).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask<UIAF?> TryCatchGetUIAFFromClipboardAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await clipboardInterop.DeserializeFromJsonAsync<UIAF>().ConfigureAwait(false);
|
||||
return await dependencies.ClipboardProvider.DeserializeFromJsonAsync<UIAF>().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService?.Error(ex, SH.ViewModelImportFromClipboardErrorTitle);
|
||||
dependencies.InfoBarService.Error(ex, SH.ViewModelImportFromClipboardErrorTitle);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryImportAsync(EntityAchievementArchive archive, UIAF uiaf)
|
||||
{
|
||||
if (uiaf.IsCurrentVersionSupported())
|
||||
if (!uiaf.IsCurrentVersionSupported())
|
||||
{
|
||||
AchievementImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync<AchievementImportDialog>(uiaf).ConfigureAwait(false);
|
||||
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ImportResult result;
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
infoBarService.Success($"{result}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelImportWarningTitle, SH.ViewModelAchievementImportWarningMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
AchievementImportDialog importDialog = await dependencies.ContentDialogFactory
|
||||
.CreateInstanceAsync<AchievementImportDialog>(uiaf).ConfigureAwait(false);
|
||||
(bool isOk, ImportStrategy strategy) = await importDialog.GetImportStrategyAsync().ConfigureAwait(false);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
ContentDialog dialog = await dependencies.ContentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAchievementImportProgress)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ImportResult result;
|
||||
using (await dialog.BlockAsync(dependencies.TaskContext).ConfigureAwait(false))
|
||||
{
|
||||
result = await dependencies.AchievementService.ImportFromUIAFAsync(archive, uiaf.List, strategy).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
dependencies.InfoBarService.Success($"{result}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.IO.DataTransfer;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementImporterDependencies
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IClipboardProvider clipboardProvider;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public IFileSystemPickerInteraction FileSystemPickerInteraction { get => fileSystemPickerInteraction; }
|
||||
|
||||
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
|
||||
|
||||
public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; }
|
||||
|
||||
public IAchievementService AchievementService { get => achievementService; }
|
||||
|
||||
public IClipboardProvider ClipboardProvider { get => clipboardProvider; }
|
||||
|
||||
public IInfoBarService InfoBarService { get => infoBarService; }
|
||||
|
||||
public ITaskContext TaskContext { get => taskContext; }
|
||||
}
|
||||
@@ -5,8 +5,6 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Model.InterChange.Achievement;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
@@ -23,9 +21,6 @@ using SortDirection = CommunityToolkit.WinUI.Collections.SortDirection;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 成就视图模型
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
@@ -34,14 +29,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
private readonly SortDescription uncompletedItemsFirstSortDescription = new(nameof(AchievementView.IsChecked), SortDirection.Ascending);
|
||||
private readonly SortDescription completionTimeSortDescription = new(nameof(AchievementView.Time), SortDirection.Descending);
|
||||
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly AchievementImporter achievementImporter;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly AchievementViewModelDependencies dependencies;
|
||||
|
||||
private AdvancedCollectionView<AchievementView>? achievements;
|
||||
private List<AchievementGoalView>? achievementGoals;
|
||||
@@ -52,18 +40,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
private string searchText = string.Empty;
|
||||
private string? finishDescription;
|
||||
|
||||
/// <summary>
|
||||
/// 成就存档集合
|
||||
/// </summary>
|
||||
public ObservableCollection<EntityAchievementArchive>? Archives
|
||||
{
|
||||
get => archives;
|
||||
set => SetProperty(ref archives, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中的成就存档
|
||||
/// </summary>
|
||||
public EntityAchievementArchive? SelectedArchive
|
||||
{
|
||||
get => selectedArchive;
|
||||
@@ -71,38 +53,24 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (SetProperty(ref selectedArchive, value))
|
||||
{
|
||||
if (IsViewDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
achievementService.CurrentArchive = value;
|
||||
dependencies.AchievementService.CurrentArchive = value;
|
||||
UpdateAchievementsAsync(value).SafeForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成就视图
|
||||
/// </summary>
|
||||
public AdvancedCollectionView<AchievementView>? Achievements
|
||||
{
|
||||
get => achievements;
|
||||
set => SetProperty(ref achievements, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成就分类
|
||||
/// </summary>
|
||||
public List<AchievementGoalView>? AchievementGoals
|
||||
{
|
||||
get => achievementGoals;
|
||||
set => SetProperty(ref achievementGoals, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中的成就分类
|
||||
/// </summary>
|
||||
public AchievementGoalView? SelectedAchievementGoal
|
||||
{
|
||||
get => selectedAchievementGoal;
|
||||
@@ -116,27 +84,18 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索文本
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get => searchText;
|
||||
set => SetProperty(ref searchText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 未完成优先
|
||||
/// </summary>
|
||||
public bool IsUncompletedItemsFirst
|
||||
{
|
||||
get => isUncompletedItemsFirst;
|
||||
set => SetProperty(ref isUncompletedItemsFirst, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完成进度描述
|
||||
/// </summary>
|
||||
public string? FinishDescription
|
||||
{
|
||||
get => finishDescription;
|
||||
@@ -160,36 +119,30 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
if (!await dependencies.MetadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
List<AchievementGoalView> sortedGoals;
|
||||
ObservableCollection<EntityAchievementArchive> archives;
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
List<MetadataAchievementGoal> goals = await metadataService
|
||||
.GetAchievementGoalListAsync(CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
|
||||
archives = achievementService.ArchiveCollection;
|
||||
}
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
AchievementGoals = sortedGoals;
|
||||
Archives = archives;
|
||||
SelectedArchive = achievementService.CurrentArchive;
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
List<AchievementGoalView> sortedGoals;
|
||||
ObservableCollection<EntityAchievementArchive> archives;
|
||||
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
List<MetadataAchievementGoal> goals = await dependencies.MetadataService
|
||||
.GetAchievementGoalListAsync(CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
sortedGoals = goals.SortBy(goal => goal.Order).SelectList(AchievementGoalView.From);
|
||||
archives = dependencies.AchievementService.ArchiveCollection;
|
||||
}
|
||||
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
|
||||
AchievementGoals = sortedGoals;
|
||||
Archives = archives;
|
||||
SelectedArchive = dependencies.AchievementService.CurrentArchive;
|
||||
return true;
|
||||
}
|
||||
|
||||
[GeneratedRegex("\\d\\.\\d")]
|
||||
@@ -200,25 +153,25 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (Archives is not null)
|
||||
{
|
||||
AchievementArchiveCreateDialog dialog = await contentDialogFactory.CreateInstanceAsync<AchievementArchiveCreateDialog>().ConfigureAwait(false);
|
||||
AchievementArchiveCreateDialog dialog = await dependencies.ContentDialogFactory.CreateInstanceAsync<AchievementArchiveCreateDialog>().ConfigureAwait(false);
|
||||
(bool isOk, string name) = await dialog.GetInputAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ArchiveAddResult result = await achievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false);
|
||||
ArchiveAddResult result = await dependencies.AchievementService.AddArchiveAsync(EntityAchievementArchive.From(name)).ConfigureAwait(false);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case ArchiveAddResult.Added:
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
SelectedArchive = achievementService.CurrentArchive;
|
||||
infoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name));
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
SelectedArchive = dependencies.AchievementService.CurrentArchive;
|
||||
dependencies.InfoBarService.Success(SH.FormatViewModelAchievementArchiveAdded(name));
|
||||
break;
|
||||
case ArchiveAddResult.InvalidName:
|
||||
infoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelAchievementArchiveInvalidName);
|
||||
break;
|
||||
case ArchiveAddResult.AlreadyExists:
|
||||
infoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name));
|
||||
dependencies.InfoBarService.Warning(SH.FormatViewModelAchievementArchiveAlreadyExists(name));
|
||||
break;
|
||||
default:
|
||||
throw Must.NeverHappen();
|
||||
@@ -234,7 +187,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
string title = SH.FormatViewModelAchievementRemoveArchiveTitle(SelectedArchive.Name);
|
||||
string content = SH.ViewModelAchievementRemoveArchiveContent;
|
||||
ContentDialogResult result = await contentDialogFactory
|
||||
ContentDialogResult result = await dependencies.ContentDialogFactory
|
||||
.CreateForConfirmCancelAsync(title, content)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -244,11 +197,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
|
||||
await dependencies.AchievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Re-select first archive
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
SelectedArchive = Archives.FirstOrDefault();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -263,21 +216,21 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (SelectedArchive is not null && Achievements is not null)
|
||||
{
|
||||
(bool isOk, ValueFile file) = fileSystemPickerInteraction.SaveFile(
|
||||
(bool isOk, ValueFile file) = dependencies.FileSystemPickerInteraction.SaveFile(
|
||||
SH.ViewModelAchievementUIAFExportPickerTitle,
|
||||
$"{achievementService.CurrentArchive?.Name}.json",
|
||||
$"{dependencies.AchievementService.CurrentArchive?.Name}.json",
|
||||
[(SH.ViewModelAchievementExportFileType, "*.json")]);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
UIAF uiaf = await achievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false);
|
||||
if (await file.SerializeToJsonAsync(uiaf, options).ConfigureAwait(false))
|
||||
UIAF uiaf = await dependencies.AchievementService.ExportToUIAFAsync(SelectedArchive).ConfigureAwait(false);
|
||||
if (await file.SerializeToJsonAsync(uiaf, dependencies.JsonSerializerOptions).ConfigureAwait(false))
|
||||
{
|
||||
infoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
|
||||
dependencies.InfoBarService.Success(SH.ViewModelExportSuccessTitle, SH.ViewModelExportSuccessMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
|
||||
dependencies.InfoBarService.Warning(SH.ViewModelExportWarningTitle, SH.ViewModelExportWarningMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,20 +239,20 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
[Command("ImportUIAFFromClipboardCommand")]
|
||||
private async Task ImportUIAFFromClipboardAsync()
|
||||
{
|
||||
if (await achievementImporter.FromClipboardAsync().ConfigureAwait(false))
|
||||
if (await dependencies.AchievementImporter.FromClipboardAsync().ConfigureAwait(false))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(achievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(achievementService.CurrentArchive).ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(dependencies.AchievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(dependencies.AchievementService.CurrentArchive).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ImportUIAFFromFileCommand")]
|
||||
private async Task ImportUIAFFromFileAsync()
|
||||
{
|
||||
if (await achievementImporter.FromFileAsync().ConfigureAwait(false))
|
||||
if (await dependencies.AchievementImporter.FromFileAsync().ConfigureAwait(false))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(achievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(achievementService.CurrentArchive).ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(dependencies.AchievementService.CurrentArchive);
|
||||
await UpdateAchievementsAsync(dependencies.AchievementService.CurrentArchive).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,11 +264,11 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
return;
|
||||
}
|
||||
|
||||
List<MetadataAchievement> achievements = await metadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false);
|
||||
List<MetadataAchievement> achievements = await dependencies.MetadataService.GetAchievementListAsync(CancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (TryGetAchievements(archive, achievements, out List<AchievementView>? combined))
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
await dependencies.TaskContext.SwitchToMainThreadAsync();
|
||||
|
||||
Achievements = new(combined, true);
|
||||
UpdateAchievementsFinishPercent();
|
||||
@@ -328,12 +281,12 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
try
|
||||
{
|
||||
combined = achievementService.GetAchievementViewList(archive, achievements);
|
||||
combined = dependencies.AchievementService.GetAchievementViewList(archive, achievements);
|
||||
return true;
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
dependencies.InfoBarService.Error(ex);
|
||||
combined = default;
|
||||
return false;
|
||||
}
|
||||
@@ -418,7 +371,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
|
||||
{
|
||||
if (achievement is not null)
|
||||
{
|
||||
achievementService.SaveAchievement(achievement);
|
||||
dependencies.AchievementService.SaveAchievement(achievement);
|
||||
UpdateAchievementsFinishPercent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Picker;
|
||||
using Snap.Hutao.Service.Achievement;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Scoped)]
|
||||
internal sealed partial class AchievementViewModelDependencies
|
||||
{
|
||||
private readonly IFileSystemPickerInteraction fileSystemPickerInteraction;
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly AchievementImporter achievementImporter;
|
||||
private readonly IAchievementService achievementService;
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly JsonSerializerOptions jsonSerializerOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public IFileSystemPickerInteraction FileSystemPickerInteraction { get => fileSystemPickerInteraction; }
|
||||
|
||||
public JsonSerializerOptions JsonSerializerOptions { get => jsonSerializerOptions; }
|
||||
|
||||
public IContentDialogFactory ContentDialogFactory { get => contentDialogFactory; }
|
||||
|
||||
public AchievementImporter AchievementImporter { get => achievementImporter; }
|
||||
|
||||
public IAchievementService AchievementService { get => achievementService; }
|
||||
|
||||
public IMetadataService MetadataService { get => metadataService; }
|
||||
|
||||
public IInfoBarService InfoBarService { get => infoBarService; }
|
||||
|
||||
public ITaskContext TaskContext { get => taskContext; }
|
||||
}
|
||||
@@ -1,12 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game.PathAbstraction;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
internal interface IViewModelSupportLaunchExecution
|
||||
{
|
||||
void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry);
|
||||
LaunchGameShared Shared { get; }
|
||||
|
||||
GameAccount? SelectedGameAccount { get; }
|
||||
|
||||
void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Service.Game.Launching;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
internal static class LaunchGameLaunchExecution
|
||||
{
|
||||
public static async ValueTask LaunchExecutionAsync(this IViewModelSupportLaunchExecution launchExecution)
|
||||
{
|
||||
IServiceProvider root = Ioc.Default;
|
||||
IInfoBarService infoBarService = root.GetRequiredService<IInfoBarService>();
|
||||
ILogger<IViewModelSupportLaunchExecution> logger = root.GetRequiredService<ILogger<IViewModelSupportLaunchExecution>>();
|
||||
|
||||
LaunchScheme? scheme = launchExecution.Shared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
try
|
||||
{
|
||||
// Root service provider is required.
|
||||
LaunchExecutionContext context = new(root, launchExecution, scheme, launchExecution.SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,39 +2,104 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Game.Configuration;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.View.Page;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
internal static class LaunchGameShared
|
||||
[Injection(InjectAs.Transient)]
|
||||
[ConstructorGenerated]
|
||||
internal sealed partial class LaunchGameShared
|
||||
{
|
||||
public static LaunchScheme? GetCurrentLaunchSchemeFromConfigFile(IGameServiceFacade gameService, IInfoBarService infoBarService)
|
||||
private readonly IContentDialogFactory contentDialogFactory;
|
||||
private readonly INavigationService navigationService;
|
||||
private readonly IGameServiceFacade gameService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly LaunchOptions launchOptions;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
public LaunchScheme? GetCurrentLaunchSchemeFromConfigFile()
|
||||
{
|
||||
ChannelOptions options = gameService.GetChannelOptions();
|
||||
|
||||
if (options.ErrorKind is ChannelOptionsErrorKind.None)
|
||||
switch (options.ErrorKind)
|
||||
{
|
||||
try
|
||||
{
|
||||
return KnownLaunchSchemes.Get().Single(scheme => scheme.Equals(options));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
if (!IgnoredInvalidChannelOptions.Contains(options))
|
||||
case ChannelOptionsErrorKind.None:
|
||||
try
|
||||
{
|
||||
// 后台收集
|
||||
throw ThrowHelper.NotSupported($"不支持的 MultiChannel: {options}");
|
||||
return KnownLaunchSchemes.Get().Single(scheme => scheme.Equals(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath));
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
if (!IgnoredInvalidChannelOptions.Contains(options))
|
||||
{
|
||||
// 后台收集
|
||||
HutaoException.Throw(HutaoExceptionKind.GameConfigInvalidChannelOptions, $"不支持的 ChannelOptions: {options}");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case ChannelOptionsErrorKind.ConfigurationFileNotFound:
|
||||
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath), SH.ViewModelLaunchGameFixConfigurationFileButtonText, HandleConfigurationFileNotFoundCommand);
|
||||
break;
|
||||
case ChannelOptionsErrorKind.GamePathNullOrEmpty:
|
||||
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath), SH.ViewModelLaunchGameSetGamePathButtonText, HandleGamePathNullOrEmptyCommand);
|
||||
break;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
[Command("HandleConfigurationFileNotFoundCommand")]
|
||||
private async Task HandleConfigurationFileNotFoundAsync()
|
||||
{
|
||||
if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName);
|
||||
string dataFolder = isOversea ? GameConstants.GenshinImpactData : GameConstants.YuanShenData;
|
||||
string persistentScriptVersionFile = Path.Combine(gameFileSystem.GameDirectory, dataFolder, "Persistent", "ScriptVersion");
|
||||
string version = await File.ReadAllTextAsync(persistentScriptVersionFile).ConfigureAwait(false);
|
||||
|
||||
LaunchGameConfigurationFixDialog dialog = await contentDialogFactory
|
||||
.CreateInstanceAsync<LaunchGameConfigurationFixDialog>()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea);
|
||||
dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly);
|
||||
(bool isOk, LaunchScheme launchScheme) = await dialog.GetLaunchSchemeAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
string gameBiz = launchScheme.IsOversea ? "hk4e_global" : "hk4e_cn";
|
||||
string content = $"""
|
||||
[General]
|
||||
channel={launchScheme.Channel:D}
|
||||
cps=mihoyo
|
||||
game_version={version}
|
||||
sub_channel={launchScheme.SubChannel:D}
|
||||
sdk_version=
|
||||
game_biz={gameBiz}
|
||||
""";
|
||||
|
||||
await File.WriteAllTextAsync(gameFileSystem.GameConfigFilePath, content).ConfigureAwait(false);
|
||||
infoBarService.Success(SH.ViewModelLaunchGameFixConfigurationFileSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("HandleGamePathNullOrEmptyCommand")]
|
||||
private void HandleGamePathNullOrEmpty()
|
||||
{
|
||||
navigationService.Navigate<LaunchGamePage>(INavigationAwaiter.Default);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
private readonly LaunchStatusOptions launchStatusOptions;
|
||||
private readonly IGameLocatorFactory gameLocatorFactory;
|
||||
private readonly ILogger<LaunchGameViewModel> logger;
|
||||
private readonly LaunchGameShared launchGameShared;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly ResourceClient resourceClient;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
@@ -59,6 +60,8 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
private GamePathEntry? selectedGamePathEntry;
|
||||
private GameAccountFilter? gameAccountFilter;
|
||||
|
||||
LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; }
|
||||
|
||||
public LaunchOptions LaunchOptions { get => launchOptions; }
|
||||
|
||||
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
|
||||
@@ -82,7 +85,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
|
||||
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
|
||||
|
||||
[AlsoAsyncSets(nameof(SelectedScheme), nameof(GameAccountsView))]
|
||||
public bool GamePathSelectedAndValid
|
||||
{
|
||||
get => gamePathSelectedAndValid;
|
||||
@@ -99,7 +101,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
{
|
||||
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
|
||||
{
|
||||
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
|
||||
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
await SetSelectedSchemeAsync(scheme).ConfigureAwait(true);
|
||||
@@ -148,7 +150,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
|
||||
public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); }
|
||||
|
||||
[AlsoSets(nameof(GamePathSelectedAndValid))]
|
||||
public GamePathEntry? SelectedGamePathEntry
|
||||
{
|
||||
get => selectedGamePathEntry;
|
||||
@@ -180,6 +181,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
[Command("IdentifyMonitorsCommand")]
|
||||
private static async Task IdentifyMonitorsAsync()
|
||||
{
|
||||
await IdentifyMonitorWindow.IdentifyAllMonitorsAsync(3);
|
||||
}
|
||||
|
||||
[Command("SetGamePathCommand")]
|
||||
private async Task SetGamePathAsync()
|
||||
{
|
||||
@@ -209,21 +216,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
[Command("LaunchCommand")]
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
LaunchExecutionContext context = new(Ioc.Default, this, SelectedScheme, SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
await this.LaunchExecutionAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("DetectGameAccountCommand")]
|
||||
@@ -244,7 +237,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
SelectedGameAccount = account;
|
||||
}
|
||||
}
|
||||
catch (UserdataCorruptedException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
@@ -253,47 +246,54 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
[Command("AttachGameAccountCommand")]
|
||||
private void AttachGameAccountToCurrentUserGameRole(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
if (userService.Current?.SelectedUserGameRole is { } role)
|
||||
{
|
||||
gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (userService.Current?.SelectedUserGameRole is { } role)
|
||||
{
|
||||
gameService.AttachGameAccountToUid(gameAccount, role.GameUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ModifyGameAccountCommand")]
|
||||
private async Task ModifyGameAccountAsync(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await gameService.ModifyGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("RemoveGameAccountCommand")]
|
||||
private async Task RemoveGameAccountAsync(GameAccount? gameAccount)
|
||||
{
|
||||
if (gameAccount is not null)
|
||||
if (gameAccount is null)
|
||||
{
|
||||
await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await gameService.RemoveGameAccountAsync(gameAccount).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("OpenScreenshotFolderCommand")]
|
||||
private async Task OpenScreenshotFolderAsync()
|
||||
{
|
||||
string game = LaunchOptions.GamePath;
|
||||
string? directory = Path.GetDirectoryName(game);
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
string screenshot = Path.Combine(directory, "ScreenShot");
|
||||
if (Directory.Exists(screenshot))
|
||||
if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
{
|
||||
await Windows.System.Launcher.LaunchFolderPathAsync(screenshot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(gameFileSystem.ScreenShotDirectory))
|
||||
{
|
||||
await Windows.System.Launcher.LaunchFolderPathAsync(gameFileSystem.ScreenShotDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,12 +301,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
{
|
||||
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;
|
||||
|
||||
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
|
||||
UpdateGameResourceAsync(value).SafeForget();
|
||||
}
|
||||
|
||||
async ValueTask UpdateGameResourceAsync(LaunchScheme? scheme)
|
||||
@@ -340,28 +340,4 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Command("IdentifyMonitorsCommand")]
|
||||
private async Task IdentifyMonitorsAsync()
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1));
|
||||
}
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
await Delay.FromSeconds(3).ConfigureAwait(true);
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Snap.Hutao.ViewModel.Game;
|
||||
internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSlim<View.Page.LaunchGamePage>, IViewModelSupportLaunchExecution
|
||||
{
|
||||
private readonly LaunchStatusOptions launchStatusOptions;
|
||||
private readonly ILogger<LaunchGameViewModelSlim> logger;
|
||||
private readonly LaunchGameShared launchGameShared;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly IGameServiceFacade gameService;
|
||||
private readonly ITaskContext taskContext;
|
||||
@@ -31,6 +31,8 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
private GameAccount? selectedGameAccount;
|
||||
private GameAccountFilter? gameAccountFilter;
|
||||
|
||||
LaunchGameShared IViewModelSupportLaunchExecution.Shared { get => launchGameShared; }
|
||||
|
||||
public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; }
|
||||
|
||||
public AdvancedCollectionView<GameAccount>? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); }
|
||||
@@ -40,14 +42,10 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
/// </summary>
|
||||
public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); }
|
||||
|
||||
public void SetGamePathEntriesAndSelectedGamePathEntry(ImmutableList<GamePathEntry> gamePathEntries, GamePathEntry? selectedEntry)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
{
|
||||
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
|
||||
LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile();
|
||||
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
|
||||
|
||||
try
|
||||
@@ -58,7 +56,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
SelectedGameAccount ??= gameService.DetectCurrentGameAccount(scheme);
|
||||
}
|
||||
}
|
||||
catch (UserdataCorruptedException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
@@ -75,23 +73,6 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
|
||||
[Command("LaunchCommand")]
|
||||
private async Task LaunchAsync()
|
||||
{
|
||||
IInfoBarService infoBarService = ServiceProvider.GetRequiredService<IInfoBarService>();
|
||||
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
|
||||
|
||||
try
|
||||
{
|
||||
LaunchExecutionContext context = new(Ioc.Default, this, scheme, SelectedGameAccount);
|
||||
LaunchExecutionResult result = await new LaunchExecutionInvoker().InvokeAsync(context).ConfigureAwait(false);
|
||||
|
||||
if (result.Kind is not LaunchExecutionResultKind.Ok)
|
||||
{
|
||||
infoBarService.Warning(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Launch failed");
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
await this.LaunchExecutionAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user