mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
fix game package convert
This commit is contained in:
@@ -44,14 +44,12 @@
|
||||
<CornerRadius x:Key="CompatCornerRadiusRight">0,6,6,0</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
|
||||
<!-- OpenPaneLength -->
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
|
||||
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength2">304</x:Double>
|
||||
<!-- Length -->
|
||||
<GridLength x:Key="CompatGridLength2">288</GridLength>
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength2">304</x:Double>
|
||||
|
||||
<x:Double x:Key="HomeAdaptiveCardHeight">180</x:Double>
|
||||
|
||||
<x:Double x:Key="ContentDialogMinHeight">64</x:Double>
|
||||
<!-- ProgressBar -->
|
||||
<x:Double x:Key="LargeBackgroundProgressBarOpacity">0.2</x:Double>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Core.IO;
|
||||
@@ -14,7 +15,7 @@ internal static class DirectoryOperation
|
||||
return false;
|
||||
}
|
||||
|
||||
Directory.Move(sourceDirName, destDirName);
|
||||
FileSystem.MoveDirectory(sourceDirName, destDirName, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,15 +131,13 @@ internal sealed class RuntimeOptions : IOptions<RuntimeOptions>
|
||||
|
||||
private static bool GetElevated()
|
||||
{
|
||||
#if DEBUG_AS_FAKE_ELEVATED
|
||||
return true;
|
||||
#else
|
||||
|
||||
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
WindowsPrincipal principal = new(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void DetectWebView2Environment(ref string webView2Version, ref bool isWebView2Supported)
|
||||
|
||||
@@ -1473,6 +1473,33 @@ namespace Snap.Hutao.Resource.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 备份:{0} 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ServiceGamePackageConvertMoveFileBackupFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("ServiceGamePackageConvertMoveFileBackupFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 重命名:{0} 到:{1} 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ServiceGamePackageConvertMoveFileRenameFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("ServiceGamePackageConvertMoveFileRenameFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 替换:{0} 的本地化字符串。
|
||||
/// </summary>
|
||||
internal static string ServiceGamePackageConvertMoveFileRestoreFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("ServiceGamePackageConvertMoveFileRestoreFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 重命名数据文件夹名称失败 的本地化字符串。
|
||||
/// </summary>
|
||||
|
||||
@@ -644,6 +644,15 @@
|
||||
<data name="ServiceGameLocatorUnityLogGamePathNotFound" xml:space="preserve">
|
||||
<value>在 Unity 日志文件中找不到游戏路径</value>
|
||||
</data>
|
||||
<data name="ServiceGamePackageConvertMoveFileBackupFormat" xml:space="preserve">
|
||||
<value>备份:{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGamePackageConvertMoveFileRenameFormat" xml:space="preserve">
|
||||
<value>重命名:{0} 到:{1}</value>
|
||||
</data>
|
||||
<data name="ServiceGamePackageConvertMoveFileRestoreFormat" xml:space="preserve">
|
||||
<value>替换:{0}</value>
|
||||
</data>
|
||||
<data name="ServiceGamePackageRenameDataFolderFailed" xml:space="preserve">
|
||||
<value>重命名数据文件夹名称失败</value>
|
||||
</data>
|
||||
|
||||
@@ -163,7 +163,7 @@ internal sealed partial class GameService : IGameService
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
return changed || !LaunchSchemeMatchesExecutable(scheme, Path.GetFileName(gamePath));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -244,7 +244,7 @@ internal sealed partial class GameService : IGameService
|
||||
}
|
||||
|
||||
string gamePath = appOptions.GamePath;
|
||||
ArgumentNullException.ThrowIfNullOrEmpty(gamePath);
|
||||
ArgumentException.ThrowIfNullOrEmpty(gamePath);
|
||||
|
||||
using (Process game = ProcessInterop.InitializeGameProcess(launchOptions, gamePath))
|
||||
{
|
||||
|
||||
@@ -18,6 +18,15 @@ internal sealed partial class LaunchScheme
|
||||
private const string SdkStaticLauncherBilibiliKey = "KAtdSsoQ";
|
||||
private const string SdkStaticLauncherGlobalKey = "gcStgarh";
|
||||
|
||||
private static readonly LaunchScheme ServerChineseChannelOfficialSubChannelDefault = new()
|
||||
{
|
||||
LauncherId = SdkStaticLauncherChineseId,
|
||||
Key = SdkStaticLauncherChineseKey,
|
||||
Channel = ChannelType.Official,
|
||||
SubChannel = SubChannelType.Default,
|
||||
IsOversea = false,
|
||||
};
|
||||
|
||||
private static readonly LaunchScheme ServerChineseChannelOfficialSubChannelOfficial = new()
|
||||
{
|
||||
LauncherId = SdkStaticLauncherChineseId,
|
||||
@@ -81,6 +90,7 @@ internal sealed partial class LaunchScheme
|
||||
return new List<LaunchScheme>()
|
||||
{
|
||||
// 官服
|
||||
ServerChineseChannelOfficialSubChannelDefault,
|
||||
ServerChineseChannelOfficialSubChannelOfficial,
|
||||
ServerChineseChannelOfficialSubChannelNoTapTap,
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ internal sealed partial class PackageConverter
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly RuntimeOptions runtimeOptions;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly ILogger<PackageConverter> logger;
|
||||
|
||||
/// <summary>
|
||||
/// 异步检查替换游戏资源
|
||||
@@ -253,7 +254,9 @@ internal sealed partial class PackageConverter
|
||||
}
|
||||
|
||||
// Cache no matching item, download
|
||||
Directory.CreateDirectory(context.ServerCacheTargetFolder);
|
||||
string? directory = Path.GetDirectoryName(cacheFile);
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
Directory.CreateDirectory(directory);
|
||||
using (FileStream fileStream = File.Create(cacheFile))
|
||||
{
|
||||
string remoteUrl = context.GetScatteredFilesUrl(remoteName);
|
||||
@@ -288,24 +291,10 @@ internal sealed partial class PackageConverter
|
||||
|
||||
private async ValueTask<bool> ReplaceGameResourceAsync(List<ItemOperationInfo> operations, PackageConvertContext context, IProgress<PackageReplaceStatus> progress)
|
||||
{
|
||||
// 重命名 _Data 目录
|
||||
try
|
||||
{
|
||||
DirectoryOperation.Move(context.FromDataFolder, context.ToDataFolder);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Access to the path is denied.
|
||||
// When user install the game in special folder like 'Program Files'
|
||||
throw ThrowHelper.GameFileOperation(SH.ServiceGamePackageRenameDataFolderFailed, ex);
|
||||
}
|
||||
|
||||
// 执行下载与移动操作
|
||||
foreach (ItemOperationInfo info in operations)
|
||||
{
|
||||
progress.Report(new($"{info.Remote}"));
|
||||
|
||||
(bool backup, bool target) = info.Type switch
|
||||
(bool moveToBackup, bool moveToTarget) = info.Type switch
|
||||
{
|
||||
ItemOperationType.Backup => (true, false),
|
||||
ItemOperationType.Replace => (true, true),
|
||||
@@ -313,27 +302,52 @@ internal sealed partial class PackageConverter
|
||||
_ => (false, false),
|
||||
};
|
||||
|
||||
if (backup)
|
||||
// 先备份
|
||||
if (moveToBackup)
|
||||
{
|
||||
string localFileName = info.Local.RelativePath.Format(context.FromDataFolder);
|
||||
string localFileName = info.Local.RelativePath.Format(context.FromDataFolderName);
|
||||
progress.Report(new(SH.ServiceGamePackageConvertMoveFileBackupFormat.Format(localFileName)));
|
||||
|
||||
string localFilePath = context.GetGameFolderFilePath(localFileName);
|
||||
string? directory = Path.GetDirectoryName(localFilePath);
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
Directory.CreateDirectory(directory);
|
||||
File.Move(localFilePath, context.GetServerCacheBackupFilePath(localFileName), true);
|
||||
string cacheFilePath = context.GetServerCacheBackupFilePath(localFileName);
|
||||
string? cacheFileDirectory = Path.GetDirectoryName(cacheFilePath);
|
||||
ArgumentException.ThrowIfNullOrEmpty(cacheFileDirectory);
|
||||
Directory.CreateDirectory(cacheFileDirectory);
|
||||
|
||||
logger.LogInformation("Backing file from:{Src} to:{Dst}", localFilePath, cacheFilePath);
|
||||
FileOperation.Move(localFilePath, cacheFilePath, true);
|
||||
}
|
||||
|
||||
if (target)
|
||||
// 后替换
|
||||
if (moveToTarget)
|
||||
{
|
||||
string targetFileName = info.Remote.RelativePath.Format(context.ToDataFolder);
|
||||
string targetFileName = info.Remote.RelativePath.Format(context.ToDataFolderName);
|
||||
progress.Report(new(SH.ServiceGamePackageConvertMoveFileRestoreFormat.Format(targetFileName)));
|
||||
|
||||
string targetFilePath = context.GetGameFolderFilePath(targetFileName);
|
||||
string? directory = Path.GetDirectoryName(targetFilePath);
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
Directory.CreateDirectory(directory);
|
||||
File.Move(context.GetServerCacheTargetFilePath(targetFileName), targetFilePath, true);
|
||||
string? targetFileDirectory = Path.GetDirectoryName(targetFilePath);
|
||||
string cacheFilePath = context.GetServerCacheTargetFilePath(targetFileName);
|
||||
ArgumentException.ThrowIfNullOrEmpty(targetFileDirectory);
|
||||
Directory.CreateDirectory(targetFileDirectory);
|
||||
|
||||
logger.LogInformation("Restoring file from:{Src} to:{Dst}", cacheFilePath, targetFilePath);
|
||||
FileOperation.Move(cacheFilePath, targetFilePath, true);
|
||||
}
|
||||
}
|
||||
|
||||
// 重命名 _Data 目录
|
||||
try
|
||||
{
|
||||
progress.Report(new(SH.ServiceGamePackageConvertMoveFileRenameFormat.Format(context.FromDataFolderName, context.ToDataFolderName)));
|
||||
DirectoryOperation.Move(context.FromDataFolder, context.ToDataFolder);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Access to the path is denied.
|
||||
// When user install the game in special folder like 'Program Files'
|
||||
throw ThrowHelper.GameFileOperation(SH.ServiceGamePackageRenameDataFolderFailed, ex);
|
||||
}
|
||||
|
||||
// 重新下载所有 *pkg_version 文件
|
||||
await ReplacePackageVersionFilesAsync(context).ConfigureAwait(false);
|
||||
return true;
|
||||
|
||||
@@ -14,11 +14,11 @@ internal sealed class PackageReplaceStatus : ICloneable<PackageReplaceStatus>
|
||||
/// <summary>
|
||||
/// 构造一个新的包更新状态
|
||||
/// </summary>
|
||||
/// <param name="description">描述</param>
|
||||
public PackageReplaceStatus(string description)
|
||||
/// <param name="name">描述</param>
|
||||
public PackageReplaceStatus(string name)
|
||||
{
|
||||
Name = default!;
|
||||
Description = description;
|
||||
Name = name;
|
||||
Description = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -34,23 +34,27 @@ internal sealed class PackageReplaceStatus : ICloneable<PackageReplaceStatus>
|
||||
Description = $"{Converters.ToFileSizeString(bytesRead)}/{Converters.ToFileSizeString(totalBytes)}";
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
private PackageReplaceStatus()
|
||||
{
|
||||
}
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否有进度
|
||||
/// </summary>
|
||||
public bool IsIndeterminate { get => Percent < 0; }
|
||||
public string Description { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 进度
|
||||
/// </summary>
|
||||
public double Percent { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 是否有进度
|
||||
/// </summary>
|
||||
public bool IsIndeterminate { get => Percent < 0; }
|
||||
|
||||
/// <summary>
|
||||
/// 克隆
|
||||
/// </summary>
|
||||
@@ -58,6 +62,11 @@ internal sealed class PackageReplaceStatus : ICloneable<PackageReplaceStatus>
|
||||
public PackageReplaceStatus Clone()
|
||||
{
|
||||
// 进度需要在主线程上创建
|
||||
return new(Description) { Percent = Percent };
|
||||
return new()
|
||||
{
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
Percent = Percent,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,22 @@
|
||||
IsIndeterminate="{Binding State.IsIndeterminate}"
|
||||
Maximum="1"
|
||||
Value="{Binding State.Percent}"/>
|
||||
<TextBlock
|
||||
MinWidth="360"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding State.Description}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding State.Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding State.Description}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -10,6 +10,7 @@ using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service;
|
||||
using Snap.Hutao.Service.Game;
|
||||
using Snap.Hutao.Service.Game.Package;
|
||||
using Snap.Hutao.Service.Navigation;
|
||||
using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.User;
|
||||
@@ -188,7 +189,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
{
|
||||
// Channel changed, we need to change local file.
|
||||
LaunchGamePackageConvertDialog dialog = await contentDialogFactory.CreateInstanceAsync<LaunchGamePackageConvertDialog>().ConfigureAwait(false);
|
||||
Progress<Service.Game.Package.PackageReplaceStatus> progress = new(state => dialog.State = state.Clone());
|
||||
IProgress<PackageReplaceStatus> progress = taskContext.CreateProgressForMainThread<PackageReplaceStatus>(state => dialog.State = state/*.Clone()*/);
|
||||
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
if (!await gameService.EnsureGameResourceAsync(SelectedScheme, progress).ConfigureAwait(false))
|
||||
@@ -212,6 +213,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ExceptionFormat.Format(ex));
|
||||
infoBarService.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user