mirror of
https://github.com/wangdage12/Snap.Hutao.git
synced 2026-02-18 02:42:15 +08:00
Compare commits
17 Commits
1.17.3.0
...
56c36a01ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56c36a01ae | ||
|
|
da6f248509 | ||
|
|
068eb65fef | ||
|
|
09a8cded2f | ||
|
|
c38fdf30d0 | ||
|
|
bc1ff03d0a | ||
|
|
b288860c3b | ||
|
|
1e40a6e576 | ||
|
|
d342b37dc0 | ||
|
|
179177a77c | ||
|
|
6c68a55d81 | ||
|
|
7bd61c8035 | ||
|
|
c19b71e2c4 | ||
|
|
45b7383fc1 | ||
|
|
c83a2f3e9d | ||
|
|
2bab0baf69 | ||
|
|
2726e74731 |
4
.github/workflows/alpha.yml
vendored
4
.github/workflows/alpha.yml
vendored
@@ -95,7 +95,7 @@ jobs:
|
||||
|
||||
- name: Cache NuGet packages
|
||||
if: ${{ needs.select-runner.outputs.runner == 'windows-latest' }}
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/Snap.Hutao.csproj') }}
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
|
||||
- name: Upload signed msix
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}
|
||||
path: ${{ github.workspace }}/src/output/Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix
|
||||
|
||||
4
.github/workflows/canary.yml
vendored
4
.github/workflows/canary.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
- name: Cache NuGet packages
|
||||
if: ${{ steps.merge.outputs.continue == 'true' }}
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/Snap.Hutao.csproj') }}
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
|
||||
- name: Upload signed msix
|
||||
if: ${{ success() && steps.merge.outputs.continue == 'true' }}
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Snap.Hutao.Canary-${{ steps.cake.outputs.version }}
|
||||
path: ${{ github.workspace }}/src/output/Snap.Hutao.Canary-${{ steps.cake.outputs.version }}.msix
|
||||
|
||||
2
.github/workflows/lock_closed_issues.yml
vendored
2
.github/workflows/lock_closed_issues.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
- uses: dessant/lock-threads@v6
|
||||
with:
|
||||
issue-inactive-days: '30'
|
||||
issue-comment: 'This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related topic.'
|
||||
|
||||
2
.github/workflows/msi-build.yml
vendored
2
.github/workflows/msi-build.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
run: dotnet build src/Snap.Hutao/Snap.Hutao.Installer/Snap.Hutao.Installer.wixproj -c Release
|
||||
|
||||
- name: Upload MSI Artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Snap.Hutao-MSI
|
||||
path: |
|
||||
|
||||
43
README.md
43
README.md
@@ -6,6 +6,8 @@
|
||||
|
||||
该版本注入功能暂不可用,并且由于缺失资源和开发能力,不建议长期使用
|
||||
|
||||
有条件的话可以加入discord服务器:https://discord.gg/ucH3mgeWpQ
|
||||
|
||||
**English**
|
||||
Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed for modern Windows platform to improve the gaming experience for desktop players.
|
||||
|
||||
@@ -25,28 +27,16 @@ Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed
|
||||
项目启动位置已升级为 VS2026 的 slnx 格式 Snap.Hutao\src\Snap.Hutao\Snap.Hutao.slnx
|
||||
> [!WARNING]
|
||||
> 要使该项目可以长期运行,我们需要以下资源
|
||||
> 1. `src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/SaltConstants.cs`中的新签名值
|
||||
> 2. 元数据的编写
|
||||
> 3. 图片资源
|
||||
> 1. 元数据的编写
|
||||
> 2. 图片资源
|
||||
|
||||
已同步原作者的元数据
|
||||
|
||||
V6.2的元数据已在编写中
|
||||
测试仓库位置:http://server.wdg.cloudns.ch:3000/wdg1122/Snap.Metadata.Test
|
||||
**目前元数据的编写进度:**
|
||||
|
||||
| 项目(V6.2) | 是否完成 |
|
||||
| ----------- | ----------- |
|
||||
| 新角色的基本数据 | ✔️ |
|
||||
| 新版本角色/怪物基础数值 | ❔ |
|
||||
| 新角色的详细资料、名片等 | ❌ |
|
||||
| 新武器 | ✔️ |
|
||||
| 新材料 | ❇️ |
|
||||
| 新怪物 | ❇️ |
|
||||
| 新圣遗物 | / |
|
||||
| 新卡池 | ❇️ |
|
||||
| 新成就 | ✔️ |
|
||||
| 深境螺旋 | 💠 |
|
||||
| 幻想真境剧诗 | 💠 |
|
||||
| 幽境危战 | ✔️ |
|
||||
| 总体数据 | ✔️ |
|
||||
|
||||
✔️:已完成
|
||||
❌:未编写
|
||||
@@ -81,30 +71,21 @@ https://deepwiki.com/DGP-Studio/Snap.Hutao.Server
|
||||
https://github.com/wangdage12/Snap.Metadata
|
||||
|
||||
镜像:
|
||||

|
||||

|
||||
|
||||
http://server.wdg.cloudns.ch:3000/wdg1122/Snap.Metadata
|
||||
|
||||

|
||||
|
||||
http://serverjp.wdg.cloudns.ch:3000/wdg1122/Snap.Metadata
|
||||
http://htgit.wdg.cloudns.ch/wdg1122/Snap.Metadata
|
||||
|
||||
---
|
||||
|
||||
**临时API:**
|
||||
|
||||

|
||||

|
||||
|
||||
http://server.wdg.cloudns.ch:5222/
|
||||
https://htserver.wdg.cloudns.ch/api/
|
||||
|
||||
|
||||

|
||||
|
||||
http://serverjp.wdg.cloudns.ch:5222/
|
||||
|
||||
---
|
||||
|
||||
**临时资源站:**
|
||||
http://server.wdg.cloudns.ch:8007/
|
||||
|
||||
http://serverjp.wdg.cloudns.ch:8001/
|
||||
https://htserver.wdg.cloudns.ch/
|
||||
|
||||
BIN
bin/unlockfps.exe
Normal file
BIN
bin/unlockfps.exe
Normal file
Binary file not shown.
@@ -2,7 +2,7 @@
|
||||
<Package
|
||||
Name="Snap.Hutao"
|
||||
Manufacturer="Millennium Science Technology R-D Inst"
|
||||
Version="1.0.0.0"
|
||||
Version="1.17.4.0"
|
||||
UpgradeCode="121203be-60cb-408f-92cc-7080f6598e68"
|
||||
Scope="perMachine">
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -193,6 +193,32 @@ internal sealed class ProcessFactory
|
||||
|
||||
public static void StartUsingShellExecuteRunAs(string fileName)
|
||||
{
|
||||
// 尝试从app包中启动
|
||||
try
|
||||
{
|
||||
global::System.Diagnostics.Process.Start(new global::System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas",
|
||||
});
|
||||
}catch
|
||||
{
|
||||
// 如果失败且filename含有Snap.Hutao.Unpackaged,就直接用Snap.Hutao.exe重启
|
||||
if (fileName.Contains("Snap.Hutao.Unpackaged"))
|
||||
{
|
||||
string currentDirectory = Directory.GetCurrentDirectory();
|
||||
string unpackagedPath = Path.Combine(currentDirectory, "Snap.Hutao.exe");
|
||||
if (File.Exists(unpackagedPath))
|
||||
{
|
||||
fileName = unpackagedPath;
|
||||
}
|
||||
// 否则抛出异常
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
// 重新尝试启动
|
||||
global::System.Diagnostics.Process.Start(new global::System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
@@ -200,4 +226,6 @@ internal sealed class ProcessFactory
|
||||
Verb = "runas",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ internal static class WeaponIds
|
||||
13502U, 13505U,
|
||||
14501U, 14502U,
|
||||
15501U, 15502U,
|
||||
15515U, 15518U
|
||||
15515U, 11518U
|
||||
];
|
||||
|
||||
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
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
@@ -13,7 +13,7 @@
|
||||
<Identity
|
||||
Name="60568DGPStudio.SnapHutao"
|
||||
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
|
||||
Version="1.17.1.0" />
|
||||
Version="1.18.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Snap Hutao</DisplayName>
|
||||
|
||||
@@ -24,8 +24,10 @@ internal static class AvatarViewBuilderExtension
|
||||
{
|
||||
if (detailedCharacter.Costumes is [{ Id: { } id }, ..])
|
||||
{
|
||||
MetadataCostume costume = avatar.Costumes.Single(c => c.Id == id);
|
||||
MetadataCostume? costume = avatar.Costumes.SingleOrDefault(c => c.Id == id);
|
||||
|
||||
if (costume != null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(costume.FrontIcon);
|
||||
ArgumentNullException.ThrowIfNull(costume.SideIcon);
|
||||
|
||||
@@ -34,6 +36,13 @@ internal static class AvatarViewBuilderExtension
|
||||
builder.View.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Costume not found in metadata, fallback to default avatar icon
|
||||
builder.View.Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
|
||||
builder.View.SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.View.Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
|
||||
builder.View.SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon);
|
||||
|
||||
@@ -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.Win32;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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); }
|
||||
|
||||
[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]
|
||||
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();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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;
|
||||
|
||||
internal sealed class LaunchExecutionGameIslandHandler : AbstractLaunchExecutionHandler
|
||||
internal sealed class LaunchExecutionGameIslandHandler : AbstractLaunchExecutionHandler, IDisposable
|
||||
{
|
||||
private readonly bool resume;
|
||||
private GameIslandInterop? interop;
|
||||
private GameFpsUnlockInterop? interop;
|
||||
|
||||
public LaunchExecutionGameIslandHandler(bool resume)
|
||||
{
|
||||
@@ -63,4 +63,9 @@ internal sealed class LaunchExecutionGameIslandHandler : AbstractLaunchExecution
|
||||
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)
|
||||
{
|
||||
// 如果启用了Island(FPS解锁),则跳过启动游戏进程
|
||||
// 因为unlockfps.exe会负责启动游戏
|
||||
if (context.LaunchOptions.IsIslandEnabled.Value)
|
||||
{
|
||||
context.Progress.Report(new(SH.ServiceGameLaunchPhaseProcessStarted));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.Process.Start();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Factory.Progress;
|
||||
using Snap.Hutao.Factory.Process;
|
||||
using Snap.Hutao.Service.Game.FileSystem;
|
||||
using Snap.Hutao.Service.Game.Launching.Context;
|
||||
using Snap.Hutao.Service.Game.Launching.Handler;
|
||||
@@ -100,9 +101,16 @@ internal abstract class AbstractLaunchExecutionInvoker
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -114,7 +122,7 @@ internal abstract class AbstractLaunchExecutionInvoker
|
||||
TaskContext = taskContext,
|
||||
Messenger = context.ServiceProvider.GetRequiredService<IMessenger>(),
|
||||
LaunchOptions = context.LaunchOptions,
|
||||
Process = process,
|
||||
Process = process ?? new NullProcess(),
|
||||
IsOversea = targetScheme.IsOversea,
|
||||
};
|
||||
|
||||
@@ -123,7 +131,8 @@ internal abstract class AbstractLaunchExecutionInvoker
|
||||
await handler.ExecuteAsync(executionContext).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (process.IsRunning)
|
||||
// 只有在没有启用Island且进程存在时才等待退出
|
||||
if (process is { IsRunning: true })
|
||||
{
|
||||
progress.Report(new(SH.ServiceGameLaunchPhaseWaitingProcessExit));
|
||||
try
|
||||
@@ -139,6 +148,12 @@ internal abstract class AbstractLaunchExecutionInvoker
|
||||
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));
|
||||
|
||||
@@ -16,8 +16,8 @@ internal sealed class DefaultLaunchExecutionInvoker : AbstractLaunchExecutionInv
|
||||
new LaunchExecutionGameResourceHandler(convertOnly: false),
|
||||
new LaunchExecutionGameIdentityHandler(),
|
||||
new LaunchExecutionWindowsHDRHandler(),
|
||||
new LaunchExecutionGameProcessStartHandler(),
|
||||
new LaunchExecutionGameIslandHandler(resume: false),
|
||||
new LaunchExecutionGameProcessStartHandler(),
|
||||
new LaunchExecutionOverlayHandler(),
|
||||
new LaunchExecutionStarwardPlayTimeStatisticsHandler(),
|
||||
new LaunchExecutionBetterGenshinImpactAutomationHandler()
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<UseWPF>False</UseWPF>
|
||||
<!-- 配置版本号 -->
|
||||
<Version>1.18.0.0</Version>
|
||||
|
||||
<UseWindowsForms>False</UseWindowsForms>
|
||||
<ImplicitUsings>False</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -68,6 +71,14 @@
|
||||
<Delete Files="@(LibFiles)" />
|
||||
</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 -->
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="ApiEndpoints.csv" />
|
||||
@@ -288,7 +299,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.3.14">
|
||||
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.3.15">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -641,30 +641,31 @@
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFovHotSwitchHeader}"
|
||||
IsOn="{Binding LaunchOptions.IsSetFieldOfViewEnabled.Value, Mode=TwoWay}"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameHotSwitchDescription}"/>
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
ToolTipService.ToolTip=""/>
|
||||
<NumberBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Padding="10,8,6,6"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFovHeader}"
|
||||
IsEnabled="{Binding LaunchOptions.IsSetFieldOfViewEnabled.Value}"
|
||||
IsEnabled="False"
|
||||
LargeChange="10"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFovDescription}"
|
||||
Value="{Binding LaunchOptions.TargetFov.Value, Mode=TwoWay}"/>
|
||||
ToolTipService.ToolTip=""
|
||||
Value="45"/>
|
||||
<ToggleSwitch
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogHeader}"
|
||||
IsEnabled="{Binding LaunchOptions.IsSetFieldOfViewEnabled}"
|
||||
IsOn="{Binding LaunchOptions.DisableFog.Value, Mode=TwoWay}"
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogDescription}"/>
|
||||
ToolTipService.ToolTip=""/>
|
||||
|
||||
<ToggleSwitch
|
||||
x:Name="TargetFpsToggleSwitch"
|
||||
@@ -703,9 +704,13 @@
|
||||
Subtitle="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFpsTeachingTipSubtitle}"
|
||||
Target="{x:Bind TargetFpsToggleSwitch}"/>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<NumberBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Padding="10,8,6,6"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameTargetFpsHeader}"
|
||||
IsEnabled="{Binding LaunchOptions.IsSetTargetFrameRateEnabled.Value}"
|
||||
@@ -714,13 +719,15 @@
|
||||
SpinButtonPlacementMode="Inline"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameUnlockFpsDescription}"
|
||||
Value="{Binding LaunchOptions.TargetFps.Value, Mode=TwoWay}"/>
|
||||
|
||||
</Grid>
|
||||
<ToggleSwitch
|
||||
x:Name="FixLowFovSceneToggleSwitch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
IsEnabled="{Binding LaunchOptions.IsSetFieldOfViewEnabled}"
|
||||
IsOn="{Binding LaunchOptions.FixLowFovScene.Value, Mode=TwoWay}"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameFixLowFovSceneDescription}">
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
ToolTipService.ToolTip="">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
@@ -756,10 +763,11 @@
|
||||
x:Name="HideQuestBannerToggleSwitch"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
IsOn="{Binding LaunchOptions.HideQuestBanner.Value, Mode=TwoWay}"
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameHotSwitchDescription}">
|
||||
ToolTipService.ToolTip="">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
@@ -798,34 +806,38 @@
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameRemoveOpenTeamProgressHeader}"
|
||||
IsOn="{Binding LaunchOptions.RemoveOpenTeamProgress.Value, Mode=TwoWay}"
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameRemoveOpenTeamProgressDescription}"/>
|
||||
ToolTipService.ToolTip=""/>
|
||||
<ToggleSwitch
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameEventCameraMoveHotSwitchHeader}"
|
||||
IsOn="{Binding LaunchOptions.DisableEventCameraMove.Value, Mode=TwoWay}"
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameHotSwitchDescription}"/>
|
||||
ToolTipService.ToolTip=""/>
|
||||
|
||||
<ToggleSwitch
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Header="{shuxm:ResourceString Name=ViewOverlayDisableShowDamageTextToolTip}"
|
||||
IsOn="{Binding LaunchOptions.DisableShowDamageText.Value, Mode=TwoWay}"
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
OffContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOff}"
|
||||
OnContent="{shuxm:ResourceString Name=ViewPageLaunchGameDisableFogOn}"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameHotSwitchDescription}"/>
|
||||
ToolTipService.ToolTip=""/>
|
||||
<ToggleSwitch
|
||||
x:Name="RedirectCombineEntryToggleSwitch"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
IsOn="{Binding LaunchOptions.RedirectCombineEntry.Value, Mode=TwoWay}"
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
Style="{ThemeResource DefaultToggleSwitchStyle}"
|
||||
ToolTipService.ToolTip="{shuxm:ResourceString Name=ViewPageLaunchGameIslandRedirectCombineEntryDescription}">
|
||||
ToolTipService.ToolTip="">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shuxb:ToggleSwitchHeaderHitTestVisibleBehavior/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
@@ -867,35 +879,46 @@
|
||||
Grid.Row="3"
|
||||
Grid.Column="2"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandUsingTouchScreenHeader}"
|
||||
IsEnabled="{Binding LaunchOptions.IsGameRunning.Value, Converter={StaticResource BoolNegationConverter}}"
|
||||
IsOn="{Binding LaunchOptions.UsingTouchScreen.Value, Mode=TwoWay}"/>
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
ToolTipService.ToolTip=""/>
|
||||
|
||||
<ToggleSwitch
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowOriginalResinHeader}"
|
||||
IsOn="{Binding LaunchOptions.ResinListItemId000106Allowed.Value, Mode=TwoWay}"/>
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
ToolTipService.ToolTip=""/>
|
||||
<ToggleSwitch
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowPrimogemHeader}"
|
||||
IsOn="{Binding LaunchOptions.ResinListItemId000201Allowed.Value, Mode=TwoWay}"/>
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
ToolTipService.ToolTip=""/>
|
||||
<ToggleSwitch
|
||||
Grid.Row="4"
|
||||
Grid.Column="2"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowFragileResinHeader}"
|
||||
IsOn="{Binding LaunchOptions.ResinListItemId107009Allowed.Value, Mode=TwoWay}"/>
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
ToolTipService.ToolTip=""/>
|
||||
|
||||
<ToggleSwitch
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowTransientResinHeader}"
|
||||
IsOn="{Binding LaunchOptions.ResinListItemId107012Allowed.Value, Mode=TwoWay}"/>
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
ToolTipService.ToolTip=""/>
|
||||
<ToggleSwitch
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Header="{shuxm:ResourceString Name=ViewPageLaunchGameIslandResinListItemAllowCondensedResinHeader}"
|
||||
IsOn="{Binding LaunchOptions.ResinListItemId220007Allowed.Value, Mode=TwoWay}"/>
|
||||
IsEnabled="False"
|
||||
IsOn="False"
|
||||
ToolTipService.ToolTip=""/>
|
||||
</Grid>
|
||||
</ContentControl>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.DataSigning;
|
||||
|
||||
/// <summary>
|
||||
/// Salt constants for data signing
|
||||
/// Values are obtained from https://github.com/UIGF-org/Hoyolab.Salt
|
||||
/// This file should normally be generated by Snap.Hutao.SourceGeneration.Automation.SaltConstantGenerator
|
||||
/// But is provided manually when the generator fails to fetch values from the network.
|
||||
///
|
||||
/// IMPORTANT: For local builds, you must manually obtain salt values from:
|
||||
/// https://github.com/UIGF-org/Hoyolab.Salt
|
||||
/// </summary>
|
||||
|
||||
internal static class SaltConstants
|
||||
{
|
||||
// Version numbers - Update these according to the current miHoYo app versions
|
||||
public const string CNVersion = "2.95.1";
|
||||
public const string OSVersion = "2.54.0";
|
||||
|
||||
// Salt keys for Chinese (CN) server
|
||||
// These are placeholder values - MUST be replaced with actual values from UIGF-org/Hoyolab.Salt
|
||||
public const string CNK2 = "sfYPEgpxkOe1I3XVMLdwp1Lyt9ORgZsq";
|
||||
public const string CNLK2 = "sidQFEglajEz7FA0Aj7HQPV88zpf17SO";
|
||||
|
||||
// Salt keys for Overseas (OS) server
|
||||
public const string OSK2 = "599uqkwc0dlqu3h6epzjzfhgyyrd44ae";
|
||||
public const string OSLK2 = "rk4xg2hakoi26nljpr099fv9fck1ah10";
|
||||
|
||||
// Note: The actual salt values are security-sensitive and should not be committed
|
||||
// to public repositories. For local builds, obtain them from the UIGF organization
|
||||
// and replace the placeholders above.
|
||||
}
|
||||
Reference in New Issue
Block a user