diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs index cacd06f3..6bda9d53 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/SettingEntry.cs @@ -86,6 +86,11 @@ internal sealed class SettingEntry /// public const string LaunchMonitor = "Launch.Monitor"; + /// + /// 启动游戏 多倍启动 + /// + public const string MultipleInstances = "Launch.MultipleInstances"; + /// /// 语言 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs index 9a58c27a..b3ea980d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs @@ -3723,6 +3723,24 @@ namespace Snap.Hutao.Resource.Localization { } } + /// + /// 查找类似 多倍启动你的原神,你可以使用胡桃来多次打开原神并且不受到影响 的本地化字符串。 + /// + internal static string ViewPageLaunchGameMultipleInstancesDescription { + get { + return ResourceManager.GetString("ViewPageLaunchGameMultipleInstancesDescription", resourceCulture); + } + } + + /// + /// 查找类似 多倍启动 的本地化字符串。 + /// + internal static string ViewPageLaunchGameMultipleInstancesHeader { + get { + return ResourceManager.GetString("ViewPageLaunchGameMultipleInstancesHeader", resourceCulture); + } + } + /// /// 查找类似 游戏选项 的本地化字符串。 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index f306750c..52e80233 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -1338,6 +1338,12 @@ 显示器 + + 多倍启动你的原神,你可以使用胡桃来多次打开原神并且不受到影响 + + + 多倍启动 + 游戏选项 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs index 46c10a46..aa5bdda8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/GameService.cs @@ -8,6 +8,7 @@ using Snap.Hutao.Core; using Snap.Hutao.Core.Database; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO.Ini; +using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Service.Game.Locator; @@ -311,7 +312,7 @@ internal sealed class GameService : IGameService /// public async ValueTask LaunchAsync(LaunchOptions options) { - if (IsGameRunning()) + if (!options.MultipleInstances && IsGameRunning()) { return; } @@ -347,6 +348,15 @@ internal sealed class GameService : IGameService using (await gameSemaphore.EnterAsync().ConfigureAwait(false)) { + if (options.MultipleInstances && Activation.GetElevated()) + { + await LaunchGameAsync(game, gamePath); + } + else + { + await LaunchGameAsync(game); + } + if (options.UnlockFps) { IGameFpsUnlocker unlocker = new GameFpsUnlocker(game, options.TargetFps); @@ -355,21 +365,52 @@ internal sealed class GameService : IGameService TimeSpan findModuleLimit = TimeSpan.FromMilliseconds(10000); TimeSpan adjustFpsDelay = TimeSpan.FromMilliseconds(2000); - if (game.Start()) - { - await unlocker.UnlockAsync(findModuleDelay, findModuleLimit, adjustFpsDelay).ConfigureAwait(false); - } - } - else - { - if (game.Start()) - { - await game.WaitForExitAsync().ConfigureAwait(false); - } + await unlocker.UnlockAsync(findModuleDelay, findModuleLimit, adjustFpsDelay).ConfigureAwait(false); } } } + /// + /// 为了实现多开 需要修改mhypbase.dll名称 这是必须的步骤 + /// + /// 游戏线程 + /// 游戏路径 + /// 是否成功替换文件 + public async Task LaunchMultipleInstancesGameAsync(Process gameProcess, string? gamePath) + { + if (gamePath == null) + { + return false; + } + + DirectoryInfo directoryInfo = new DirectoryInfo(gamePath); + if (directoryInfo.Parent == null) + { + return false; + } + + string? gameDirectory = directoryInfo.Parent.FullName.ToString(); + string? mhypbasePath = $@"{gameDirectory}\mhypbase.dll"; + string? tempPath = $@"{gameDirectory}\mhypbase.dll.backup"; + if (File.Exists(mhypbasePath)) + { + File.Move(mhypbasePath, tempPath); + } + else if (!File.Exists(tempPath)) + { + return false; + } + + gameProcess.Start(); + + // wait 12sec for loading library files + await Task.Delay(12000); + + File.Move(tempPath, mhypbasePath); + + return false; + } + /// public async ValueTask DetectGameAccountAsync() { @@ -470,4 +511,24 @@ internal sealed class GameService : IGameService return (launchScheme.IsOversea && gameFileName == GenshinImpactFileName) || (!launchScheme.IsOversea && gameFileName == YuanShenFileName); } + + private async Task LaunchGameAsync(Process gameProcess, string? gamePath = null) + { + try + { + if (gamePath == null) + { + gameProcess.Start(); + } + else + { + await LaunchMultipleInstancesGameAsync(gameProcess, gamePath); + return; + } + } + catch + { + return; + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs index 2ce84853..554c4cec 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Game/LaunchOptions.cs @@ -35,6 +35,7 @@ internal sealed class LaunchOptions : ObservableObject, IOptions private bool? unlockFps; private int? targetFps; private NameValue? monitor; + private bool? multipleInstances; /// /// 构造一个新的启动游戏选项 @@ -357,6 +358,40 @@ internal sealed class LaunchOptions : ObservableObject, IOptions } } + /// + /// 多次启动原神 + /// + public bool MultipleInstances + { + get + { + if (multipleInstances == null) + { + using (IServiceScope scope = serviceScopeFactory.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + string? value = appDbContext.Settings.SingleOrDefault(e => e.Key == SettingEntry.MultipleInstances)?.Value; + multipleInstances = value != null && bool.Parse(value); + } + } + + return multipleInstances.Value; + } + + set + { + if (SetProperty(ref multipleInstances, value)) + { + using (IServiceScope scope = serviceScopeFactory.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + appDbContext.Settings.ExecuteDeleteWhere(e => e.Key == SettingEntry.MultipleInstances); + appDbContext.Settings.AddAndSave(new(SettingEntry.MultipleInstances, value.ToString())); + } + } + } + } + /// public LaunchOptions Value { get => this; } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml index b8e58b5a..d33f1ace 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/LaunchGamePage.xaml @@ -257,6 +257,17 @@ IsOpen="{Binding IsElevated}" Message="{shcm:ResourceString Name=ViewPageLaunchGameAdvanceHint}" Severity="Error"/> + + + + + (); - if (gameService.IsGameRunning()) + if (!Options.MultipleInstances && gameService.IsGameRunning()) { return; }