fix game package convert

This commit is contained in:
DismissedLight
2023-08-20 21:50:39 +08:00
parent 368d0bfbd7
commit f2fea5a0e6
11 changed files with 138 additions and 57 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
};
}
}

View File

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

View File

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