mirror of
https://github.com/wangdage12/Snap.Hutao.git
synced 2026-02-18 02:42:15 +08:00
Compare commits
2 Commits
1.18.0.0_T
...
56c36a01ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56c36a01ae | ||
|
|
da6f248509 |
BIN
bin/unlockfps.exe
Normal file
BIN
bin/unlockfps.exe
Normal file
Binary file not shown.
45
src/Snap.Hutao/Snap.Hutao/Factory/Process/NullProcess.cs
Normal file
45
src/Snap.Hutao/Snap.Hutao/Factory/Process/NullProcess.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core.Diagnostics;
|
||||||
|
using Snap.Hutao.Win32.Foundation;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Factory.Process;
|
||||||
|
|
||||||
|
internal sealed class NullProcess : IProcess
|
||||||
|
{
|
||||||
|
public int Id => 0;
|
||||||
|
|
||||||
|
public nint Handle => 0;
|
||||||
|
|
||||||
|
public HWND MainWindowHandle => default;
|
||||||
|
|
||||||
|
public bool HasExited => true;
|
||||||
|
|
||||||
|
public int ExitCode => 0;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeMainThread()
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForExit()
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Kill()
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ internal static class WeaponIds
|
|||||||
13502U, 13505U,
|
13502U, 13505U,
|
||||||
14501U, 14502U,
|
14501U, 14502U,
|
||||||
15501U, 15502U,
|
15501U, 15502U,
|
||||||
15515U, 15518U
|
15515U, 11518U
|
||||||
];
|
];
|
||||||
|
|
||||||
public static bool IsOrangeStandardWish(in WeaponId weaponId)
|
public static bool IsOrangeStandardWish(in WeaponId weaponId)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<Package
|
<Package
|
||||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="60568DGPStudio.SnapHutao"
|
Name="60568DGPStudio.SnapHutao"
|
||||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||||
Version="1.17.4.0" />
|
Version="1.18.0.0" />
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Snap Hutao</DisplayName>
|
<DisplayName>Snap Hutao</DisplayName>
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core.Setting;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Island;
|
||||||
|
|
||||||
|
internal static class FpsConfigTest
|
||||||
|
{
|
||||||
|
// 测试用,手动更新FPS配置文件
|
||||||
|
public static void TestConfigUpdate()
|
||||||
|
{
|
||||||
|
// 直接从LocalSetting读取当前FPS设置
|
||||||
|
int currentFps = LocalSetting.Get(SettingKeys.LaunchTargetFps, 60);
|
||||||
|
|
||||||
|
// 配置文件路径
|
||||||
|
string configPath = Path.Combine(AppContext.BaseDirectory, "fps_config.ini");
|
||||||
|
|
||||||
|
// 读取当前配置
|
||||||
|
if (File.Exists(configPath))
|
||||||
|
{
|
||||||
|
string[] lines = File.ReadAllLines(configPath);
|
||||||
|
int configFps = 60;
|
||||||
|
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
if (line.StartsWith("FPS="))
|
||||||
|
{
|
||||||
|
configFps = int.Parse(line.Substring(4));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Current FPS from LocalSetting: {currentFps}");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Current FPS from config file: {configFps}");
|
||||||
|
|
||||||
|
if (currentFps != configFps)
|
||||||
|
{
|
||||||
|
// 更新配置文件
|
||||||
|
for (int i = 0; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
if (lines[i].StartsWith("FPS="))
|
||||||
|
{
|
||||||
|
lines[i] = $"FPS={currentFps}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllLines(configPath, lines);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Updated config file with FPS: {currentFps}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core;
|
||||||
|
using Snap.Hutao.Core.Diagnostics;
|
||||||
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
|
using Snap.Hutao.Core.Setting;
|
||||||
|
using Snap.Hutao.Service.Game.FileSystem;
|
||||||
|
using Snap.Hutao.Service.Game.Launching.Context;
|
||||||
|
using Snap.Hutao.Web.Hutao;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Island;
|
||||||
|
|
||||||
|
internal sealed class GameFpsUnlockInterop : IGameIslandInterop, IDisposable
|
||||||
|
{
|
||||||
|
private const string UnlockerExecutableName = "unlockfps.exe";
|
||||||
|
private const string UnlockerConfigName = "fps_config.ini";
|
||||||
|
|
||||||
|
private readonly bool resume;
|
||||||
|
|
||||||
|
private string? unlockerPath;
|
||||||
|
private string? gamePath;
|
||||||
|
private Process? unlockerProcess;
|
||||||
|
|
||||||
|
public GameFpsUnlockInterop(bool resume)
|
||||||
|
{
|
||||||
|
this.resume = resume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask BeforeAsync(BeforeLaunchExecutionContext context)
|
||||||
|
{
|
||||||
|
if (resume)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取unlocker.exe路径,放在Snap.Hutao同一目录下
|
||||||
|
string hutaoDirectory = AppContext.BaseDirectory;
|
||||||
|
unlockerPath = Path.Combine(hutaoDirectory, UnlockerExecutableName);
|
||||||
|
|
||||||
|
if (!File.Exists(unlockerPath))
|
||||||
|
{
|
||||||
|
throw HutaoException.InvalidOperation("未找到unlockfps.exe文件,请将genshin-fps-unlock-master编译后的unlockfps.exe放置在Snap.Hutao同目录下");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取游戏路径
|
||||||
|
gamePath = context.FileSystem.GameFilePath;
|
||||||
|
|
||||||
|
// 验证游戏路径
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"Game path from Snap.Hutao: {gamePath}",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Info);
|
||||||
|
|
||||||
|
|
||||||
|
if (!File.Exists(gamePath))
|
||||||
|
{
|
||||||
|
throw HutaoException.InvalidOperation($"游戏文件不存在: {gamePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建配置文件
|
||||||
|
await CreateUnlockerConfigAsync(context).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// 启动解锁器进程
|
||||||
|
await StartUnlockerProcessAsync(context, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask WaitForExitAsync(LaunchExecutionContext context, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (resume)
|
||||||
|
{
|
||||||
|
// 恢复模式下,尝试连接已存在的解锁器进程
|
||||||
|
await MonitorExistingUnlockerAsync(context, token).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监控解锁器进程状态(解锁器会自动启动并监控游戏)
|
||||||
|
await MonitorUnlockerProcessAsync(context, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask CreateUnlockerConfigAsync(BeforeLaunchExecutionContext context)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(gamePath))
|
||||||
|
{
|
||||||
|
throw HutaoException.NotSupported("游戏路径未初始化");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接在unlocker同目录创建配置文件
|
||||||
|
string unlockerConfigPath = Path.Combine(Path.GetDirectoryName(unlockerPath)!, UnlockerConfigName);
|
||||||
|
int targetFps = context.LaunchOptions.TargetFps.Value;
|
||||||
|
|
||||||
|
string configContent = $"[Setting]\nPath={gamePath}\nFPS={targetFps}";
|
||||||
|
|
||||||
|
// 添加重试机制处理可能的权限问题
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await File.WriteAllTextAsync(unlockerConfigPath, configContent).ConfigureAwait(false);
|
||||||
|
break; // 成功写入,退出循环
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
if (i == 2)
|
||||||
|
{
|
||||||
|
throw HutaoException.InvalidOperation($"无法写入配置文件 {unlockerConfigPath},请检查权限");
|
||||||
|
}
|
||||||
|
await Task.Delay(500).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
if (i == 2)
|
||||||
|
{
|
||||||
|
throw HutaoException.InvalidOperation($"无法写入配置文件 {unlockerConfigPath},文件可能被占用");
|
||||||
|
}
|
||||||
|
await Task.Delay(500).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask StartUnlockerProcessAsync(BeforeLaunchExecutionContext context, CancellationToken token)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
string configPath = Path.Combine(Path.GetDirectoryName(unlockerPath)!, UnlockerConfigName);
|
||||||
|
if (!File.Exists(configPath))
|
||||||
|
{
|
||||||
|
throw HutaoException.InvalidOperation($"配置文件不存在: {configPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string configContent = await File.ReadAllTextAsync(configPath).ConfigureAwait(false);
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"Starting unlocker with config: {configContent}",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Info);
|
||||||
|
|
||||||
|
ProcessStartInfo startInfo = new()
|
||||||
|
{
|
||||||
|
FileName = unlockerPath,
|
||||||
|
WorkingDirectory = Path.GetDirectoryName(unlockerPath),
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Normal,
|
||||||
|
};
|
||||||
|
|
||||||
|
unlockerProcess = new Process { StartInfo = startInfo };
|
||||||
|
|
||||||
|
|
||||||
|
unlockerProcess.Start();
|
||||||
|
|
||||||
|
|
||||||
|
Task outputTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
while (!unlockerProcess.StandardOutput.EndOfStream)
|
||||||
|
{
|
||||||
|
string line = await unlockerProcess.StandardOutput.ReadLineAsync().ConfigureAwait(false);
|
||||||
|
if (line != null)
|
||||||
|
{
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"Unlocker output: {line}",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Task errorTask = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
while (!unlockerProcess.StandardError.EndOfStream)
|
||||||
|
{
|
||||||
|
string line = await unlockerProcess.StandardError.ReadLineAsync().ConfigureAwait(false);
|
||||||
|
if (line != null)
|
||||||
|
{
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"Unlocker error: {line}",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待解锁器初始化
|
||||||
|
await Task.Delay(5000).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw HutaoException.Throw($"启动FPS解锁器失败: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask MonitorExistingUnlockerAsync(LaunchExecutionContext context, CancellationToken token)
|
||||||
|
{
|
||||||
|
// 恢复模式下,检查是否有解锁器进程在运行
|
||||||
|
Process[] processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(UnlockerExecutableName));
|
||||||
|
if (processes.Length == 0)
|
||||||
|
{
|
||||||
|
// 没有找到解锁器进程,但游戏在运行,这是正常情况
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlockerProcess = processes[0];
|
||||||
|
await MonitorUnlockerProcessAsync(context, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask MonitorUnlockerProcessAsync(LaunchExecutionContext context, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (unlockerProcess is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (PeriodicTimer timer = new(TimeSpan.FromSeconds(2)))
|
||||||
|
{
|
||||||
|
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
// 检查解锁器进程状态
|
||||||
|
if (unlockerProcess.HasExited)
|
||||||
|
{
|
||||||
|
// 解锁器已退出,这意味着游戏也已退出
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步FPS设置(如果用户在运行时修改了)
|
||||||
|
await SyncFpsSettingsAsync(context.LaunchOptions).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保解锁器进程已清理
|
||||||
|
CleanupUnlockerProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask SyncFpsSettingsAsync(LaunchOptions launchOptions)
|
||||||
|
{
|
||||||
|
if (unlockerProcess is null || unlockerProcess.HasExited)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string configPath = Path.Combine(Path.GetDirectoryName(unlockerPath)!, UnlockerConfigName);
|
||||||
|
if (File.Exists(configPath))
|
||||||
|
{
|
||||||
|
string[] lines = await File.ReadAllLinesAsync(configPath).ConfigureAwait(false);
|
||||||
|
int currentFps = launchOptions.TargetFps.Value;
|
||||||
|
|
||||||
|
bool needsUpdate = false;
|
||||||
|
for (int i = 0; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
if (lines[i].StartsWith("FPS="))
|
||||||
|
{
|
||||||
|
int configFps = int.Parse(lines[i].Substring(4));
|
||||||
|
if (configFps != currentFps)
|
||||||
|
{
|
||||||
|
lines[i] = $"FPS={currentFps}";
|
||||||
|
needsUpdate = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsUpdate)
|
||||||
|
{
|
||||||
|
// 添加重试机制处理可能的权限问题
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await File.WriteAllLinesAsync(configPath, lines).ConfigureAwait(false);
|
||||||
|
break; // 成功写入,退出循环
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
if (i == 2) // 最后一次尝试
|
||||||
|
{
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"无法写入配置文件 {configPath},请检查权限",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Task.Delay(500).ConfigureAwait(false); // 等待500ms后重试
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
if (i == 2) // 最后一次尝试
|
||||||
|
{
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"无法写入配置文件 {configPath},文件可能被占用",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Task.Delay(500).ConfigureAwait(false); // 等待500ms后重试
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 同步配置失败,记录但不影响主流程
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"Failed to sync FPS settings: {ex.Message}",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void CleanupUnlockerProcess()
|
||||||
|
{
|
||||||
|
if (unlockerProcess is not null && !unlockerProcess.HasExited)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
unlockerProcess.Kill();
|
||||||
|
unlockerProcess.WaitForExit();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 忽略清理过程中的错误
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"Failed to cleanup unlocker process: {ex.Message}",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Warning);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
unlockerProcess.Dispose();
|
||||||
|
unlockerProcess = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
CleanupUnlockerProcess();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ using Snap.Hutao.Service.Game.FileSystem;
|
|||||||
using Snap.Hutao.Service.Game.PathAbstraction;
|
using Snap.Hutao.Service.Game.PathAbstraction;
|
||||||
using Snap.Hutao.Win32;
|
using Snap.Hutao.Win32;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game;
|
namespace Snap.Hutao.Service.Game;
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ internal sealed partial class LaunchOptions : DbStoreOptions, IRestrictedGamePat
|
|||||||
public IObservableProperty<bool> IsSetTargetFrameRateEnabled { get => field ??= CreateProperty(SettingKeys.LaunchIsSetTargetFrameRateEnabled, true); }
|
public IObservableProperty<bool> IsSetTargetFrameRateEnabled { get => field ??= CreateProperty(SettingKeys.LaunchIsSetTargetFrameRateEnabled, true); }
|
||||||
|
|
||||||
[field: MaybeNull]
|
[field: MaybeNull]
|
||||||
public IObservableProperty<int> TargetFps { get => field ??= CreateProperty(SettingKeys.LaunchTargetFps, InitializeTargetFpsWithScreenFps); }
|
public IObservableProperty<int> TargetFps { get => field ??= CreateProperty(SettingKeys.LaunchTargetFps, InitializeTargetFpsWithScreenFps).WithValueChangedCallback(OnTargetFpsChanged); }
|
||||||
|
|
||||||
[field: MaybeNull]
|
[field: MaybeNull]
|
||||||
public IObservableProperty<bool> RemoveOpenTeamProgress { get => field ??= CreateProperty(SettingKeys.LaunchRemoveOpenTeamProgress, false); }
|
public IObservableProperty<bool> RemoveOpenTeamProgress { get => field ??= CreateProperty(SettingKeys.LaunchRemoveOpenTeamProgress, false); }
|
||||||
@@ -165,6 +167,98 @@ internal sealed partial class LaunchOptions : DbStoreOptions, IRestrictedGamePat
|
|||||||
return HutaoNative.Instance.MakeDeviceCapabilities().GetPrimaryScreenVerticalRefreshRate();
|
return HutaoNative.Instance.MakeDeviceCapabilities().GetPrimaryScreenVerticalRefreshRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnTargetFpsChanged(int newFps)
|
||||||
|
{
|
||||||
|
// 异步更新配置文件,避免阻塞UI线程
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string configPath = Path.Combine(AppContext.BaseDirectory, "fps_config.ini");
|
||||||
|
|
||||||
|
|
||||||
|
if (File.Exists(configPath))
|
||||||
|
{
|
||||||
|
string[] lines = await File.ReadAllLinesAsync(configPath).ConfigureAwait(false);
|
||||||
|
bool needsUpdate = true;
|
||||||
|
|
||||||
|
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
if (line.StartsWith("FPS="))
|
||||||
|
{
|
||||||
|
int configFps = int.Parse(line.Substring(4));
|
||||||
|
if (configFps == newFps)
|
||||||
|
{
|
||||||
|
needsUpdate = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置文件
|
||||||
|
if (needsUpdate)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
if (lines[i].StartsWith("FPS="))
|
||||||
|
{
|
||||||
|
lines[i] = $"FPS={newFps}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await File.WriteAllLinesAsync(configPath, lines).ConfigureAwait(false);
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"Updated fps_config.ini with new FPS: {newFps}",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
if (i == 2)
|
||||||
|
{
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"无法写入配置文件 {configPath},请检查权限",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Task.Delay(500).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
if (i == 2)
|
||||||
|
{
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"无法写入配置文件 {configPath},文件可能被占用",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Task.Delay(500).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 记录错误
|
||||||
|
SentrySdk.AddBreadcrumb(
|
||||||
|
$"Failed to update fps_config.ini: {ex.Message}",
|
||||||
|
category: "fps.unlocker",
|
||||||
|
level: Sentry.BreadcrumbLevel.Warning);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static ImmutableArray<NameValue<int>> InitializeMonitors()
|
private static ImmutableArray<NameValue<int>> InitializeMonitors()
|
||||||
{
|
{
|
||||||
ImmutableArray<NameValue<int>>.Builder monitors = ImmutableArray.CreateBuilder<NameValue<int>>();
|
ImmutableArray<NameValue<int>>.Builder monitors = ImmutableArray.CreateBuilder<NameValue<int>>();
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ using Snap.Hutao.Service.Notification;
|
|||||||
|
|
||||||
namespace Snap.Hutao.Service.Game.Launching.Handler;
|
namespace Snap.Hutao.Service.Game.Launching.Handler;
|
||||||
|
|
||||||
internal sealed class LaunchExecutionGameIslandHandler : AbstractLaunchExecutionHandler
|
internal sealed class LaunchExecutionGameIslandHandler : AbstractLaunchExecutionHandler, IDisposable
|
||||||
{
|
{
|
||||||
private readonly bool resume;
|
private readonly bool resume;
|
||||||
private GameIslandInterop? interop;
|
private GameFpsUnlockInterop? interop;
|
||||||
|
|
||||||
public LaunchExecutionGameIslandHandler(bool resume)
|
public LaunchExecutionGameIslandHandler(bool resume)
|
||||||
{
|
{
|
||||||
@@ -63,4 +63,9 @@ internal sealed class LaunchExecutionGameIslandHandler : AbstractLaunchExecution
|
|||||||
GameLifeCycle.IsIslandConnected.Value = false;
|
GameLifeCycle.IsIslandConnected.Value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
interop?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,14 @@ internal sealed class LaunchExecutionGameProcessStartHandler : AbstractLaunchExe
|
|||||||
|
|
||||||
public override async ValueTask ExecuteAsync(LaunchExecutionContext context)
|
public override async ValueTask ExecuteAsync(LaunchExecutionContext context)
|
||||||
{
|
{
|
||||||
|
// 如果启用了Island(FPS解锁),则跳过启动游戏进程
|
||||||
|
// 因为unlockfps.exe会负责启动游戏
|
||||||
|
if (context.LaunchOptions.IsIslandEnabled.Value)
|
||||||
|
{
|
||||||
|
context.Progress.Report(new(SH.ServiceGameLaunchPhaseProcessStarted));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.Process.Start();
|
context.Process.Start();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using Snap.Hutao.Core.Diagnostics;
|
using Snap.Hutao.Core.Diagnostics;
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
using Snap.Hutao.Factory.Progress;
|
using Snap.Hutao.Factory.Progress;
|
||||||
|
using Snap.Hutao.Factory.Process;
|
||||||
using Snap.Hutao.Service.Game.FileSystem;
|
using Snap.Hutao.Service.Game.FileSystem;
|
||||||
using Snap.Hutao.Service.Game.Launching.Context;
|
using Snap.Hutao.Service.Game.Launching.Context;
|
||||||
using Snap.Hutao.Service.Game.Launching.Handler;
|
using Snap.Hutao.Service.Game.Launching.Handler;
|
||||||
@@ -100,9 +101,16 @@ internal abstract class AbstractLaunchExecutionInvoker
|
|||||||
|
|
||||||
fileSystemReference.Exchange(beforeContext.FileSystem);
|
fileSystemReference.Exchange(beforeContext.FileSystem);
|
||||||
|
|
||||||
using (IProcess? process = CreateProcess(beforeContext))
|
// unlockfps.exe会负责启动游戏
|
||||||
|
IProcess? process = null;
|
||||||
|
if (!beforeContext.LaunchOptions.IsIslandEnabled.Value)
|
||||||
{
|
{
|
||||||
if (process is null)
|
process = CreateProcess(beforeContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (process)
|
||||||
|
{
|
||||||
|
if (process is null && !beforeContext.LaunchOptions.IsIslandEnabled.Value)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -114,7 +122,7 @@ internal abstract class AbstractLaunchExecutionInvoker
|
|||||||
TaskContext = taskContext,
|
TaskContext = taskContext,
|
||||||
Messenger = context.ServiceProvider.GetRequiredService<IMessenger>(),
|
Messenger = context.ServiceProvider.GetRequiredService<IMessenger>(),
|
||||||
LaunchOptions = context.LaunchOptions,
|
LaunchOptions = context.LaunchOptions,
|
||||||
Process = process,
|
Process = process ?? new NullProcess(),
|
||||||
IsOversea = targetScheme.IsOversea,
|
IsOversea = targetScheme.IsOversea,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,7 +131,8 @@ internal abstract class AbstractLaunchExecutionInvoker
|
|||||||
await handler.ExecuteAsync(executionContext).ConfigureAwait(false);
|
await handler.ExecuteAsync(executionContext).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.IsRunning)
|
// 只有在没有启用Island且进程存在时才等待退出
|
||||||
|
if (process is { IsRunning: true })
|
||||||
{
|
{
|
||||||
progress.Report(new(SH.ServiceGameLaunchPhaseWaitingProcessExit));
|
progress.Report(new(SH.ServiceGameLaunchPhaseWaitingProcessExit));
|
||||||
try
|
try
|
||||||
@@ -139,6 +148,12 @@ internal abstract class AbstractLaunchExecutionInvoker
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (beforeContext.LaunchOptions.IsIslandEnabled.Value)
|
||||||
|
{
|
||||||
|
progress.Report(new(SH.ServiceGameLaunchPhaseWaitingProcessExit));
|
||||||
|
await taskContext.SwitchToBackgroundAsync();
|
||||||
|
await Task.Delay(30000).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.Report(new(SH.ServiceGameLaunchPhaseProcessExited));
|
progress.Report(new(SH.ServiceGameLaunchPhaseProcessExited));
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ internal sealed class DefaultLaunchExecutionInvoker : AbstractLaunchExecutionInv
|
|||||||
new LaunchExecutionGameResourceHandler(convertOnly: false),
|
new LaunchExecutionGameResourceHandler(convertOnly: false),
|
||||||
new LaunchExecutionGameIdentityHandler(),
|
new LaunchExecutionGameIdentityHandler(),
|
||||||
new LaunchExecutionWindowsHDRHandler(),
|
new LaunchExecutionWindowsHDRHandler(),
|
||||||
new LaunchExecutionGameProcessStartHandler(),
|
|
||||||
new LaunchExecutionGameIslandHandler(resume: false),
|
new LaunchExecutionGameIslandHandler(resume: false),
|
||||||
|
new LaunchExecutionGameProcessStartHandler(),
|
||||||
new LaunchExecutionOverlayHandler(),
|
new LaunchExecutionOverlayHandler(),
|
||||||
new LaunchExecutionStarwardPlayTimeStatisticsHandler(),
|
new LaunchExecutionStarwardPlayTimeStatisticsHandler(),
|
||||||
new LaunchExecutionBetterGenshinImpactAutomationHandler()
|
new LaunchExecutionBetterGenshinImpactAutomationHandler()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<UseWinUI>true</UseWinUI>
|
<UseWinUI>true</UseWinUI>
|
||||||
<UseWPF>False</UseWPF>
|
<UseWPF>False</UseWPF>
|
||||||
<!-- 配置版本号 -->
|
<!-- 配置版本号 -->
|
||||||
<Version>1.17.4.0</Version>
|
<Version>1.18.0.0</Version>
|
||||||
|
|
||||||
<UseWindowsForms>False</UseWindowsForms>
|
<UseWindowsForms>False</UseWindowsForms>
|
||||||
<ImplicitUsings>False</ImplicitUsings>
|
<ImplicitUsings>False</ImplicitUsings>
|
||||||
@@ -71,6 +71,14 @@
|
|||||||
<Delete Files="@(LibFiles)" />
|
<Delete Files="@(LibFiles)" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<!-- 复制unlockfps.exe到输出目录 -->
|
||||||
|
<Target Name="CopyUnlockFpsExe" AfterTargets="Build">
|
||||||
|
<ItemGroup>
|
||||||
|
<UnlockFpsExeSource Include="$(MSBuildThisFileDirectory)..\..\..\bin\unlockfps.exe" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Copy SourceFiles="@(UnlockFpsExeSource)" DestinationFolder="$(OutputPath)" ContinueOnError="true" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
<!-- Analyzer Files -->
|
<!-- Analyzer Files -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AdditionalFiles Include="ApiEndpoints.csv" />
|
<AdditionalFiles Include="ApiEndpoints.csv" />
|
||||||
|
|||||||
@@ -641,30 +641,31 @@
|
|||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFovHotSwitchHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFovHotSwitchHeader}"
|
||||||
IsOn="{Binding LaunchOptions.IsSetFieldOfViewEnabled.Value, Mode=TwoWay}"
|
IsEnabled="False"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameHotSwitchDescription}"/>
|
IsOn="False"
|
||||||
|
ToolTipService.ToolTip=""/>
|
||||||
<NumberBox
|
<NumberBox
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Padding="10,8,6,6"
|
Padding="10,8,6,6"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFovHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFovHeader}"
|
||||||
IsEnabled="{Binding LaunchOptions.IsSetFieldOfViewEnabled.Value}"
|
IsEnabled="False"
|
||||||
LargeChange="10"
|
LargeChange="10"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
Minimum="0"
|
Minimum="0"
|
||||||
SmallChange="1"
|
SmallChange="1"
|
||||||
SpinButtonPlacementMode="Inline"
|
SpinButtonPlacementMode="Inline"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFovDescription}"
|
ToolTipService.ToolTip=""
|
||||||
Value="{Binding LaunchOptions.TargetFov.Value, Mode=TwoWay}"/>
|
Value="45"/>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogHeader}"
|
||||||
IsEnabled="{Binding LaunchOptions.IsSetFieldOfViewEnabled}"
|
IsEnabled="False"
|
||||||
IsOn="{Binding LaunchOptions.DisableFog.Value, Mode=TwoWay}"
|
IsOn="False"
|
||||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogDescription}"/>
|
ToolTipService.ToolTip=""/>
|
||||||
|
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
x:Name="TargetFpsToggleSwitch"
|
x:Name="TargetFpsToggleSwitch"
|
||||||
@@ -703,24 +704,30 @@
|
|||||||
Subtitle="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFpsTeachingTipSubtitle}"
|
Subtitle="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFpsTeachingTipSubtitle}"
|
||||||
Target="{x:Bind TargetFpsToggleSwitch}"/>
|
Target="{x:Bind TargetFpsToggleSwitch}"/>
|
||||||
|
|
||||||
<NumberBox
|
<Grid Grid.Row="1" Grid.Column="1">
|
||||||
Grid.Row="1"
|
<Grid.RowDefinitions>
|
||||||
Grid.Column="1"
|
<RowDefinition Height="Auto"/>
|
||||||
Padding="10,8,6,6"
|
<RowDefinition Height="Auto"/>
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFpsHeader}"
|
</Grid.RowDefinitions>
|
||||||
IsEnabled="{Binding LaunchOptions.IsSetTargetFrameRateEnabled.Value}"
|
<NumberBox
|
||||||
Maximum="120"
|
Grid.Row="0"
|
||||||
Minimum="1"
|
Padding="10,8,6,6"
|
||||||
SpinButtonPlacementMode="Inline"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFpsHeader}"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameUnlockFpsDescription}"
|
IsEnabled="{Binding LaunchOptions.IsSetTargetFrameRateEnabled.Value}"
|
||||||
Value="{Binding LaunchOptions.TargetFps.Value, Mode=TwoWay}"/>
|
Maximum="120"
|
||||||
|
Minimum="1"
|
||||||
|
SpinButtonPlacementMode="Inline"
|
||||||
|
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameUnlockFpsDescription}"
|
||||||
|
Value="{Binding LaunchOptions.TargetFps.Value, Mode=TwoWay}"/>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
x:Name="FixLowFovSceneToggleSwitch"
|
x:Name="FixLowFovSceneToggleSwitch"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
IsEnabled="{Binding LaunchOptions.IsSetFieldOfViewEnabled}"
|
IsEnabled="False"
|
||||||
IsOn="{Binding LaunchOptions.FixLowFovScene.Value, Mode=TwoWay}"
|
IsOn="False"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameFixLowFovSceneDescription}">
|
ToolTipService.ToolTip="">
|
||||||
<mxi:Interaction.Behaviors>
|
<mxi:Interaction.Behaviors>
|
||||||
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
||||||
</mxi:Interaction.Behaviors>
|
</mxi:Interaction.Behaviors>
|
||||||
@@ -756,10 +763,11 @@
|
|||||||
x:Name="HideQuestBannerToggleSwitch"
|
x:Name="HideQuestBannerToggleSwitch"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsOn="{Binding LaunchOptions.HideQuestBanner.Value, Mode=TwoWay}"
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameHotSwitchDescription}">
|
ToolTipService.ToolTip="">
|
||||||
<mxi:Interaction.Behaviors>
|
<mxi:Interaction.Behaviors>
|
||||||
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
||||||
</mxi:Interaction.Behaviors>
|
</mxi:Interaction.Behaviors>
|
||||||
@@ -798,34 +806,38 @@
|
|||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameRemoveOpenTeamProgressHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameRemoveOpenTeamProgressHeader}"
|
||||||
IsOn="{Binding LaunchOptions.RemoveOpenTeamProgress.Value, Mode=TwoWay}"
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameRemoveOpenTeamProgressDescription}"/>
|
ToolTipService.ToolTip=""/>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameEventCameraMoveHotSwitchHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameEventCameraMoveHotSwitchHeader}"
|
||||||
IsOn="{Binding LaunchOptions.DisableEventCameraMove.Value, Mode=TwoWay}"
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameHotSwitchDescription}"/>
|
ToolTipService.ToolTip=""/>
|
||||||
|
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Header="{shuxm:ResourceString Name=ViewOverlayDisableShowDamageTextToolTip}"
|
Header="{shuxm:ResourceString Name=ViewOverlayDisableShowDamageTextToolTip}"
|
||||||
IsOn="{Binding LaunchOptions.DisableShowDamageText.Value, Mode=TwoWay}"
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameHotSwitchDescription}"/>
|
ToolTipService.ToolTip=""/>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
x:Name="RedirectCombineEntryToggleSwitch"
|
x:Name="RedirectCombineEntryToggleSwitch"
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
IsOn="{Binding LaunchOptions.RedirectCombineEntry.Value, Mode=TwoWay}"
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
Style="{ThemeResource DefaultToggleSwitchStyle}"
|
Style="{ThemeResource DefaultToggleSwitchStyle}"
|
||||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameIslandRedirectCombineEntryDescription}">
|
ToolTipService.ToolTip="">
|
||||||
<mxi:Interaction.Behaviors>
|
<mxi:Interaction.Behaviors>
|
||||||
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
||||||
</mxi:Interaction.Behaviors>
|
</mxi:Interaction.Behaviors>
|
||||||
@@ -867,35 +879,46 @@
|
|||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandUsingTouchScreenHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandUsingTouchScreenHeader}"
|
||||||
IsEnabled="{Binding LaunchOptions.IsGameRunning.Value, Converter={StaticResource BoolNegationConverter}}"
|
IsEnabled="False"
|
||||||
IsOn="{Binding LaunchOptions.UsingTouchScreen.Value, Mode=TwoWay}"/>
|
IsOn="False"
|
||||||
|
ToolTipService.ToolTip=""/>
|
||||||
|
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowOriginalResinHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowOriginalResinHeader}"
|
||||||
IsOn="{Binding LaunchOptions.ResinListItemId000106Allowed.Value, Mode=TwoWay}"/>
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
|
ToolTipService.ToolTip=""/>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowPrimogemHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowPrimogemHeader}"
|
||||||
IsOn="{Binding LaunchOptions.ResinListItemId000201Allowed.Value, Mode=TwoWay}"/>
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
|
ToolTipService.ToolTip=""/>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowFragileResinHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowFragileResinHeader}"
|
||||||
IsOn="{Binding LaunchOptions.ResinListItemId107009Allowed.Value, Mode=TwoWay}"/>
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
|
ToolTipService.ToolTip=""/>
|
||||||
|
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowTransientResinHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowTransientResinHeader}"
|
||||||
IsOn="{Binding LaunchOptions.ResinListItemId107012Allowed.Value, Mode=TwoWay}"/>
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
|
ToolTipService.ToolTip=""/>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowCondensedResinHeader}"
|
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowCondensedResinHeader}"
|
||||||
IsOn="{Binding LaunchOptions.ResinListItemId220007Allowed.Value, Mode=TwoWay}"/>
|
IsEnabled="False"
|
||||||
|
IsOn="False"
|
||||||
|
ToolTipService.ToolTip=""/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ContentControl>
|
</ContentControl>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
Reference in New Issue
Block a user