Compare commits

...

1 Commits

Author SHA1 Message Date
qhy040404
d2874ce291 add ability to fix missing config.ini 2024-04-02 23:36:04 +08:00
10 changed files with 223 additions and 34 deletions

View File

@@ -1334,6 +1334,9 @@
<data name="ViewDialogLaunchGameAccountTitle" xml:space="preserve"> <data name="ViewDialogLaunchGameAccountTitle" xml:space="preserve">
<value>为账号命名</value> <value>为账号命名</value>
</data> </data>
<data name="ViewDialogLaunchGameConfigurationFixDialogTitle" xml:space="preserve">
<value>正在修复配置文件</value>
</data>
<data name="ViewDialogLaunchGamePackageConvertHint" xml:space="preserve"> <data name="ViewDialogLaunchGamePackageConvertHint" xml:space="preserve">
<value>转换可能需要花费一段时间,请勿关闭胡桃</value> <value>转换可能需要花费一段时间,请勿关闭胡桃</value>
</data> </data>
@@ -1625,6 +1628,12 @@
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve"> <data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
<value>切换服务器失败</value> <value>切换服务器失败</value>
</data> </data>
<data name="ViewModelLaunchGameFixConfigurationFileButtonText" xml:space="preserve">
<value>修复配置文件</value>
</data>
<data name="ViewModelLaunchGameFixConfigurationFileSuccess" xml:space="preserve">
<value>修复完成</value>
</data>
<data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve"> <data name="ViewModelLaunchGameIdentifyMonitorsAction" xml:space="preserve">
<value>识别显示器</value> <value>识别显示器</value>
</data> </data>
@@ -1637,6 +1646,9 @@
<data name="ViewModelLaunchGameSchemeNotSelected" xml:space="preserve"> <data name="ViewModelLaunchGameSchemeNotSelected" xml:space="preserve">
<value>尚未选择任何服务器</value> <value>尚未选择任何服务器</value>
</data> </data>
<data name="ViewModelLaunchGameSetGamePathButtonText" xml:space="preserve">
<value>设置游戏目录</value>
</data>
<data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve"> <data name="ViewModelLaunchGameSwitchGameAccountFail" xml:space="preserve">
<value>切换账号失败</value> <value>切换账号失败</value>
</data> </data>

View File

@@ -11,5 +11,5 @@ internal interface IInfoBarService
{ {
ObservableCollection<InfoBar> Collection { get; } ObservableCollection<InfoBar> Collection { get; }
void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay); void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay, string? buttonContent, ICommand? buttonCommand);
} }

View File

@@ -34,22 +34,33 @@ internal sealed class InfoBarService : IInfoBarService
get => collection ??= []; get => collection ??= [];
} }
public void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay) public void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay, string? buttonContent = default, ICommand? buttonCommand = default)
{ {
if (collection is null) if (collection is null)
{ {
return; return;
} }
PrepareInfoBarAndShowCoreAsync(severity, title, message, delay).SafeForget(logger); PrepareInfoBarAndShowCoreAsync(severity, title, message, delay, buttonContent, buttonCommand).SafeForget(logger);
} }
private async ValueTask PrepareInfoBarAndShowCoreAsync(InfoBarSeverity severity, string? title, string? message, int delay) private async ValueTask PrepareInfoBarAndShowCoreAsync(InfoBarSeverity severity, string? title, string? message, int delay, string? buttonContent, ICommand? buttonCommand)
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
Button? actionButton = default;
if (buttonContent is not null)
{
actionButton = new()
{
Content = buttonContent,
Command = buttonCommand,
};
}
InfoBar infoBar = new() InfoBar infoBar = new()
{ {
ActionButton = actionButton,
Severity = severity, Severity = severity,
Title = title, Title = title,
Message = message, Message = message,

View File

@@ -9,51 +9,101 @@ internal static class InfoBarServiceExtension
{ {
public static void Information(this IInfoBarService infoBarService, string message, int delay = 5000) public static void Information(this IInfoBarService infoBarService, string message, int delay = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, null, message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, null, message, delay, null, null);
} }
public static void Information(this IInfoBarService infoBarService, string title, string message, int delay = 5000) public static void Information(this IInfoBarService infoBarService, string title, string message, int delay = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay, null, null);
}
public static void Information(this IInfoBarService infoBarService, string title, string message, string buttonContent, int delay = 5000)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay, buttonContent, null);
}
public static void Information(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int delay = 5000)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Informational, title, message, delay, buttonContent, buttonCommand);
} }
public static void Success(this IInfoBarService infoBarService, string message, int delay = 5000) public static void Success(this IInfoBarService infoBarService, string message, int delay = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, null, message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, null, message, delay, null, null);
} }
public static void Success(this IInfoBarService infoBarService, string title, string message, int delay = 5000) public static void Success(this IInfoBarService infoBarService, string title, string message, int delay = 5000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay, null, null);
}
public static void Success(this IInfoBarService infoBarService, string title, string message, string buttonContent, int delay = 5000)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay, buttonContent, null);
}
public static void Success(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int delay = 5000)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Success, title, message, delay, buttonContent, buttonCommand);
} }
public static void Warning(this IInfoBarService infoBarService, string message, int delay = 30000) public static void Warning(this IInfoBarService infoBarService, string message, int delay = 30000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, null, message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, null, message, delay, null, null);
} }
public static void Warning(this IInfoBarService infoBarService, string title, string message, int delay = 30000) public static void Warning(this IInfoBarService infoBarService, string title, string message, int delay = 30000)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay, null, null);
}
public static void Warning(this IInfoBarService infoBarService, string title, string message, string buttonContent, int delay = 30000)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay, buttonContent, null);
}
public static void Warning(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int delay = 30000)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Warning, title, message, delay, buttonContent, buttonCommand);
} }
public static void Error(this IInfoBarService infoBarService, string message, int delay = 0) public static void Error(this IInfoBarService infoBarService, string message, int delay = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, null, message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, null, message, delay, null, null);
} }
public static void Error(this IInfoBarService infoBarService, string title, string message, int delay = 0) public static void Error(this IInfoBarService infoBarService, string title, string message, int delay = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, title, message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, title, message, delay, null, null);
}
public static void Error(this IInfoBarService infoBarService, string title, string message, string buttonContent, int delay = 0)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, title, message, delay, buttonContent, null);
}
public static void Error(this IInfoBarService infoBarService, string title, string message, string buttonContent, ICommand buttonCommand, int delay = 0)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, title, message, delay, buttonContent, buttonCommand);
} }
public static void Error(this IInfoBarService infoBarService, Exception ex, int delay = 0) public static void Error(this IInfoBarService infoBarService, Exception ex, int delay = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, ex.Message, delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, ex.Message, delay, null, null);
} }
public static void Error(this IInfoBarService infoBarService, Exception ex, string title, int delay = 0) public static void Error(this IInfoBarService infoBarService, Exception ex, string title, int delay = 0)
{ {
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay); infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay, null, null);
}
public static void Error(this IInfoBarService infoBarService, Exception ex, string title, string buttonContent, int delay = 0)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay, buttonContent, null);
}
public static void Error(this IInfoBarService infoBarService, Exception ex, string title, string buttonContent, ICommand buttonCommand, int delay = 0)
{
infoBarService.PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay, buttonContent, buttonCommand);
} }
} }

View File

@@ -176,6 +176,7 @@
<None Remove="View\Dialog\HutaoPassportResetPasswordDialog.xaml" /> <None Remove="View\Dialog\HutaoPassportResetPasswordDialog.xaml" />
<None Remove="View\Dialog\HutaoPassportUnregisterDialog.xaml" /> <None Remove="View\Dialog\HutaoPassportUnregisterDialog.xaml" />
<None Remove="View\Dialog\LaunchGameAccountNameDialog.xaml" /> <None Remove="View\Dialog\LaunchGameAccountNameDialog.xaml" />
<None Remove="View\Dialog\LaunchGameConfigurationFixDialog.xaml" />
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" /> <None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
<None Remove="View\Dialog\ReconfirmDialog.xaml" /> <None Remove="View\Dialog\ReconfirmDialog.xaml" />
<None Remove="View\Dialog\UserDialog.xaml" /> <None Remove="View\Dialog\UserDialog.xaml" />
@@ -349,6 +350,11 @@
<ItemGroup> <ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" /> <None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\LaunchGameConfigurationFixDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Page Update="Control\Theme\SegmentedOverride.xaml"> <Page Update="Control\Theme\SegmentedOverride.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@@ -0,0 +1,25 @@
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.LaunchGameConfigurationFixDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shccs="using:Snap.Hutao.Control.Collection.Selector"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvd="using:Snap.Hutao.View.Dialog"
Title="{shcm:ResourceString Name=ViewDialogLaunchGameConfigurationFixDialogTitle}"
d:DataContext="{d:DesignInstance shvd:LaunchGameConfigurationFixDialog}"
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<shccs:ComboBox2
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
DisplayMemberPath="DisplayName"
EnableMemberPath="IsNotCompatOnly"
ItemsSource="{x:Bind KnownSchemes}"
SelectedItem="{x:Bind SelectedScheme, Mode=TwoWay}"
Style="{StaticResource DefaultComboBoxStyle}"/>
</ContentDialog>

View File

@@ -0,0 +1,24 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Service.Game.Scheme;
namespace Snap.Hutao.View.Dialog;
[DependencyProperty("KnownSchemes", typeof(IEnumerable<LaunchScheme>))]
[DependencyProperty("SelectedScheme", typeof(LaunchScheme))]
internal sealed partial class LaunchGameConfigurationFixDialog : ContentDialog
{
public LaunchGameConfigurationFixDialog()
{
InitializeComponent();
}
public async ValueTask<ValueResult<bool, LaunchScheme?>> GetLaunchSchemeAsync()
{
ContentDialogResult result = await ShowAsync();
return new(result == ContentDialogResult.Primary, SelectedScheme);
}
}

View File

@@ -2,39 +2,98 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Service.Game; using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Configuration; using Snap.Hutao.Service.Game.Configuration;
using Snap.Hutao.Service.Game.Scheme; using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.View.Page;
using System.IO;
namespace Snap.Hutao.ViewModel.Game; namespace Snap.Hutao.ViewModel.Game;
internal static class LaunchGameShared [Injection(InjectAs.Transient)]
[ConstructorGenerated]
internal sealed partial class LaunchGameShared
{ {
public static LaunchScheme? GetCurrentLaunchSchemeFromConfigFile(IGameServiceFacade gameService, IInfoBarService infoBarService) private readonly IContentDialogFactory contentDialogFactory;
private readonly INavigationService navigationService;
private readonly IGameServiceFacade gameService;
private readonly IInfoBarService infoBarService;
private readonly LaunchOptions launchOptions;
private readonly ITaskContext taskContext;
public LaunchScheme? GetCurrentLaunchSchemeFromConfigFile(IGameServiceFacade gameService, IInfoBarService infoBarService)
{ {
ChannelOptions options = gameService.GetChannelOptions(); ChannelOptions options = gameService.GetChannelOptions();
if (options.ErrorKind is ChannelOptionsErrorKind.None) switch (options.ErrorKind)
{ {
try case ChannelOptionsErrorKind.None:
{ try
return KnownLaunchSchemes.Get().Single(scheme => scheme.Equals(options));
}
catch (InvalidOperationException)
{
if (!IgnoredInvalidChannelOptions.Contains(options))
{ {
// 后台收集 return KnownLaunchSchemes.Get().Single(scheme => scheme.Equals(options));
throw ThrowHelper.NotSupported($"不支持的 MultiChannel: {options}");
} }
} catch (InvalidOperationException)
} {
else if (!IgnoredInvalidChannelOptions.Contains(options))
{ {
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath)); // 后台收集
throw ThrowHelper.NotSupported($"不支持的 MultiChannel: {options}");
}
}
break;
case ChannelOptionsErrorKind.ConfigurationFileNotFound:
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath), SH.ViewModelLaunchGameFixConfigurationFileButtonText, HandleConfigurationFileNotFoundCommand);
break;
case ChannelOptionsErrorKind.GamePathNullOrEmpty:
infoBarService.Warning($"{options.ErrorKind}", SH.FormatViewModelLaunchGameMultiChannelReadFail(options.FilePath), SH.ViewModelLaunchGameSetGamePathButtonText, HandleGamePathNullOrEmptyCommand);
break;
} }
return default; return default;
} }
[Command("HandleConfigurationFileNotFoundCommand")]
private async void HandleConfigurationFileNotFoundAsync()
{
launchOptions.TryGetGameFileSystem(out GameFileSystem? gameFileSystem);
ArgumentNullException.ThrowIfNull(gameFileSystem);
bool isOversea = LaunchScheme.ExecutableIsOversea(gameFileSystem.GameFileName);
string version = await File.ReadAllTextAsync(Path.Combine(gameFileSystem.GameDirectory, isOversea ? GameConstants.GenshinImpactData : GameConstants.YuanShenData, "Persistent", "ScriptVersion")).ConfigureAwait(false);
LaunchGameConfigurationFixDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGameConfigurationFixDialog>().ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
dialog.KnownSchemes = KnownLaunchSchemes.Get().Where(scheme => scheme.IsOversea == isOversea);
dialog.SelectedScheme = dialog.KnownSchemes.First(scheme => scheme.IsNotCompatOnly);
(bool isOk, LaunchScheme? launchScheme) = await dialog.GetLaunchSchemeAsync().ConfigureAwait(false);
if (isOk)
{
ArgumentNullException.ThrowIfNull(launchScheme);
string content = $"""
[General]
channel={(int)launchScheme.Channel}
cps=mihoyo
game_version={version}
sub_channel={(int)launchScheme.SubChannel}
sdk_version=
game_biz=hk4e_{(launchScheme.IsOversea ? "global" : "cn")}
""";
await File.WriteAllTextAsync(gameFileSystem.GameConfigFilePath, content).ConfigureAwait(false);
infoBarService.Success(SH.ViewModelLaunchGameFixConfigurationFileSuccess);
}
}
[Command("HandleGamePathNullOrEmptyCommand")]
private void HandleGamePathNullOrEmpty()
{
navigationService.Navigate<LaunchGamePage>(INavigationAwaiter.Default);
}
} }

View File

@@ -40,6 +40,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
private readonly LaunchStatusOptions launchStatusOptions; private readonly LaunchStatusOptions launchStatusOptions;
private readonly IGameLocatorFactory gameLocatorFactory; private readonly IGameLocatorFactory gameLocatorFactory;
private readonly ILogger<LaunchGameViewModel> logger; private readonly ILogger<LaunchGameViewModel> logger;
private readonly LaunchGameShared launchGameShared;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly ResourceClient resourceClient; private readonly ResourceClient resourceClient;
private readonly RuntimeOptions runtimeOptions; private readonly RuntimeOptions runtimeOptions;
@@ -99,7 +100,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
{ {
using (await EnterCriticalExecutionAsync().ConfigureAwait(false)) using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{ {
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService); LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
await SetSelectedSchemeAsync(scheme).ConfigureAwait(true); await SetSelectedSchemeAsync(scheme).ConfigureAwait(true);

View File

@@ -23,6 +23,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
{ {
private readonly LaunchStatusOptions launchStatusOptions; private readonly LaunchStatusOptions launchStatusOptions;
private readonly ILogger<LaunchGameViewModelSlim> logger; private readonly ILogger<LaunchGameViewModelSlim> logger;
private readonly LaunchGameShared launchGameShared;
private readonly IInfoBarService infoBarService; private readonly IInfoBarService infoBarService;
private readonly IGameServiceFacade gameService; private readonly IGameServiceFacade gameService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
@@ -47,7 +48,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
/// <inheritdoc/> /// <inheritdoc/>
protected override async Task OpenUIAsync() protected override async Task OpenUIAsync()
{ {
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService); LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection; ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
try try
@@ -76,7 +77,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
private async Task LaunchAsync() private async Task LaunchAsync()
{ {
IInfoBarService infoBarService = ServiceProvider.GetRequiredService<IInfoBarService>(); IInfoBarService infoBarService = ServiceProvider.GetRequiredService<IInfoBarService>();
LaunchScheme? scheme = LaunchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService); LaunchScheme? scheme = launchGameShared.GetCurrentLaunchSchemeFromConfigFile(gameService, infoBarService);
try try
{ {