17 Commits

Author SHA1 Message Date
fanbook-wangdage
56c36a01ae Merge branches 'main' and 'main' of https://github.com/wangdage12/Snap.Hutao 2026-01-08 13:58:14 +08:00
fanbook-wangdage
da6f248509 支持调用genshin-fps-unlock项目来调整帧率、修复武器id问题 2026-01-08 13:57:17 +08:00
wangdage12
068eb65fef Add Discord server link to README
Added Discord server link for community engagement.
2026-01-01 12:57:42 +08:00
fanbook-wangdage
09a8cded2f Merge branch 'main' of https://github.com/wangdage12/Snap.Hutao 2025-12-26 18:36:50 +08:00
fanbook-wangdage
c38fdf30d0 修复以管理员权限重启问题 2025-12-26 18:33:20 +08:00
wangdage12
bc1ff03d0a Update README with metadata progress and synchronization
Removed outdated metadata progress details and added synchronization note.
2025-12-22 21:05:40 +08:00
wangdage12
b288860c3b Update README.md 2025-12-19 23:13:19 +08:00
wangdage12
1e40a6e576 Revise status indicators and update links
Updated status indicators and URLs in the README.
2025-12-19 22:59:02 +08:00
wangdage12
d342b37dc0 Update HarvestDirectory path in wixproj file 2025-12-19 21:28:55 +08:00
fanbook-wangdage
179177a77c Merge branch 'main' of https://github.com/wangdage12/Snap.Hutao 2025-12-19 21:20:14 +08:00
fanbook-wangdage
6c68a55d81 解决元数据导致的问题 2025-12-19 21:19:47 +08:00
wangdage12
7bd61c8035 Merge pull request #14 from wangdage12/dependabot/github_actions/dot-github/workflows/actions/upload-artifact-6
Bump actions/upload-artifact from 5 to 6 in /.github/workflows
2025-12-17 20:20:52 +08:00
wangdage12
c19b71e2c4 Merge pull request #15 from wangdage12/dependabot/github_actions/dot-github/workflows/actions/cache-5
Bump actions/cache from 4 to 5 in /.github/workflows
2025-12-17 20:20:38 +08:00
wangdage12
45b7383fc1 Merge pull request #16 from wangdage12/dependabot/github_actions/dot-github/workflows/dessant/lock-threads-6
Bump dessant/lock-threads from 5 to 6 in /.github/workflows
2025-12-17 20:20:23 +08:00
dependabot[bot]
c83a2f3e9d Bump dessant/lock-threads from 5 to 6 in /.github/workflows
Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 5 to 6.
- [Release notes](https://github.com/dessant/lock-threads/releases)
- [Changelog](https://github.com/dessant/lock-threads/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dessant/lock-threads/compare/v5...v6)

---
updated-dependencies:
- dependency-name: dessant/lock-threads
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 02:30:01 +00:00
dependabot[bot]
2bab0baf69 Bump actions/cache from 4 to 5 in /.github/workflows
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 02:29:57 +00:00
dependabot[bot]
2726e74731 Bump actions/upload-artifact from 5 to 6 in /.github/workflows
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 02:29:52 +00:00
22 changed files with 725 additions and 135 deletions

View File

@@ -95,7 +95,7 @@ jobs:
- name: Cache NuGet packages - name: Cache NuGet packages
if: ${{ needs.select-runner.outputs.runner == 'windows-latest' }} if: ${{ needs.select-runner.outputs.runner == 'windows-latest' }}
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ~/.nuget/packages path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/Snap.Hutao.csproj') }} key: ${{ runner.os }}-nuget-${{ hashFiles('**/Snap.Hutao.csproj') }}
@@ -113,7 +113,7 @@ jobs:
- name: Upload signed msix - name: Upload signed msix
if: success() && github.event_name != 'pull_request' if: success() && github.event_name != 'pull_request'
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: Snap.Hutao.Alpha-${{ steps.cake.outputs.version }} name: Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}
path: ${{ github.workspace }}/src/output/Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix path: ${{ github.workspace }}/src/output/Snap.Hutao.Alpha-${{ steps.cake.outputs.version }}.msix

View File

@@ -59,7 +59,7 @@ jobs:
- name: Cache NuGet packages - name: Cache NuGet packages
if: ${{ steps.merge.outputs.continue == 'true' }} if: ${{ steps.merge.outputs.continue == 'true' }}
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ~/.nuget/packages path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/Snap.Hutao.csproj') }} key: ${{ runner.os }}-nuget-${{ hashFiles('**/Snap.Hutao.csproj') }}
@@ -77,7 +77,7 @@ jobs:
- name: Upload signed msix - name: Upload signed msix
if: ${{ success() && steps.merge.outputs.continue == 'true' }} if: ${{ success() && steps.merge.outputs.continue == 'true' }}
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: Snap.Hutao.Canary-${{ steps.cake.outputs.version }} name: Snap.Hutao.Canary-${{ steps.cake.outputs.version }}
path: ${{ github.workspace }}/src/output/Snap.Hutao.Canary-${{ steps.cake.outputs.version }}.msix path: ${{ github.workspace }}/src/output/Snap.Hutao.Canary-${{ steps.cake.outputs.version }}.msix

View File

@@ -17,7 +17,7 @@ jobs:
action: action:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v5 - uses: dessant/lock-threads@v6
with: with:
issue-inactive-days: '30' 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.' 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.'

View File

@@ -31,7 +31,7 @@ jobs:
run: dotnet build src/Snap.Hutao/Snap.Hutao.Installer/Snap.Hutao.Installer.wixproj -c Release run: dotnet build src/Snap.Hutao/Snap.Hutao.Installer/Snap.Hutao.Installer.wixproj -c Release
- name: Upload MSI Artifact - name: Upload MSI Artifact
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v6
with: with:
name: Snap.Hutao-MSI name: Snap.Hutao-MSI
path: | path: |

View File

@@ -6,6 +6,8 @@
该版本注入功能暂不可用,并且由于缺失资源和开发能力,不建议长期使用 该版本注入功能暂不可用,并且由于缺失资源和开发能力,不建议长期使用
有条件的话可以加入discord服务器https://discord.gg/ucH3mgeWpQ
**English** **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. 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 项目启动位置已升级为 VS2026 的 slnx 格式 Snap.Hutao\src\Snap.Hutao\Snap.Hutao.slnx
> [!WARNING] > [!WARNING]
> 要使该项目可以长期运行,我们需要以下资源 > 要使该项目可以长期运行,我们需要以下资源
> 1. `src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DataSigning/SaltConstants.cs`中的新签名值 > 1. 元数据的编写
> 2. 元数据的编写 > 2. 图片资源
> 3. 图片资源
已同步原作者的元数据
V6.2的元数据已在编写中
测试仓库位置http://server.wdg.cloudns.ch:3000/wdg1122/Snap.Metadata.Test
**目前元数据的编写进度:** **目前元数据的编写进度:**
| 项目V6.2 | 是否完成 | | 项目V6.2 | 是否完成 |
| ----------- | ----------- | | ----------- | ----------- |
| 新角色的基本数据 | ✔️ | | 总体数据 | ✔️ |
| 新版本角色/怪物基础数值 | ❔ |
| 新角色的详细资料、名片等 | ❌ |
| 新武器 | ✔️ |
| 新材料 | ❇️ |
| 新怪物 | ❇️ |
| 新圣遗物 | / |
| 新卡池 | ❇️ |
| 新成就 | ✔️ |
| 深境螺旋 | 💠 |
| 幻想真境剧诗 | 💠 |
| 幽境危战 | ✔️ |
✔️:已完成 ✔️:已完成
❌:未编写 ❌:未编写
@@ -81,30 +71,21 @@ https://deepwiki.com/DGP-Studio/Snap.Hutao.Server
https://github.com/wangdage12/Snap.Metadata https://github.com/wangdage12/Snap.Metadata
镜像: 镜像:
![http://serverjp.wdg.cloudns.ch:3001/api/badge/6/status?style=flat-square](http://serverjp.wdg.cloudns.ch:3001/api/badge/6/status?style=flat-square) ![http://serverjp.wdg.cloudns.ch:3001/api/badge/11/status?style=flat-square](http://serverjp.wdg.cloudns.ch:3001/api/badge/11/status?style=flat-square)
http://server.wdg.cloudns.ch:3000/wdg1122/Snap.Metadata http://htgit.wdg.cloudns.ch/wdg1122/Snap.Metadata
![http://serverjp.wdg.cloudns.ch:3001/api/badge/7/status?style=flat-square](http://serverjp.wdg.cloudns.ch:3001/api/badge/7/status?style=flat-square)
http://serverjp.wdg.cloudns.ch:3000/wdg1122/Snap.Metadata
--- ---
**临时API** **临时API**
![http://serverjp.wdg.cloudns.ch:3001/api/badge/8/status?style=flat-square](http://serverjp.wdg.cloudns.ch:3001/api/badge/8/status?style=flat-square) ![http://serverjp.wdg.cloudns.ch:3001/api/badge/10/status?style=flat-square](http://serverjp.wdg.cloudns.ch:3001/api/badge/10/status?style=flat-square)
http://server.wdg.cloudns.ch:5222/ https://htserver.wdg.cloudns.ch/api/
![http://serverjp.wdg.cloudns.ch:3001/api/badge/9/status?style=flat-square](http://serverjp.wdg.cloudns.ch:3001/api/badge/9/status?style=flat-square)
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

Binary file not shown.

View File

@@ -2,7 +2,7 @@
<Package <Package
Name="Snap.Hutao" Name="Snap.Hutao"
Manufacturer="Millennium Science Technology R-D Inst" Manufacturer="Millennium Science Technology R-D Inst"
Version="1.0.0.0" Version="1.17.4.0"
UpgradeCode="121203be-60cb-408f-92cc-7080f6598e68" UpgradeCode="121203be-60cb-408f-92cc-7080f6598e68"
Scope="perMachine"> Scope="perMachine">

View 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
}
}

View File

@@ -193,11 +193,39 @@ internal sealed class ProcessFactory
public static void StartUsingShellExecuteRunAs(string fileName) public static void StartUsingShellExecuteRunAs(string fileName)
{ {
global::System.Diagnostics.Process.Start(new global::System.Diagnostics.ProcessStartInfo // 尝试从app包中启动
try
{ {
FileName = fileName, global::System.Diagnostics.Process.Start(new global::System.Diagnostics.ProcessStartInfo
UseShellExecute = true, {
Verb = "runas", 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,
UseShellExecute = true,
Verb = "runas",
});
}
}
} }
} }

View File

@@ -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)

View File

@@ -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.1.0" /> Version="1.18.0.0" />
<Properties> <Properties>
<DisplayName>Snap Hutao</DisplayName> <DisplayName>Snap Hutao</DisplayName>

View File

@@ -24,14 +24,23 @@ internal static class AvatarViewBuilderExtension
{ {
if (detailedCharacter.Costumes is [{ Id: { } id }, ..]) if (detailedCharacter.Costumes is [{ Id: { } id }, ..])
{ {
MetadataCostume costume = avatar.Costumes.Single(c => c.Id == id); MetadataCostume? costume = avatar.Costumes.SingleOrDefault(c => c.Id == id);
ArgumentNullException.ThrowIfNull(costume.FrontIcon); if (costume != null)
ArgumentNullException.ThrowIfNull(costume.SideIcon); {
ArgumentNullException.ThrowIfNull(costume.FrontIcon);
ArgumentNullException.ThrowIfNull(costume.SideIcon);
// Set to costume icon // Set to costume icon
builder.View.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon); builder.View.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon);
builder.View.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon); 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 else
{ {

View File

@@ -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}");
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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>>();

View File

@@ -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();
}
} }

View File

@@ -26,6 +26,14 @@ internal sealed class LaunchExecutionGameProcessStartHandler : AbstractLaunchExe
public override async ValueTask ExecuteAsync(LaunchExecutionContext context) public override async ValueTask ExecuteAsync(LaunchExecutionContext context)
{ {
// 如果启用了IslandFPS解锁则跳过启动游戏进程
// 因为unlockfps.exe会负责启动游戏
if (context.LaunchOptions.IsIslandEnabled.Value)
{
context.Progress.Report(new(SH.ServiceGameLaunchPhaseProcessStarted));
return;
}
try try
{ {
context.Process.Start(); context.Process.Start();

View File

@@ -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));

View File

@@ -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()

View File

@@ -10,6 +10,9 @@
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<UseWinUI>true</UseWinUI> <UseWinUI>true</UseWinUI>
<UseWPF>False</UseWPF> <UseWPF>False</UseWPF>
<!-- 配置版本号 -->
<Version>1.18.0.0</Version>
<UseWindowsForms>False</UseWindowsForms> <UseWindowsForms>False</UseWindowsForms>
<ImplicitUsings>False</ImplicitUsings> <ImplicitUsings>False</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@@ -68,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" />
@@ -288,7 +299,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.3.14"> <PackageReference Include="Snap.Hutao.SourceGeneration" Version="1.3.15">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -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>

View File

@@ -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.
}