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