This commit is contained in:
DismissedLight
2024-06-23 17:18:11 +08:00
parent b4f7bf934e
commit b02f2b47c8
25 changed files with 154 additions and 168 deletions

View File

@@ -13,15 +13,22 @@ namespace Snap.Hutao.Core.IO;
internal static class DirectoryOperation
{
public static bool Move(string sourceDirName, string destDirName)
public static bool TryMove(string sourceDirName, string destDirName)
{
if (!Directory.Exists(sourceDirName))
{
return false;
}
FileSystem.MoveDirectory(sourceDirName, destDirName, true);
return true;
try
{
FileSystem.MoveDirectory(sourceDirName, destDirName, true);
return true;
}
catch
{
return false;
}
}
public static unsafe bool UnsafeRename(string path, string name, FILEOPERATION_FLAGS flags = FILEOPERATION_FLAGS.FOF_ALLOWUNDO | FILEOPERATION_FLAGS.FOF_NOCONFIRMMKDIR)

View File

@@ -32,8 +32,15 @@ internal static class FileOperation
if (overwrite)
{
File.Move(sourceFileName, destFileName, true);
return true;
try
{
File.Move(sourceFileName, destFileName, true);
return true;
}
catch
{
return false;
}
}
if (File.Exists(destFileName))
@@ -41,8 +48,15 @@ internal static class FileOperation
return false;
}
File.Move(sourceFileName, destFileName, false);
return true;
try
{
File.Move(sourceFileName, destFileName, false);
return true;
}
catch
{
return false;
}
}
public static bool Delete(string path)

View File

@@ -9,6 +9,7 @@ using System.Net.Http;
namespace Snap.Hutao.Core.IO.Http.Sharding;
// TODO: refactor to use tree structure to calculate shards
internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
{
private const int ShardSize = 4 * 1024 * 1024;

View File

@@ -3,7 +3,15 @@
namespace Snap.Hutao.Core.IO;
/// <summary>
/// 流复制状态
/// </summary>
internal sealed record StreamCopyStatus(long BytesCopied, long TotalBytes);
internal sealed class StreamCopyStatus
{
public StreamCopyStatus(long bytesCopied, long totalBytes)
{
BytesCopied = bytesCopied;
TotalBytes = totalBytes;
}
public long BytesCopied { get; }
public long TotalBytes { get; }
}

View File

@@ -12,15 +12,8 @@ namespace Snap.Hutao.Core.IO;
[HighQuality]
internal readonly struct TempFile : IDisposable
{
/// <summary>
/// 路径
/// </summary>
public readonly string Path;
/// <summary>
/// 构造一个新的临时文件
/// </summary>
/// <param name="delete">是否在创建时删除文件</param>
private TempFile(bool delete)
{
try
@@ -38,11 +31,6 @@ internal readonly struct TempFile : IDisposable
}
}
/// <summary>
/// 创建临时文件并复制内容
/// </summary>
/// <param name="file">源文件</param>
/// <returns>临时文件</returns>
public static TempFile? CopyFrom(string file)
{
TempFile temporaryFile = new(false);
@@ -57,9 +45,6 @@ internal readonly struct TempFile : IDisposable
}
}
/// <summary>
/// 删除临时文件
/// </summary>
public void Dispose()
{
try

View File

@@ -5,76 +5,58 @@ using System.IO;
namespace Snap.Hutao.Core.IO;
/// <summary>
/// 临时文件流
/// </summary>
internal sealed class TempFileStream : Stream
{
private readonly string path;
private readonly FileStream stream;
/// <summary>
/// 构造一个新的临时的文件流
/// </summary>
/// <param name="mode">文件模式</param>
/// <param name="access">访问方式</param>
public TempFileStream(FileMode mode, FileAccess access)
{
path = Path.GetTempFileName();
stream = File.Open(path, mode, access);
}
/// <inheritdoc/>
public override bool CanRead { get => stream.CanRead; }
/// <inheritdoc/>
public override bool CanSeek { get => stream.CanSeek; }
/// <inheritdoc/>
public override bool CanWrite { get => stream.CanWrite; }
/// <inheritdoc/>
public override long Length { get => stream.Length; }
/// <inheritdoc/>
public override long Position { get => stream.Position; set => stream.Position = value; }
/// <inheritdoc/>
public override void Flush()
{
stream.Flush();
}
/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
return stream.Read(buffer, offset, count);
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
return stream.Seek(offset, origin);
}
/// <inheritdoc/>
public override void SetLength(long value)
{
stream.SetLength(value);
}
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
{
stream.Write(buffer, offset, count);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
stream.Dispose();
File.Delete(path);
if (disposing)
{
stream.Dispose();
File.Delete(path);
}
}
}

View File

@@ -5,17 +5,10 @@ using System.IO;
namespace Snap.Hutao.Core.IO;
/// <summary>
/// 文件路径
/// </summary>
internal readonly struct ValueFile
{
private readonly string value;
/// <summary>
/// Initializes a new instance of the <see cref="ValueFile"/> struct.
/// </summary>
/// <param name="value">value</param>
private ValueFile(string value)
{
this.value = value;
@@ -31,55 +24,6 @@ internal readonly struct ValueFile
return new(value);
}
/// <summary>
/// 异步反序列化文件中的内容
/// </summary>
/// <typeparam name="T">内容的类型</typeparam>
/// <param name="options">序列化选项</param>
/// <returns>操作是否成功,反序列化后的内容</returns>
public async ValueTask<ValueResult<bool, T?>> DeserializeFromJsonAsync<T>(JsonSerializerOptions options)
where T : class
{
try
{
using (FileStream stream = File.OpenRead(value))
{
T? t = await JsonSerializer.DeserializeAsync<T>(stream, options).ConfigureAwait(false);
return new(true, t);
}
}
catch (Exception ex)
{
_ = ex;
return new(false, null);
}
}
/// <summary>
/// 将对象异步序列化入文件
/// </summary>
/// <typeparam name="T">对象的类型</typeparam>
/// <param name="obj">对象</param>
/// <param name="options">序列化选项</param>
/// <returns>操作是否成功</returns>
public async ValueTask<bool> SerializeToJsonAsync<T>(T obj, JsonSerializerOptions options)
{
try
{
using (FileStream stream = File.Create(value))
{
await JsonSerializer.SerializeAsync(stream, obj, options).ConfigureAwait(false);
}
return true;
}
catch (Exception)
{
return false;
}
}
/// <inheritdoc/>
[SuppressMessage("", "CA1307")]
public override int GetHashCode()
{

View File

@@ -0,0 +1,44 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
namespace Snap.Hutao.Core.IO;
internal static class ValueFileExtension
{
public static async ValueTask<ValueResult<bool, T?>> DeserializeFromJsonAsync<T>(this ValueFile file, JsonSerializerOptions options)
where T : class
{
try
{
using (FileStream stream = File.OpenRead(file))
{
T? t = await JsonSerializer.DeserializeAsync<T>(stream, options).ConfigureAwait(false);
return new(true, t);
}
}
catch (Exception ex)
{
_ = ex;
return new(false, null);
}
}
public static async ValueTask<bool> SerializeToJsonAsync<T>(this ValueFile file, T obj, JsonSerializerOptions options)
{
try
{
using (FileStream stream = File.Create(file))
{
await JsonSerializer.SerializeAsync(stream, obj, options).ConfigureAwait(false);
}
return true;
}
catch (Exception)
{
return false;
}
}
}

View File

@@ -5,17 +5,12 @@ using System.Globalization;
namespace Snap.Hutao.Core.Json.Converter;
/// <summary>
/// 实现日期的转换
/// 此转换器无法实现无损往返
/// 必须在反序列化后调整 Offset
/// </summary>
/// 此转换器无法实现无损往返 必须在反序列化后调整 Offset
[HighQuality]
internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
private const string Format = "yyyy-MM-dd HH:mm:ss";
/// <inheritdoc/>
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.GetString() is { } dataTimeString)
@@ -29,7 +24,6 @@ internal class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
return default;
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.DateTime.ToString(Format, CultureInfo.InvariantCulture));

View File

@@ -6,15 +6,11 @@ using System.Globalization;
namespace Snap.Hutao.Core.Json.Converter;
/// <summary>
/// 逗号分隔列表转换器
/// </summary>
[HighQuality]
internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEnumerable<int>>
{
private const char Comma = ',';
/// <inheritdoc/>
public override IEnumerable<int> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.GetString() is { } source)
@@ -25,7 +21,6 @@ internal sealed class SeparatorCommaInt32EnumerableConverter : JsonConverter<IEn
return [];
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, IEnumerable<int> value, JsonSerializerOptions options)
{
writer.WriteStringValue(string.Join(Comma, value));

View File

@@ -6,10 +6,6 @@ using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Json.Converter;
/// <summary>
/// 枚举转换器
/// </summary>
/// <typeparam name="TEnum">枚举的类型</typeparam>
[HighQuality]
internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
where TEnum : struct, Enum
@@ -19,18 +15,12 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
private readonly JsonSerializeType readAs;
private readonly JsonSerializeType writeAs;
/// <summary>
/// 构造一个新的枚举转换器
/// </summary>
/// <param name="readAs">读取</param>
/// <param name="writeAs">写入</param>
public UnsafeEnumConverter(JsonSerializeType readAs, JsonSerializeType writeAs)
{
this.readAs = readAs;
this.writeAs = writeAs;
}
/// <inheritdoc/>
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConverTEnum, JsonSerializerOptions options)
{
if (readAs == JsonSerializeType.Number)
@@ -46,13 +36,12 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
throw new JsonException();
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
switch (writeAs)
{
case JsonSerializeType.Number:
WriteEnumValue(writer, value, enumTypeCode);
WriteNumberValue(writer, value, enumTypeCode);
break;
case JsonSerializeType.NumberString:
writer.WriteStringValue(value.ToString("D"));
@@ -123,7 +112,7 @@ internal sealed class UnsafeEnumConverter<TEnum> : JsonConverter<TEnum>
throw new JsonException();
}
private static void WriteEnumValue(Utf8JsonWriter writer, TEnum value, TypeCode typeCode)
private static void WriteNumberValue(Utf8JsonWriter writer, TEnum value, TypeCode typeCode)
{
switch (typeCode)
{

View File

@@ -6,14 +6,8 @@ using System.Text.Json.Serialization.Metadata;
namespace Snap.Hutao.Core.Json;
/// <summary>
/// Json 选项
/// </summary>
internal static class JsonOptions
{
/// <summary>
/// 默认的Json序列化选项
/// </summary>
public static readonly JsonSerializerOptions Default = new()
{
AllowTrailingCommas = true,

View File

@@ -6,18 +6,10 @@ using System.Text.Json.Serialization.Metadata;
namespace Snap.Hutao.Core.Json;
/// <summary>
/// Json 类型信息解析器
/// </summary>
[HighQuality]
internal static class JsonTypeInfoResolvers
{
private static readonly Type JsonEnumAttributeType = typeof(JsonEnumAttribute);
/// <summary>
/// 解析枚举类型
/// </summary>
/// <param name="typeInfo">Json 类型信息</param>
public static void ResolveEnumType(JsonTypeInfo typeInfo)
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)

View File

@@ -18,12 +18,10 @@ using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.ViewModel.Guide;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace Snap.Hutao.Core.LifeCycle;
/// <summary>
/// 激活
/// </summary>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IAppActivation))]
@@ -44,7 +42,6 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
private readonly SemaphoreSlim activateSemaphore = new(1);
/// <inheritdoc/>
public void Activate(HutaoActivationArguments args)
{
HandleActivationExclusiveAsync(args).SafeForget();
@@ -90,7 +87,6 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
HandleAppNotificationActivationAsync(args.Arguments, false).SafeForget();
}
/// <inheritdoc/>
public void PostInitialization()
{
RunPostInitializationAsync().SafeForget();
@@ -210,10 +206,13 @@ internal sealed partial class AppActivation : IAppActivation, IAppActivationActi
await taskContext.SwitchToMainThreadAsync();
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipboard);
await serviceProvider
#pragma warning disable CA1849
// We can't await here to navigate to Achievment Page, the Achievement
// ViewModel requires the Metadata Service to be initialized.
serviceProvider
.GetRequiredService<INavigationService>()
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true)
.ConfigureAwait(false);
.Navigate<View.Page.AchievementPage>(navigationAwaiter, true);
#pragma warning restore CA1849
break;
}
}

View File

@@ -5,6 +5,18 @@ using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.LifeCycle.InterProcess;
// Layout:
// 0 1 2 3 4 Bytes
// ┌─────────┬──────┬─────────┬─────────────┐
// │ Version │ Type │ Command │ ContentType │
// ├─────────┴──────┴─────────┴─────────────┤ 4 Bytes
// │ ContentLength │
// ├────────────────────────────────────────┤ 8 Bytes
// │ │
// │─────────────── Checksum ───────────────│
// │ │
// └────────────────────────────────────────┘ 16 Bytes
// Any content will be placed after the header.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct PipePacketHeader
{

View File

@@ -35,7 +35,6 @@ internal static class PipeStreamExtension
}
}
[SkipLocalsInit]
public static unsafe void ReadPacket(this PipeStream stream, out PipePacketHeader header)
{
fixed (PipePacketHeader* pHeader = &header)

View File

@@ -89,6 +89,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
serverStream.WritePacketWithJsonContent(PrivateNamedPipe.Version, PipePacketType.Response, PipePacketCommand.ResponseElevationStatus, resp);
serverStream.Flush();
break;
case (PipePacketType.Request, PipePacketCommand.RedirectActivation):
HutaoActivationArguments? hutaoArgs = serverStream.ReadJsonContent<HutaoActivationArguments>(in header);
if (hutaoArgs is not null)
@@ -98,6 +99,7 @@ internal sealed partial class PrivateNamedPipeServer : IDisposable
messageDispatcher.RedirectActivation(hutaoArgs);
break;
case (PipePacketType.SessionTermination, _):
serverStream.Disconnect();
if (header.Command is PipePacketCommand.Exit)

View File

@@ -41,6 +41,7 @@ internal sealed partial class SettingEntry
public const string LaunchScreenHeight = "Launch.ScreenHeight";
public const string LaunchIsScreenHeightEnabled = "Launch.IsScreenHeightEnabled";
public const string LaunchUnlockFps = "Launch.UnlockFps";
public const string LaunchUnlockerKind = "Launch.UnlockerKind";
public const string LaunchTargetFps = "Launch.TargetFps";
public const string LaunchMonitor = "Launch.Monitor";
public const string LaunchIsMonitorEnabled = "Launch.IsMonitorEnabled";

View File

@@ -18,6 +18,18 @@ internal abstract partial class DbStoreOptions : ObservableObject
{
private readonly IServiceProvider serviceProvider;
protected static T? EnumParse<T>(string input)
where T : struct, Enum
{
return Enum.Parse<T>(input);
}
protected static string EnumToStringOrEmpty<T>(T? input)
where T : struct, Enum
{
return input.ToStringOrEmpty();
}
protected string GetOption(ref string? storage, string key, string defaultValue = "")
{
return GetOption(ref storage, key, () => defaultValue);

View File

@@ -84,16 +84,4 @@ internal sealed partial class AppOptions : DbStoreOptions
get => GetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl);
set => SetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl, value);
}
private static T? EnumParse<T>(string input)
where T : struct, Enum
{
return Enum.Parse<T>(input);
}
private static string EnumToStringOrEmpty<T>(T? input)
where T : struct, Enum
{
return input.ToStringOrEmpty();
}
}

View File

@@ -7,6 +7,7 @@ using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Game.PathAbstraction;
using Snap.Hutao.Service.Game.Unlocker;
using Snap.Hutao.Win32.Graphics.Gdi;
using System.Collections.Immutable;
using System.Globalization;
@@ -38,6 +39,7 @@ internal sealed class LaunchOptions : DbStoreOptions
private int? screenHeight;
private bool? isScreenHeightEnabled;
private bool? unlockFps;
private GameFpsUnlockerKind? unlockerKind;
private int? targetFps;
private NameValue<int>? monitor;
private bool? isMonitorEnabled;
@@ -167,6 +169,12 @@ internal sealed class LaunchOptions : DbStoreOptions
set => SetOption(ref unlockFps, SettingEntry.LaunchUnlockFps, value);
}
public GameFpsUnlockerKind UnlockerKind
{
get => GetOption(ref unlockerKind, SettingEntry.LaunchUnlockerKind, EnumParse<GameFpsUnlockerKind>, GameFpsUnlockerKind.Legacy).Value;
set => SetOption(ref unlockerKind, SettingEntry.LaunchUnlockerKind, value, EnumToStringOrEmpty);
}
public int TargetFps
{
get => GetOption(ref targetFps, SettingEntry.LaunchTargetFps, primaryScreenFps);

View File

@@ -25,7 +25,12 @@ internal sealed class LaunchExecutionUnlockFpsHandler : ILaunchExecutionDelegate
return;
}
IslandGameFpsUnlocker unlocker = new(context.ServiceProvider, context.Process, new(gameFileSystem, 100, 20000, 2000), progress);
UnlockOptions unlockOptions = new(gameFileSystem, 100, 20000, 2000);
IGameFpsUnlocker unlocker = context.Options.UnlockerKind switch
{
GameFpsUnlockerKind.Island => new IslandGameFpsUnlocker(context.ServiceProvider, context.Process, unlockOptions, progress),
_ => new DefaultGameFpsUnlocker(context.ServiceProvider, context.Process, unlockOptions, progress),
};
try
{

View File

@@ -304,7 +304,7 @@ internal sealed partial class PackageConverter
try
{
progress.Report(new(SH.FormatServiceGamePackageConvertMoveFileRenameFormat(context.FromDataFolderName, context.ToDataFolderName)));
DirectoryOperation.Move(context.FromDataFolder, context.ToDataFolder);
DirectoryOperation.TryMove(context.FromDataFolder, context.ToDataFolder);
}
catch (IOException ex)
{

View File

@@ -0,0 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.Unlocker;
internal enum GameFpsUnlockerKind
{
Legacy,
Island,
}

View File

@@ -6,12 +6,14 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics.CodeAnalysis;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.Game.PathAbstraction;
using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Service.Game.Unlocker;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
using Snap.Hutao.UI.Xaml.Data;
@@ -69,7 +71,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
public List<LaunchScheme> KnownSchemes { get; } = KnownLaunchSchemes.Get();
[AlsoAsyncSets(nameof(GamePackage), nameof(GameAccountsView))]
public LaunchScheme? SelectedScheme
{
get => selectedScheme;