mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
fix developer hint visibility
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ desktop.ini
|
|||||||
*.pubxml
|
*.pubxml
|
||||||
|
|
||||||
.vs/
|
.vs/
|
||||||
|
src/Snap.Hutao/_ReSharper.Caches
|
||||||
|
|
||||||
src/Snap.Hutao/Snap.Hutao/bin/
|
src/Snap.Hutao/Snap.Hutao/bin/
|
||||||
src/Snap.Hutao/Snap.Hutao/obj/
|
src/Snap.Hutao/Snap.Hutao/obj/
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ internal sealed partial class AutoHeightBehavior : BehaviorBase<FrameworkElement
|
|||||||
|
|
||||||
private void UpdateElement()
|
private void UpdateElement()
|
||||||
{
|
{
|
||||||
AssociatedObject.Height = (double)AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
|
AssociatedObject.Height = AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,6 @@ internal sealed partial class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
|||||||
|
|
||||||
private void UpdateElement()
|
private void UpdateElement()
|
||||||
{
|
{
|
||||||
AssociatedObject.Width = (double)AssociatedObject.Height * (TargetWidth / TargetHeight);
|
AssociatedObject.Width = AssociatedObject.Height * (TargetWidth / TargetHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ internal sealed class CachedImage : ImageEx
|
|||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// BitmapImage initialize with a uri will increase image quality and loading speed.
|
// BitmapImage initialize with a uri will increase image quality and loading speed.
|
||||||
return new BitmapImage(new(file));
|
return new BitmapImage(file.ToUri());
|
||||||
}
|
}
|
||||||
catch (COMException)
|
catch (COMException)
|
||||||
{
|
{
|
||||||
@@ -54,9 +54,5 @@ internal sealed class CachedImage : ImageEx
|
|||||||
// task was explicitly canceled
|
// task was explicitly canceled
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的单色图像
|
/// 构造一个新的单色图像
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CompositionImage()
|
protected CompositionImage()
|
||||||
{
|
{
|
||||||
serviceProvider = Ioc.Default;
|
serviceProvider = Ioc.Default;
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
|||||||
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
|
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
|
||||||
{
|
{
|
||||||
TaskCompletionSource loadCompleteTaskSource = new();
|
TaskCompletionSource loadCompleteTaskSource = new();
|
||||||
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(new(file));
|
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
|
||||||
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
|
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
|
||||||
await loadCompleteTaskSource.Task.ConfigureAwait(true);
|
await loadCompleteTaskSource.Task.ConfigureAwait(true);
|
||||||
return surface;
|
return surface;
|
||||||
|
|||||||
@@ -20,21 +20,23 @@ internal sealed partial class Gradient : CompositionImage
|
|||||||
private double imageAspectRatio;
|
private double imageAspectRatio;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void OnUpdateVisual(SpriteVisual spriteVisual)
|
protected override void OnUpdateVisual(SpriteVisual? spriteVisual)
|
||||||
{
|
{
|
||||||
if (spriteVisual is not null)
|
if (spriteVisual is null)
|
||||||
{
|
{
|
||||||
Height = Math.Clamp(ActualWidth / imageAspectRatio, 0D, MaxHeight);
|
return;
|
||||||
spriteVisual.Size = ActualSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Height = Math.Clamp(ActualWidth / imageAspectRatio, 0D, MaxHeight);
|
||||||
|
spriteVisual.Size = ActualSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
|
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
|
||||||
{
|
{
|
||||||
TaskCompletionSource loadCompleteTaskSource = new();
|
TaskCompletionSource loadCompleteTaskSource = new();
|
||||||
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(new(file));
|
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
|
||||||
surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult();
|
surface.LoadCompleted += (_, _) => loadCompleteTaskSource.TrySetResult();
|
||||||
await loadCompleteTaskSource.Task.ConfigureAwait(true);
|
await loadCompleteTaskSource.Task.ConfigureAwait(true);
|
||||||
imageAspectRatio = surface.NaturalSize.AspectRatio();
|
imageAspectRatio = surface.NaturalSize.AspectRatio();
|
||||||
return surface;
|
return surface;
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ namespace Snap.Hutao.Control.Image;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 渐变锚点
|
/// 渐变锚点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Offset">便宜</param>
|
|
||||||
/// <param name="Color">颜色</param>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal readonly struct GradientStop
|
internal readonly struct GradientStop
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ internal sealed class MonoChrome : CompositionImage
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface)
|
protected override SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface)
|
||||||
{
|
{
|
||||||
CompositionColorBrush blackLayerBursh = compositor.CreateColorBrush(Colors.Black);
|
CompositionColorBrush blackLayerBrush = compositor.CreateColorBrush(Colors.Black);
|
||||||
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f);
|
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f);
|
||||||
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBursh, imageSurfaceBrush, BlendEffectMode.Overlay);
|
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBrush, imageSurfaceBrush, BlendEffectMode.Overlay);
|
||||||
CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush);
|
CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush);
|
||||||
|
|
||||||
backgroundBrush = compositor.CreateColorBrush();
|
backgroundBrush = compositor.CreateColorBrush();
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ internal struct Rgba32
|
|||||||
/// 构造一个新的 RGBA8 颜色
|
/// 构造一个新的 RGBA8 颜色
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hex">色值字符串</param>
|
/// <param name="hex">色值字符串</param>
|
||||||
public unsafe Rgba32(string hex)
|
public Rgba32(string hex)
|
||||||
: this(Convert.ToUInt32(hex, 16))
|
: this(Convert.ToUInt32(hex, 16))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.Panel;
|
namespace Snap.Hutao.Control.Panel;
|
||||||
@@ -35,7 +34,7 @@ internal sealed partial class AspectRatio : Microsoft.UI.Xaml.Controls.Control
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更高
|
// 更高
|
||||||
else if (ratioAvailable < ratio)
|
if (ratioAvailable < ratio)
|
||||||
{
|
{
|
||||||
double newHeight = availableSize.Width / ratio;
|
double newHeight = availableSize.Width / ratio;
|
||||||
return new Size(availableSize.Width, newHeight);
|
return new Size(availableSize.Width, newHeight);
|
||||||
|
|||||||
@@ -47,19 +47,6 @@ internal static class Property<TOwner>
|
|||||||
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
|
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注册依赖属性
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TProperty">属性的类型</typeparam>
|
|
||||||
/// <param name="name">属性名称</param>
|
|
||||||
/// <param name="defaultValue">封装的默认值</param>
|
|
||||||
/// <param name="callback">属性更改回调</param>
|
|
||||||
/// <returns>注册的依赖属性</returns>
|
|
||||||
public static DependencyProperty DependBoxed<TProperty>(string name, object defaultValue, Action<DependencyObject, DependencyPropertyChangedEventArgs> callback)
|
|
||||||
{
|
|
||||||
return DependencyProperty.Register(name, typeof(TProperty), typeof(TOwner), new(defaultValue, new(callback)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册依赖属性
|
/// 注册依赖属性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ internal class ScopedPage : Page
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="extra">额外内容</param>
|
/// <param name="extra">额外内容</param>
|
||||||
/// <returns>任务</returns>
|
/// <returns>任务</returns>
|
||||||
public async Task NotifyRecipentAsync(INavigationData extra)
|
public async Task NotifyRecipientAsync(INavigationData extra)
|
||||||
{
|
{
|
||||||
if (extra.Data != null && DataContext is INavigationRecipient recipient)
|
if (extra.Data != null && DataContext is INavigationRecipient recipient)
|
||||||
{
|
{
|
||||||
@@ -100,7 +100,7 @@ internal class ScopedPage : Page
|
|||||||
{
|
{
|
||||||
if (e.Parameter is INavigationData extra)
|
if (e.Parameter is INavigationData extra)
|
||||||
{
|
{
|
||||||
NotifyRecipentAsync(extra).SafeForget();
|
NotifyRecipientAsync(extra).SafeForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ internal sealed class DescriptionTextBlock : ContentControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// color tag
|
// color tag
|
||||||
else if (description.Slice(i, 2).SequenceEqual("<c"))
|
else if (description.Slice(i, 2) is "<c")
|
||||||
{
|
{
|
||||||
AppendText(text, description[last..i]);
|
AppendText(text, description[last..i]);
|
||||||
Rgba32 color = new(description.Slice(i + 8, 8).ToString());
|
Rgba32 color = new(description.Slice(i + 8, 8).ToString());
|
||||||
@@ -90,7 +90,7 @@ internal sealed class DescriptionTextBlock : ContentControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// italic
|
// italic
|
||||||
else if (description.Slice(i, 2).SequenceEqual("<i"))
|
else if (description.Slice(i, 2) is "<i")
|
||||||
{
|
{
|
||||||
AppendText(text, description[last..i]);
|
AppendText(text, description[last..i]);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.Abstraction;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TSelf">自身类型</typeparam>
|
/// <typeparam name="TSelf">自身类型</typeparam>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal interface ICloneable<TSelf>
|
internal interface ICloneable<out TSelf>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 克隆
|
/// 克隆
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Abstraction;
|
|||||||
/// <typeparam name="T1">元组的第一个类型</typeparam>
|
/// <typeparam name="T1">元组的第一个类型</typeparam>
|
||||||
/// <typeparam name="T2">元组的第二个类型</typeparam>
|
/// <typeparam name="T2">元组的第二个类型</typeparam>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal interface IDeconstructable<T1, T2>
|
internal interface IDeconstruct<T1, T2>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解构
|
/// 解构
|
||||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Core.Annotation;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 指示此方法为命令的调用方法
|
/// 指示此方法为命令的调用方法
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||||
internal sealed class CommandAttribute : Attribute
|
internal sealed class CommandAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Snap.Hutao.Core.Annotation;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 指示此类自动生成构造器
|
/// 指示此类自动生成构造器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
internal sealed class ConstructorGeneratedAttribute : Attribute
|
internal sealed class ConstructorGeneratedAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ namespace Snap.Hutao.Core.Caching;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 为图像缓存提供抽象
|
/// 为图像缓存提供抽象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">缓存类型</typeparam>
|
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal interface IImageCache : ICastableService
|
internal interface IImageCache : ICastService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the file path containing cached item for given Uri
|
/// Gets the file path containing cached item for given Uri
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
|||||||
/// Initializes a new instance of the <see cref="ImageCache"/> class.
|
/// Initializes a new instance of the <see cref="ImageCache"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serviceProvider">服务提供器</param>
|
/// <param name="serviceProvider">服务提供器</param>
|
||||||
/// <param name="logger">日志器</param>
|
|
||||||
/// <param name="httpClientFactory">http客户端工厂</param>
|
|
||||||
public ImageCache(IServiceProvider serviceProvider)
|
public ImageCache(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
logger = serviceProvider.GetRequiredService<ILogger<ImageCache>>();
|
logger = serviceProvider.GetRequiredService<ILogger<ImageCache>>();
|
||||||
@@ -84,7 +82,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Remove(in ReadOnlySpan<Uri> uriForCachedItems)
|
public void Remove(in ReadOnlySpan<Uri> uriForCachedItems)
|
||||||
{
|
{
|
||||||
if (uriForCachedItems == null || uriForCachedItems.Length <= 0)
|
if (uriForCachedItems.Length <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -112,26 +110,28 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
|||||||
string fileName = GetCacheFileName(uri);
|
string fileName = GetCacheFileName(uri);
|
||||||
string filePath = Path.Combine(GetCacheFolder(), fileName);
|
string filePath = Path.Combine(GetCacheFolder(), fileName);
|
||||||
|
|
||||||
if (!File.Exists(filePath) || new FileInfo(filePath).Length == 0)
|
if (File.Exists(filePath) && new FileInfo(filePath).Length != 0)
|
||||||
{
|
{
|
||||||
TaskCompletionSource taskCompletionSource = new();
|
return filePath;
|
||||||
try
|
}
|
||||||
{
|
|
||||||
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
|
|
||||||
{
|
|
||||||
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
|
||||||
{
|
|
||||||
await task.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
concurrentTasks.TryRemove(fileName, out _);
|
TaskCompletionSource taskCompletionSource = new();
|
||||||
}
|
try
|
||||||
finally
|
{
|
||||||
|
if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task))
|
||||||
{
|
{
|
||||||
taskCompletionSource.TrySetResult();
|
await DownloadFileAsync(uri, filePath).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
else if (concurrentTasks.TryGetValue(fileName, out Task? task))
|
||||||
|
{
|
||||||
|
await task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
concurrentTasks.TryRemove(fileName, out _);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
taskCompletionSource.TrySetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
return filePath;
|
return filePath;
|
||||||
@@ -198,21 +198,24 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (message.StatusCode == HttpStatusCode.NotFound)
|
|
||||||
|
switch (message.StatusCode)
|
||||||
{
|
{
|
||||||
// directly goto https://static.hut.ao
|
case HttpStatusCode.NotFound:
|
||||||
retryCount += 3;
|
// directly goto https://static.hut.ao
|
||||||
}
|
retryCount += 3;
|
||||||
else if (message.StatusCode == HttpStatusCode.TooManyRequests)
|
break;
|
||||||
{
|
case HttpStatusCode.TooManyRequests:
|
||||||
retryCount++;
|
{
|
||||||
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? RetryCountToDelay[retryCount];
|
retryCount++;
|
||||||
logger.LogInformation("Retry {uri} after {delay}.", uri, delay);
|
TimeSpan delay = message.Headers.RetryAfter?.Delta ?? RetryCountToDelay[retryCount];
|
||||||
await Task.Delay(delay).ConfigureAwait(false);
|
logger.LogInformation("Retry {uri} after {delay}.", uri, delay);
|
||||||
}
|
await Task.Delay(delay).ConfigureAwait(false);
|
||||||
else
|
break;
|
||||||
{
|
}
|
||||||
return;
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,13 +228,15 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
|||||||
|
|
||||||
private string GetCacheFolder()
|
private string GetCacheFolder()
|
||||||
{
|
{
|
||||||
if (cacheFolder == null)
|
if (cacheFolder is not null)
|
||||||
{
|
{
|
||||||
baseFolder ??= serviceProvider.GetRequiredService<HutaoOptions>().LocalCache;
|
return cacheFolder!;
|
||||||
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
|
|
||||||
cacheFolder = info.FullName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseFolder ??= serviceProvider.GetRequiredService<HutaoOptions>().LocalCache;
|
||||||
|
DirectoryInfo info = Directory.CreateDirectory(Path.Combine(baseFolder, CacheFolderName));
|
||||||
|
cacheFolder = info.FullName;
|
||||||
|
|
||||||
return cacheFolder!;
|
return cacheFolder!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,11 +58,13 @@ internal sealed class CommandLineBuilder
|
|||||||
s.Append(WhiteSpace);
|
s.Append(WhiteSpace);
|
||||||
s.Append(key);
|
s.Append(key);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
s.Append(WhiteSpace);
|
continue;
|
||||||
s.Append(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.Append(WhiteSpace);
|
||||||
|
s.Append(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.ToString();
|
return s.ToString();
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 可转换类型服务
|
/// 可转换类型服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface ICastableService
|
internal interface ICastService
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -4,12 +4,12 @@
|
|||||||
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
namespace Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 海外服/Hoyolab 可区分
|
/// 海外服/HoYoLAB 可区分
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IOverseaSupport
|
internal interface IOverseaSupport
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否为 海外服/Hoyolab
|
/// 是否为 海外服/HoYoLAB
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsOversea { get; }
|
public bool IsOversea { get; }
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
|||||||
/// 由源生成器生成注入代码
|
/// 由源生成器生成注入代码
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
internal sealed class HttpClientAttribute : Attribute
|
internal sealed class HttpClientAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
|||||||
/// 配置首选Http消息处理器特性
|
/// 配置首选Http消息处理器特性
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||||
internal sealed class PrimaryHttpMessageHandlerAttribute : Attribute
|
internal sealed class PrimaryHttpMessageHandlerAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.MaxConnectionsPerServer"/>
|
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.MaxConnectionsPerServer"/>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.DependencyInjection;
|
|||||||
/// 对象扩展
|
/// 对象扩展
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal static class CastableServiceExtension
|
internal static class CastServiceExtension
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see langword="as"/> 的链式调用扩展
|
/// <see langword="as"/> 的链式调用扩展
|
||||||
@@ -17,7 +17,7 @@ internal static class CastableServiceExtension
|
|||||||
/// <typeparam name="T">目标转换类型</typeparam>
|
/// <typeparam name="T">目标转换类型</typeparam>
|
||||||
/// <param name="service">对象</param>
|
/// <param name="service">对象</param>
|
||||||
/// <returns>转换类型后的对象</returns>
|
/// <returns>转换类型后的对象</returns>
|
||||||
public static T? As<T>(this ICastableService service)
|
public static T? As<T>(this ICastService service)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
return service as T;
|
return service as T;
|
||||||
@@ -48,7 +48,7 @@ internal static class DependencyInjection
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static IServiceProvider InitializeCulture(this IServiceProvider serviceProvider)
|
private static void InitializeCulture(this IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
|
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
|
||||||
appOptions.PreviousCulture = CultureInfo.CurrentCulture;
|
appOptions.PreviousCulture = CultureInfo.CurrentCulture;
|
||||||
@@ -58,7 +58,5 @@ internal static class DependencyInjection
|
|||||||
CultureInfo.CurrentCulture = cultureInfo;
|
CultureInfo.CurrentCulture = cultureInfo;
|
||||||
CultureInfo.CurrentUICulture = cultureInfo;
|
CultureInfo.CurrentUICulture = cultureInfo;
|
||||||
ApplicationLanguages.PrimaryLanguageOverride = cultureInfo.Name;
|
ApplicationLanguages.PrimaryLanguageOverride = cultureInfo.Name;
|
||||||
|
|
||||||
return serviceProvider;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@ internal static partial class IocHttpClientConfiguration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 默认配置
|
/// 默认配置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="serviceProvider">服务提供器</param>
|
||||||
/// <param name="client">配置后的客户端</param>
|
/// <param name="client">配置后的客户端</param>
|
||||||
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
|
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
|
||||||
{
|
{
|
||||||
@@ -68,7 +69,7 @@ internal static partial class IocHttpClientConfiguration
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 对于需要添加动态密钥的客户端使用此配置
|
/// 对于需要添加动态密钥的客户端使用此配置
|
||||||
/// Hoyolab app
|
/// HoYoLAB app
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client">配置后的客户端</param>
|
/// <param name="client">配置后的客户端</param>
|
||||||
private static void XRpc3Configuration(HttpClient client)
|
private static void XRpc3Configuration(HttpClient client)
|
||||||
@@ -84,7 +85,7 @@ internal static partial class IocHttpClientConfiguration
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 对于需要添加动态密钥的客户端使用此配置
|
/// 对于需要添加动态密钥的客户端使用此配置
|
||||||
/// Hoyolab web
|
/// HoYoLAB web
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client">配置后的客户端</param>
|
/// <param name="client">配置后的客户端</param>
|
||||||
private static void XRpc4Configuration(HttpClient client)
|
private static void XRpc4Configuration(HttpClient client)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
namespace Snap.Hutao.Core.Diagnostics;
|
||||||
|
|
||||||
|
internal readonly struct MeasureExecutionToken : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ValueStopwatch stopwatch;
|
||||||
|
private readonly ILogger logger;
|
||||||
|
private readonly string callerName;
|
||||||
|
|
||||||
|
public MeasureExecutionToken(in ValueStopwatch stopwatch, ILogger logger, string callerName)
|
||||||
|
{
|
||||||
|
this.stopwatch = stopwatch;
|
||||||
|
this.logger = logger;
|
||||||
|
this.callerName = callerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
logger.LogInformation("{caller} toke {time} ms.", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,10 +43,10 @@ internal readonly struct ValueStopwatch
|
|||||||
/// <param name="logger">日志器</param>
|
/// <param name="logger">日志器</param>
|
||||||
/// <param name="callerName">调用方法名称</param>
|
/// <param name="callerName">调用方法名称</param>
|
||||||
/// <returns>结束测量</returns>
|
/// <returns>结束测量</returns>
|
||||||
public static IDisposable MeasureExecution(ILogger logger, [CallerMemberName] string callerName = default!)
|
public static MeasureExecutionToken MeasureExecution(ILogger logger, [CallerMemberName] string callerName = default!)
|
||||||
{
|
{
|
||||||
ValueStopwatch stopwatch = StartNew();
|
ValueStopwatch stopwatch = StartNew();
|
||||||
return new MeasureExecutionDisposable(stopwatch, logger, callerName);
|
return new MeasureExecutionToken(stopwatch, logger, callerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -75,25 +75,4 @@ internal readonly struct ValueStopwatch
|
|||||||
{
|
{
|
||||||
return new TimeSpan(GetElapsedTimestamp());
|
return new TimeSpan(GetElapsedTimestamp());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("", "SA1400")]
|
|
||||||
[SuppressMessage("", "SA1600")]
|
|
||||||
file readonly struct MeasureExecutionDisposable : IDisposable
|
|
||||||
{
|
|
||||||
private readonly ValueStopwatch stopwatch;
|
|
||||||
private readonly ILogger logger;
|
|
||||||
private readonly string callerName;
|
|
||||||
|
|
||||||
public MeasureExecutionDisposable(in ValueStopwatch stopwatch, ILogger logger, string callerName)
|
|
||||||
{
|
|
||||||
this.stopwatch = stopwatch;
|
|
||||||
this.logger = logger;
|
|
||||||
this.callerName = callerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
logger.LogInformation("{caller} toke {time} ms.", callerName, stopwatch.GetElapsedTime().TotalMilliseconds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ internal sealed class ExceptionFormat
|
|||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine(SectionSeparator);
|
builder.AppendLine(SectionSeparator);
|
||||||
builder.Append(exception.ToString());
|
builder.Append(exception);
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,17 +21,17 @@ internal sealed partial class ClipboardInterop : IClipboardInterop
|
|||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
DataPackageView view = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
|
DataPackageView view = Clipboard.GetContent();
|
||||||
|
|
||||||
if (view.Contains(StandardDataFormats.Text))
|
if (!view.Contains(StandardDataFormats.Text))
|
||||||
{
|
{
|
||||||
string json = await view.GetTextAsync();
|
return null;
|
||||||
|
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
|
||||||
return JsonSerializer.Deserialize<T>(json, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
string json = await view.GetTextAsync();
|
||||||
|
|
||||||
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
|
return JsonSerializer.Deserialize<T>(json, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -41,8 +41,8 @@ internal sealed partial class ClipboardInterop : IClipboardInterop
|
|||||||
{
|
{
|
||||||
DataPackage content = new() { RequestedOperation = DataPackageOperation.Copy };
|
DataPackage content = new() { RequestedOperation = DataPackageOperation.Copy };
|
||||||
content.SetText(text);
|
content.SetText(text);
|
||||||
Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(content);
|
Clipboard.SetContent(content);
|
||||||
Windows.ApplicationModel.DataTransfer.Clipboard.Flush();
|
Clipboard.Flush();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -59,8 +59,8 @@ internal sealed partial class ClipboardInterop : IClipboardInterop
|
|||||||
RandomAccessStreamReference reference = RandomAccessStreamReference.CreateFromStream(stream);
|
RandomAccessStreamReference reference = RandomAccessStreamReference.CreateFromStream(stream);
|
||||||
DataPackage content = new() { RequestedOperation = DataPackageOperation.Copy };
|
DataPackage content = new() { RequestedOperation = DataPackageOperation.Copy };
|
||||||
content.SetBitmap(reference);
|
content.SetBitmap(reference);
|
||||||
Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(content);
|
Clipboard.SetContent(content);
|
||||||
Windows.ApplicationModel.DataTransfer.Clipboard.Flush();
|
Clipboard.Flush();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -20,23 +20,24 @@ internal static class FileOperation
|
|||||||
/// <returns>是否发生了移动操作</returns>
|
/// <returns>是否发生了移动操作</returns>
|
||||||
public static bool Move(string sourceFileName, string destFileName, bool overwrite)
|
public static bool Move(string sourceFileName, string destFileName, bool overwrite)
|
||||||
{
|
{
|
||||||
if (File.Exists(sourceFileName))
|
if (!File.Exists(sourceFileName))
|
||||||
{
|
{
|
||||||
if (overwrite)
|
return false;
|
||||||
{
|
|
||||||
File.Move(sourceFileName, destFileName, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!File.Exists(destFileName))
|
|
||||||
{
|
|
||||||
File.Move(sourceFileName, destFileName, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
if (overwrite)
|
||||||
|
{
|
||||||
|
File.Move(sourceFileName, destFileName, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(destFileName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Move(sourceFileName, destFileName, false);
|
||||||
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,13 +37,13 @@ internal sealed class IniParameter : IniElement
|
|||||||
/// <returns>是否修改了值</returns>
|
/// <returns>是否修改了值</returns>
|
||||||
public bool Set(string value)
|
public bool Set(string value)
|
||||||
{
|
{
|
||||||
if (Value != value)
|
if (Value == value)
|
||||||
{
|
{
|
||||||
Value = value;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
Value = value;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ internal sealed class IniSection : IniElement
|
|||||||
/// 构造一个新的Ini 节
|
/// 构造一个新的Ini 节
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">名称</param>
|
/// <param name="name">名称</param>
|
||||||
/// <param name="elements">元素</param>
|
|
||||||
public IniSection(string name)
|
public IniSection(string name)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|||||||
@@ -20,28 +20,28 @@ internal static class IniSerializer
|
|||||||
{
|
{
|
||||||
using (StreamReader reader = new(fileStream))
|
using (StreamReader reader = new(fileStream))
|
||||||
{
|
{
|
||||||
while (reader.ReadLine() is string line)
|
while (reader.ReadLine() is { } line)
|
||||||
{
|
{
|
||||||
if (line.Length > 0)
|
if (line.Length <= 0)
|
||||||
{
|
{
|
||||||
if (line[0] == '[')
|
continue;
|
||||||
{
|
|
||||||
yield return new IniSection(line[1..^1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line[0] == ';')
|
|
||||||
{
|
|
||||||
yield return new IniComment(line[1..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.IndexOf('=') > 0)
|
|
||||||
{
|
|
||||||
string[] parameters = line.Split('=', 2);
|
|
||||||
yield return new IniParameter(parameters[0], parameters[1]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
if (line[0] == '[')
|
||||||
|
{
|
||||||
|
yield return new IniSection(line[1..^1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line[0] == ';')
|
||||||
|
{
|
||||||
|
yield return new IniComment(line[1..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.IndexOf('=') > 0)
|
||||||
|
{
|
||||||
|
string[] parameters = line.Split('=', 2);
|
||||||
|
yield return new IniParameter(parameters[0], parameters[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ internal sealed class StreamCopyWorker<TStatus>
|
|||||||
/// <param name="source">源</param>
|
/// <param name="source">源</param>
|
||||||
/// <param name="destination">目标</param>
|
/// <param name="destination">目标</param>
|
||||||
/// <param name="statusFactory">状态工厂</param>
|
/// <param name="statusFactory">状态工厂</param>
|
||||||
/// <param name="totalBytes">总字节</param>
|
|
||||||
/// <param name="bufferSize">字节尺寸</param>
|
/// <param name="bufferSize">字节尺寸</param>
|
||||||
public StreamCopyWorker(Stream source, Stream destination, Func<long, TStatus> statusFactory, int bufferSize = 81920)
|
public StreamCopyWorker(Stream source, Stream destination, Func<long, TStatus> statusFactory, int bufferSize = 81920)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ internal sealed class TempFile : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 路径
|
/// 路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Path { get; private set; }
|
public string Path { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建临时文件并复制内容
|
/// 创建临时文件并复制内容
|
||||||
@@ -57,15 +57,6 @@ internal sealed class TempFile : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 作为写入模式打开
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>文件流</returns>
|
|
||||||
public FileStream OpenWrite()
|
|
||||||
{
|
|
||||||
return File.OpenWrite(Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 删除临时文件
|
/// 删除临时文件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Core.Json.Annotation;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Json 枚举类型
|
/// Json 枚举类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
internal class JsonEnumAttribute : Attribute
|
internal class JsonEnumAttribute : Attribute
|
||||||
{
|
{
|
||||||
private static readonly Type UnsafeEnumConverterType = typeof(UnsafeEnumConverter<>);
|
private static readonly Type UnsafeEnumConverterType = typeof(UnsafeEnumConverter<>);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (reader.GetString() is string dataTimeString)
|
if (reader.GetString() is { } dataTimeString)
|
||||||
{
|
{
|
||||||
return DateTimeOffset.Parse(dataTimeString);
|
return DateTimeOffset.Parse(dataTimeString);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEn
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IEnumerable<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override IEnumerable<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (reader.GetString() is string source)
|
if (reader.GetString() is { } source)
|
||||||
{
|
{
|
||||||
return EnumerateNumbers(source);
|
return EnumerateNumbers(source);
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEn
|
|||||||
|
|
||||||
private static IEnumerable<int> EnumerateNumbers(string source)
|
private static IEnumerable<int> EnumerateNumbers(string source)
|
||||||
{
|
{
|
||||||
foreach (StringSegment id in new StringTokenizer(source, Comma.Enumerate().ToArray()))
|
foreach (StringSegment id in new StringTokenizer(source, new[] { Comma }))
|
||||||
{
|
{
|
||||||
yield return int.Parse(id.AsSpan());
|
yield return int.Parse(id.AsSpan());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
|||||||
return GetEnum(ref reader, enumTypeCode);
|
return GetEnum(ref reader, enumTypeCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.GetString() is string str)
|
if (reader.GetString() is { } str)
|
||||||
{
|
{
|
||||||
return Enum.Parse<TEnum>(str);
|
return Enum.Parse<TEnum>(str);
|
||||||
}
|
}
|
||||||
@@ -116,6 +116,8 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
throw new JsonException();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new JsonException();
|
throw new JsonException();
|
||||||
|
|||||||
@@ -27,16 +27,20 @@ internal static class JsonTypeInfoResolvers
|
|||||||
|
|
||||||
foreach (JsonPropertyInfo property in typeInfo.Properties)
|
foreach (JsonPropertyInfo property in typeInfo.Properties)
|
||||||
{
|
{
|
||||||
if (property.PropertyType.IsEnum)
|
if (!property.PropertyType.IsEnum)
|
||||||
{
|
{
|
||||||
if (property.AttributeProvider is System.Reflection.ICustomAttributeProvider provider)
|
continue;
|
||||||
{
|
}
|
||||||
object[] attributes = provider.GetCustomAttributes(JsonEnumAttributeType, false);
|
|
||||||
if (attributes.SingleOrDefault() is JsonEnumAttribute attr)
|
if (property.AttributeProvider is not { } provider)
|
||||||
{
|
{
|
||||||
property.CustomConverter = attr.CreateConverter(property);
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
object[] attributes = provider.GetCustomAttributes(JsonEnumAttributeType, false);
|
||||||
|
if (attributes.SingleOrDefault() is JsonEnumAttribute attr)
|
||||||
|
{
|
||||||
|
property.CustomConverter = attr.CreateConverter(property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ internal static class AppActivationArgumentsExtensions
|
|||||||
public static bool TryGetProtocolActivatedUri(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out Uri? uri)
|
public static bool TryGetProtocolActivatedUri(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out Uri? uri)
|
||||||
{
|
{
|
||||||
uri = null;
|
uri = null;
|
||||||
if (activatedEventArgs.Data is IProtocolActivatedEventArgs protocolArgs)
|
if (activatedEventArgs.Data is not IProtocolActivatedEventArgs protocolArgs)
|
||||||
{
|
{
|
||||||
uri = protocolArgs.Uri;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
uri = protocolArgs.Uri;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,12 +39,12 @@ internal static class AppActivationArgumentsExtensions
|
|||||||
public static bool TryGetLaunchActivatedArgument(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
|
public static bool TryGetLaunchActivatedArgument(this AppActivationArguments activatedEventArgs, [NotNullWhen(true)] out string? arguments)
|
||||||
{
|
{
|
||||||
arguments = null;
|
arguments = null;
|
||||||
if (activatedEventArgs.Data is ILaunchActivatedEventArgs launchArgs)
|
if (activatedEventArgs.Data is not ILaunchActivatedEventArgs launchArgs)
|
||||||
{
|
{
|
||||||
arguments = launchArgs.Arguments.Trim();
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
arguments = launchArgs.Arguments.Trim();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace Snap.Hutao.Core.LifeCycle;
|
|||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal static class AppInstanceExtension
|
internal static class AppInstanceExtension
|
||||||
{
|
{
|
||||||
private static readonly WaitCallback RunActionWaitCallback = new(RunAction);
|
private static readonly WaitCallback RunActionWaitCallback = RunAction;
|
||||||
|
|
||||||
// Hold the reference here to prevent memory corruption.
|
// Hold the reference here to prevent memory corruption.
|
||||||
private static HANDLE redirectEventHandle = HANDLE.Null;
|
private static HANDLE redirectEventHandle = HANDLE.Null;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ internal sealed class Feature : ObservableObject
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
LocalSetting.Set(settingKey, value);
|
LocalSetting.Set(settingKey, value);
|
||||||
OnPropertyChanged(nameof(Value));
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ internal sealed class FeatureOptions : IReadOnlyCollection<Feature>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启用实时便笺无感验证
|
/// 启用实时便笺无感验证
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Feature IsDailyNoteSlientVerificationEnabled { get; } = new("IsDailyNoteSlientVerificationEnabled", "启用实时便笺无感验证", "IsDailyNoteSlientVerificationEnabled", true);
|
public Feature IsDailyNoteSilentVerificationEnabled { get; } = new("IsDailyNoteSilentVerificationEnabled", "启用实时便笺无感验证", "IsDailyNoteSilentVerificationEnabled", true);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int Count { get => 1; }
|
public int Count { get => 1; }
|
||||||
@@ -22,7 +22,7 @@ internal sealed class FeatureOptions : IReadOnlyCollection<Feature>
|
|||||||
public IEnumerator<Feature> GetEnumerator()
|
public IEnumerator<Feature> GetEnumerator()
|
||||||
{
|
{
|
||||||
// TODO: Use source generator
|
// TODO: Use source generator
|
||||||
yield return IsDailyNoteSlientVerificationEnabled;
|
yield return IsDailyNoteSilentVerificationEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ internal static class StaticResource
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 完成所有合约
|
/// 取消完成所有合约
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void UnfulfillAllContracts()
|
public static void FailAllContracts()
|
||||||
{
|
{
|
||||||
SetContractsState(false);
|
SetContractsState(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ internal sealed class JumpListInterop : IJumpListInterop
|
|||||||
list.Items.Clear();
|
list.Items.Clear();
|
||||||
|
|
||||||
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, SH.CoreJumpListHelperLaunchGameItemDisplayName);
|
JumpListItem launchGameItem = JumpListItem.CreateWithArguments(Activation.LaunchGame, SH.CoreJumpListHelperLaunchGameItemDisplayName);
|
||||||
launchGameItem.Logo = new("ms-appx:///Resource/Icon/UI_GuideIcon_PlayMethod.png");
|
launchGameItem.Logo = "ms-appx:///Resource/Icon/UI_GuideIcon_PlayMethod.png".ToUri();
|
||||||
|
|
||||||
list.Items.Add(launchGameItem);
|
list.Items.Add(launchGameItem);
|
||||||
|
|
||||||
|
|||||||
@@ -68,13 +68,15 @@ internal sealed class ScheduleTaskInterop : IScheduleTaskInterop
|
|||||||
string tempFolder = ApplicationData.Current.TemporaryFolder.Path;
|
string tempFolder = ApplicationData.Current.TemporaryFolder.Path;
|
||||||
string fullName = Path.Combine(tempFolder, "Script", $"{name}.vbs");
|
string fullName = Path.Combine(tempFolder, "Script", $"{name}.vbs");
|
||||||
|
|
||||||
if (!File.Exists(fullName) || forceCreate)
|
if (File.Exists(fullName) && !forceCreate)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.Combine(tempFolder, "Script"));
|
return fullName;
|
||||||
string script = $"""CreateObject("WScript.Shell").Run "cmd /c start {url}", 0, False""";
|
|
||||||
File.WriteAllText(fullName, script);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.Combine(tempFolder, "Script"));
|
||||||
|
string script = $"""CreateObject("WScript.Shell").Run "cmd /c start {url}", 0, False""";
|
||||||
|
File.WriteAllText(fullName, script);
|
||||||
|
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ internal class ConcurrentCancellationTokenSource
|
|||||||
/// 注册取消令牌
|
/// 注册取消令牌
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>取消令牌</returns>
|
/// <returns>取消令牌</returns>
|
||||||
public CancellationToken CancelPreviousOne()
|
public CancellationToken Register()
|
||||||
{
|
{
|
||||||
source.Cancel();
|
source.Cancel();
|
||||||
source = new();
|
source = new();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ internal static class SemaphoreSlimExtension
|
|||||||
/// <param name="semaphoreSlim">信号量</param>
|
/// <param name="semaphoreSlim">信号量</param>
|
||||||
/// <param name="token">取消令牌</param>
|
/// <param name="token">取消令牌</param>
|
||||||
/// <returns>可释放的对象,用于释放信号量</returns>
|
/// <returns>可释放的对象,用于释放信号量</returns>
|
||||||
public static async ValueTask<IDisposable> EnterAsync(this SemaphoreSlim semaphoreSlim, CancellationToken token = default)
|
public static async ValueTask<SemaphoreSlimToken> EnterAsync(this SemaphoreSlim semaphoreSlim, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -27,24 +27,6 @@ internal static class SemaphoreSlimExtension
|
|||||||
ThrowHelper.OperationCanceled(SH.CoreThreadingSemaphoreSlimDisposed, ex);
|
ThrowHelper.OperationCanceled(SH.CoreThreadingSemaphoreSlimDisposed, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SemaphoreSlimReleaser(semaphoreSlim);
|
return new SemaphoreSlimToken(semaphoreSlim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("", "SA1201")]
|
|
||||||
[SuppressMessage("", "SA1400")]
|
|
||||||
[SuppressMessage("", "SA1600")]
|
|
||||||
file readonly struct SemaphoreSlimReleaser : IDisposable
|
|
||||||
{
|
|
||||||
private readonly SemaphoreSlim semaphoreSlim;
|
|
||||||
|
|
||||||
public SemaphoreSlimReleaser(SemaphoreSlim semaphoreSlim)
|
|
||||||
{
|
|
||||||
this.semaphoreSlim = semaphoreSlim;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
semaphoreSlim.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Snap.Hutao.Core.Threading;
|
||||||
|
|
||||||
|
[SuppressMessage("", "SA1201")]
|
||||||
|
internal readonly struct SemaphoreSlimToken : IDisposable
|
||||||
|
{
|
||||||
|
private readonly SemaphoreSlim semaphoreSlim;
|
||||||
|
|
||||||
|
public SemaphoreSlimToken(SemaphoreSlim semaphoreSlim)
|
||||||
|
{
|
||||||
|
this.semaphoreSlim = semaphoreSlim;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
semaphoreSlim.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.Threading;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TResult">结果类型</typeparam>
|
/// <typeparam name="TResult">结果类型</typeparam>
|
||||||
/// <typeparam name="TValue">值类型</typeparam>
|
/// <typeparam name="TValue">值类型</typeparam>
|
||||||
internal readonly struct ValueResult<TResult, TValue> : IDeconstructable<TResult, TValue>
|
internal readonly struct ValueResult<TResult, TValue> : IDeconstruct<TResult, TValue>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否成功
|
/// 是否成功
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
|||||||
{
|
{
|
||||||
private readonly TWindow window;
|
private readonly TWindow window;
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
private readonly WindowSubclass<TWindow> subclass;
|
private readonly WindowSubclass<TWindow>? subclass;
|
||||||
|
|
||||||
private ExtendedWindow(TWindow window, IServiceProvider serviceProvider)
|
private ExtendedWindow(TWindow window, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
@@ -79,7 +79,7 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
|||||||
UpdateSystemBackdrop(appOptions.BackdropType);
|
UpdateSystemBackdrop(appOptions.BackdropType);
|
||||||
appOptions.PropertyChanged += OnOptionsPropertyChanged;
|
appOptions.PropertyChanged += OnOptionsPropertyChanged;
|
||||||
|
|
||||||
bool subClassApplied = subclass.Initialize();
|
subclass!.Initialize();
|
||||||
|
|
||||||
serviceProvider.GetRequiredService<IMessenger>().Register(this);
|
serviceProvider.GetRequiredService<IMessenger>().Register(this);
|
||||||
|
|
||||||
@@ -122,8 +122,8 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
|
|||||||
|
|
||||||
UpdateTitleButtonColor();
|
UpdateTitleButtonColor();
|
||||||
UpdateDragRectangles();
|
UpdateDragRectangles();
|
||||||
options.TitleBar.ActualThemeChanged += (s, e) => UpdateTitleButtonColor();
|
options.TitleBar.ActualThemeChanged += (_, _) => UpdateTitleButtonColor();
|
||||||
options.TitleBar.SizeChanged += (s, e) => UpdateDragRectangles();
|
options.TitleBar.SizeChanged += (_, _) => UpdateDragRectangles();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ namespace Snap.Hutao.Core.Windowing;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 为扩展窗体提供必要的选项
|
/// 为扩展窗体提供必要的选项
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
|
||||||
internal interface IWindowOptionsSource
|
internal interface IWindowOptionsSource
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -23,20 +23,20 @@ internal static class Persistence
|
|||||||
/// 设置窗体位置
|
/// 设置窗体位置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
/// <typeparam name="TWindow">窗体类型</typeparam>
|
||||||
/// <param name="window">选项窗口param>
|
/// <param name="window">选项窗口</param>
|
||||||
public static void RecoverOrInit<TWindow>(TWindow window)
|
public static void RecoverOrInit<TWindow>(TWindow window)
|
||||||
where TWindow : Window, IWindowOptionsSource
|
where TWindow : Window, IWindowOptionsSource
|
||||||
{
|
{
|
||||||
WindowOptions options = window.WindowOptions;
|
WindowOptions options = window.WindowOptions;
|
||||||
|
|
||||||
// Set first launch size.
|
// Set first launch size
|
||||||
double scale = GetScaleForWindowHandle(options.Hwnd);
|
double scale = GetScaleForWindowHandle(options.Hwnd);
|
||||||
SizeInt32 transformedSize = options.InitSize.Scale(scale);
|
SizeInt32 transformedSize = options.InitSize.Scale(scale);
|
||||||
RectInt32 rect = StructMarshal.RectInt32(transformedSize);
|
RectInt32 rect = StructMarshal.RectInt32(transformedSize);
|
||||||
|
|
||||||
if (options.PersistSize)
|
if (options.PersistSize)
|
||||||
{
|
{
|
||||||
RectInt32 persistedRect = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (ulong)(CompactRect)rect);
|
RectInt32 persistedRect = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (CompactRect)rect);
|
||||||
if (persistedRect.Size() >= options.InitSize.Size())
|
if (persistedRect.Size() >= options.InitSize.Size())
|
||||||
{
|
{
|
||||||
rect = persistedRect;
|
rect = persistedRect;
|
||||||
@@ -108,24 +108,24 @@ internal static class Persistence
|
|||||||
rect.Y = workAreaRect.Y + ((workAreaRect.Height - rect.Height) / 2);
|
rect.Y = workAreaRect.Y + ((workAreaRect.Height - rect.Height) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct CompactRect
|
private readonly struct CompactRect
|
||||||
{
|
{
|
||||||
public short X;
|
private readonly short x;
|
||||||
public short Y;
|
private readonly short y;
|
||||||
public short Width;
|
private readonly short width;
|
||||||
public short Height;
|
private readonly short height;
|
||||||
|
|
||||||
private CompactRect(int x, int y, int width, int height)
|
private CompactRect(int x, int y, int width, int height)
|
||||||
{
|
{
|
||||||
X = (short)x;
|
this.x = (short)x;
|
||||||
Y = (short)y;
|
this.y = (short)y;
|
||||||
Width = (short)width;
|
this.width = (short)width;
|
||||||
Height = (short)height;
|
this.height = (short)height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator RectInt32(CompactRect rect)
|
public static implicit operator RectInt32(CompactRect rect)
|
||||||
{
|
{
|
||||||
return new(rect.X, rect.Y, rect.Width, rect.Height);
|
return new(rect.x, rect.y, rect.width, rect.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static explicit operator CompactRect(RectInt32 rect)
|
public static explicit operator CompactRect(RectInt32 rect)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ namespace Snap.Hutao.Core.Windowing;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Window 选项
|
/// Window 选项
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TWindow">窗体类型</typeparam>
|
|
||||||
internal readonly struct WindowOptions
|
internal readonly struct WindowOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -43,24 +43,28 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
|
|||||||
{
|
{
|
||||||
WindowOptions options = window.WindowOptions;
|
WindowOptions options = window.WindowOptions;
|
||||||
|
|
||||||
windowProc = new(OnSubclassProcedure);
|
windowProc = OnSubclassProcedure;
|
||||||
bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0);
|
bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0);
|
||||||
|
|
||||||
bool titleBarHooked = true;
|
bool titleBarHooked = true;
|
||||||
|
|
||||||
// only hook up drag bar proc when use legacy Window.ExtendsContentIntoTitleBar
|
// only hook up drag bar proc when use legacy Window.ExtendsContentIntoTitleBar
|
||||||
if (options.UseLegacyDragBarImplementation)
|
if (!options.UseLegacyDragBarImplementation)
|
||||||
{
|
{
|
||||||
titleBarHooked = false;
|
return windowHooked && titleBarHooked;
|
||||||
HWND hwndDragBar = FindWindowEx(options.Hwnd, default, "DRAG_BAR_WINDOW_CLASS", default);
|
|
||||||
|
|
||||||
if (!hwndDragBar.IsNull)
|
|
||||||
{
|
|
||||||
dragBarProc = new(OnDragBarProcedure);
|
|
||||||
titleBarHooked = SetWindowSubclass(hwndDragBar, dragBarProc, DragBarSubclassId, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
titleBarHooked = false;
|
||||||
|
HWND hwndDragBar = FindWindowEx(options.Hwnd, default, "DRAG_BAR_WINDOW_CLASS", default);
|
||||||
|
|
||||||
|
if (hwndDragBar.IsNull)
|
||||||
|
{
|
||||||
|
return windowHooked && titleBarHooked;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragBarProc = OnDragBarProcedure;
|
||||||
|
titleBarHooked = SetWindowSubclass(hwndDragBar, dragBarProc, DragBarSubclassId, 0);
|
||||||
|
|
||||||
return windowHooked && titleBarHooked;
|
return windowHooked && titleBarHooked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,11 +76,13 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
|
|||||||
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
|
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
|
||||||
windowProc = null;
|
windowProc = null;
|
||||||
|
|
||||||
if (options.UseLegacyDragBarImplementation)
|
if (!options.UseLegacyDragBarImplementation)
|
||||||
{
|
{
|
||||||
RemoveWindowSubclass(options.Hwnd, dragBarProc, DragBarSubclassId);
|
return;
|
||||||
dragBarProc = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RemoveWindowSubclass(options.Hwnd, dragBarProc, DragBarSubclassId);
|
||||||
|
dragBarProc = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("", "SH002")]
|
[SuppressMessage("", "SH002")]
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ internal static class DateTimeOffsetExtension
|
|||||||
/// <returns>转换的时间</returns>
|
/// <returns>转换的时间</returns>
|
||||||
public static DateTimeOffset FromUnixTime(long? timestamp, in DateTimeOffset defaultValue)
|
public static DateTimeOffset FromUnixTime(long? timestamp, in DateTimeOffset defaultValue)
|
||||||
{
|
{
|
||||||
if (timestamp is long value)
|
if (timestamp is { } value)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,7 +47,19 @@ internal static partial class EnumerableExtension
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static TSource? FirstOrFirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
|
public static TSource? FirstOrFirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
|
||||||
{
|
{
|
||||||
return source.FirstOrDefault(predicate) ?? source.FirstOrDefault();
|
TSource? first = default;
|
||||||
|
|
||||||
|
foreach (TSource element in source)
|
||||||
|
{
|
||||||
|
first ??= element;
|
||||||
|
|
||||||
|
if (predicate(element))
|
||||||
|
{
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -68,19 +80,11 @@ internal static partial class EnumerableExtension
|
|||||||
/// <typeparam name="T">Type of array elements.</typeparam>
|
/// <typeparam name="T">Type of array elements.</typeparam>
|
||||||
/// <param name="collection">Collection to convert. Cannot be <see langword="null"/>.</param>
|
/// <param name="collection">Collection to convert. Cannot be <see langword="null"/>.</param>
|
||||||
/// <param name="separator">Delimiter between elements in the final string.</param>
|
/// <param name="separator">Delimiter between elements in the final string.</param>
|
||||||
/// <param name="defaultValue">A string to be returned if collection has no elements.</param>
|
|
||||||
/// <returns>Converted collection into string.</returns>
|
/// <returns>Converted collection into string.</returns>
|
||||||
public static string ToString<T>(this IEnumerable<T> collection, string separator, string defaultValue = "")
|
public static string ToString<T>(this IEnumerable<T> collection, char separator = ',')
|
||||||
{
|
{
|
||||||
string result = string.Join(separator, collection);
|
string result = string.Join(separator, collection);
|
||||||
if (result.Length > 0)
|
return result.Length > 0 ? result : string.Empty;
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -94,13 +98,6 @@ internal static partial class EnumerableExtension
|
|||||||
public static string ToString<T>(this IEnumerable<T> collection, char separator = ',', string defaultValue = "")
|
public static string ToString<T>(this IEnumerable<T> collection, char separator = ',', string defaultValue = "")
|
||||||
{
|
{
|
||||||
string result = string.Join(separator, collection);
|
string result = string.Join(separator, collection);
|
||||||
if (result.Length > 0)
|
return result.Length > 0 ? result : defaultValue;
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,12 +19,12 @@ internal static class MemoryCacheExtension
|
|||||||
/// <returns>是否移除成功</returns>
|
/// <returns>是否移除成功</returns>
|
||||||
public static bool TryRemove(this IMemoryCache memoryCache, string key, out object? value)
|
public static bool TryRemove(this IMemoryCache memoryCache, string key, out object? value)
|
||||||
{
|
{
|
||||||
if (memoryCache.TryGetValue(key, out value))
|
if (!memoryCache.TryGetValue(key, out value))
|
||||||
{
|
{
|
||||||
memoryCache.Remove(key);
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
memoryCache.Remove(key);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Snap.Hutao.Extension;
|
namespace Snap.Hutao.Extension;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -14,8 +16,9 @@ internal static class ObjectExtension
|
|||||||
/// <typeparam name="T">数据类型</typeparam>
|
/// <typeparam name="T">数据类型</typeparam>
|
||||||
/// <param name="source">源</param>
|
/// <param name="source">源</param>
|
||||||
/// <returns>数组</returns>
|
/// <returns>数组</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static T[] ToArray<T>(this T source)
|
public static T[] ToArray<T>(this T source)
|
||||||
{
|
{
|
||||||
return new T[] { source };
|
return new[] { source };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,13 +20,6 @@ internal interface IPickerFactory
|
|||||||
/// <returns>经过初始化的 <see cref="FileOpenPicker"/></returns>
|
/// <returns>经过初始化的 <see cref="FileOpenPicker"/></returns>
|
||||||
FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes);
|
FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取 经过初始化的 <see cref="FileSavePicker"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>经过初始化的 <see cref="FileSavePicker"/></returns>
|
|
||||||
[Obsolete]
|
|
||||||
FileSavePicker GetFileSavePicker();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取 经过初始化的 <see cref="FileSavePicker"/>
|
/// 获取 经过初始化的 <see cref="FileSavePicker"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -49,12 +49,6 @@ internal class PickerFactory : IPickerFactory
|
|||||||
return picker;
|
return picker;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public FileSavePicker GetFileSavePicker()
|
|
||||||
{
|
|
||||||
return GetInitializedPicker<FileSavePicker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public FileSavePicker GetFileSavePicker(PickerLocationId location, string fileName, string commitButton, IDictionary<string, IList<string>> fileTypes)
|
public FileSavePicker GetFileSavePicker(PickerLocationId location, string fileName, string commitButton, IDictionary<string, IList<string>> fileTypes)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Snap.Hutao.Core.Windowing;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Message;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 背景类型改变消息
|
|
||||||
/// </summary>
|
|
||||||
[HighQuality]
|
|
||||||
internal sealed class BackdropTypeChangedMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的背景类型改变消息
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="backdropType">背景类型</param>
|
|
||||||
public BackdropTypeChangedMessage(BackdropType backdropType)
|
|
||||||
{
|
|
||||||
BackdropType = backdropType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 背景类型
|
|
||||||
/// </summary>
|
|
||||||
public BackdropType BackdropType { get; set; }
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@ internal abstract class ValueChangedMessage<TValue>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="oldValue">旧值</param>
|
/// <param name="oldValue">旧值</param>
|
||||||
/// <param name="newValue">新值</param>
|
/// <param name="newValue">新值</param>
|
||||||
public ValueChangedMessage(TValue? oldValue, TValue? newValue)
|
protected ValueChangedMessage(TValue? oldValue, TValue? newValue)
|
||||||
{
|
{
|
||||||
OldValue = oldValue;
|
OldValue = oldValue;
|
||||||
NewValue = newValue;
|
NewValue = newValue;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ internal sealed class InventoryReliquaryConfiguration : IEntityTypeConfiguration
|
|||||||
builder.Property(e => e.AppendPropIdList)
|
builder.Property(e => e.AppendPropIdList)
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasConversion(
|
.HasConversion(
|
||||||
list => string.Join(',', list),
|
list => list.ToString(','),
|
||||||
text => text.Split(',', StringSplitOptions.None).Select(x => int.Parse(x)).ToList());
|
text => text.Split(',', StringSplitOptions.None).Select(int.Parse).ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ internal enum SchemeType
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 国服官服
|
/// 国服官服
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Officical,
|
Official,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 渠道服
|
/// 渠道服
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ internal sealed class User : ISelectable
|
|||||||
/// <returns>新创建的用户</returns>
|
/// <returns>新创建的用户</returns>
|
||||||
public static User Create(Cookie cookie, bool isOversea)
|
public static User Create(Cookie cookie, bool isOversea)
|
||||||
{
|
{
|
||||||
_ = cookie.TryGetSToken(isOversea, out Cookie? stoken);
|
_ = cookie.TryGetSToken(isOversea, out Cookie? sToken);
|
||||||
_ = cookie.TryGetLToken(out Cookie? ltoken);
|
_ = cookie.TryGetLToken(out Cookie? lToken);
|
||||||
_ = cookie.TryGetCookieToken(out Cookie? cookieToken);
|
_ = cookie.TryGetCookieToken(out Cookie? cookieToken);
|
||||||
|
|
||||||
return new() { SToken = stoken, LToken = ltoken, CookieToken = cookieToken };
|
return new() { SToken = sToken, LToken = lToken, CookieToken = cookieToken };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ namespace Snap.Hutao.Model.Intrinsic.Immutable;
|
|||||||
/// 本地化的不可变的原生枚举
|
/// 本地化的不可变的原生枚举
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal static class IntrinsicImmutables
|
internal static class IntrinsicImmutable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 所属地区
|
/// 所属地区
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Core.Json.Annotation;
|
|
||||||
using Snap.Hutao.Model.Intrinsic;
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
using Snap.Hutao.Model.Primitive;
|
using Snap.Hutao.Model.Primitive;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ internal sealed class AvatarBaseValue : BaseValue
|
|||||||
public PropertyCurveValue GetPropertyCurveValue(FightProperty fightProperty)
|
public PropertyCurveValue GetPropertyCurveValue(FightProperty fightProperty)
|
||||||
{
|
{
|
||||||
// TODO: impl
|
// TODO: impl
|
||||||
return null;
|
return default!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,14 +72,9 @@ internal sealed class FetterInfo
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ConstellationAfter))
|
return string.IsNullOrEmpty(ConstellationAfter)
|
||||||
{
|
? ConstellationBefore
|
||||||
return ConstellationBefore;
|
: ConstellationAfter;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return ConstellationAfter;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,9 @@ internal sealed class AvatarCardConverter : ValueConverter<string, Uri>
|
|||||||
/// <returns>链接</returns>
|
/// <returns>链接</returns>
|
||||||
public static Uri IconNameToUri(string name)
|
public static Uri IconNameToUri(string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(name))
|
return string.IsNullOrEmpty(name)
|
||||||
{
|
? UIAvatarIconCostumeCard
|
||||||
return UIAvatarIconCostumeCard;
|
: Web.HutaoEndpoints.StaticFile("AvatarCard", $"{name}_Card.png").ToUri();
|
||||||
}
|
|
||||||
|
|
||||||
return Web.HutaoEndpoints.StaticFile("AvatarCard", $"{name}_Card.png").ToUri();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ internal static class FightPropertyFormat
|
|||||||
{
|
{
|
||||||
return method switch
|
return method switch
|
||||||
{
|
{
|
||||||
FormatMethod.Integer => $"{Math.Round((double)value, MidpointRounding.AwayFromZero)}",
|
FormatMethod.Integer => $"{MathF.Round(value, MidpointRounding.AwayFromZero)}",
|
||||||
FormatMethod.Percent => string.Format("{0:P1}", value),
|
FormatMethod.Percent => $"{value:P1}",
|
||||||
_ => value.ToString(),
|
_ => value.ToString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,9 @@ internal sealed class ItemIconConverter : ValueConverter<string, Uri>
|
|||||||
/// <returns>链接</returns>
|
/// <returns>链接</returns>
|
||||||
public static Uri IconNameToUri(string name)
|
public static Uri IconNameToUri(string name)
|
||||||
{
|
{
|
||||||
if (name.StartsWith("UI_RelicIcon_"))
|
return name.StartsWith("UI_RelicIcon_")
|
||||||
{
|
? RelicIconConverter.IconNameToUri(name)
|
||||||
return RelicIconConverter.IconNameToUri(name);
|
: Web.HutaoEndpoints.StaticFile("ItemIcon", $"{name}.png").ToUri();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Web.HutaoEndpoints.StaticFile("ItemIcon", $"{name}.png").ToUri();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -23,14 +23,9 @@ internal sealed class SkillIconConverter : ValueConverter<string, Uri>
|
|||||||
return Web.HutaoEndpoints.UIIconNone;
|
return Web.HutaoEndpoints.UIIconNone;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.StartsWith("UI_Talent_"))
|
return name.StartsWith("UI_Talent_")
|
||||||
{
|
? Web.HutaoEndpoints.StaticFile("Talent", $"{name}.png").ToUri()
|
||||||
return Web.HutaoEndpoints.StaticFile("Talent", $"{name}.png").ToUri();
|
: Web.HutaoEndpoints.StaticFile("Skill", $"{name}.png").ToUri();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Web.HutaoEndpoints.StaticFile("Skill", $"{name}.png").ToUri();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using System.Text.Json.Serialization.Metadata;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Model.Primitive.Converter;
|
namespace Snap.Hutao.Model.Primitive.Converter;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Id 转换器
|
/// Id 转换器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TWrapper">包装类型</typeparam>
|
/// <typeparam name="TWrapper">包装类型</typeparam>
|
||||||
internal unsafe sealed class IdentityConverter<TWrapper> : JsonConverter<TWrapper>
|
internal sealed unsafe class IdentityConverter<TWrapper> : JsonConverter<TWrapper>
|
||||||
where TWrapper : unmanaged
|
where TWrapper : unmanaged
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -22,14 +20,14 @@ internal unsafe sealed class IdentityConverter<TWrapper> : JsonConverter<TWrappe
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override TWrapper ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override TWrapper ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
if (reader.TokenType is not JsonTokenType.PropertyName)
|
||||||
{
|
{
|
||||||
string? value = reader.GetString();
|
throw new JsonException();
|
||||||
_ = uint.TryParse(value,out uint result);
|
|
||||||
return *(TWrapper*)&result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new JsonException();
|
string? value = reader.GetString();
|
||||||
|
_ = uint.TryParse(value, out uint result);
|
||||||
|
return *(TWrapper*)&result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ internal struct GachaLogFetchContext
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 为下一个物品页面重置
|
/// 为下一个物品页面重置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configType">卡池类型</param>
|
|
||||||
public void ResetForProcessingPage()
|
public void ResetForProcessingPage()
|
||||||
{
|
{
|
||||||
FetchStatus = new(CurrentType);
|
FetchStatus = new(CurrentType);
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ internal readonly struct GachaLogServiceContext
|
|||||||
uint place = id.Place();
|
uint place = id.Place();
|
||||||
return place switch
|
return place switch
|
||||||
{
|
{
|
||||||
8U => IdAvatarMap![id],
|
8U => IdAvatarMap[id],
|
||||||
5U => IdWeaponMap![id],
|
5U => IdWeaponMap[id],
|
||||||
_ => throw Must.NeverHappen($"Id places: {place}"),
|
_ => throw Must.NeverHappen($"Id places: {place}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ internal sealed partial class GachaLogQueryWebCacheProvider : IGachaLogQueryProv
|
|||||||
? GameConstants.GenshinImpactData
|
? GameConstants.GenshinImpactData
|
||||||
: GameConstants.YuanShenData;
|
: GameConstants.YuanShenData;
|
||||||
|
|
||||||
return Path.Combine(Path.GetDirectoryName(path)!, dataFolder, @"webCaches\Cache\Cache_Data\data_2");
|
// TODO: make sure how the cache file located.
|
||||||
|
return Path.Combine(Path.GetDirectoryName(path)!, dataFolder, @"webCaches\2.13.0.1\Cache\Cache_Data\data_2");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ internal enum RefreshOption
|
|||||||
WebCache,
|
WebCache,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通过Stoken刷新
|
/// 通过SToken刷新
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SToken,
|
SToken,
|
||||||
|
|
||||||
|
|||||||
@@ -29,14 +29,11 @@ namespace Snap.Hutao.Service.Game;
|
|||||||
[Injection(InjectAs.Singleton, typeof(IGameService))]
|
[Injection(InjectAs.Singleton, typeof(IGameService))]
|
||||||
internal sealed partial class GameService : IGameService
|
internal sealed partial class GameService : IGameService
|
||||||
{
|
{
|
||||||
private const string GamePathKey = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}";
|
|
||||||
|
|
||||||
private readonly PackageConverter packageConverter;
|
private readonly PackageConverter packageConverter;
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
private readonly LaunchOptions launchOptions;
|
private readonly LaunchOptions launchOptions;
|
||||||
private readonly HutaoOptions hutaoOptions;
|
private readonly HutaoOptions hutaoOptions;
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
private readonly IMemoryCache memoryCache;
|
|
||||||
private readonly AppOptions appOptions;
|
private readonly AppOptions appOptions;
|
||||||
|
|
||||||
private volatile int runningGamesCounter;
|
private volatile int runningGamesCounter;
|
||||||
@@ -277,12 +274,12 @@ internal sealed partial class GameService : IGameService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool isfirstInstance = Interlocked.Increment(ref runningGamesCounter) == 1;
|
bool isFirstInstance = Interlocked.Increment(ref runningGamesCounter) == 1;
|
||||||
|
|
||||||
game.Start();
|
game.Start();
|
||||||
|
|
||||||
bool isAdvancedOptionsAllowed = hutaoOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled;
|
bool isAdvancedOptionsAllowed = hutaoOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled;
|
||||||
if (isAdvancedOptionsAllowed && launchOptions.MultipleInstances && !isfirstInstance)
|
if (isAdvancedOptionsAllowed && launchOptions.MultipleInstances && !isFirstInstance)
|
||||||
{
|
{
|
||||||
ProcessInterop.DisableProtection(game, gamePath);
|
ProcessInterop.DisableProtection(game, gamePath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,14 +146,15 @@ internal sealed class LaunchOptions : DbStoreOptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 目标帧率
|
/// 目标帧率
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[AllowNull]
|
||||||
public NameValue<int> Monitor
|
public NameValue<int> Monitor
|
||||||
{
|
{
|
||||||
get => GetOption(ref monitor, SettingEntry.LaunchMonitor, index => Monitors[int.Parse(index) - 1], Monitors[0]);
|
get => GetOption(ref monitor, SettingEntry.LaunchMonitor, index => Monitors[int.Parse(index) - 1], Monitors[0]);
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value != null)
|
if (value is not null)
|
||||||
{
|
{
|
||||||
SetOption(ref monitor, SettingEntry.LaunchMonitor, value, selected => selected.Value.ToString() ?? "1");
|
SetOption(ref monitor, SettingEntry.LaunchMonitor, value, selected => selected.Value.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,16 +27,16 @@ internal sealed partial class UnityLogGameLocator : IGameLocator
|
|||||||
|
|
||||||
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
string logFilePathChinese = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt");
|
string logFilePathChinese = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt");
|
||||||
string logFilePathOvsesea = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\Genshin Impact\output_log.txt");
|
string logFilePathOversea = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\Genshin Impact\output_log.txt");
|
||||||
|
|
||||||
// Fallback to the CN server.
|
// Fallback to the CN server.
|
||||||
string logFilePathFinal = File.Exists(logFilePathOvsesea) ? logFilePathOvsesea : logFilePathChinese;
|
string logFilePathFinal = File.Exists(logFilePathOversea) ? logFilePathOversea : logFilePathChinese;
|
||||||
|
|
||||||
using (TempFile? tempFile = TempFile.CopyFrom(logFilePathFinal))
|
using (TempFile? tempFile = TempFile.CopyFrom(logFilePathFinal))
|
||||||
{
|
{
|
||||||
if (tempFile != null)
|
if (tempFile != null)
|
||||||
{
|
{
|
||||||
string content = File.ReadAllText(tempFile.Path);
|
string content = await File.ReadAllTextAsync(tempFile.Path).ConfigureAwait(false);
|
||||||
|
|
||||||
Match matchResult = WarmupFileLine().Match(content);
|
Match matchResult = WarmupFileLine().Match(content);
|
||||||
if (!matchResult.Success)
|
if (!matchResult.Success)
|
||||||
|
|||||||
@@ -322,11 +322,13 @@ internal sealed partial class PackageConverter
|
|||||||
{
|
{
|
||||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is string raw)
|
while (await reader.ReadLineAsync().ConfigureAwait(false) is string raw)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(raw))
|
if (string.IsNullOrEmpty(raw))
|
||||||
{
|
{
|
||||||
VersionItem item = JsonSerializer.Deserialize<VersionItem>(raw, options)!;
|
continue;
|
||||||
results.Add(item.RemoteName, item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VersionItem item = JsonSerializer.Deserialize<VersionItem>(raw, options)!;
|
||||||
|
results.Add(item.RemoteName, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,25 +343,27 @@ internal sealed partial class PackageConverter
|
|||||||
{
|
{
|
||||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is string raw)
|
while (await reader.ReadLineAsync().ConfigureAwait(false) is string raw)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(raw))
|
if (string.IsNullOrEmpty(raw))
|
||||||
{
|
{
|
||||||
VersionItem item = JsonSerializer.Deserialize<VersionItem>(raw, options)!;
|
continue;
|
||||||
|
|
||||||
string remoteName = item.RemoteName;
|
|
||||||
|
|
||||||
// 我们已经提前重命名了整个 Data 文件夹 所以需要将 RemoteName 中的 Data 同样替换
|
|
||||||
if (remoteName.StartsWith(YuanShenData) || remoteName.StartsWith(GenshinImpactData))
|
|
||||||
{
|
|
||||||
remoteName = direction switch
|
|
||||||
{
|
|
||||||
ConvertDirection.OverseaToChinese => $"{YuanShenData}{remoteName[GenshinImpactData.Length..]}",
|
|
||||||
ConvertDirection.ChineseToOversea => $"{GenshinImpactData}{remoteName[YuanShenData.Length..]}",
|
|
||||||
_ => remoteName,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
results.Add(remoteName, item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VersionItem item = JsonSerializer.Deserialize<VersionItem>(raw, options)!;
|
||||||
|
|
||||||
|
string remoteName = item.RemoteName;
|
||||||
|
|
||||||
|
// 我们已经提前重命名了整个 Data 文件夹 所以需要将 RemoteName 中的 Data 同样替换
|
||||||
|
if (remoteName.StartsWith(YuanShenData) || remoteName.StartsWith(GenshinImpactData))
|
||||||
|
{
|
||||||
|
remoteName = direction switch
|
||||||
|
{
|
||||||
|
ConvertDirection.OverseaToChinese => $"{YuanShenData}{remoteName[GenshinImpactData.Length..]}",
|
||||||
|
ConvertDirection.ChineseToOversea => $"{GenshinImpactData}{remoteName[YuanShenData.Length..]}",
|
||||||
|
_ => remoteName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
results.Add(remoteName, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ internal static class RegistryInterop
|
|||||||
{
|
{
|
||||||
string paths = Environment.GetEnvironmentVariable("Path")!;
|
string paths = Environment.GetEnvironmentVariable("Path")!;
|
||||||
|
|
||||||
foreach (StringSegment path in new StringTokenizer(paths, ';'.Enumerate().ToArray()))
|
foreach (StringSegment path in new StringTokenizer(paths, ';'.ToArray()))
|
||||||
{
|
{
|
||||||
if (path.HasValue && path.Length > 0)
|
if (path is { HasValue: true, Length: > 0 })
|
||||||
{
|
{
|
||||||
if (path.Value.Contains("WindowsPowerShell", StringComparison.OrdinalIgnoreCase))
|
if (path.Value.Contains("WindowsPowerShell", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
|||||||
{
|
{
|
||||||
private readonly Process gameProcess;
|
private readonly Process gameProcess;
|
||||||
private readonly LaunchOptions launchOptions;
|
private readonly LaunchOptions launchOptions;
|
||||||
private readonly ILogger<GameFpsUnlocker> logger;
|
|
||||||
private readonly UnlockerStatus status = new();
|
private readonly UnlockerStatus status = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -37,7 +36,6 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
|||||||
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess)
|
public GameFpsUnlocker(IServiceProvider serviceProvider, Process gameProcess)
|
||||||
{
|
{
|
||||||
launchOptions = serviceProvider.GetRequiredService<LaunchOptions>();
|
launchOptions = serviceProvider.GetRequiredService<LaunchOptions>();
|
||||||
logger = serviceProvider.GetRequiredService<ILogger<GameFpsUnlocker>>();
|
|
||||||
this.gameProcess = gameProcess;
|
this.gameProcess = gameProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,14 +63,14 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
|||||||
|
|
||||||
memory = new VirtualMemory(unityPlayer.Size + userAssembly.Size);
|
memory = new VirtualMemory(unityPlayer.Size + userAssembly.Size);
|
||||||
byte* lpBuffer = (byte*)memory.Pointer;
|
byte* lpBuffer = (byte*)memory.Pointer;
|
||||||
return ReadProcessMemory((HANDLE)process.Handle, (void*)unityPlayer.Address, lpBuffer, unityPlayer.Size, default)
|
return ReadProcessMemory((HANDLE)process.Handle, (void*)unityPlayer.Address, lpBuffer, unityPlayer.Size)
|
||||||
&& ReadProcessMemory((HANDLE)process.Handle, (void*)userAssembly.Address, lpBuffer + unityPlayer.Size, userAssembly.Size, default);
|
&& ReadProcessMemory((HANDLE)process.Handle, (void*)userAssembly.Address, lpBuffer + unityPlayer.Size, userAssembly.Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe bool UnsafeReadProcessMemory(Process process, nuint baseAddress, out nuint value)
|
private static unsafe bool UnsafeReadProcessMemory(Process process, nuint baseAddress, out nuint value)
|
||||||
{
|
{
|
||||||
ulong temp = 0;
|
ulong temp = 0;
|
||||||
bool result = ReadProcessMemory((HANDLE)process.Handle, (void*)baseAddress, (byte*)&temp, 8, default);
|
bool result = ReadProcessMemory((HANDLE)process.Handle, (void*)baseAddress, (byte*)&temp, 8);
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
ThrowHelper.InvalidOperation(SH.ServiceGameUnlockerReadProcessMemoryPointerAddressFailed, null);
|
ThrowHelper.InvalidOperation(SH.ServiceGameUnlockerReadProcessMemoryPointerAddressFailed, null);
|
||||||
@@ -84,13 +82,13 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
|||||||
|
|
||||||
private static unsafe bool UnsafeWriteProcessMemory(Process process, nuint baseAddress, int value)
|
private static unsafe bool UnsafeWriteProcessMemory(Process process, nuint baseAddress, int value)
|
||||||
{
|
{
|
||||||
return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, &value, sizeof(int), default);
|
return WriteProcessMemory((HANDLE)process.Handle, (void*)baseAddress, &value, sizeof(int));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe FindModuleResult UnsafeTryFindModule(in HANDLE hProcess, in ReadOnlySpan<char> moduleName, out Module module)
|
private static unsafe FindModuleResult UnsafeTryFindModule(in HANDLE hProcess, in ReadOnlySpan<char> moduleName, out Module module)
|
||||||
{
|
{
|
||||||
HMODULE[] buffer = new HMODULE[128];
|
HMODULE[] buffer = new HMODULE[128];
|
||||||
uint actualSize = 0;
|
uint actualSize;
|
||||||
fixed (HMODULE* pBuffer = buffer)
|
fixed (HMODULE* pBuffer = buffer)
|
||||||
{
|
{
|
||||||
if (!K32EnumProcessModules(hProcess, pBuffer, unchecked((uint)(buffer.Length * sizeof(HMODULE))), out actualSize))
|
if (!K32EnumProcessModules(hProcess, pBuffer, unchecked((uint)(buffer.Length * sizeof(HMODULE))), out actualSize))
|
||||||
@@ -158,7 +156,7 @@ internal sealed class GameFpsUnlocker : IGameFpsUnlocker
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out GameModule info)
|
private static FindModuleResult UnsafeGetGameModuleInfo(in HANDLE hProcess, out GameModule info)
|
||||||
{
|
{
|
||||||
FindModuleResult unityPlayerResult = UnsafeTryFindModule(hProcess, "UnityPlayer.dll", out Module unityPlayer);
|
FindModuleResult unityPlayerResult = UnsafeTryFindModule(hProcess, "UnityPlayer.dll", out Module unityPlayer);
|
||||||
FindModuleResult userAssemblyResult = UnsafeTryFindModule(hProcess, "UserAssembly.dll", out Module userAssembly);
|
FindModuleResult userAssemblyResult = UnsafeTryFindModule(hProcess, "UserAssembly.dll", out Module userAssembly);
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ internal sealed partial class HutaoService : IHutaoService
|
|||||||
{
|
{
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
|
||||||
if (appDbContext.ObjectCache.SingleOrDefault(e => e.Key == key) is ObjectCacheEntry entry)
|
if (appDbContext.ObjectCache.SingleOrDefault(e => e.Key == key) is { } entry)
|
||||||
{
|
{
|
||||||
if (entry.IsExpired)
|
if (entry.IsExpired)
|
||||||
{
|
{
|
||||||
@@ -105,14 +105,14 @@ internal sealed partial class HutaoService : IHutaoService
|
|||||||
{
|
{
|
||||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
|
||||||
appDbContext.ObjectCache.AddAndSave(new()
|
await appDbContext.ObjectCache.AddAndSaveAsync(new()
|
||||||
{
|
{
|
||||||
Key = key,
|
Key = key,
|
||||||
|
|
||||||
// We hold the cache for 4 hours
|
// We hold the cache for 4 hours
|
||||||
ExpireTime = DateTimeOffset.Now.Add(CacheExpireTime),
|
ExpireTime = DateTimeOffset.Now.Add(CacheExpireTime),
|
||||||
Value = JsonSerializer.Serialize(data, options),
|
Value = JsonSerializer.Serialize(data, options),
|
||||||
});
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ internal sealed partial class HutaoUserService : IHutaoUserService, IHutaoUserSe
|
|||||||
options.LoginSucceed(userName, response.Data);
|
options.LoginSucceed(userName, response.Data);
|
||||||
|
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
Web.Response.Response<UserInfo> userInfoResponse = await passportClient.GetUserInfoAsync(response.Data).ConfigureAwait(false);
|
Web.Response.Response<UserInfo> userInfoResponse = await passportClient.GetUserInfoAsync(response.Data, token).ConfigureAwait(false);
|
||||||
if (userInfoResponse.IsOk())
|
if (userInfoResponse.IsOk())
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Service.Hutao;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 胡桃用户服务
|
/// 胡桃用户服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal interface IHutaoUserService : ICastableService
|
internal interface IHutaoUserService : ICastService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步初始化
|
/// 异步初始化
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Snap.Hutao.Service.Metadata;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
[SuppressMessage("", "SA1124")]
|
[SuppressMessage("", "SA1124")]
|
||||||
internal interface IMetadataService : ICastableService
|
internal interface IMetadataService : ICastService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步初始化服务,尝试更新元数据
|
/// 异步初始化服务,尝试更新元数据
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Navigation;
|
|||||||
/// 导航服务
|
/// 导航服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[HighQuality]
|
||||||
internal interface INavigationService : ICastableService
|
internal interface INavigationService : ICastService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 导航到指定类型的页面
|
/// 导航到指定类型的页面
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ internal sealed partial class NavigationService : INavigationService, INavigatio
|
|||||||
{
|
{
|
||||||
if (frame!.Content is ScopedPage scopedPage)
|
if (frame!.Content is ScopedPage scopedPage)
|
||||||
{
|
{
|
||||||
await scopedPage.NotifyRecipentAsync((INavigationData)data).ConfigureAwait(false);
|
await scopedPage.NotifyRecipientAsync((INavigationData)data).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,8 @@
|
|||||||
Margin="0,6,0,0"
|
Margin="0,6,0,0"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Text="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudDeveloperHint}"/>
|
Text="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudDeveloperHint}"
|
||||||
|
Visibility="{Binding HutaoCloudViewModel.Options.IsLicensedDeveloper, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ScrollViewer Grid.Row="1" Margin="0,0,0,8">
|
<ScrollViewer Grid.Row="1" Margin="0,0,0,8">
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
|||||||
if (SelectedProject != null)
|
if (SelectedProject != null)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
CancellationToken token = StatisticsCancellationTokenSource.CancelPreviousOne();
|
CancellationToken token = StatisticsCancellationTokenSource.Register();
|
||||||
ObservableCollection<StatisticsCultivateItem> statistics;
|
ObservableCollection<StatisticsCultivateItem> statistics;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
|||||||
[Command("ResetStaticResourceCommand")]
|
[Command("ResetStaticResourceCommand")]
|
||||||
private void ResetStaticResource()
|
private void ResetStaticResource()
|
||||||
{
|
{
|
||||||
StaticResource.UnfulfillAllContracts();
|
StaticResource.FailAllContracts();
|
||||||
AppInstance.Restart(string.Empty);
|
AppInstance.Restart(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,31 +36,31 @@ internal static class AvatarFilter
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntrinsicImmutables.ElementNames.Contains(value))
|
if (IntrinsicImmutable.ElementNames.Contains(value))
|
||||||
{
|
{
|
||||||
matches.Add(avatar.FetterInfo.VisionBefore == value);
|
matches.Add(avatar.FetterInfo.VisionBefore == value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntrinsicImmutables.AssociationTypes.Contains(value))
|
if (IntrinsicImmutable.AssociationTypes.Contains(value))
|
||||||
{
|
{
|
||||||
matches.Add(avatar.FetterInfo.Association.GetLocalizedDescriptionOrDefault() == value);
|
matches.Add(avatar.FetterInfo.Association.GetLocalizedDescriptionOrDefault() == value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntrinsicImmutables.WeaponTypes.Contains(value))
|
if (IntrinsicImmutable.WeaponTypes.Contains(value))
|
||||||
{
|
{
|
||||||
matches.Add(avatar.Weapon.GetLocalizedDescriptionOrDefault() == value);
|
matches.Add(avatar.Weapon.GetLocalizedDescriptionOrDefault() == value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntrinsicImmutables.ItemQualities.Contains(value))
|
if (IntrinsicImmutable.ItemQualities.Contains(value))
|
||||||
{
|
{
|
||||||
matches.Add(avatar.Quality.GetLocalizedDescriptionOrDefault() == value);
|
matches.Add(avatar.Quality.GetLocalizedDescriptionOrDefault() == value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntrinsicImmutables.BodyTypes.Contains(value))
|
if (IntrinsicImmutable.BodyTypes.Contains(value))
|
||||||
{
|
{
|
||||||
matches.Add(avatar.Body.GetLocalizedDescriptionOrDefault() == value);
|
matches.Add(avatar.Body.GetLocalizedDescriptionOrDefault() == value);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -36,19 +36,19 @@ internal static class WeaponFilter
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntrinsicImmutables.WeaponTypes.Contains(value))
|
if (IntrinsicImmutable.WeaponTypes.Contains(value))
|
||||||
{
|
{
|
||||||
matches.Add(weapon.WeaponType.GetLocalizedDescriptionOrDefault() == value);
|
matches.Add(weapon.WeaponType.GetLocalizedDescriptionOrDefault() == value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntrinsicImmutables.ItemQualities.Contains(value))
|
if (IntrinsicImmutable.ItemQualities.Contains(value))
|
||||||
{
|
{
|
||||||
matches.Add(weapon.Quality.GetLocalizedDescriptionOrDefault() == value);
|
matches.Add(weapon.Quality.GetLocalizedDescriptionOrDefault() == value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IntrinsicImmutables.FightProperties.Contains(value))
|
if (IntrinsicImmutable.FightProperties.Contains(value))
|
||||||
{
|
{
|
||||||
matches.Add(weapon.GrowCurves.ElementAtOrDefault(1)?.Type.GetLocalizedDescriptionOrDefault() == value);
|
matches.Add(weapon.GrowCurves.ElementAtOrDefault(1)?.Type.GetLocalizedDescriptionOrDefault() == value);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user