This commit is contained in:
Lightczx
2023-12-14 14:48:56 +08:00
parent 176baeb5c6
commit ac78df369c
19 changed files with 587 additions and 407 deletions

View File

@@ -12,7 +12,7 @@ internal enum SchemeType
/// <summary>
/// 国际服
/// </summary>
Mihoyo,
Hoyoverse,
/// <summary>
/// 国服官服

View File

@@ -13,6 +13,8 @@ internal sealed partial class SettingEntry
/// </summary>
public const string GamePath = "GamePath";
public const string GamePathEntries = "GamePathEntries";
/// <summary>
/// PowerShell 路径
/// </summary>

View File

@@ -2189,6 +2189,9 @@
<data name="ViewPageOpenScreenshotFolderAction" xml:space="preserve">
<value>打开截图文件夹</value>
</data>
<data name="ViewPageResetGamePathAction" xml:space="preserve">
<value>选择游戏路径</value>
</data>
<data name="ViewPageSettingAboutHeader" xml:space="preserve">
<value>关于 胡桃</value>
</data>

View File

@@ -6,6 +6,8 @@ using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Game.PathAbstraction;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
@@ -15,20 +17,12 @@ namespace Snap.Hutao.Service;
[Injection(InjectAs.Singleton)]
internal sealed partial class AppOptions : DbStoreOptions
{
private string? gamePath;
private string? powerShellPath;
private bool? isEmptyHistoryWishVisible;
private BackdropType? backdropType;
private CultureInfo? currentCulture;
private bool? isAdvancedLaunchOptionsEnabled;
private string? geetestCustomCompositeUrl;
public string GamePath
{
get => GetOption(ref gamePath, SettingEntry.GamePath);
set => SetOption(ref gamePath, SettingEntry.GamePath, value);
}
public string PowerShellPath
{
get
@@ -80,14 +74,6 @@ internal sealed partial class AppOptions : DbStoreOptions
set => SetOption(ref currentCulture, SettingEntry.Culture, value, value => value.Name);
}
public bool IsAdvancedLaunchOptionsEnabled
{
// DO NOT MOVE TO OTHER CLASS
// We use this property in SettingPage binding
get => GetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled);
set => SetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled, value);
}
public string GeetestCustomCompositeUrl
{
get => GetOption(ref geetestCustomCompositeUrl, SettingEntry.GeetestCustomCompositeUrl);

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT license.
using Snap.Hutao.Model;
using Snap.Hutao.Service.Game.PathAbstraction;
using System.Collections.Immutable;
using System.Globalization;
using System.IO;
@@ -9,39 +11,6 @@ namespace Snap.Hutao.Service;
internal static class AppOptionsExtension
{
public static bool TryGetGameFolderAndFileName(this AppOptions appOptions, [NotNullWhen(true)] out string? gameFolder, [NotNullWhen(true)] out string? gameFileName)
{
string gamePath = appOptions.GamePath;
gameFolder = Path.GetDirectoryName(gamePath);
if (string.IsNullOrEmpty(gameFolder))
{
gameFileName = default;
return false;
}
gameFileName = Path.GetFileName(gamePath);
if (string.IsNullOrEmpty(gameFileName))
{
return false;
}
return true;
}
public static bool TryGetGamePathAndGameFileName(this AppOptions appOptions, out string gamePath, [NotNullWhen(true)] out string? gameFileName)
{
gamePath = appOptions.GamePath;
gameFileName = Path.GetFileName(gamePath);
if (string.IsNullOrEmpty(gameFileName))
{
return false;
}
return true;
}
public static NameValue<CultureInfo>? GetCurrentCultureForSelectionOrDefault(this AppOptions appOptions)
{
return appOptions.Cultures.SingleOrDefault(c => c.Value == appOptions.CurrentCulture);

View File

@@ -13,11 +13,11 @@ namespace Snap.Hutao.Service.Game.Configuration;
[Injection(InjectAs.Singleton, typeof(IGameChannelOptionsService))]
internal sealed partial class GameChannelOptionsService : IGameChannelOptionsService
{
private readonly AppOptions appOptions;
private readonly LaunchOptions launchOptions;
public ChannelOptions GetChannelOptions()
{
string gamePath = appOptions.GamePath;
string gamePath = launchOptions.GamePath;
string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFileName);
bool isOversea = string.Equals(Path.GetFileName(gamePath), GenshinImpactFileName, StringComparison.OrdinalIgnoreCase);
@@ -38,7 +38,7 @@ internal sealed partial class GameChannelOptionsService : IGameChannelOptionsSer
public bool SetChannelOptions(LaunchScheme scheme)
{
string gamePath = appOptions.GamePath;
string gamePath = launchOptions.GamePath;
string? directory = Path.GetDirectoryName(gamePath);
ArgumentException.ThrowIfNullOrEmpty(directory);
string configPath = Path.Combine(directory, ConfigFileName);

View File

@@ -5,6 +5,7 @@ using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Game.Account;
using Snap.Hutao.Service.Game.Configuration;
using Snap.Hutao.Service.Game.Package;
using Snap.Hutao.Service.Game.PathAbstraction;
using Snap.Hutao.Service.Game.Process;
using Snap.Hutao.Service.Game.Scheme;
using System.Collections.ObjectModel;

View File

@@ -6,6 +6,8 @@ using Microsoft.UI.Windowing;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Game.PathAbstraction;
using System.Collections.Immutable;
using System.Globalization;
using Windows.Graphics;
using Windows.Win32.Foundation;
@@ -24,7 +26,10 @@ internal sealed class LaunchOptions : DbStoreOptions
private readonly int primaryScreenHeight;
private readonly int primaryScreenFps;
private string? gamePath;
private ImmutableList<GamePathEntry>? gamePathEntries;
private bool? isEnabled;
private bool? isAdvancedLaunchOptionsEnabled;
private bool? isFullScreen;
private bool? isBorderless;
private bool? isExclusive;
@@ -85,12 +90,32 @@ internal sealed class LaunchOptions : DbStoreOptions
}
}
public string GamePath
{
get => GetOption(ref gamePath, SettingEntry.GamePath);
set => SetOption(ref gamePath, SettingEntry.GamePath, value);
}
public ImmutableList<GamePathEntry> GamePathEntries
{
// Because DbStoreOptions can't detect collection change, We use
// ImmutableList to imply that the whole list needs to be replaced
get => GetOption(ref gamePathEntries, SettingEntry.GamePathEntries, raw => JsonSerializer.Deserialize<ImmutableList<GamePathEntry>>(raw), []);
set => SetOption(ref gamePathEntries, SettingEntry.GamePathEntries, value, value => JsonSerializer.Serialize(value));
}
public bool IsEnabled
{
get => GetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, true);
set => SetOption(ref isEnabled, SettingEntry.LaunchIsLaunchOptionsEnabled, value);
}
public bool IsAdvancedLaunchOptionsEnabled
{
get => GetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled);
set => SetOption(ref isAdvancedLaunchOptionsEnabled, SettingEntry.IsAdvancedLaunchOptionsEnabled, value);
}
public bool IsFullScreen
{
get => GetOption(ref isFullScreen, SettingEntry.LaunchIsFullScreen);

View File

@@ -0,0 +1,87 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
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 TryGetGameFolderAndFileName(this LaunchOptions options, [NotNullWhen(true)] out string? gameFolder, [NotNullWhen(true)] out string? gameFileName)
{
string gamePath = options.GamePath;
gameFolder = Path.GetDirectoryName(gamePath);
if (string.IsNullOrEmpty(gameFolder))
{
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 ImmutableList<GamePathEntry> GetGamePathEntries(this LaunchOptions options, out GamePathEntry? entry)
{
string gamePath = options.GamePath;
if (string.IsNullOrEmpty(gamePath))
{
entry = default;
return options.GamePathEntries;
}
if (options.GamePathEntries.SingleOrDefault(entry => string.Equals(entry.Path, options.GamePath, StringComparison.OrdinalIgnoreCase)) is { } existed)
{
entry = existed;
return options.GamePathEntries;
}
entry = GamePathEntry.Create(options.GamePath);
return [.. options.GamePathEntries, entry];
}
public static ImmutableList<GamePathEntry> RemoveGamePathEntry(this LaunchOptions options, GamePathEntry? entry, out GamePathEntry? selected)
{
if (entry is not null)
{
if (string.Equals(options.GamePath, entry.Path, StringComparison.OrdinalIgnoreCase))
{
options.GamePath = string.Empty;
}
options.GamePathEntries = options.GamePathEntries.Remove(entry);
}
return options.GetGamePathEntries(out selected);
}
public static ImmutableList<GamePathEntry> UpdateGamePathAndRefreshEntries(this LaunchOptions options, string gamePath)
{
options.GamePath = gamePath;
ImmutableList<GamePathEntry> entries = options.GetGamePathEntries(out _);
options.GamePathEntries = entries;
return entries;
}
}

View File

@@ -16,12 +16,12 @@ internal sealed partial class GamePackageService : IGamePackageService
{
private readonly PackageConverter packageConverter;
private readonly IServiceProvider serviceProvider;
private readonly LaunchOptions launchOptions;
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;
public async ValueTask<bool> EnsureGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress)
{
if (!appOptions.TryGetGameFolderAndFileName(out string? gameFolder, out string? gameFileName))
if (!launchOptions.TryGetGameFolderAndFileName(out string? gameFolder, out string? gameFileName))
{
return false;
}
@@ -58,7 +58,7 @@ internal sealed partial class GamePackageService : IGamePackageService
string exeName = launchScheme.IsOversea ? GenshinImpactFileName : YuanShenFileName;
await taskContext.SwitchToMainThreadAsync();
appOptions.GamePath = Path.Combine(gameFolder, exeName);
launchOptions.UpdateGamePathAndRefreshEntries(Path.Combine(gameFolder, exeName));
}
await packageConverter.EnsureDeprecatedFilesAndSdkAsync(resource, gameFolder).ConfigureAwait(false);

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.PathAbstraction;
internal sealed class GamePathEntry
{
[JsonPropertyName("Path")]
public string Path { get; set; } = default!;
[JsonIgnore]
public GamePathKind Kind { get => GetKind(Path); }
public static GamePathEntry Create(string path)
{
return new()
{
Path = path,
};
}
private static GamePathKind GetKind(string path)
{
return GamePathKind.None;
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.PathAbstraction;
internal enum GamePathKind
{
None,
ChineseClient,
OverseaClient,
ChineseCloud,
OverseaCloud,
}

View File

@@ -2,20 +2,21 @@
// Licensed under the MIT license.
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.Game.PathAbstraction;
namespace Snap.Hutao.Service.Game;
namespace Snap.Hutao.Service.Game.PathAbstraction;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IGamePathService))]
internal sealed partial class GamePathService : IGamePathService
{
private readonly IServiceProvider serviceProvider;
private readonly AppOptions appOptions;
private readonly LaunchOptions launchOptions;
public async ValueTask<ValueResult<bool, string>> SilentGetGamePathAsync()
{
// Cannot find in setting
if (string.IsNullOrEmpty(appOptions.GamePath))
if (string.IsNullOrEmpty(launchOptions.GamePath))
{
IGameLocatorFactory locatorFactory = serviceProvider.GetRequiredService<IGameLocatorFactory>();
@@ -40,7 +41,7 @@ internal sealed partial class GamePathService : IGamePathService
if (isOk)
{
// Save result.
appOptions.GamePath = path;
launchOptions.UpdateGamePathAndRefreshEntries(path);
}
else
{
@@ -48,9 +49,9 @@ internal sealed partial class GamePathService : IGamePathService
}
}
if (!string.IsNullOrEmpty(appOptions.GamePath))
if (!string.IsNullOrEmpty(launchOptions.GamePath))
{
return new(true, appOptions.GamePath);
return new(true, launchOptions.GamePath);
}
else
{

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game;
namespace Snap.Hutao.Service.Game.PathAbstraction;
internal interface IGamePathService
{

View File

@@ -52,7 +52,7 @@ internal sealed partial class GameProcessService : IGameProcessService
return;
}
if (!appOptions.TryGetGamePathAndGameFileName(out string gamePath, out string? gameFileName))
if (!launchOptions.TryGetGamePathAndGameFileName(out string gamePath, out string? gameFileName))
{
ArgumentException.ThrowIfNullOrEmpty(gamePath);
return; // null check passing, actually never reach.
@@ -73,7 +73,7 @@ internal sealed partial class GameProcessService : IGameProcessService
await Starward.LaunchForPlayTimeStatisticsAsync(isOversea).ConfigureAwait(false);
}
if (runtimeOptions.IsElevated && appOptions.IsAdvancedLaunchOptionsEnabled && launchOptions.UnlockFps)
if (runtimeOptions.IsElevated && launchOptions.IsAdvancedLaunchOptionsEnabled && launchOptions.UnlockFps)
{
progress.Report(new(LaunchPhase.UnlockingFps, SH.ServiceGameLaunchPhaseUnlockingFps));
try

View File

@@ -38,7 +38,12 @@
<DataTemplate x:Key="GameAccountListTemplate">
<Grid>
<StackPanel Margin="0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="0,12">
<TextBlock Text="{Binding Name}"/>
<TextBlock
Opacity="0.8"
@@ -47,9 +52,9 @@
</StackPanel>
<StackPanel
x:Name="ButtonPanel"
Grid.Column="1"
HorizontalAlignment="Right"
Orientation="Horizontal"
Visibility="Collapsed">
Orientation="Horizontal">
<Button
MinWidth="48"
Margin="4,8"
@@ -78,264 +83,290 @@
FontFamily="{StaticResource SymbolThemeFontFamily}"
ToolTipService.ToolTip="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountRemoveToolTip}"/>
</StackPanel>
</Grid>
</DataTemplate>
<Grid.Resources>
<Storyboard x:Name="ButtonPanelVisibleStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonPanel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="ButtonPanelCollapsedStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonPanel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="PointerEntered">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelVisibleStoryboard}"/>
</mxic:EventTriggerBehavior>
<mxic:EventTriggerBehavior EventName="PointerExited">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelCollapsedStoryboard}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
<DataTemplate x:Key="GamePathEntryListTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Padding="0,16"
VerticalAlignment="Center"
Text="{Binding Path}"/>
<Button
Grid.Column="1"
MinWidth="48"
Margin="4,8,0,8"
VerticalAlignment="Stretch"
Command="{Binding DataContext.RemoveGamePathEntryCommand, Source={StaticResource BindingProxy}}"
CommandParameter="{Binding}"
Content="&#xE74D;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
ToolTipService.ToolTip="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountRemoveToolTip}"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid>
<Grid
Height="{StaticResource AppBarThemeCompactHeight}"
VerticalAlignment="Top"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
IsHitTestVisible="False"/>
<Pivot>
<Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right">
<CommandBar.Content>
<TextBlock
Margin="12,14,12,0"
VerticalAlignment="Center"
Text="{Binding LaunchStatusOptions.LaunchStatus.Description}"/>
</CommandBar.Content>
<AppBarButton
Command="{Binding OpenScreenshotFolderCommand}"
Icon="{shcm:FontIcon Glyph=&#xED25;}"
Label="{shcm:ResourceString Name=ViewPageOpenScreenshotFolderAction}"/>
<AppBarButton
Command="{Binding LaunchCommand}"
Icon="{shcm:FontIcon Glyph=&#xE7FC;}"
Label="{shcm:ResourceString Name=ViewPageLaunchGameAction}"/>
</CommandBar>
</Pivot.RightHeader>
<Grid Visibility="{Binding GamePathSelectedAndValid, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid
Height="{StaticResource AppBarThemeCompactHeight}"
VerticalAlignment="Top"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
IsHitTestVisible="False"/>
<Pivot>
<Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right">
<CommandBar.Content>
<TextBlock
Margin="12,14,12,0"
VerticalAlignment="Center"
Text="{Binding LaunchStatusOptions.LaunchStatus.Description}"/>
</CommandBar.Content>
<AppBarButton
Command="{Binding OpenScreenshotFolderCommand}"
Icon="{shcm:FontIcon Glyph=&#xED25;}"
Label="{shcm:ResourceString Name=ViewPageOpenScreenshotFolderAction}"/>
<AppBarButton
Command="{Binding ResetGamePathCommand}"
Icon="{shcm:FontIcon Glyph=&#xEBC4;}"
Label="{shcm:ResourceString Name=ViewPageResetGamePathAction}"/>
<AppBarButton
Command="{Binding LaunchCommand}"
Icon="{shcm:FontIcon Glyph=&#xE7FC;}"
Label="{shcm:ResourceString Name=ViewPageLaunchGameAction}"/>
</CommandBar>
</Pivot.RightHeader>
<PivotItem Header="{shcm:ResourceString Name=ViewPageLaunchGameOptionsHeader}">
<ScrollViewer Grid.RowSpan="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="1000"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="16" Spacing="{StaticResource SettingsCardSpacing}">
<InfoBar
IsClosable="False"
IsOpen="True"
Message="{shcm:ResourceString Name=ViewPageLaunchGameConfigurationSaveHint}"
Severity="Informational"/>
<!-- 文件 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameFileHeader}"/>
<cwc:SettingsCard
Header="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8AB;}"
IsEnabled="{Binding RuntimeOptions.IsElevated}">
<cwc:SettingsCard.Description>
<StackPanel>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeDescription}"/>
<TextBlock
Foreground="{ThemeResource SystemControlErrorTextForegroundBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeWarning}"/>
</StackPanel>
</cwc:SettingsCard.Description>
<StackPanel Orientation="Horizontal">
<shvc:Elevation Margin="0,0,36,0" Visibility="{Binding RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
<shc:SizeRestrictedContentControl>
<shccs:ComboBox2
DisplayMemberPath="DisplayName"
EnableMemberPath="IsNotCompatOnly"
ItemsSource="{Binding KnownSchemes}"
SelectedItem="{Binding SelectedScheme, Mode=TwoWay}"
Style="{StaticResource DefaultComboBoxStyle}"/>
</shc:SizeRestrictedContentControl>
</StackPanel>
</cwc:SettingsCard>
<!-- 注册表 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameRegistryHeader}"/>
<cwc:SettingsCard
ActionIconToolTip="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountDetectAction}"
Command="{Binding DetectGameAccountCommand}"
Description="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE748;}"
IsClickEnabled="True"/>
<Border Style="{StaticResource BorderCardStyle}">
<ListView
ItemTemplate="{StaticResource GameAccountListTemplate}"
ItemsSource="{Binding GameAccounts}"
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
</Border>
<!-- 进程 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameProcessHeader}"/>
<cwc:SettingsExpander
shch:SettingsExpanderHelper.IsItemsEnabled="{Binding LaunchOptions.IsEnabled}"
Description="{shcm:ResourceString Name=ViewPageLaunchGameArgumentsDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameArgumentsHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE943;}"
IsExpanded="True">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsEnabled, Mode=TwoWay}"/>
<cwc:SettingsExpander.Items>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceExclusiveDescription}" Header="-window-mode exclusive">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsExclusive, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceFullscreenDescription}" Header="-screen-fullscreen">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsFullScreen, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceBorderlessDescription}" Header="-popupwindow">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsBorderless, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioDescription}" Header="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioHeader}">
<shc:SizeRestrictedContentControl Margin="0,0,136,0">
<ComboBox
ItemsSource="{Binding LaunchOptions.AspectRatios}"
PlaceholderText="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioPlaceHolder}"
SelectedItem="{Binding LaunchOptions.SelectedAspectRatio, Mode=TwoWay}"/>
</shc:SizeRestrictedContentControl>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenWidthDescription}" Header="-screen-width">
<StackPanel Orientation="Horizontal" Spacing="16">
<NumberBox
Width="156"
Padding="12,6,0,0"
VerticalAlignment="Center"
IsEnabled="{Binding LaunchOptions.IsScreenWidthEnabled}"
Value="{Binding LaunchOptions.ScreenWidth, Mode=TwoWay}"/>
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsScreenWidthEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenHeightDescription}" Header="-screen-height">
<StackPanel Orientation="Horizontal" Spacing="16">
<NumberBox
Width="156"
Padding="12,6,0,0"
VerticalAlignment="Center"
IsEnabled="{Binding LaunchOptions.IsScreenHeightEnabled}"
Value="{Binding LaunchOptions.ScreenHeight, Mode=TwoWay}"/>
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsScreenHeightEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameMonitorsDescription}" Header="-monitor">
<StackPanel Orientation="Horizontal" Spacing="16">
<shc:SizeRestrictedContentControl>
<ComboBox
DisplayMemberPath="Name"
IsEnabled="{Binding LaunchOptions.IsMonitorEnabled}"
ItemsSource="{Binding LaunchOptions.Monitors}"
SelectedItem="{Binding LaunchOptions.Monitor, Mode=TwoWay}"/>
</shc:SizeRestrictedContentControl>
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsMonitorEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE785;}"
IsEnabled="{Binding RuntimeOptions.IsElevated}"
Visibility="{Binding AppOptions.IsAdvancedLaunchOptionsEnabled, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel Orientation="Horizontal">
<shvc:Elevation Margin="0,0,36,0" Visibility="{Binding RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
<NumberBox
MinWidth="156"
Padding="10,8,0,0"
Maximum="720"
Minimum="60"
SpinButtonPlacementMode="Inline"
Value="{Binding LaunchOptions.TargetFps, Mode=TwoWay}"/>
<ToggleSwitch
Width="120"
IsOn="{Binding LaunchOptions.UnlockFps, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOff}"
OnContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOn}"/>
</StackPanel>
</cwc:SettingsCard>
<!-- 进程间 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameInterProcessHeader}"/>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGamePlayTimeDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGamePlayTimeHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xEC92;}">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.UseStarwardPlayTimeStatistics, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGameDiscordActivityDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameDiscordActivityHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8CF;}">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.SetDiscordActivityWhenPlaying, Mode=TwoWay}"/>
</cwc:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
</PivotItem>
<PivotItem Header="{shcm:ResourceString Name=ViewPageLaunchGameResourceHeader}">
<Grid>
<ScrollViewer Visibility="{Binding GameResource, Converter={StaticResource EmptyObjectToBoolConverter}}">
<PivotItem Header="{shcm:ResourceString Name=ViewPageLaunchGameOptionsHeader}">
<ScrollViewer Grid.RowSpan="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="1000"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<shvc:LaunchGameResourceExpander
Margin="16,16,16,0"
cw:Effects.Shadow="{ThemeResource CompatCardShadow}"
DataContext="{Binding GameResource.PreDownloadGame.Latest, Mode=OneWay}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameResourcePreDownloadHeader}"
Visibility="{Binding FallbackValue={StaticResource VisibilityCollapsed}, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
<ItemsControl
Margin="0,0,0,0"
ItemTemplate="{StaticResource GameResourceTemplate}"
ItemsSource="{Binding GameResource.PreDownloadGame.Diffs, Mode=OneWay}"/>
<shvc:LaunchGameResourceExpander
Margin="16,16,16,0"
cw:Effects.Shadow="{ThemeResource CompatCardShadow}"
DataContext="{Binding GameResource.Game.Latest, Mode=OneWay}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameResourceLatestHeader}"/>
<ItemsControl
Margin="0,0,0,16"
ItemTemplate="{StaticResource GameResourceTemplate}"
ItemsSource="{Binding GameResource.Game.Diffs, Mode=OneWay}"/>
<StackPanel Margin="16" Spacing="{StaticResource SettingsCardSpacing}">
<InfoBar
IsClosable="False"
IsOpen="True"
Message="{shcm:ResourceString Name=ViewPageLaunchGameConfigurationSaveHint}"
Severity="Informational"/>
<!-- 文件 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameFileHeader}"/>
<cwc:SettingsCard
Header="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8AB;}"
IsEnabled="{Binding RuntimeOptions.IsElevated}">
<cwc:SettingsCard.Description>
<StackPanel>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeDescription}"/>
<TextBlock
Foreground="{ThemeResource SystemControlErrorTextForegroundBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{shcm:ResourceString Name=ViewPageLaunchGameSwitchSchemeWarning}"/>
</StackPanel>
</cwc:SettingsCard.Description>
<StackPanel Orientation="Horizontal">
<shvc:Elevation Margin="0,0,36,0" Visibility="{Binding RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
<shc:SizeRestrictedContentControl>
<shccs:ComboBox2
DisplayMemberPath="DisplayName"
EnableMemberPath="IsNotCompatOnly"
ItemsSource="{Binding KnownSchemes}"
SelectedItem="{Binding SelectedScheme, Mode=TwoWay}"
Style="{StaticResource DefaultComboBoxStyle}"/>
</shc:SizeRestrictedContentControl>
</StackPanel>
</cwc:SettingsCard>
<!-- 注册表 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameRegistryHeader}"/>
<cwc:SettingsCard
ActionIconToolTip="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountDetectAction}"
Command="{Binding DetectGameAccountCommand}"
Description="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameSwitchAccountHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE748;}"
IsClickEnabled="True"/>
<Border Style="{StaticResource BorderCardStyle}">
<ListView
ItemTemplate="{StaticResource GameAccountListTemplate}"
ItemsSource="{Binding GameAccounts}"
SelectedItem="{Binding SelectedGameAccount, Mode=TwoWay}"/>
</Border>
<!-- 进程 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameProcessHeader}"/>
<cwc:SettingsExpander
shch:SettingsExpanderHelper.IsItemsEnabled="{Binding LaunchOptions.IsEnabled}"
Description="{shcm:ResourceString Name=ViewPageLaunchGameArgumentsDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameArgumentsHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE943;}"
IsExpanded="True">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsEnabled, Mode=TwoWay}"/>
<cwc:SettingsExpander.Items>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceExclusiveDescription}" Header="-window-mode exclusive">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsExclusive, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceFullscreenDescription}" Header="-screen-fullscreen">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsFullScreen, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceBorderlessDescription}" Header="-popupwindow">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsBorderless, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioDescription}" Header="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioHeader}">
<shc:SizeRestrictedContentControl Margin="0,0,136,0">
<ComboBox
ItemsSource="{Binding LaunchOptions.AspectRatios}"
PlaceholderText="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceAspectRatioPlaceHolder}"
SelectedItem="{Binding LaunchOptions.SelectedAspectRatio, Mode=TwoWay}"/>
</shc:SizeRestrictedContentControl>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenWidthDescription}" Header="-screen-width">
<StackPanel Orientation="Horizontal" Spacing="16">
<NumberBox
Width="156"
Padding="12,6,0,0"
VerticalAlignment="Center"
IsEnabled="{Binding LaunchOptions.IsScreenWidthEnabled}"
Value="{Binding LaunchOptions.ScreenWidth, Mode=TwoWay}"/>
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsScreenWidthEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameAppearanceScreenHeightDescription}" Header="-screen-height">
<StackPanel Orientation="Horizontal" Spacing="16">
<NumberBox
Width="156"
Padding="12,6,0,0"
VerticalAlignment="Center"
IsEnabled="{Binding LaunchOptions.IsScreenHeightEnabled}"
Value="{Binding LaunchOptions.ScreenHeight, Mode=TwoWay}"/>
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsScreenHeightEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
<cwc:SettingsCard Description="{shcm:ResourceString Name=ViewPageLaunchGameMonitorsDescription}" Header="-monitor">
<StackPanel Orientation="Horizontal" Spacing="16">
<shc:SizeRestrictedContentControl>
<ComboBox
DisplayMemberPath="Name"
IsEnabled="{Binding LaunchOptions.IsMonitorEnabled}"
ItemsSource="{Binding LaunchOptions.Monitors}"
SelectedItem="{Binding LaunchOptions.Monitor, Mode=TwoWay}"/>
</shc:SizeRestrictedContentControl>
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsMonitorEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE785;}"
IsEnabled="{Binding RuntimeOptions.IsElevated}"
Visibility="{Binding LaunchOptions.IsAdvancedLaunchOptionsEnabled, Converter={StaticResource BoolToVisibilityConverter}}">
<StackPanel Orientation="Horizontal">
<shvc:Elevation Margin="0,0,36,0" Visibility="{Binding RuntimeOptions.IsElevated, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
<NumberBox
MinWidth="156"
Padding="10,8,0,0"
Maximum="720"
Minimum="60"
SpinButtonPlacementMode="Inline"
Value="{Binding LaunchOptions.TargetFps, Mode=TwoWay}"/>
<ToggleSwitch
Width="120"
IsOn="{Binding LaunchOptions.UnlockFps, Mode=TwoWay}"
OffContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOff}"
OnContent="{shcm:ResourceString Name=ViewPageLaunchGameUnlockFpsOn}"/>
</StackPanel>
</cwc:SettingsCard>
<!-- 进程间 -->
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageLaunchGameInterProcessHeader}"/>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGamePlayTimeDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGamePlayTimeHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xEC92;}">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.UseStarwardPlayTimeStatistics, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard
Description="{shcm:ResourceString Name=ViewPageLaunchGameDiscordActivityDescription}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameDiscordActivityHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE8CF;}">
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.SetDiscordActivityWhenPlaying, Mode=TwoWay}"/>
</cwc:SettingsCard>
</StackPanel>
</Grid>
</ScrollViewer>
<shvc:LoadingView IsLoading="{Binding GameResource, Converter={StaticResource EmptyObjectToBoolRevertConverter}}"/>
</Grid>
</PivotItem>
</Pivot>
</PivotItem>
<PivotItem Header="{shcm:ResourceString Name=ViewPageLaunchGameResourceHeader}">
<Grid>
<ScrollViewer Visibility="{Binding GameResource, Converter={StaticResource EmptyObjectToBoolConverter}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="1000"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<shvc:LaunchGameResourceExpander
Margin="16,16,16,0"
cw:Effects.Shadow="{ThemeResource CompatCardShadow}"
DataContext="{Binding GameResource.PreDownloadGame.Latest, Mode=OneWay}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameResourcePreDownloadHeader}"
Visibility="{Binding FallbackValue={StaticResource VisibilityCollapsed}, Converter={StaticResource EmptyObjectToVisibilityConverter}}"/>
<ItemsControl
Margin="0,0,0,0"
ItemTemplate="{StaticResource GameResourceTemplate}"
ItemsSource="{Binding GameResource.PreDownloadGame.Diffs, Mode=OneWay}"/>
<shvc:LaunchGameResourceExpander
Margin="16,16,16,0"
cw:Effects.Shadow="{ThemeResource CompatCardShadow}"
DataContext="{Binding GameResource.Game.Latest, Mode=OneWay}"
Header="{shcm:ResourceString Name=ViewPageLaunchGameResourceLatestHeader}"/>
<ItemsControl
Margin="0,0,0,16"
ItemTemplate="{StaticResource GameResourceTemplate}"
ItemsSource="{Binding GameResource.Game.Diffs, Mode=OneWay}"/>
</StackPanel>
</Grid>
</ScrollViewer>
<shvc:LoadingView IsLoading="{Binding GameResource, Converter={StaticResource EmptyObjectToBoolRevertConverter}}"/>
</Grid>
</PivotItem>
</Pivot>
</Grid>
<Grid Visibility="{Binding GamePathSelectedAndValid, Converter={StaticResource BoolToVisibilityRevertConverter}}">
<StackPanel
MaxWidth="600"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="3">
<Border Style="{ThemeResource BorderCardStyle}">
<ListView
ItemTemplate="{StaticResource GamePathEntryListTemplate}"
ItemsSource="{Binding GamePathEntries}"
SelectedItem="{Binding SelectedGamePathEntry, Mode=TwoWay}"/>
</Border>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingSetGamePathAction}"
Command="{Binding SetGamePathCommand}"
Header="{shcm:ResourceString Name=ViewPageSettingSetGamePathHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE7FC;}"
IsClickEnabled="True">
<cwc:SettingsCard.Description>
<StackPanel>
<TextBlock Foreground="{ThemeResource SystemErrorTextColor}" Text="{shcm:ResourceString Name=ViewPageSettingSetGamePathHint}"/>
</StackPanel>
</cwc:SettingsCard.Description>
</cwc:SettingsCard>
</StackPanel>
</Grid>
</Grid>
</shc:ScopedPage>

View File

@@ -307,20 +307,6 @@
</cwc:SettingsExpander>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingGameHeader}"/>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingSetGamePathAction}"
Command="{Binding SetGamePathCommand}"
Header="{shcm:ResourceString Name=ViewPageSettingSetGamePathHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE7FC;}"
IsClickEnabled="True">
<cwc:SettingsCard.Description>
<StackPanel>
<TextBlock Foreground="{ThemeResource SystemErrorTextColor}" Text="{shcm:ResourceString Name=ViewPageSettingSetGamePathHint}"/>
<TextBlock Text="{Binding AppOptions.GamePath}"/>
</StackPanel>
</cwc:SettingsCard.Description>
</cwc:SettingsCard>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingSetGamePathAction}"
@@ -418,13 +404,13 @@
<TextBlock
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Text="{shcm:ResourceString Name=ViewPageSettingFeaturesDangerousHint}"
Visibility="{Binding AppOptions.IsAdvancedLaunchOptionsEnabled, Converter={StaticResource BoolToVisibilityConverter}}"/>
<TextBlock Text="{shcm:ResourceString Name=ViewPageSettingIsAdvancedLaunchOptionsEnabledDescription}" Visibility="{Binding AppOptions.IsAdvancedLaunchOptionsEnabled, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
Visibility="{Binding LaunchOptions.IsAdvancedLaunchOptionsEnabled, Converter={StaticResource BoolToVisibilityConverter}}"/>
<TextBlock Text="{shcm:ResourceString Name=ViewPageSettingIsAdvancedLaunchOptionsEnabledDescription}" Visibility="{Binding LaunchOptions.IsAdvancedLaunchOptionsEnabled, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
</StackPanel>
</cwc:SettingsCard.Description>
<StackPanel Orientation="Horizontal">
<shvc:Elevation Visibility="{Binding HutaoOptions.IsElevated, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
<ToggleSwitch Width="120" IsOn="{Binding AppOptions.IsAdvancedLaunchOptionsEnabled, Mode=TwoWay}"/>
<ToggleSwitch Width="120" IsOn="{Binding LaunchOptions.IsAdvancedLaunchOptionsEnabled, Mode=TwoWay}"/>
</StackPanel>
</cwc:SettingsCard>
<cwc:SettingsCard
@@ -435,10 +421,7 @@
IsClickEnabled="True">
<cwc:SettingsCard.Description>
<StackPanel>
<TextBlock
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Text="{shcm:ResourceString Name=ViewPageSettingDangerousHint}"
Visibility="{Binding AppOptions.IsAdvancedLaunchOptionsEnabled, Converter={StaticResource BoolToVisibilityConverter}}"/>
<TextBlock Foreground="{ThemeResource SystemFillColorCriticalBrush}" Text="{shcm:ResourceString Name=ViewPageSettingDangerousHint}"/>
<TextBlock Text="{shcm:ResourceString Name=ViewPageSettingDeleteUserDescription}"/>
</StackPanel>
</cwc:SettingsCard.Description>

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core;
@@ -11,13 +12,15 @@ using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Configuration;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.Game.Package;
using Snap.Hutao.Service.Game.PathAbstraction;
using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
@@ -38,7 +41,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
private readonly IContentDialogFactory contentDialogFactory;
private readonly LaunchStatusOptions launchStatusOptions;
private readonly INavigationService navigationService;
private readonly IGameLocatorFactory gameLocatorFactory;
private readonly IProgressFactory progressFactory;
private readonly IInfoBarService infoBarService;
private readonly ResourceClient resourceClient;
@@ -54,6 +57,9 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
private ObservableCollection<GameAccount>? gameAccounts;
private GameAccount? selectedGameAccount;
private GameResource? gameResource;
private bool gamePathSelectedAndValid;
private ImmutableList<GamePathEntry> gamePathEntries;
private GamePathEntry? selectedGamePathEntry;
public List<LaunchScheme> KnownSchemes { get; } = KnownLaunchSchemes.Get();
@@ -99,68 +105,127 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
public GameResource? GameResource { get => gameResource; set => SetProperty(ref gameResource, value); }
protected override async ValueTask<bool> InitializeUIAsync()
public bool GamePathSelectedAndValid
{
if (File.Exists(AppOptions.GamePath))
get => gamePathSelectedAndValid;
set
{
try
if (SetProperty(ref gamePathSelectedAndValid, value) && value)
{
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
InitializeUICoreAsync().SafeForget();
}
}
}
public ImmutableList<GamePathEntry> GamePathEntries { get => gamePathEntries; set => SetProperty(ref gamePathEntries, value); }
public GamePathEntry? SelectedGamePathEntry
{
get => selectedGamePathEntry;
set => UpdateSelectedGamePathEntry(value, true);
}
protected override ValueTask<bool> InitializeUIAsync()
{
GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
SelectedGamePathEntry = entry;
return ValueTask.FromResult(true);
}
private async ValueTask InitializeUICoreAsync()
{
try
{
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
ChannelOptions options = gameService.GetChannelOptions();
if (string.IsNullOrEmpty(options.ConfigFilePath))
{
ChannelOptions options = gameService.GetChannelOptions();
if (string.IsNullOrEmpty(options.ConfigFilePath))
try
{
try
SelectedScheme = KnownSchemes.Single(scheme => scheme.Equals(options));
}
catch (InvalidOperationException)
{
if (!IgnoredInvalidChannelOptions.Contains(options))
{
SelectedScheme = KnownSchemes.Single(scheme => scheme.Equals(options));
}
catch (InvalidOperationException)
{
if (!IgnoredInvalidChannelOptions.Contains(options))
{
// 后台收集
throw new NotSupportedException($"不支持的 MultiChannel: {options}");
}
// 后台收集
throw new NotSupportedException($"不支持的 MultiChannel: {options}");
}
}
else
{
infoBarService.Warning(SH.FormatViewModelLaunchGameMultiChannelReadFail(options.ConfigFilePath));
}
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
await taskContext.SwitchToMainThreadAsync();
GameAccounts = accounts;
// Sync uid
if (memoryCache.TryRemove(DesiredUid, out object? value) && value is string uid)
{
SelectedGameAccount = GameAccounts.FirstOrDefault(g => g.AttachUid == uid);
}
// Try set to the current account.
SelectedGameAccount ??= gameService.DetectCurrentGameAccount();
}
}
catch (UserdataCorruptedException ex)
{
infoBarService.Error(ex);
}
catch (OperationCanceledException)
{
else
{
infoBarService.Warning(SH.FormatViewModelLaunchGameMultiChannelReadFail(options.ConfigFilePath));
}
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;
await taskContext.SwitchToMainThreadAsync();
GameAccounts = accounts;
// Sync uid
if (memoryCache.TryRemove(DesiredUid, out object? value) && value is string uid)
{
SelectedGameAccount = GameAccounts.FirstOrDefault(g => g.AttachUid == uid);
}
// Try set to the current account.
SelectedGameAccount ??= gameService.DetectCurrentGameAccount();
}
}
else
catch (UserdataCorruptedException ex)
{
infoBarService.Warning(SH.ViewModelLaunchGamePathInvalid);
await taskContext.SwitchToMainThreadAsync();
await navigationService
.NavigateAsync<View.Page.SettingPage>(INavigationAwaiter.Default, true)
.ConfigureAwait(false);
infoBarService.Error(ex);
}
catch (OperationCanceledException)
{
}
}
private void UpdateSelectedGamePathEntry(GamePathEntry? value, bool setBack)
{
if (SetProperty(ref selectedGamePathEntry, value) && setBack)
{
launchOptions.GamePath = value?.Path ?? string.Empty;
GamePathSelectedAndValid = File.Exists(launchOptions.GamePath);
}
}
[Command("SetGamePathCommand")]
private async Task SetGamePathAsync()
{
IGameLocator locator = gameLocatorFactory.Create(GameLocationSource.Manual);
(bool isOk, string path) = await locator.LocateGamePathAsync().ConfigureAwait(false);
if (!isOk)
{
return;
}
return true;
await taskContext.SwitchToMainThreadAsync();
try
{
GamePathEntries = launchOptions.UpdateGamePathAndRefreshEntries(path);
}
catch (SqliteException ex)
{
// 文件夹权限不足,无法写入数据库
infoBarService.Error(ex, SH.ViewModelSettingSetGamePathDatabaseFailedTitle);
}
}
[Command("ResetGamePathCommand")]
private void ResetGamePath()
{
SelectedGamePathEntry = default;
}
[Command("RemoveGamePathEntryCommand")]
private void RemoveGamePathEntry(GamePathEntry? entry)
{
GamePathEntries = launchOptions.RemoveGamePathEntry(entry, out GamePathEntry? selected);
SelectedGamePathEntry = selected;
}
[Command("LaunchCommand")]
@@ -187,6 +252,11 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
infoBarService.Warning(SH.ViewModelLaunchGameEnsureGameResourceFail);
return;
}
else
{
GamePathEntries = launchOptions.GetGamePathEntries(out GamePathEntry? entry);
UpdateSelectedGamePathEntry(entry, false);
}
}
if (SelectedGameAccount is not null)
@@ -265,7 +335,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
[Command("OpenScreenshotFolderCommand")]
private async Task OpenScreenshotFolderAsync()
{
string game = appOptions.GamePath;
string game = LaunchOptions.GamePath;
string? directory = Path.GetDirectoryName(game);
ArgumentException.ThrowIfNullOrEmpty(directory);
string screenshot = Path.Combine(directory, "ScreenShot");

View File

@@ -16,6 +16,7 @@ using Snap.Hutao.Factory.Picker;
using Snap.Hutao.Model;
using Snap.Hutao.Service;
using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Game;
using Snap.Hutao.Service.Game.Locator;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Navigation;
@@ -53,6 +54,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
private readonly HutaoUserOptions hutaoUserOptions;
private readonly IInfoBarService infoBarService;
private readonly RuntimeOptions runtimeOptions;
private readonly LaunchOptions launchOptions;
private readonly HotKeyOptions hotKeyOptions;
private readonly IUserService userService;
private readonly ITaskContext taskContext;
@@ -74,6 +76,8 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
public HotKeyOptions HotKeyOptions { get => hotKeyOptions; }
public LaunchOptions LaunchOptions { get => launchOptions; }
public HutaoPassportViewModel Passport { get => hutaoPassportViewModel; }
public NameValue<BackdropType>? SelectedBackdropType
@@ -157,27 +161,6 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
await Launcher.LaunchUriAsync(new("ms-windows-store://pdp/?productid=9PH4NXJ2JN52"));
}
[Command("SetGamePathCommand")]
private async Task SetGamePathAsync()
{
IGameLocator locator = gameLocatorFactory.Create(GameLocationSource.Manual);
(bool isOk, string path) = await locator.LocateGamePathAsync().ConfigureAwait(false);
if (isOk)
{
await taskContext.SwitchToMainThreadAsync();
try
{
AppOptions.GamePath = path;
}
catch (SqliteException ex)
{
// 文件夹权限不足,无法写入数据库
infoBarService.Error(ex, SH.ViewModelSettingSetGamePathDatabaseFailedTitle);
}
}
}
[Command("SetPowerShellPathCommand")]
private async Task SetPowerShellPathAsync()
{
@@ -193,7 +176,7 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
[Command("DeleteGameWebCacheCommand")]
private void DeleteGameWebCache()
{
string gamePath = AppOptions.GamePath;
string gamePath = launchOptions.GamePath;
if (!string.IsNullOrEmpty(gamePath))
{