mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
1 Commits
feat/ident
...
feat/refre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a02162f76d |
@@ -124,6 +124,9 @@ dotnet_diagnostic.SA1623.severity = none
|
||||
# SA1636: File header copyright text should match
|
||||
dotnet_diagnostic.SA1636.severity = none
|
||||
|
||||
# SA1414: Tuple types in signatures should have element names
|
||||
dotnet_diagnostic.SA1414.severity = none
|
||||
|
||||
# SA0001: XML comment analysis disabled
|
||||
dotnet_diagnostic.SA0001.severity = none
|
||||
csharp_style_prefer_parameter_null_checking = true:suggestion
|
||||
@@ -322,6 +325,7 @@ dotnet_diagnostic.CA2227.severity = suggestion
|
||||
# CA2251: 使用 “string.Equals”
|
||||
dotnet_diagnostic.CA2251.severity = suggestion
|
||||
csharp_style_prefer_primary_constructors = true:suggestion
|
||||
dotnet_diagnostic.SA1010.severity = none
|
||||
|
||||
[*.vb]
|
||||
#### 命名样式 ####
|
||||
|
||||
@@ -11,14 +11,11 @@ namespace Snap.Hutao.Core.IO.Ini;
|
||||
[HighQuality]
|
||||
internal static class IniSerializer
|
||||
{
|
||||
public static List<IniElement> DeserializeFromFile(string filePath)
|
||||
{
|
||||
using (FileStream readStream = File.OpenRead(filePath))
|
||||
{
|
||||
return Deserialize(readStream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化
|
||||
/// </summary>
|
||||
/// <param name="fileStream">文件流</param>
|
||||
/// <returns>Ini 元素集合</returns>
|
||||
public static List<IniElement> Deserialize(FileStream fileStream)
|
||||
{
|
||||
List<IniElement> results = [];
|
||||
@@ -53,14 +50,11 @@ internal static class IniSerializer
|
||||
return results;
|
||||
}
|
||||
|
||||
public static void SerializeToFile(string filePath, IEnumerable<IniElement> elements)
|
||||
{
|
||||
using (FileStream writeStream = File.Create(filePath))
|
||||
{
|
||||
Serialize(writeStream, elements);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化
|
||||
/// </summary>
|
||||
/// <param name="fileStream">写入的流</param>
|
||||
/// <param name="elements">元素</param>
|
||||
public static void Serialize(FileStream fileStream, IEnumerable<IniElement> elements)
|
||||
{
|
||||
using (StreamWriter writer = new(fileStream))
|
||||
|
||||
@@ -69,9 +69,4 @@ internal static class StructMarshal
|
||||
{
|
||||
return new(point.X, point.Y, size.Width, size.Height);
|
||||
}
|
||||
|
||||
public static SizeInt32 SizeInt32(RectInt32 rect)
|
||||
{
|
||||
return new(rect.Width, rect.Height);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<Window
|
||||
x:Class="Snap.Hutao.IdentifyMonitorWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
mc:Ignorable="d">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="3">
|
||||
<TextBlock Text="{shcm:ResourceString Name=WindowIdentifyMonitorHeader}"/>
|
||||
<TextBlock
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind Monitor}"
|
||||
TextAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace Snap.Hutao;
|
||||
|
||||
internal sealed partial class IdentifyMonitorWindow : Window
|
||||
{
|
||||
public IdentifyMonitorWindow(DisplayArea displayArea, int index)
|
||||
{
|
||||
InitializeComponent();
|
||||
Monitor = $"{displayArea.DisplayId.Value:X8}:{index}";
|
||||
|
||||
OverlappedPresenter presenter = OverlappedPresenter.Create();
|
||||
presenter.SetBorderAndTitleBar(false, false);
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
presenter.IsResizable = false;
|
||||
AppWindow.SetPresenter(presenter);
|
||||
|
||||
PointInt32 point = new(40, 32);
|
||||
SizeInt32 size = StructMarshal.SizeInt32(displayArea.WorkArea).Scale(0.1);
|
||||
AppWindow.MoveAndResize(StructMarshal.RectInt32(point, size), displayArea);
|
||||
}
|
||||
|
||||
public string Monitor { get; private set; }
|
||||
}
|
||||
@@ -1559,9 +1559,6 @@
|
||||
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
|
||||
<value>切换服务器失败</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
|
||||
<value>识别显示器</value>
|
||||
</data>
|
||||
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
|
||||
<value>无法读取游戏配置文件: {0},可能是文件不存在或权限不足</value>
|
||||
</data>
|
||||
@@ -2969,7 +2966,4 @@
|
||||
<data name="WebResponseRequestExceptionFormat" xml:space="preserve">
|
||||
<value>[{0}] 中的 [{1}] 网络请求异常,请稍后再试</value>
|
||||
</data>
|
||||
<data name="WindowIdentifyMonitorHeader" xml:space="preserve">
|
||||
<value>显示器编号</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -29,9 +29,10 @@ internal readonly struct ChannelOptions
|
||||
/// </summary>
|
||||
public readonly bool IsOversea;
|
||||
|
||||
public readonly ChannelOptionsErrorKind ErrorKind;
|
||||
|
||||
public readonly string? FilePath;
|
||||
/// <summary>
|
||||
/// 配置文件路径 当不为 null 时则存在文件读写问题
|
||||
/// </summary>
|
||||
public readonly string? ConfigFilePath;
|
||||
|
||||
public ChannelOptions(ChannelType channel, SubChannelType subChannel, bool isOversea)
|
||||
{
|
||||
@@ -47,20 +48,15 @@ internal readonly struct ChannelOptions
|
||||
IsOversea = isOversea;
|
||||
}
|
||||
|
||||
private ChannelOptions(ChannelOptionsErrorKind errorKind, string? filePath)
|
||||
private ChannelOptions(bool isOversea, string? configFilePath)
|
||||
{
|
||||
ErrorKind = errorKind;
|
||||
FilePath = filePath;
|
||||
IsOversea = isOversea;
|
||||
ConfigFilePath = configFilePath;
|
||||
}
|
||||
|
||||
public static ChannelOptions ConfigurationFileNotFound(string filePath)
|
||||
public static ChannelOptions FileNotFound(bool isOversea, string configFilePath)
|
||||
{
|
||||
return new(ChannelOptionsErrorKind.ConfigurationFileNotFound, filePath);
|
||||
}
|
||||
|
||||
public static ChannelOptions GamePathNullOrEmpty()
|
||||
{
|
||||
return new(ChannelOptionsErrorKind.GamePathNullOrEmpty, string.Empty);
|
||||
return new(isOversea, configFilePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Configuration;
|
||||
|
||||
internal enum ChannelOptionsErrorKind
|
||||
{
|
||||
None,
|
||||
ConfigurationFileNotFound,
|
||||
GamePathNullOrEmpty,
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Core.IO.Ini;
|
||||
using Snap.Hutao.Service.Game.Scheme;
|
||||
using System.IO;
|
||||
using static Snap.Hutao.Service.Game.GameConstants;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Configuration;
|
||||
|
||||
@@ -15,22 +17,25 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer
|
||||
|
||||
public ChannelOptions GetChannelOptions()
|
||||
{
|
||||
if (!launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
if (!launchOptions.TryGetGamePathAndFilePathByName(ConfigFileName, out string gamePath, out string? configPath))
|
||||
{
|
||||
return ChannelOptions.GamePathNullOrEmpty();
|
||||
throw ThrowHelper.InvalidOperation($"Invalid game path: {gamePath}");
|
||||
}
|
||||
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName);
|
||||
bool isOversea = LaunchScheme.ExecutableIsOversea(Path.GetFileName(gamePath));
|
||||
|
||||
if (!File.Exists(gameFileSystem.GameConfigFilePath))
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
return ChannelOptions.ConfigurationFileNotFound(gameFileSystem.GameConfigFilePath);
|
||||
return ChannelOptions.FileNotFound(isOversea, configPath);
|
||||
}
|
||||
|
||||
List<IniParameter> parameters = IniSerializer.DeserializeFromFile(gameFileSystem.GameConfigFilePath).OfType<IniParameter>().ToList();
|
||||
string? channel = parameters.FirstOrDefault(p => p.Key == ChannelOptions.ChannelName)?.Value;
|
||||
string? subChannel = parameters.FirstOrDefault(p => p.Key == ChannelOptions.SubChannelName)?.Value;
|
||||
using (FileStream stream = File.OpenRead(configPath))
|
||||
{
|
||||
List<IniParameter> parameters = IniSerializer.Deserialize(stream).OfType<IniParameter>().ToList();
|
||||
string? channel = parameters.FirstOrDefault(p => p.Key == ChannelOptions.ChannelName)?.Value;
|
||||
string? subChannel = parameters.FirstOrDefault(p => p.Key == ChannelOptions.SubChannelName)?.Value;
|
||||
|
||||
return new(channel, subChannel, isOversea);
|
||||
return new(channel, subChannel, isOversea);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ namespace Snap.Hutao.Service.Game;
|
||||
internal static class GameConstants
|
||||
{
|
||||
public const string ConfigFileName = "config.ini";
|
||||
public const string PCGameSDKFilePath = @"YuanShen_Data\Plugins\PCGameSDK.dll";
|
||||
public const string YuanShenFileName = "YuanShen.exe";
|
||||
public const string YuanShenFileNameUpper = "YUANSHEN.EXE";
|
||||
public const string GenshinImpactFileName = "GenshinImpact.exe";
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Service.Game;
|
||||
|
||||
internal sealed class GameFileSystem
|
||||
{
|
||||
private readonly string gameFilePath;
|
||||
|
||||
private string? gameFileName;
|
||||
private string? gameDirectory;
|
||||
private string? gameConfigFilePath;
|
||||
private string? pcGameSDKFilePath;
|
||||
|
||||
public GameFileSystem(string gameFilePath)
|
||||
{
|
||||
this.gameFilePath = gameFilePath;
|
||||
}
|
||||
|
||||
public string GameFilePath { get => gameFilePath; }
|
||||
|
||||
public string GameFileName { get => gameFileName ??= Path.GetFileName(gameFilePath); }
|
||||
|
||||
public string GameDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
gameDirectory ??= Path.GetDirectoryName(gameFilePath);
|
||||
ArgumentException.ThrowIfNullOrEmpty(gameDirectory);
|
||||
return gameDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
public string GameConfigFilePath { get => gameConfigFilePath ??= Path.Combine(GameDirectory, GameConstants.ConfigFileName); }
|
||||
|
||||
public string PCGameSDKFilePath { get => pcGameSDKFilePath ??= Path.Combine(GameDirectory, GameConstants.PCGameSDKFilePath); }
|
||||
}
|
||||
@@ -3,25 +3,70 @@
|
||||
|
||||
using Snap.Hutao.Service.Game.PathAbstraction;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Service.Game;
|
||||
|
||||
internal static class LaunchOptionsExtension
|
||||
{
|
||||
public static bool TryGetGameFileSystem(this LaunchOptions options, [NotNullWhen(true)] out GameFileSystem? fileSystem)
|
||||
public static bool TryGetGamePathAndGameDirectory(this LaunchOptions options, out string gamePath, [NotNullWhen(true)] out string? gameDirectory)
|
||||
{
|
||||
string gamePath = options.GamePath;
|
||||
gamePath = options.GamePath;
|
||||
|
||||
if (string.IsNullOrEmpty(gamePath))
|
||||
gameDirectory = Path.GetDirectoryName(gamePath);
|
||||
if (string.IsNullOrEmpty(gameDirectory))
|
||||
{
|
||||
fileSystem = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
fileSystem = new GameFileSystem(gamePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetGameDirectoryAndGameFileName(this LaunchOptions options, [NotNullWhen(true)] out string? gameDirectory, [NotNullWhen(true)] out string? gameFileName)
|
||||
{
|
||||
string gamePath = options.GamePath;
|
||||
|
||||
gameDirectory = Path.GetDirectoryName(gamePath);
|
||||
if (string.IsNullOrEmpty(gameDirectory))
|
||||
{
|
||||
gameFileName = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
gameFileName = Path.GetFileName(gamePath);
|
||||
if (string.IsNullOrEmpty(gameFileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetGamePathAndGameFileName(this LaunchOptions options, out string gamePath, [NotNullWhen(true)] out string? gameFileName)
|
||||
{
|
||||
gamePath = options.GamePath;
|
||||
|
||||
gameFileName = Path.GetFileName(gamePath);
|
||||
if (string.IsNullOrEmpty(gameFileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetGamePathAndFilePathByName(this LaunchOptions options, string fileName, out string gamePath, [NotNullWhen(true)] out string? filePath)
|
||||
{
|
||||
if (options.TryGetGamePathAndGameDirectory(out gamePath, out string? gameDirectory))
|
||||
{
|
||||
filePath = Path.Combine(gameDirectory, fileName);
|
||||
return true;
|
||||
}
|
||||
|
||||
filePath = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ImmutableList<GamePathEntry> GetGamePathEntries(this LaunchOptions options, out GamePathEntry? entry)
|
||||
{
|
||||
string gamePath = options.GamePath;
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.Win32.SafeHandles;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Service.Game.Package;
|
||||
using Snap.Hutao.Service.Game.PathAbstraction;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
@@ -20,68 +19,38 @@ internal sealed class LaunchExecutionEnsureGameResourceHandler : ILaunchExecutio
|
||||
{
|
||||
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
|
||||
{
|
||||
if (!context.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
IServiceProvider serviceProvider = context.ServiceProvider;
|
||||
IContentDialogFactory contentDialogFactory = serviceProvider.GetRequiredService<IContentDialogFactory>();
|
||||
IProgressFactory progressFactory = serviceProvider.GetRequiredService<IProgressFactory>();
|
||||
|
||||
LaunchGamePackageConvertDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGamePackageConvertDialog>().ConfigureAwait(false);
|
||||
IProgress<PackageConvertStatus> convertProgress = progressFactory.CreateForMainThread<PackageConvertStatus>(state => dialog.State = state);
|
||||
|
||||
using (await dialog.BlockAsync(context.TaskContext).ConfigureAwait(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldConvert(context, gameFileSystem))
|
||||
{
|
||||
IServiceProvider serviceProvider = context.ServiceProvider;
|
||||
IContentDialogFactory contentDialogFactory = serviceProvider.GetRequiredService<IContentDialogFactory>();
|
||||
IProgressFactory progressFactory = serviceProvider.GetRequiredService<IProgressFactory>();
|
||||
|
||||
LaunchGamePackageConvertDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGamePackageConvertDialog>().ConfigureAwait(false);
|
||||
IProgress<PackageConvertStatus> convertProgress = progressFactory.CreateForMainThread<PackageConvertStatus>(state => dialog.State = state);
|
||||
|
||||
using (await dialog.BlockAsync(context.TaskContext).ConfigureAwait(false))
|
||||
if (!await EnsureGameResourceAsync(context, convertProgress).ConfigureAwait(false))
|
||||
{
|
||||
if (!await EnsureGameResourceAsync(context, gameFileSystem, convertProgress).ConfigureAwait(false))
|
||||
{
|
||||
// context.Result is set in EnsureGameResourceAsync
|
||||
return;
|
||||
}
|
||||
|
||||
await context.TaskContext.SwitchToMainThreadAsync();
|
||||
ImmutableList<GamePathEntry> gamePathEntries = context.Options.GetGamePathEntries(out GamePathEntry? selected);
|
||||
context.ViewModel.SetGamePathEntriesAndSelectedGamePathEntry(gamePathEntries, selected);
|
||||
// context.Result is set in EnsureGameResourceAsync
|
||||
return;
|
||||
}
|
||||
|
||||
await context.TaskContext.SwitchToMainThreadAsync();
|
||||
ImmutableList<GamePathEntry> gamePathEntries = context.Options.GetGamePathEntries(out GamePathEntry? selected);
|
||||
context.ViewModel.SetGamePathEntriesAndSelectedGamePathEntry(gamePathEntries, selected);
|
||||
}
|
||||
|
||||
await next().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool ShouldConvert(LaunchExecutionContext context, GameFileSystem gameFileSystem)
|
||||
private static async ValueTask<bool> EnsureGameResourceAsync(LaunchExecutionContext context, IProgress<PackageConvertStatus> progress)
|
||||
{
|
||||
// Configuration file changed
|
||||
if (context.ChannelOptionsChanged)
|
||||
if (!context.Options.TryGetGameDirectoryAndGameFileName(out string? gameFolder, out string? gameFileName))
|
||||
{
|
||||
return true;
|
||||
context.Result.Kind = LaunchExecutionResultKind.NoActiveGamePath;
|
||||
context.Result.ErrorMessage = SH.ServiceGameLaunchExecutionGamePathNotValid;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Executable name not match
|
||||
if (!context.Scheme.ExecutableMatches(gameFileSystem.GameFileName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!context.Scheme.IsOversea)
|
||||
{
|
||||
// [It's Bilibili channel xor PCGameSDK.dll exists] means we need to convert
|
||||
if (context.Scheme.Channel is ChannelType.Bili ^ File.Exists(gameFileSystem.PCGameSDKFilePath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async ValueTask<bool> EnsureGameResourceAsync(LaunchExecutionContext context, GameFileSystem gameFileSystem, IProgress<PackageConvertStatus> progress)
|
||||
{
|
||||
string gameFolder = gameFileSystem.GameDirectory;
|
||||
string gameFileName = gameFileSystem.GameFileName;
|
||||
|
||||
context.Logger.LogInformation("Game folder: {GameFolder}", gameFolder);
|
||||
|
||||
if (!CheckDirectoryPermissions(gameFolder))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Launching.Handler;
|
||||
|
||||
internal sealed class LaunchExecutionEnsureSchemeHandler : ILaunchExecutionDelegateHandler
|
||||
internal sealed class LaunchExecutionEnsureSchemeNotExistsHandler : ILaunchExecutionDelegateHandler
|
||||
{
|
||||
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
|
||||
{
|
||||
@@ -14,7 +14,7 @@ internal sealed class LaunchExecutionEnsureSchemeHandler : ILaunchExecutionDeleg
|
||||
return;
|
||||
}
|
||||
|
||||
context.Logger.LogInformation("Scheme [{Scheme}] is selected", context.Scheme.DisplayName);
|
||||
context.Logger.LogInformation("Scheme[{Scheme}] is selected", context.Scheme.DisplayName);
|
||||
await next().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Service.Game.Launching.Handler;
|
||||
|
||||
@@ -9,19 +10,21 @@ internal sealed class LaunchExecutionGameProcessInitializationHandler : ILaunchE
|
||||
{
|
||||
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
|
||||
{
|
||||
if (!context.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
if (!context.Options.TryGetGamePathAndGameFileName(out string gamePath, out string? gameFileName))
|
||||
{
|
||||
context.Result.Kind = LaunchExecutionResultKind.NoActiveGamePath;
|
||||
context.Result.ErrorMessage = SH.ServiceGameLaunchExecutionGamePathNotValid;
|
||||
return;
|
||||
}
|
||||
|
||||
context.Progress.Report(new(LaunchPhase.ProcessInitializing, SH.ServiceGameLaunchPhaseProcessInitializing));
|
||||
using (context.Process = InitializeGameProcess(context, gameFileSystem))
|
||||
using (context.Process = InitializeGameProcess(context, gamePath))
|
||||
{
|
||||
await next().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static System.Diagnostics.Process InitializeGameProcess(LaunchExecutionContext context, GameFileSystem gameFileSystem)
|
||||
private static System.Diagnostics.Process InitializeGameProcess(LaunchExecutionContext context, string gamePath)
|
||||
{
|
||||
LaunchOptions launchOptions = context.Options;
|
||||
|
||||
@@ -48,10 +51,10 @@ internal sealed class LaunchExecutionGameProcessInitializationHandler : ILaunchE
|
||||
StartInfo = new()
|
||||
{
|
||||
Arguments = commandLine,
|
||||
FileName = gameFileSystem.GameFilePath,
|
||||
FileName = gamePath,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas",
|
||||
WorkingDirectory = gameFileSystem.GameDirectory,
|
||||
WorkingDirectory = Path.GetDirectoryName(gamePath),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,19 +11,22 @@ internal sealed class LaunchExecutionSetChannelOptionsHandler : ILaunchExecution
|
||||
{
|
||||
public async ValueTask OnExecutionAsync(LaunchExecutionContext context, LaunchExecutionDelegate next)
|
||||
{
|
||||
if (!context.TryGetGameFileSystem(out GameFileSystem? gameFileSystem))
|
||||
if (!context.Options.TryGetGamePathAndFilePathByName(GameConstants.ConfigFileName, out string gamePath, out string? configPath))
|
||||
{
|
||||
// context.Result is set in TryGetGameFileSystem
|
||||
context.Result.Kind = LaunchExecutionResultKind.NoActiveGamePath;
|
||||
context.Result.ErrorMessage = SH.ServiceGameLaunchExecutionGamePathNotValid;
|
||||
return;
|
||||
}
|
||||
|
||||
string configPath = gameFileSystem.GameConfigFilePath;
|
||||
context.Logger.LogInformation("Game config file path: {ConfigPath}", configPath);
|
||||
|
||||
List<IniElement> elements = default!;
|
||||
try
|
||||
{
|
||||
elements = [.. IniSerializer.DeserializeFromFile(configPath)];
|
||||
using (FileStream readStream = File.OpenRead(configPath))
|
||||
{
|
||||
elements = [.. IniSerializer.Deserialize(readStream)];
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
@@ -44,27 +47,32 @@ internal sealed class LaunchExecutionSetChannelOptionsHandler : ILaunchExecution
|
||||
return;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
|
||||
foreach (IniElement element in elements)
|
||||
{
|
||||
if (element is IniParameter parameter)
|
||||
{
|
||||
if (parameter.Key is ChannelOptions.ChannelName)
|
||||
{
|
||||
context.ChannelOptionsChanged = parameter.Set(context.Scheme.Channel.ToString("D")) || context.ChannelOptionsChanged;
|
||||
changed = parameter.Set(context.Scheme.Channel.ToString("D")) || changed;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parameter.Key is ChannelOptions.SubChannelName)
|
||||
{
|
||||
context.ChannelOptionsChanged = parameter.Set(context.Scheme.SubChannel.ToString("D")) || context.ChannelOptionsChanged;
|
||||
changed = parameter.Set(context.Scheme.SubChannel.ToString("D")) || changed;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.ChannelOptionsChanged)
|
||||
if (changed)
|
||||
{
|
||||
IniSerializer.SerializeToFile(configPath, elements);
|
||||
using (FileStream writeStream = File.Create(configPath))
|
||||
{
|
||||
IniSerializer.Serialize(writeStream, elements);
|
||||
}
|
||||
}
|
||||
|
||||
await next().ConfigureAwait(false);
|
||||
|
||||
@@ -15,8 +15,6 @@ internal sealed partial class LaunchExecutionContext
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly LaunchOptions options;
|
||||
|
||||
private GameFileSystem? gameFileSystem;
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
public LaunchExecutionContext(IServiceProvider serviceProvider, IViewModelSupportLaunchExecution viewModel, LaunchScheme? scheme, GameAccount? account)
|
||||
: this(serviceProvider)
|
||||
@@ -38,34 +36,13 @@ internal sealed partial class LaunchExecutionContext
|
||||
|
||||
public LaunchOptions Options { get => options; }
|
||||
|
||||
public IViewModelSupportLaunchExecution ViewModel { get; private set; } = default!;
|
||||
public IViewModelSupportLaunchExecution ViewModel { get; set; } = default!;
|
||||
|
||||
public LaunchScheme Scheme { get; private set; } = default!;
|
||||
public LaunchScheme Scheme { get; set; } = default!;
|
||||
|
||||
public GameAccount? Account { get; private set; }
|
||||
|
||||
public bool ChannelOptionsChanged { get; set; }
|
||||
public GameAccount? Account { get; set; }
|
||||
|
||||
public IProgress<LaunchStatus> Progress { get; set; } = default!;
|
||||
|
||||
public System.Diagnostics.Process Process { get; set; } = default!;
|
||||
|
||||
public bool TryGetGameFileSystem([NotNullWhen(true)] out GameFileSystem? gameFileSystem)
|
||||
{
|
||||
if (this.gameFileSystem is not null)
|
||||
{
|
||||
gameFileSystem = this.gameFileSystem;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Options.TryGetGameFileSystem(out gameFileSystem))
|
||||
{
|
||||
Result.Kind = LaunchExecutionResultKind.NoActiveGamePath;
|
||||
Result.ErrorMessage = SH.ServiceGameLaunchExecutionGamePathNotValid;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.gameFileSystem = gameFileSystem;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ internal sealed class LaunchExecutionInvoker
|
||||
{
|
||||
handlers = [];
|
||||
handlers.Enqueue(new LaunchExecutionEnsureGameNotRunningHandler());
|
||||
handlers.Enqueue(new LaunchExecutionEnsureSchemeHandler());
|
||||
handlers.Enqueue(new LaunchExecutionEnsureSchemeNotExistsHandler());
|
||||
handlers.Enqueue(new LaunchExecutionSetChannelOptionsHandler());
|
||||
handlers.Enqueue(new LaunchExecutionEnsureGameResourceHandler());
|
||||
handlers.Enqueue(new LaunchExecutionSetGameAccountHandler());
|
||||
|
||||
@@ -93,6 +93,7 @@ internal sealed partial class PackageConverter
|
||||
ZipFile.ExtractToDirectory(sdkWebStream, gameFolder, true);
|
||||
}
|
||||
|
||||
// TODO: verify sdk md5
|
||||
if (File.Exists(sdkDllBackup) && File.Exists(sdkVersionBackup))
|
||||
{
|
||||
File.Delete(sdkDllBackup);
|
||||
|
||||
@@ -9,7 +9,7 @@ internal sealed class GamePathEntry
|
||||
public string Path { get; set; } = default!;
|
||||
|
||||
[JsonIgnore]
|
||||
public GamePathEntryKind Kind { get => GetKind(Path); }
|
||||
public GamePathKind Kind { get => GetKind(Path); }
|
||||
|
||||
public static GamePathEntry Create(string path)
|
||||
{
|
||||
@@ -19,8 +19,8 @@ internal sealed class GamePathEntry
|
||||
};
|
||||
}
|
||||
|
||||
private static GamePathEntryKind GetKind(string path)
|
||||
private static GamePathKind GetKind(string path)
|
||||
{
|
||||
return GamePathEntryKind.None;
|
||||
return GamePathKind.None;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace Snap.Hutao.Service.Game.PathAbstraction;
|
||||
|
||||
internal enum GamePathEntryKind
|
||||
internal enum GamePathKind
|
||||
{
|
||||
None,
|
||||
ChineseClient,
|
||||
@@ -107,7 +107,6 @@
|
||||
<None Remove="Control\Theme\Uri.xaml" />
|
||||
<None Remove="Control\Theme\WindowOverride.xaml" />
|
||||
<None Remove="GuideWindow.xaml" />
|
||||
<None Remove="IdentifyMonitorWindow.xaml" />
|
||||
<None Remove="IdentityStructs.json" />
|
||||
<None Remove="LaunchGameWindow.xaml" />
|
||||
<None Remove="Resource\BlurBackground.png" />
|
||||
@@ -322,7 +321,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.556">
|
||||
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.507">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -545,13 +544,7 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="IdentifyMonitorWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="View\Control\HutaoStatisticsCard.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsUseCloudThirdPartyMobile, Mode=TwoWay}"/>
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioDescription}" Header="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioHeader}">
|
||||
<shc:SizeRestrictedContentControl Margin="0,0,136,0" VerticalAlignment="Center">
|
||||
<shc:SizeRestrictedContentControl Margin="0,0,136,0">
|
||||
<ComboBox
|
||||
ItemsSource="{Binding LaunchOptions.AspectRatios}"
|
||||
PlaceholderText="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioPlaceHolder}"
|
||||
@@ -259,11 +259,7 @@
|
||||
</cwc:SettingsCard>
|
||||
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameMonitorsDescription}" Header="-monitor">
|
||||
<StackPanel Orientation="Horizontal" Spacing="16">
|
||||
<Button
|
||||
Width="120"
|
||||
Command="{Binding IdentifyMonitorsCommand}"
|
||||
Content="{shcm:ResourceString Name=ViewModelLaunchGameIdentifyMonitorsAction}"/>
|
||||
<shc:SizeRestrictedContentControl VerticalAlignment="Center">
|
||||
<shc:SizeRestrictedContentControl>
|
||||
<ComboBox
|
||||
DisplayMemberPath="Name"
|
||||
IsEnabled="{Binding LaunchOptions.IsMonitorEnabled}"
|
||||
|
||||
@@ -13,9 +13,17 @@ internal static class LaunchGameShared
|
||||
{
|
||||
public static LaunchScheme? GetCurrentLaunchSchemeFromConfigFile(IGameServiceFacade gameService, IInfoBarService infoBarService)
|
||||
{
|
||||
ChannelOptions options = gameService.GetChannelOptions();
|
||||
ChannelOptions options;
|
||||
try
|
||||
{
|
||||
options = gameService.GetChannelOptions();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (options.ErrorKind is ChannelOptionsErrorKind.None)
|
||||
if (string.IsNullOrEmpty(options.ConfigFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -32,7 +40,7 @@ internal static class LaunchGameShared
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath));
|
||||
infoBarService.Warning(SH.FormatViewModelLaunchGameMultiChannelReadFail(options.ConfigFilePath));
|
||||
}
|
||||
|
||||
return default;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
@@ -20,7 +19,6 @@ using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Game;
|
||||
|
||||
@@ -340,28 +338,4 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Command("IdentifyMonitorsCommand")]
|
||||
private async Task IdentifyMonitorsAsync()
|
||||
{
|
||||
List<IdentifyMonitorWindow> windows = [];
|
||||
|
||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||
for (int i = 0; i < displayAreas.Count; i++)
|
||||
{
|
||||
windows.Add(new IdentifyMonitorWindow(displayAreas[i], i + 1));
|
||||
}
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
await Delay.FromSeconds(3).ConfigureAwait(true);
|
||||
|
||||
foreach (IdentifyMonitorWindow window in windows)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,18 @@ internal sealed partial class FolderViewModel : ObservableObject
|
||||
|
||||
public string? Size { get => size; set => SetProperty(ref size, value); }
|
||||
|
||||
public async ValueTask SetFolderSizeAsync()
|
||||
public async ValueTask RefreshFolderSizeAsync()
|
||||
{
|
||||
await SetFolderSizeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Command("OpenFolderCommand")]
|
||||
private async Task OpenDataFolderAsync()
|
||||
{
|
||||
await Launcher.LaunchFolderPathAsync(folder);
|
||||
}
|
||||
|
||||
private async ValueTask SetFolderSizeAsync()
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
long totalSize = 0;
|
||||
@@ -39,10 +50,4 @@ internal sealed partial class FolderViewModel : ObservableObject
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
Size = SH.FormatViewModelSettingFolderSizeDescription(Converters.ToFileSizeString(totalSize));
|
||||
}
|
||||
|
||||
[Command("OpenFolderCommand")]
|
||||
private async Task OpenDataFolderAsync()
|
||||
{
|
||||
await Launcher.LaunchFolderPathAsync(folder);
|
||||
}
|
||||
}
|
||||
@@ -284,11 +284,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
|
||||
Directory.Delete(cacheFolder, true);
|
||||
}
|
||||
|
||||
if (DataFolderView is not null)
|
||||
{
|
||||
await DataFolderView.SetFolderSizeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
DataFolderView?.RefreshFolderSizeAsync().SafeForget();
|
||||
infoBarService.Information(SH.ViewModelSettingActionComplete);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user