mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
package convert impl
This commit is contained in:
@@ -8,7 +8,7 @@ namespace Snap.Hutao.Installer;
|
|||||||
|
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
private const string AppxKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Appx";
|
private const string AppxKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock";
|
||||||
private const string ValueName = "AllowDevelopmentWithoutDevLicense";
|
private const string ValueName = "AllowDevelopmentWithoutDevLicense";
|
||||||
|
|
||||||
public static async Task Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ public partial class App : Application
|
|||||||
/// Initializes the singleton application object.
|
/// Initializes the singleton application object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">日志器</param>
|
/// <param name="logger">日志器</param>
|
||||||
/// <param name="appCenter">App Center</param>
|
|
||||||
public App(ILogger<App> logger)
|
public App(ILogger<App> logger)
|
||||||
{
|
{
|
||||||
// load app resource
|
// load app resource
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ internal static class IocConfiguration
|
|||||||
{
|
{
|
||||||
if (context.Database.GetPendingMigrations().Any())
|
if (context.Database.GetPendingMigrations().Any())
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
Debug.WriteLine("[Debug] Performing AppDbContext Migrations");
|
Debug.WriteLine("[Debug] Performing AppDbContext Migrations");
|
||||||
|
#endif
|
||||||
context.Database.Migrate();
|
context.Database.Migrate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/Snap.Hutao/Snap.Hutao/Core/IO/FileDigest.cs
Normal file
31
src/Snap.Hutao/Snap.Hutao/Core/IO/FileDigest.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.IO;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件摘要
|
||||||
|
/// </summary>
|
||||||
|
internal static class FileDigest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 异步获取文件 Md5 摘要
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">文件路径</param>
|
||||||
|
/// <param name="token">取消令牌</param>
|
||||||
|
/// <returns>文件 Md5 摘要</returns>
|
||||||
|
public static async Task<string> GetMd5Async(string filePath, CancellationToken token)
|
||||||
|
{
|
||||||
|
using (FileStream stream = File.OpenRead(filePath))
|
||||||
|
{
|
||||||
|
using (MD5 md5 = MD5.Create())
|
||||||
|
{
|
||||||
|
byte[] bytes = await md5.ComputeHashAsync(stream, token).ConfigureAwait(false);
|
||||||
|
return System.Convert.ToHexString(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -109,4 +109,4 @@ internal readonly struct FilePath : IEquatable<FilePath>
|
|||||||
{
|
{
|
||||||
return Value.GetHashCode();
|
return Value.GetHashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,10 @@ internal static class SettingKeys
|
|||||||
public const string LaunchTimes = "LaunchTimes";
|
public const string LaunchTimes = "LaunchTimes";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 静态资源合约V1
|
/// 静态资源合约
|
||||||
|
/// 新增合约时 请注意
|
||||||
|
/// <see cref="StaticResource.FulfillAllContracts"/>
|
||||||
|
/// 与 <see cref="StaticResource.IsAnyUnfulfilledContractPresent"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string StaticResourceV1Contract = "StaticResourceV1Contract";
|
public const string StaticResourceV1Contract = "StaticResourceV1Contract";
|
||||||
|
|
||||||
@@ -37,4 +40,9 @@ internal static class SettingKeys
|
|||||||
/// 静态资源合约V3 刷新 Skill Talent
|
/// 静态资源合约V3 刷新 Skill Talent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string StaticResourceV3Contract = "StaticResourceV3Contract";
|
public const string StaticResourceV3Contract = "StaticResourceV3Contract";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 静态资源合约V4 刷新 AvatarIcon
|
||||||
|
/// </summary>
|
||||||
|
public const string StaticResourceV4Contract = "StaticResourceV4Contract";
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,17 @@ namespace Snap.Hutao.Core.Setting;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class StaticResource
|
internal static class StaticResource
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 完成所有合约
|
||||||
|
/// </summary>
|
||||||
|
public static void FulfillAllContracts()
|
||||||
|
{
|
||||||
|
LocalSetting.Set(SettingKeys.StaticResourceV1Contract, true);
|
||||||
|
LocalSetting.Set(SettingKeys.StaticResourceV2Contract, true);
|
||||||
|
LocalSetting.Set(SettingKeys.StaticResourceV3Contract, true);
|
||||||
|
LocalSetting.Set(SettingKeys.StaticResourceV4Contract, true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 提供的合约是否未完成
|
/// 提供的合约是否未完成
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -26,6 +37,7 @@ internal static class StaticResource
|
|||||||
{
|
{
|
||||||
return !LocalSetting.Get(SettingKeys.StaticResourceV1Contract, false)
|
return !LocalSetting.Get(SettingKeys.StaticResourceV1Contract, false)
|
||||||
|| (!LocalSetting.Get(SettingKeys.StaticResourceV2Contract, false))
|
|| (!LocalSetting.Get(SettingKeys.StaticResourceV2Contract, false))
|
||||||
|| (!LocalSetting.Get(SettingKeys.StaticResourceV3Contract, false));
|
|| (!LocalSetting.Get(SettingKeys.StaticResourceV3Contract, false))
|
||||||
|
|| (!LocalSetting.Get(SettingKeys.StaticResourceV4Contract, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,45 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
namespace Snap.Hutao.Model.Binding.LaunchGame;
|
namespace Snap.Hutao.Model.Binding.LaunchGame;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 服务器方案
|
|
||||||
/// </summary>
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动方案
|
/// 启动方案
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LaunchScheme
|
public class LaunchScheme
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 已知的启动方案
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ImmutableList<LaunchScheme> KnownSchemes = new List<LaunchScheme>()
|
||||||
|
{
|
||||||
|
new LaunchScheme("官方服 | 天空岛", "eYd89JmJ", "18", "1", "1"),
|
||||||
|
new LaunchScheme("渠道服 | 世界树", "KAtdSsoQ", "17", "14", "0"),
|
||||||
|
new LaunchScheme("国际服 | 部分支持", "gcStgarh", "10", "1", "0"),
|
||||||
|
}.ToImmutableList();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的启动方案
|
/// 构造一个新的启动方案
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">名称</param>
|
/// <param name="displayName">名称</param>
|
||||||
/// <param name="channel">通道</param>
|
/// <param name="channel">通道</param>
|
||||||
/// <param name="cps">通道描述字符串</param>
|
|
||||||
/// <param name="subChannel">子通道</param>
|
/// <param name="subChannel">子通道</param>
|
||||||
/// <param name="launcherId">启动器Id</param>
|
/// <param name="launcherId">启动器Id</param>
|
||||||
public LaunchScheme(string name, string channel, string subChannel, string launcherId)
|
private LaunchScheme(string displayName, string key, string launcherId, string channel, string subChannel)
|
||||||
{
|
{
|
||||||
Name = name;
|
DisplayName = displayName;
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
SubChannel = subChannel;
|
SubChannel = subChannel;
|
||||||
LauncherId = launcherId;
|
LauncherId = launcherId;
|
||||||
|
Key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 名称
|
/// 名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通道
|
/// 通道
|
||||||
@@ -46,4 +55,14 @@ public class LaunchScheme
|
|||||||
/// 启动器Id
|
/// 启动器Id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LauncherId { get; set; }
|
public string LauncherId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API Key
|
||||||
|
/// </summary>
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否为海外
|
||||||
|
/// </summary>
|
||||||
|
public bool IsOversea { get => LauncherId == "10"; }
|
||||||
}
|
}
|
||||||
@@ -101,12 +101,14 @@ public static class AvatarIds
|
|||||||
{
|
{
|
||||||
Name = "旅行者",
|
Name = "旅行者",
|
||||||
Icon = "UI_AvatarIcon_PlayerBoy",
|
Icon = "UI_AvatarIcon_PlayerBoy",
|
||||||
|
SideIcon = "UI_AvatarIcon_Side_PlayerBoy",
|
||||||
Quality = Intrinsic.ItemQuality.QUALITY_ORANGE,
|
Quality = Intrinsic.ItemQuality.QUALITY_ORANGE,
|
||||||
},
|
},
|
||||||
[PlayerGirl] = new()
|
[PlayerGirl] = new()
|
||||||
{
|
{
|
||||||
Name = "旅行者",
|
Name = "旅行者",
|
||||||
Icon = "UI_AvatarIcon_PlayerGirl",
|
Icon = "UI_AvatarIcon_PlayerGirl",
|
||||||
|
SideIcon = "UI_AvatarIcon_Side_PlayerGirl",
|
||||||
Quality = Intrinsic.ItemQuality.QUALITY_ORANGE,
|
Quality = Intrinsic.ItemQuality.QUALITY_ORANGE,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<Identity
|
<Identity
|
||||||
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
|
||||||
Publisher="CN=DGP Studio"
|
Publisher="CN=DGP Studio&comma DissmissedLight"
|
||||||
Version="1.3.13.0" />
|
Version="1.3.13.0" />
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<Dependencies>
|
<Dependencies>
|
||||||
<!--<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />-->
|
<!--<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />-->
|
||||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22000.0" />
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.22621.0" />
|
||||||
</Dependencies>
|
</Dependencies>
|
||||||
|
|
||||||
<Resources>
|
<Resources>
|
||||||
|
|||||||
@@ -74,11 +74,8 @@ public static class GachaStatisticsExtensions
|
|||||||
|
|
||||||
private static Color GetColorByName(string name)
|
private static Color GetColorByName(string name)
|
||||||
{
|
{
|
||||||
byte[] codes = MD5.HashData(Encoding.UTF8.GetBytes(name));
|
Span<byte> codes = MD5.HashData(Encoding.UTF8.GetBytes(name));
|
||||||
Span<byte> first = new(codes, 0, 5);
|
Color color = Color.FromArgb(255, codes.Slice(0, 5).Average(), codes.Slice(5, 5).Average(), codes.Slice(10, 5).Average());
|
||||||
Span<byte> second = new(codes, 5, 5);
|
|
||||||
Span<byte> third = new(codes, 10, 5);
|
|
||||||
Color color = Color.FromArgb(255, first.Average(), second.Average(), third.Average());
|
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ internal class GachaStatisticsFactory : IGachaStatisticsFactory
|
|||||||
bool isEmptyHistoryWishVisible = entry.GetBoolean();
|
bool isEmptyHistoryWishVisible = entry.GetBoolean();
|
||||||
|
|
||||||
IOrderedEnumerable<GachaItem> orderedItems = items.OrderBy(i => i.Id);
|
IOrderedEnumerable<GachaItem> orderedItems = items.OrderBy(i => i.Id);
|
||||||
return await Task.Run(() => CreateCore(orderedItems, historyWishBuilders, idAvatarMap, idWeaponMap, isEmptyHistoryWishVisible)).ConfigureAwait(false);
|
await ThreadHelper.SwitchToBackgroundAsync();
|
||||||
|
return CreateCore(orderedItems, historyWishBuilders, idAvatarMap, idWeaponMap, isEmptyHistoryWishVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GachaStatistics CreateCore(
|
private static GachaStatistics CreateCore(
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Snap.Hutao.Core;
|
||||||
|
using Snap.Hutao.Core.Database;
|
||||||
|
using Snap.Hutao.Core.IO.Ini;
|
||||||
|
using Snap.Hutao.Extension;
|
||||||
|
using Snap.Hutao.Model.Binding.LaunchGame;
|
||||||
|
using Snap.Hutao.Model.Entity;
|
||||||
|
using Snap.Hutao.Model.Entity.Database;
|
||||||
|
using Snap.Hutao.Service.Game.Locator;
|
||||||
|
using Snap.Hutao.Service.Game.Unlocker;
|
||||||
|
using Snap.Hutao.View.Dialog;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏文件操作异常
|
||||||
|
/// </summary>
|
||||||
|
internal class GameFileOperationException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的用户数据损坏异常
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">消息</param>
|
||||||
|
/// <param name="innerException">内部错误</param>
|
||||||
|
public GameFileOperationException(string message, Exception innerException)
|
||||||
|
: base($"游戏文件操作失败: {message}", innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,11 @@ using Snap.Hutao.Model.Binding.LaunchGame;
|
|||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
using Snap.Hutao.Model.Entity.Database;
|
using Snap.Hutao.Model.Entity.Database;
|
||||||
using Snap.Hutao.Service.Game.Locator;
|
using Snap.Hutao.Service.Game.Locator;
|
||||||
|
using Snap.Hutao.Service.Game.Package;
|
||||||
using Snap.Hutao.Service.Game.Unlocker;
|
using Snap.Hutao.Service.Game.Unlocker;
|
||||||
using Snap.Hutao.View.Dialog;
|
using Snap.Hutao.View.Dialog;
|
||||||
|
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||||
|
using Snap.Hutao.Web.Response;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -24,13 +27,15 @@ namespace Snap.Hutao.Service.Game;
|
|||||||
/// 游戏服务
|
/// 游戏服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Injection(InjectAs.Singleton, typeof(IGameService))]
|
[Injection(InjectAs.Singleton, typeof(IGameService))]
|
||||||
internal class GameService : IGameService, IDisposable
|
[SuppressMessage("", "CA1001")]
|
||||||
|
internal class GameService : IGameService
|
||||||
{
|
{
|
||||||
private const string GamePathKey = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}";
|
private const string GamePathKey = $"{nameof(GameService)}.Cache.{SettingEntry.GamePath}";
|
||||||
private const string ConfigFile = "config.ini";
|
private const string ConfigFile = "config.ini";
|
||||||
|
|
||||||
private readonly IServiceScopeFactory scopeFactory;
|
private readonly IServiceScopeFactory scopeFactory;
|
||||||
private readonly IMemoryCache memoryCache;
|
private readonly IMemoryCache memoryCache;
|
||||||
|
private readonly PackageConverter packageConverter;
|
||||||
private readonly SemaphoreSlim gameSemaphore = new(1);
|
private readonly SemaphoreSlim gameSemaphore = new(1);
|
||||||
|
|
||||||
private ObservableCollection<GameAccount>? gameAccounts;
|
private ObservableCollection<GameAccount>? gameAccounts;
|
||||||
@@ -40,10 +45,12 @@ internal class GameService : IGameService, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scopeFactory">范围工厂</param>
|
/// <param name="scopeFactory">范围工厂</param>
|
||||||
/// <param name="memoryCache">内存缓存</param>
|
/// <param name="memoryCache">内存缓存</param>
|
||||||
public GameService(IServiceScopeFactory scopeFactory, IMemoryCache memoryCache)
|
/// <param name="packageConverter">游戏文件包转换器</param>
|
||||||
|
public GameService(IServiceScopeFactory scopeFactory, IMemoryCache memoryCache, PackageConverter packageConverter)
|
||||||
{
|
{
|
||||||
this.scopeFactory = scopeFactory;
|
this.scopeFactory = scopeFactory;
|
||||||
this.memoryCache = memoryCache;
|
this.memoryCache = memoryCache;
|
||||||
|
this.packageConverter = packageConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -159,15 +166,26 @@ internal class GameService : IGameService, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetMultiChannel(LaunchScheme scheme)
|
public bool SetMultiChannel(LaunchScheme scheme)
|
||||||
{
|
{
|
||||||
string gamePath = GetGamePathSkipLocator();
|
string gamePath = GetGamePathSkipLocator();
|
||||||
string configPath = Path.Combine(Path.GetDirectoryName(gamePath)!, ConfigFile);
|
string configPath = Path.Combine(Path.GetDirectoryName(gamePath)!, ConfigFile);
|
||||||
|
|
||||||
List<IniElement> elements;
|
List<IniElement> elements;
|
||||||
using (FileStream readStream = File.OpenRead(configPath))
|
try
|
||||||
{
|
{
|
||||||
elements = IniSerializer.Deserialize(readStream).ToList();
|
using (FileStream readStream = File.OpenRead(configPath))
|
||||||
|
{
|
||||||
|
elements = IniSerializer.Deserialize(readStream).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException dnfEx)
|
||||||
|
{
|
||||||
|
throw new GameFileOperationException($"找不到游戏配置文件 {configPath}", dnfEx);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException uaEx)
|
||||||
|
{
|
||||||
|
throw new GameFileOperationException($"无法读取或保存配置文件,请以管理员模式重试。", uaEx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
@@ -203,10 +221,36 @@ internal class GameService : IGameService, IDisposable
|
|||||||
IniSerializer.Serialize(writeStream, elements);
|
IniSerializer.Serialize(writeStream, elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task<bool> ReplaceGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress)
|
||||||
|
{
|
||||||
|
string gamePath = GetGamePathSkipLocator();
|
||||||
|
string gameFolder = Path.GetDirectoryName(gamePath)!;
|
||||||
|
string gameFileName = Path.GetFileName(gamePath);
|
||||||
|
|
||||||
|
if (launchScheme.IsOversea && gameFileName == "GenshinImpact.exe")
|
||||||
|
{
|
||||||
|
// Already that scheme, no need to replace files
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (!launchScheme.IsOversea && gameFileName == "YuanShen.exe")
|
||||||
|
{
|
||||||
|
// Already that scheme, no need to replace files
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we still need to handle the Bilibili scheme.
|
||||||
|
await packageConverter.ReplaceGameResourceAsync(launchScheme, gameFolder, progress).ConfigureAwait(false);
|
||||||
|
// We need to change the gamePath if we switch.
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[SuppressMessage("", "IDE0046")]
|
|
||||||
public bool IsGameRunning()
|
public bool IsGameRunning()
|
||||||
{
|
{
|
||||||
if (gameSemaphore.CurrentCount == 0)
|
if (gameSemaphore.CurrentCount == 0)
|
||||||
@@ -387,10 +431,4 @@ internal class GameService : IGameService, IDisposable
|
|||||||
await scope.ServiceProvider.GetRequiredService<AppDbContext>().GameAccounts.RemoveAndSaveAsync(gameAccount).ConfigureAwait(false);
|
await scope.ServiceProvider.GetRequiredService<AppDbContext>().GameAccounts.RemoveAndSaveAsync(gameAccount).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
gameSemaphore?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using Snap.Hutao.Model.Binding.LaunchGame;
|
using Snap.Hutao.Model.Binding.LaunchGame;
|
||||||
using Snap.Hutao.Model.Entity;
|
using Snap.Hutao.Model.Entity;
|
||||||
|
using Snap.Hutao.Service.Game.Package;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Snap.Hutao.Service.Game;
|
namespace Snap.Hutao.Service.Game;
|
||||||
@@ -83,6 +84,14 @@ internal interface IGameService
|
|||||||
/// <returns>任务</returns>
|
/// <returns>任务</returns>
|
||||||
ValueTask RemoveGameAccountAsync(GameAccount gameAccount);
|
ValueTask RemoveGameAccountAsync(GameAccount gameAccount);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 替换游戏资源
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="launchScheme">目标启动方案</param>
|
||||||
|
/// <param name="progress">进度</param>
|
||||||
|
/// <returns>是否替换成功</returns>
|
||||||
|
Task<bool> ReplaceGameResourceAsync(LaunchScheme launchScheme, IProgress<PackageReplaceStatus> progress);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 修改注册表中的账号信息
|
/// 修改注册表中的账号信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -94,5 +103,6 @@ internal interface IGameService
|
|||||||
/// 设置多通道值
|
/// 设置多通道值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scheme">方案</param>
|
/// <param name="scheme">方案</param>
|
||||||
void SetMultiChannel(LaunchScheme scheme);
|
/// <returns>是否更改了ini文件</returns>
|
||||||
|
bool SetMultiChannel(LaunchScheme scheme);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 转换方向
|
||||||
|
/// </summary>
|
||||||
|
internal enum ConvertDirection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 国际服转国服
|
||||||
|
/// </summary>
|
||||||
|
OverseaToChinese,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 国服转国际服
|
||||||
|
/// </summary>
|
||||||
|
ChineseToOversea,
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包操作
|
||||||
|
/// </summary>
|
||||||
|
[DebuggerDisplay("Action:{Type} Target:{Target} Cache:{Cache}")]
|
||||||
|
internal class ItemOperationInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的包操作
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">操作类型</param>
|
||||||
|
/// <param name="target">目标</param>
|
||||||
|
/// <param name="cache">缓存</param>
|
||||||
|
public ItemOperationInfo(ItemOperationType type, VersionItem target, VersionItem cache)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Target = target.RemoteName;
|
||||||
|
Cache = cache.RemoteName;
|
||||||
|
Md5 = target.Md5;
|
||||||
|
TotalBytes = target.FileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作的类型
|
||||||
|
/// </summary>
|
||||||
|
public ItemOperationType Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标文件
|
||||||
|
/// </summary>
|
||||||
|
public string Target { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移动至中时的名称
|
||||||
|
/// </summary>
|
||||||
|
public string Cache { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件的目标Md5
|
||||||
|
/// </summary>
|
||||||
|
public string Md5 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件的目标大小 Byte
|
||||||
|
/// </summary>
|
||||||
|
public long TotalBytes { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包文件操作的类型
|
||||||
|
/// </summary>
|
||||||
|
internal enum ItemOperationType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 添加
|
||||||
|
/// </summary>
|
||||||
|
Add,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除
|
||||||
|
/// </summary>
|
||||||
|
Remove,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 替换
|
||||||
|
/// </summary>
|
||||||
|
Replace,
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||||
|
using Snap.Hutao.Core.IO;
|
||||||
|
using Snap.Hutao.Model.Binding.LaunchGame;
|
||||||
|
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||||
|
using Snap.Hutao.Web.Response;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 游戏文件包转换器
|
||||||
|
/// </summary>
|
||||||
|
[HttpClient(HttpClientConfigration.Default)]
|
||||||
|
internal class PackageConverter
|
||||||
|
{
|
||||||
|
private const string GenshinImpactData = "GenshinImpact_Data";
|
||||||
|
private const string YuanShenData = "YuanShen_Data";
|
||||||
|
|
||||||
|
private readonly ResourceClient resourceClient;
|
||||||
|
private readonly JsonSerializerOptions options;
|
||||||
|
private readonly HttpClient httpClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的游戏文件转换器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resourceClient">资源客户端</param>
|
||||||
|
/// <param name="options">Json序列化选项</param>
|
||||||
|
/// <param name="httpClient">http客户端</param>
|
||||||
|
public PackageConverter(ResourceClient resourceClient, JsonSerializerOptions options, HttpClient httpClient)
|
||||||
|
{
|
||||||
|
this.resourceClient = resourceClient;
|
||||||
|
this.options = options;
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步替换游戏资源
|
||||||
|
/// 调用前需要确认本地文件与服务器上的不同
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetScheme">目标启动方案</param>
|
||||||
|
/// <param name="gameFolder">游戏目录</param>
|
||||||
|
/// <param name="progress">进度</param>
|
||||||
|
/// <returns>任务</returns>
|
||||||
|
public async Task<bool> ReplaceGameResourceAsync(LaunchScheme targetScheme, string gameFolder, IProgress<PackageReplaceStatus> progress)
|
||||||
|
{
|
||||||
|
await ThreadHelper.SwitchToBackgroundAsync();
|
||||||
|
progress.Report(new("查询游戏资源信息"));
|
||||||
|
Response<GameResource> response = await resourceClient.GetResourceAsync(targetScheme).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (response.IsOk())
|
||||||
|
{
|
||||||
|
GameResource remoteGameResouce = response.Data;
|
||||||
|
|
||||||
|
string scatteredFilesUrl = remoteGameResouce.Game.Latest.DecompressedPath;
|
||||||
|
Uri pkgVersionUri = new($"{scatteredFilesUrl}/pkg_version");
|
||||||
|
ConvertDirection direction = targetScheme.IsOversea ? ConvertDirection.ChineseToOversea : ConvertDirection.OverseaToChinese;
|
||||||
|
|
||||||
|
progress.Report(new("下载包版本信息"));
|
||||||
|
Dictionary<string, VersionItem> remoteItems;
|
||||||
|
using (Stream remoteSteam = await httpClient.GetStreamAsync(pkgVersionUri).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
remoteItems = await GetVersionItemsAsync(remoteSteam).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, VersionItem> localItems;
|
||||||
|
using (FileStream localSteam = File.OpenRead(Path.Combine(gameFolder, "pkg_version")))
|
||||||
|
{
|
||||||
|
localItems = await GetVersionItemsAsync(localSteam, direction, ConvertRemoteName).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<ItemOperationInfo> diffOperations = GetItemOperationInfos(remoteItems, localItems);
|
||||||
|
var a = diffOperations.ToList();
|
||||||
|
await ReplaceGameResourceCoreAsync(diffOperations, gameFolder, scatteredFilesUrl, direction, progress).ConfigureAwait(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ConvertRemoteName(string remoteName, ConvertDirection direction)
|
||||||
|
{
|
||||||
|
// 我们已经提前重命名了整个 Data 文件夹 所以需要将 RemoteName 中的 Data 同样替换
|
||||||
|
if (remoteName.StartsWith(YuanShenData) || remoteName.StartsWith(GenshinImpactData))
|
||||||
|
{
|
||||||
|
return direction switch
|
||||||
|
{
|
||||||
|
ConvertDirection.OverseaToChinese => $"{YuanShenData}{remoteName[GenshinImpactData.Length..]}",
|
||||||
|
ConvertDirection.ChineseToOversea => $"{GenshinImpactData}{remoteName[YuanShenData.Length..]}",
|
||||||
|
_ => remoteName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return remoteName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<ItemOperationInfo> GetItemOperationInfos(Dictionary<string, VersionItem> remote, Dictionary<string, VersionItem> local)
|
||||||
|
{
|
||||||
|
foreach ((string remoteName, VersionItem remoteItem) in remote)
|
||||||
|
{
|
||||||
|
if (local.TryGetValue(remoteName, out VersionItem? localItem))
|
||||||
|
{
|
||||||
|
if (remoteItem.Md5 != localItem.Md5)
|
||||||
|
{
|
||||||
|
// 本地发现了同名且不同MD5的项,需要替换为服务器上的项
|
||||||
|
yield return new(ItemOperationType.Replace, remoteItem, localItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
local.Remove(remoteName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 本地没有发现同名项
|
||||||
|
yield return new(ItemOperationType.Add, remoteItem, remoteItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<ItemOperationInfo> removes = local.Select(kvp => new ItemOperationInfo(ItemOperationType.Remove, kvp.Value, kvp.Value));
|
||||||
|
|
||||||
|
foreach (ItemOperationInfo item in removes)
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenameDataFolder(string gameFolder, ConvertDirection direction)
|
||||||
|
{
|
||||||
|
string yuanShenData = Path.Combine(gameFolder, YuanShenData);
|
||||||
|
string genshinImpactData = Path.Combine(gameFolder, GenshinImpactData);
|
||||||
|
|
||||||
|
// We have check the exe path previously
|
||||||
|
// so we assume the data folder is present
|
||||||
|
if (direction == ConvertDirection.ChineseToOversea)
|
||||||
|
{
|
||||||
|
Directory.Move(yuanShenData, genshinImpactData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Directory.Move(genshinImpactData, yuanShenData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MoveToCache(string cacheFolder, string cacheName, string targetFullPath)
|
||||||
|
{
|
||||||
|
string cacheFilePath = Path.Combine(cacheFolder, cacheName);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)!);
|
||||||
|
File.Move(targetFullPath, cacheFilePath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReplaceGameResourceCoreAsync(IEnumerable<ItemOperationInfo> operations, string gameFolder, string scatteredFilesUrl, ConvertDirection direction, IProgress<PackageReplaceStatus> progress)
|
||||||
|
{
|
||||||
|
// 重命名 _Data 目录
|
||||||
|
RenameDataFolder(gameFolder, direction);
|
||||||
|
|
||||||
|
// Ensure cache folder
|
||||||
|
string cacheFolder = Path.Combine(gameFolder, "Screenshot", "HutaoCache");
|
||||||
|
|
||||||
|
// 执行下载与移动操作
|
||||||
|
foreach (ItemOperationInfo info in operations)
|
||||||
|
{
|
||||||
|
progress.Report(new($"{info.Target}"));
|
||||||
|
|
||||||
|
string targetFilePath = Path.Combine(gameFolder, info.Target);
|
||||||
|
string cacheFilePath = Path.Combine(cacheFolder, info.Cache);
|
||||||
|
|
||||||
|
switch (info.Type)
|
||||||
|
{
|
||||||
|
case ItemOperationType.Add:
|
||||||
|
await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info).ConfigureAwait(false);
|
||||||
|
break;
|
||||||
|
case ItemOperationType.Replace:
|
||||||
|
{
|
||||||
|
MoveToCache(cacheFolder, info.Cache, targetFilePath);
|
||||||
|
await ReplaceFromCacheOrWebAsync(cacheFilePath, targetFilePath, scatteredFilesUrl, info).ConfigureAwait(false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ItemOperationType.Remove:
|
||||||
|
MoveToCache(cacheFolder, info.Cache, targetFilePath);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新下载所有 *pkg_version 文件
|
||||||
|
await ReplacePackageVersionsAsync(scatteredFilesUrl, gameFolder).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReplaceFromCacheOrWebAsync(string cacheFilePath, string targetFilePath, string scatteredFilesUrl, ItemOperationInfo info)
|
||||||
|
{
|
||||||
|
if (File.Exists(cacheFilePath))
|
||||||
|
{
|
||||||
|
string remoteMd5 = await FileDigest.GetMd5Async(cacheFilePath, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
if (info.Md5 == remoteMd5.ToLowerInvariant() && new FileInfo(cacheFilePath).Length == info.TotalBytes)
|
||||||
|
{
|
||||||
|
// Valid, move it to target path
|
||||||
|
// There shouldn't be any file in the same name
|
||||||
|
File.Move(cacheFilePath, targetFilePath, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Invalid file, delete it
|
||||||
|
File.Delete(cacheFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache no item, download it anyway.
|
||||||
|
using (FileStream fileStream = File.Create(targetFilePath))
|
||||||
|
{
|
||||||
|
using (Stream webStream = await httpClient.GetStreamAsync($"{scatteredFilesUrl}/{info.Target}").ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
await webStream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReplacePackageVersionsAsync(string scatteredFilesUrl, string gameFolder)
|
||||||
|
{
|
||||||
|
foreach (string audioPkgVersionFilePath in Directory.EnumerateFiles(gameFolder, "*pkg_version"))
|
||||||
|
{
|
||||||
|
string audioPkgVersionFileName = Path.GetFileName(audioPkgVersionFilePath);
|
||||||
|
using (FileStream audioPkgVersionFileStream = File.Create(audioPkgVersionFilePath))
|
||||||
|
{
|
||||||
|
using (Stream webStream = await httpClient.GetStreamAsync($"{scatteredFilesUrl}/{audioPkgVersionFileName}").ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
await webStream.CopyToAsync(audioPkgVersionFileStream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<string, VersionItem>> GetVersionItemsAsync(Stream stream)
|
||||||
|
{
|
||||||
|
Dictionary<string, VersionItem> results = new();
|
||||||
|
using (StreamReader reader = new(stream))
|
||||||
|
{
|
||||||
|
while (await reader.ReadLineAsync().ConfigureAwait(false) is string raw)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(raw))
|
||||||
|
{
|
||||||
|
VersionItem item = JsonSerializer.Deserialize<VersionItem>(raw, options)!;
|
||||||
|
results.Add(item.RemoteName, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<string, VersionItem>> GetVersionItemsAsync(Stream stream, ConvertDirection direction, Func<string, ConvertDirection, string> nameConverter)
|
||||||
|
{
|
||||||
|
Dictionary<string, VersionItem> results = new();
|
||||||
|
using (StreamReader reader = new(stream))
|
||||||
|
{
|
||||||
|
while (await reader.ReadLineAsync().ConfigureAwait(false) is string raw)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(raw))
|
||||||
|
{
|
||||||
|
VersionItem item = JsonSerializer.Deserialize<VersionItem>(raw, options)!;
|
||||||
|
results.Add(nameConverter(item.RemoteName, direction), item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包更新状态
|
||||||
|
/// </summary>
|
||||||
|
public class PackageReplaceStatus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的包更新状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="description">描述</param>
|
||||||
|
public PackageReplaceStatus(string description)
|
||||||
|
{
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Package;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包版本项
|
||||||
|
/// </summary>
|
||||||
|
internal class VersionItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 服务器上的名称
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("remoteName")]
|
||||||
|
public string RemoteName { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MD5校验值
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("md5")]
|
||||||
|
public string Md5 { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件尺寸
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("fileSize")]
|
||||||
|
public long FileSize { get; set; }
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||||
using Snap.Hutao.Core.Diagnostics;
|
using Snap.Hutao.Core.Diagnostics;
|
||||||
|
using Snap.Hutao.Core.IO;
|
||||||
using Snap.Hutao.Core.Logging;
|
using Snap.Hutao.Core.Logging;
|
||||||
using Snap.Hutao.Extension;
|
using Snap.Hutao.Extension;
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
@@ -131,9 +132,10 @@ internal partial class MetadataService : IMetadataService, IMetadataServiceIniti
|
|||||||
string fileFullName = $"{fileName}.json";
|
string fileFullName = $"{fileName}.json";
|
||||||
|
|
||||||
bool skip = false;
|
bool skip = false;
|
||||||
if (File.Exists(Path.Combine(metadataFolderPath, fileFullName)))
|
string fileFullPath = Path.Combine(metadataFolderPath, fileFullName);
|
||||||
|
if (File.Exists(fileFullPath))
|
||||||
{
|
{
|
||||||
skip = md5 == await GetFileMd5Async(fileFullName, token).ConfigureAwait(false);
|
skip = md5 == await FileDigest.GetMd5Async(fileFullPath, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skip)
|
if (!skip)
|
||||||
@@ -145,18 +147,6 @@ internal partial class MetadataService : IMetadataService, IMetadataServiceIniti
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetFileMd5Async(string fileFullName, CancellationToken token)
|
|
||||||
{
|
|
||||||
using (FileStream stream = File.OpenRead(Path.Combine(metadataFolderPath, fileFullName)))
|
|
||||||
{
|
|
||||||
byte[] bytes = await MD5.Create()
|
|
||||||
.ComputeHashAsync(stream, token)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
return Convert.ToHexString(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DownloadMetadataAsync(string fileFullName, CancellationToken token)
|
private async Task DownloadMetadataAsync(string fileFullName, CancellationToken token)
|
||||||
{
|
{
|
||||||
Stream sourceStream = await httpClient
|
Stream sourceStream = await httpClient
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
<None Remove="View\Dialog\GachaLogRefreshProgressDialog.xaml" />
|
<None Remove="View\Dialog\GachaLogRefreshProgressDialog.xaml" />
|
||||||
<None Remove="View\Dialog\GachaLogUrlDialog.xaml" />
|
<None Remove="View\Dialog\GachaLogUrlDialog.xaml" />
|
||||||
<None Remove="View\Dialog\GameAccountNameDialog.xaml" />
|
<None Remove="View\Dialog\GameAccountNameDialog.xaml" />
|
||||||
|
<None Remove="View\Dialog\LaunchGamePackageConvertDialog.xaml" />
|
||||||
<None Remove="View\Dialog\LoginMihoyoBBSDialog.xaml" />
|
<None Remove="View\Dialog\LoginMihoyoBBSDialog.xaml" />
|
||||||
<None Remove="View\Dialog\SignInWebViewDialog.xaml" />
|
<None Remove="View\Dialog\SignInWebViewDialog.xaml" />
|
||||||
<None Remove="View\Dialog\UserDialog.xaml" />
|
<None Remove="View\Dialog\UserDialog.xaml" />
|
||||||
@@ -99,6 +100,7 @@
|
|||||||
<None Remove="View\Page\DailyNotePage.xaml" />
|
<None Remove="View\Page\DailyNotePage.xaml" />
|
||||||
<None Remove="View\Page\GachaLogPage.xaml" />
|
<None Remove="View\Page\GachaLogPage.xaml" />
|
||||||
<None Remove="View\Page\HutaoDatabasePage.xaml" />
|
<None Remove="View\Page\HutaoDatabasePage.xaml" />
|
||||||
|
<None Remove="View\Page\HutaoDatabasePresentPage.xaml" />
|
||||||
<None Remove="View\Page\LaunchGamePage.xaml" />
|
<None Remove="View\Page\LaunchGamePage.xaml" />
|
||||||
<None Remove="View\Page\LoginMihoyoUserPage.xaml" />
|
<None Remove="View\Page\LoginMihoyoUserPage.xaml" />
|
||||||
<None Remove="View\Page\SettingPage.xaml" />
|
<None Remove="View\Page\SettingPage.xaml" />
|
||||||
@@ -171,11 +173,11 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.65" />
|
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.65" />
|
||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.138-beta">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.188-beta">
|
||||||
<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="Microsoft.Windows.SDK.BuildTools" Version="10.0.25276-preview" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25281-preview" />
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221209.1" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221209.1" />
|
||||||
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
|
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@@ -199,6 +201,16 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Dialog\LaunchGamePackageConvertDialog.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Page\HutaoDatabasePresentPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Update="View\WelcomeView.xaml">
|
<Page Update="View\WelcomeView.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
x:Class="Snap.Hutao.View.Control.StatisticsCard"
|
x:Class="Snap.Hutao.View.Control.StatisticsCard"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:converters="using:CommunityToolkit.WinUI.UI.Converters"
|
||||||
xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
|
xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
@@ -62,9 +63,17 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<converters:BoolToObjectConverter
|
||||||
|
x:Key="BoolToBrushConverter"
|
||||||
|
FalseValue="{ThemeResource SystemFillColorCriticalBackgroundBrush}"
|
||||||
|
TrueValue="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
|
||||||
|
|
||||||
<DataTemplate x:Key="OrangeGridTemplate" x:DataType="shmbg:SummaryItem">
|
<DataTemplate x:Key="OrangeGridTemplate" x:DataType="shmbg:SummaryItem">
|
||||||
<Grid Width="40" Margin="0,4,4,0">
|
<Grid Width="40" Margin="0,4,4,0">
|
||||||
<Border Style="{StaticResource BorderCardStyle}" ToolTipService.ToolTip="{Binding TimeFormatted}">
|
<Border
|
||||||
|
Background="{Binding IsUp, Converter={StaticResource BoolToBrushConverter}}"
|
||||||
|
Style="{StaticResource BorderCardStyle}"
|
||||||
|
ToolTipService.ToolTip="{Binding TimeFormatted}">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<shvc:ItemIcon
|
<shvc:ItemIcon
|
||||||
Width="40"
|
Width="40"
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||||
|
<!-- Licensed under the MIT License. -->
|
||||||
|
|
||||||
|
<ContentDialog
|
||||||
|
x:Class="Snap.Hutao.View.Dialog.LaunchGamePackageConvertDialog"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:shvd="using:Snap.Hutao.View.Dialog"
|
||||||
|
Title="ת<><D7AA><EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD>"
|
||||||
|
d:DataContext="{d:DesignInstance shvd:LaunchGamePackageConvertDialog}"
|
||||||
|
Style="{StaticResource DefaultContentDialogStyle}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="ת<><D7AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>ʱ<EFBFBD><CAB1>"/>
|
||||||
|
<TextBlock
|
||||||
|
MinWidth="480"
|
||||||
|
Margin="0,8,0,2"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{Binding Description}"/>
|
||||||
|
<ProgressBar IsIndeterminate="True"/>
|
||||||
|
</StackPanel>
|
||||||
|
</ContentDialog>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Snap.Hutao.Control;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.View.Dialog;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϸ<EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD>ת<EFBFBD><D7AA><EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class LaunchGamePackageConvertDialog : ContentDialog
|
||||||
|
{
|
||||||
|
private static readonly DependencyProperty DescriptionProperty = Property<LaunchGamePackageConvertDialog>.Depend(nameof(Description), "<22><><EFBFBD>Ժ<EFBFBD>");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD><C2B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϸ<EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD>ת<EFBFBD><D7AA><EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
public LaunchGamePackageConvertDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
XamlRoot = Ioc.Default.GetRequiredService<MainWindow>().Content.XamlRoot;
|
||||||
|
DataContext = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <20><><EFBFBD><EFBFBD>
|
||||||
|
/// </summary>
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get { return (string)GetValue(DescriptionProperty); }
|
||||||
|
set { SetValue(DescriptionProperty, value); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -138,8 +138,9 @@
|
|||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="auto"/>
|
<RowDefinition Height="auto"/>
|
||||||
<RowDefinition Height="auto"/>
|
<RowDefinition Height="auto"/>
|
||||||
|
<RowDefinition Height="auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid Margin="8">
|
<Grid Grid.Row="0" Margin="8">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="auto"/>
|
<ColumnDefinition Width="auto"/>
|
||||||
<ColumnDefinition/>
|
<ColumnDefinition/>
|
||||||
@@ -164,14 +165,20 @@
|
|||||||
Width="48"
|
Width="48"
|
||||||
Height="48"
|
Height="48"
|
||||||
Margin="8,0,0,0"
|
Margin="8,0,0,0"
|
||||||
|
Background="Transparent"
|
||||||
Command="{Binding Path=DataContext.RemoveEntryCommand, Source={StaticResource BindingProxy}}"
|
Command="{Binding Path=DataContext.RemoveEntryCommand, Source={StaticResource BindingProxy}}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Content=""
|
Content=""
|
||||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||||
|
Style="{StaticResource ButtonRevealStyle}"
|
||||||
ToolTipService.ToolTip="删除清单"/>
|
ToolTipService.ToolTip="删除清单"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ScrollViewer Grid.Row="1" Height="240">
|
<MenuFlyoutSeparator Grid.Row="1"/>
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Row="2"
|
||||||
|
Height="296"
|
||||||
|
Margin="0,2,0,0">
|
||||||
<ItemsControl Margin="8,0,8,8" ItemsSource="{Binding Items}">
|
<ItemsControl Margin="8,0,8,8" ItemsSource="{Binding Items}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
@@ -201,10 +208,9 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderBrush="{x:Null}"
|
|
||||||
BorderThickness="0"
|
|
||||||
Command="{Binding Path=DataContext.FinishStateCommand, Source={StaticResource BindingProxy}}"
|
Command="{Binding Path=DataContext.FinishStateCommand, Source={StaticResource BindingProxy}}"
|
||||||
CommandParameter="{Binding}">
|
CommandParameter="{Binding}"
|
||||||
|
Style="{StaticResource ButtonRevealStyle}">
|
||||||
<Grid Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}">
|
<Grid Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="auto"/>
|
<ColumnDefinition Width="auto"/>
|
||||||
@@ -225,7 +231,6 @@
|
|||||||
Text="{Binding Entity.Count}"/>
|
Text="{Binding Entity.Count}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<Rectangle
|
<Rectangle
|
||||||
Height="48"
|
Height="48"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Fill="{StaticResource CardBackgroundFillColorDefaultBrush}"
|
Fill="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
IsHitTestVisible="False"/>
|
IsHitTestVisible="False"/>
|
||||||
<Pivot>
|
<Pivot>
|
||||||
<Pivot.LeftHeader>
|
<Pivot.LeftHeader>
|
||||||
@@ -86,9 +86,9 @@
|
|||||||
<PivotItem Header="总览">
|
<PivotItem Header="总览">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition MaxWidth="388"/>
|
<ColumnDefinition MaxWidth="390"/>
|
||||||
<ColumnDefinition MaxWidth="388"/>
|
<ColumnDefinition MaxWidth="390"/>
|
||||||
<ColumnDefinition MaxWidth="388"/>
|
<ColumnDefinition MaxWidth="390"/>
|
||||||
<ColumnDefinition Width="auto" MinWidth="16"/>
|
<ColumnDefinition Width="auto" MinWidth="16"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<shvc:StatisticsCard
|
<shvc:StatisticsCard
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||||
xmlns:shc="using:Snap.Hutao.Control"
|
xmlns:shc="using:Snap.Hutao.Control"
|
||||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
|
||||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||||
xmlns:shmbh="using:Snap.Hutao.Model.Binding.Hutao"
|
xmlns:shmbh="using:Snap.Hutao.Model.Binding.Hutao"
|
||||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
<shc:ScopedPage
|
||||||
|
x:Class="Snap.Hutao.View.Page.HutaoDatabasePresentPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:image="using:Snap.Hutao.Control.Image"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:shc="using:Snap.Hutao.Control"
|
||||||
|
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||||
|
xmlns:shmbh="using:Snap.Hutao.Model.Binding.Hutao"
|
||||||
|
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||||
|
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||||
|
xmlns:wsc="using:WinUICommunity.SettingsUI.Controls"
|
||||||
|
d:DataContext="{d:DesignInstance shv:HutaoDatabaseViewModel}"
|
||||||
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<mxi:Interaction.Behaviors>
|
||||||
|
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||||
|
</mxi:Interaction.Behaviors>
|
||||||
|
|
||||||
|
<Page.Resources>
|
||||||
|
<DataTemplate x:Key="TeamItemTemplate" x:DataType="shmbh:Team">
|
||||||
|
<Border Margin="0,0,8,8" Style="{StaticResource BorderCardStyle}">
|
||||||
|
<Grid Margin="6">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="auto"/>
|
||||||
|
<ColumnDefinition Width="108"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ItemsControl HorizontalAlignment="Left" ItemsSource="{Binding}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<cwuc:UniformGrid ColumnSpacing="6" Columns="4"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<image:CachedImage
|
||||||
|
Width="48"
|
||||||
|
Height="48"
|
||||||
|
Margin="-4,-20,-4,-4"
|
||||||
|
Source="{Binding SideIcon}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding Rate}"/>
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,2,0,0"
|
||||||
|
Opacity="0.6"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{Binding Name}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</Page.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<CommandBar DefaultLabelPosition="Right">
|
||||||
|
<AppBarButton
|
||||||
|
Command="{Binding ExportAsImageCommand}"
|
||||||
|
CommandParameter="{Binding ElementName=RenderTargetContainer}"
|
||||||
|
Label="导出图片"/>
|
||||||
|
</CommandBar>
|
||||||
|
<ScrollViewer Grid.Row="1">
|
||||||
|
<StackPanel
|
||||||
|
Name="RenderTargetContainer"
|
||||||
|
MaxWidth="956"
|
||||||
|
Padding="16"
|
||||||
|
Background="Transparent">
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,16"
|
||||||
|
Style="{StaticResource SubheaderTextBlockStyle}"
|
||||||
|
Text="胡桃数据库 统计数据"/>
|
||||||
|
<wsc:Setting Content="{Binding Overview.RefreshTime}" Header="数据刷新时间"/>
|
||||||
|
<cwuc:UniformGrid
|
||||||
|
Margin="0,16"
|
||||||
|
ColumnSpacing="16"
|
||||||
|
Columns="3"
|
||||||
|
Orientation="Vertical"
|
||||||
|
RowSpacing="16">
|
||||||
|
<wsc:Setting Content="{Binding Overview.RecordTotal}" Header="上传记录总数"/>
|
||||||
|
<wsc:Setting Content="{Binding Overview.SpiralAbyssTotal}" Header="总计深渊记录"/>
|
||||||
|
<wsc:Setting Padding="16,8" Header="通关深渊记录">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{Binding Overview.SpiralAbyssPassedPercent}"/>
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Opacity="0.7"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{Binding Overview.SpiralAbyssPassed}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</wsc:Setting>
|
||||||
|
<wsc:Setting Padding="16,8" Header="满星深渊记录">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{Binding Overview.SpiralAbyssFullStarPercent}"/>
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Opacity="0.7"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="{Binding Overview.SpiralAbyssFullStar}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</wsc:Setting>
|
||||||
|
<wsc:Setting Content="{Binding Overview.SpiralAbyssStarAverage}" Header="平均获取渊星"/>
|
||||||
|
<wsc:Setting Content="{Binding Overview.SpiralAbyssBattleAverage}" Header="平均战斗次数"/>
|
||||||
|
</cwuc:UniformGrid>
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,16"
|
||||||
|
Style="{StaticResource TitleTextBlockStyle}"
|
||||||
|
Text="第 12 层 角色使用率"
|
||||||
|
Visibility="Collapsed"/>
|
||||||
|
<GridView
|
||||||
|
Margin="0,0,-16,-8"
|
||||||
|
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
|
||||||
|
ItemsSource="{Binding AvatarUsageRanks[0].Avatars}"
|
||||||
|
SelectionMode="None"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<shvc:BottomTextControl Text="{Binding Rate}">
|
||||||
|
<shvc:ItemIcon Icon="{Binding Icon}" Quality="{Binding Quality}"/>
|
||||||
|
</shvc:BottomTextControl>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</GridView>
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,16"
|
||||||
|
Style="{StaticResource TitleTextBlockStyle}"
|
||||||
|
Text="第 12 层 角色出场率"
|
||||||
|
Visibility="Collapsed"/>
|
||||||
|
<GridView
|
||||||
|
Margin="0,0,-16,-8"
|
||||||
|
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
|
||||||
|
ItemsSource="{Binding AvatarAppearanceRanks[0].Avatars}"
|
||||||
|
SelectionMode="None"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<shvc:BottomTextControl Text="{Binding Rate}">
|
||||||
|
<shvc:ItemIcon Icon="{Binding Icon}" Quality="{Binding Quality}"/>
|
||||||
|
</shvc:BottomTextControl>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</GridView>
|
||||||
|
|
||||||
|
<StackPanel Visibility="Visible">
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,16"
|
||||||
|
Style="{StaticResource TitleTextBlockStyle}"
|
||||||
|
Text="第 12 层 队伍出场次数"/>
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||||
|
Text="上半"/>
|
||||||
|
<GridView
|
||||||
|
Margin="0,0,-16,-8"
|
||||||
|
ItemTemplate="{StaticResource TeamItemTemplate}"
|
||||||
|
ItemsSource="{Binding TeamAppearances[0].Up}"
|
||||||
|
SelectionMode="None"/>
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||||
|
Text="下半"/>
|
||||||
|
<GridView
|
||||||
|
Margin="0,0,-16,-8"
|
||||||
|
ItemTemplate="{StaticResource TeamItemTemplate}"
|
||||||
|
ItemsSource="{Binding TeamAppearances[0].Down}"
|
||||||
|
SelectionMode="None"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Visibility="Collapsed">
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,0,0,16"
|
||||||
|
Style="{StaticResource SubheaderTextBlockStyle}"
|
||||||
|
Text="角色/命座持有率"/>
|
||||||
|
<Grid Margin="0,0,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="48"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="角色"/>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="持有"/>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="0 命"/>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="3"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="1 命"/>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="4"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="2 命"/>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="5"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="3 命"/>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="6"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="4 命"/>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="7"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="5 命"/>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="8"
|
||||||
|
Margin="6"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Text="6 命"/>
|
||||||
|
</Grid>
|
||||||
|
<ItemsControl
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
ItemsSource="{Binding AvatarConstellationInfos}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border Margin="0,0,0,4" Style="{StaticResource BorderCardStyle}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<shvc:ItemIcon
|
||||||
|
Width="48"
|
||||||
|
Height="48"
|
||||||
|
Icon="{Binding Icon}"
|
||||||
|
Quality="{Binding Quality}"/>
|
||||||
|
|
||||||
|
<Grid Grid.Column="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="1*"/>
|
||||||
|
<ColumnDefinition Width="7*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Rate}"/>
|
||||||
|
<ItemsControl Grid.Column="1" ItemsSource="{Binding Rates}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<cwuc:UniformGrid Columns="7"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Snap Hutao API @ DGP Studio"/>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
|
</shc:ScopedPage>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Control;
|
||||||
|
using Snap.Hutao.ViewModel;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.View.Page;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于展示用途的胡桃数据库页面
|
||||||
|
/// 仅用于发布相关的统计数据
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class HutaoDatabasePresentPage : ScopedPage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的胡桃数据库页面
|
||||||
|
/// </summary>
|
||||||
|
public HutaoDatabasePresentPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
InitializeWith<HutaoDatabaseViewModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<Setter Property="MinWidth" Value="156"/>
|
<Setter Property="MinWidth" Value="156"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style TargetType="NumberBox">
|
<Style TargetType="NumberBox">
|
||||||
<Setter Property="MinWidth" Value="158"/>
|
<Setter Property="MinWidth" Value="156"/>
|
||||||
</Style>
|
</Style>
|
||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -46,7 +46,12 @@
|
|||||||
<ColumnDefinition MaxWidth="800"/>
|
<ColumnDefinition MaxWidth="800"/>
|
||||||
<ColumnDefinition Width="auto"/>
|
<ColumnDefinition Width="auto"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<StackPanel Margin="16,-16,16,16">
|
<StackPanel Margin="16">
|
||||||
|
<InfoBar
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="True"
|
||||||
|
Message="所有选项仅会在启动游戏成功后保存"
|
||||||
|
Severity="Informational"/>
|
||||||
<wsc:SettingsGroup Margin="0,0,0,0" Header="常规">
|
<wsc:SettingsGroup Margin="0,0,0,0" Header="常规">
|
||||||
<wsc:Setting
|
<wsc:Setting
|
||||||
Description="切换游戏服务器,B服用户需要自备额外的 PCGameSDK.dll 文件"
|
Description="切换游戏服务器,B服用户需要自备额外的 PCGameSDK.dll 文件"
|
||||||
@@ -54,7 +59,7 @@
|
|||||||
Icon="">
|
Icon="">
|
||||||
<wsc:Setting.ActionContent>
|
<wsc:Setting.ActionContent>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
DisplayMemberPath="Name"
|
DisplayMemberPath="DisplayName"
|
||||||
ItemsSource="{Binding KnownSchemes}"
|
ItemsSource="{Binding KnownSchemes}"
|
||||||
SelectedItem="{Binding SelectedScheme, Mode=TwoWay}"/>
|
SelectedItem="{Binding SelectedScheme, Mode=TwoWay}"/>
|
||||||
</wsc:Setting.ActionContent>
|
</wsc:Setting.ActionContent>
|
||||||
@@ -198,7 +203,7 @@
|
|||||||
Header="宽度"
|
Header="宽度"
|
||||||
Icon="">
|
Icon="">
|
||||||
<wsc:Setting.ActionContent>
|
<wsc:Setting.ActionContent>
|
||||||
<NumberBox Width="160" Value="{Binding ScreenWidth, Mode=TwoWay}"/>
|
<NumberBox Width="156" Value="{Binding ScreenWidth, Mode=TwoWay}"/>
|
||||||
</wsc:Setting.ActionContent>
|
</wsc:Setting.ActionContent>
|
||||||
</wsc:Setting>
|
</wsc:Setting>
|
||||||
<wsc:Setting
|
<wsc:Setting
|
||||||
@@ -206,26 +211,54 @@
|
|||||||
Header="高度"
|
Header="高度"
|
||||||
Icon="">
|
Icon="">
|
||||||
<wsc:Setting.ActionContent>
|
<wsc:Setting.ActionContent>
|
||||||
<NumberBox Width="160" Value="{Binding ScreenHeight, Mode=TwoWay}"/>
|
<NumberBox Width="156" Value="{Binding ScreenHeight, Mode=TwoWay}"/>
|
||||||
</wsc:Setting.ActionContent>
|
</wsc:Setting.ActionContent>
|
||||||
</wsc:Setting>
|
</wsc:Setting>
|
||||||
</wsc:SettingsGroup>
|
</wsc:SettingsGroup>
|
||||||
|
|
||||||
<wsc:SettingsGroup Header="Dangerous Feature" IsEnabled="{Binding IsElevated}">
|
<wsc:SettingsGroup Header="高级功能" IsEnabled="{Binding IsElevated}">
|
||||||
|
<InfoBar
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="True"
|
||||||
|
Message="需要读写游戏进程或与游戏窗体交互,因此只在管理员模式下生效!"
|
||||||
|
Severity="Warning"/>
|
||||||
<wsc:Setting
|
<wsc:Setting
|
||||||
Description="Requires administrator privilege. Otherwise the option will be disabled."
|
Description="在启动游戏前尝试终止运行中的游戏进程"
|
||||||
Header="Unlock frame rate limit"
|
Header="快速切换账号"
|
||||||
|
Icon=""
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<wsc:Setting.ActionContent>
|
||||||
|
<ToggleSwitch
|
||||||
|
Width="120"
|
||||||
|
IsOn="False"
|
||||||
|
Style="{StaticResource ToggleSwitchSettingStyle}"/>
|
||||||
|
</wsc:Setting.ActionContent>
|
||||||
|
</wsc:Setting>
|
||||||
|
<InfoBar
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="True"
|
||||||
|
Message="下面的功能十分危险,如果您不愿承担因此可能带来的后果,请勿启用!"
|
||||||
|
Severity="Error"/>
|
||||||
|
<wsc:Setting
|
||||||
|
Description="请在游戏内关闭垂直同步选项,需要高性能的显卡以支持更高的帧率"
|
||||||
|
Header="解锁帧率限制"
|
||||||
Icon="">
|
Icon="">
|
||||||
<wsc:Setting.ActionContent>
|
<wsc:Setting.ActionContent>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
Width="120"
|
Width="120"
|
||||||
IsOn="{Binding UnlockFps, Mode=TwoWay}"
|
IsOn="{Binding UnlockFps, Mode=TwoWay}"
|
||||||
OffContent="Disable"
|
OffContent="禁用"
|
||||||
OnContent="Enable"
|
OnContent="启用"
|
||||||
Style="{StaticResource ToggleSwitchSettingStyle}"/>
|
Style="{StaticResource ToggleSwitchSettingStyle}"/>
|
||||||
</wsc:Setting.ActionContent>
|
</wsc:Setting.ActionContent>
|
||||||
</wsc:Setting>
|
</wsc:Setting>
|
||||||
<wsc:Setting Description="{Binding TargetFps}" Header="Set frame rate">
|
<wsc:Setting Header="设置当前帧率">
|
||||||
|
<wsc:Setting.Description>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="在游戏时可以随时调整"/>
|
||||||
|
<TextBlock Text="{Binding TargetFps}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</wsc:Setting.Description>
|
||||||
<wsc:Setting.ActionContent>
|
<wsc:Setting.ActionContent>
|
||||||
<Slider
|
<Slider
|
||||||
Width="400"
|
Width="400"
|
||||||
|
|||||||
@@ -109,7 +109,6 @@
|
|||||||
ItemsSource="{Binding BackdropTypes}"
|
ItemsSource="{Binding BackdropTypes}"
|
||||||
SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/>
|
SelectedItem="{Binding SelectedBackdropType, Mode=TwoWay}"/>
|
||||||
</wsc:Setting>
|
</wsc:Setting>
|
||||||
|
|
||||||
</wsc:SettingsGroup>
|
</wsc:SettingsGroup>
|
||||||
|
|
||||||
<wsc:SettingsGroup Header="祈愿记录">
|
<wsc:SettingsGroup Header="祈愿记录">
|
||||||
@@ -130,7 +129,7 @@
|
|||||||
<InfoBar
|
<InfoBar
|
||||||
IsClosable="False"
|
IsClosable="False"
|
||||||
IsOpen="True"
|
IsOpen="True"
|
||||||
Message="设置游戏路径时,请选择游戏本体(YuanShen.exe 或 Genshin Impact.exe)而不是启动器(launcher.exe)"
|
Message="设置游戏路径时,请选择游戏本体(YuanShen.exe 或 GenshinImpact.exe)而不是启动器(launcher.exe)"
|
||||||
Severity="Informational"/>
|
Severity="Informational"/>
|
||||||
<wsc:Setting
|
<wsc:Setting
|
||||||
Description="{Binding GamePath}"
|
Description="{Binding GamePath}"
|
||||||
@@ -185,7 +184,7 @@
|
|||||||
</wsc:Setting>
|
</wsc:Setting>
|
||||||
</wsc:SettingsGroup>
|
</wsc:SettingsGroup>
|
||||||
|
|
||||||
<wsc:SettingsGroup Foreground="{StaticResource SystemFillColorCriticalBrush}" Header="危险功能">
|
<wsc:SettingsGroup Foreground="{ThemeResource SystemFillColorCriticalBrush}" Header="危险功能">
|
||||||
<InfoBar
|
<InfoBar
|
||||||
IsClosable="False"
|
IsClosable="False"
|
||||||
IsOpen="True"
|
IsOpen="True"
|
||||||
@@ -193,7 +192,7 @@
|
|||||||
Severity="Error"/>
|
Severity="Error"/>
|
||||||
|
|
||||||
<wsc:Setting
|
<wsc:Setting
|
||||||
Background="{StaticResource SystemFillColorCriticalBackgroundBrush}"
|
Background="{ThemeResource SystemFillColorCriticalBackgroundBrush}"
|
||||||
Description="删除注册的计划任务,卸载前务必点击此项"
|
Description="删除注册的计划任务,卸载前务必点击此项"
|
||||||
Header="删除所有计划任务"
|
Header="删除所有计划任务"
|
||||||
Icon="">
|
Icon="">
|
||||||
|
|||||||
@@ -32,6 +32,10 @@
|
|||||||
<wsc:Setting Header="DownloadStaticFileTest">
|
<wsc:Setting Header="DownloadStaticFileTest">
|
||||||
<Button Command="{Binding DownloadStaticFileCommand}" Content="Download"/>
|
<Button Command="{Binding DownloadStaticFileCommand}" Content="Download"/>
|
||||||
</wsc:Setting>
|
</wsc:Setting>
|
||||||
|
|
||||||
|
<wsc:Setting Header="HutaoDatabasePresentTest">
|
||||||
|
<Button Command="{Binding HutaoDatabasePresentCommand}" Content="Navigate"/>
|
||||||
|
</wsc:Setting>
|
||||||
</wsc:SettingsGroup>
|
</wsc:SettingsGroup>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</shc:ScopedPage>
|
</shc:ScopedPage>
|
||||||
|
|||||||
@@ -129,24 +129,22 @@
|
|||||||
Margin="12,0,0,0"
|
Margin="12,0,0,0"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderBrush="{x:Null}"
|
|
||||||
BorderThickness="0"
|
|
||||||
Command="{Binding DataContext.CopyCookieCommand, Source={StaticResource ViewModelBindingProxy}}"
|
Command="{Binding DataContext.CopyCookieCommand, Source={StaticResource ViewModelBindingProxy}}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Content=""
|
Content=""
|
||||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||||
|
Style="{StaticResource ButtonRevealStyle}"
|
||||||
ToolTipService.ToolTip="复制 Cookie"/>
|
ToolTipService.ToolTip="复制 Cookie"/>
|
||||||
<Button
|
<Button
|
||||||
Margin="6,0,0,0"
|
Margin="6,0,0,0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderBrush="{x:Null}"
|
|
||||||
BorderThickness="0"
|
|
||||||
Command="{Binding DataContext.RemoveUserCommand, Source={StaticResource ViewModelBindingProxy}}"
|
Command="{Binding DataContext.RemoveUserCommand, Source={StaticResource ViewModelBindingProxy}}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Content=""
|
Content=""
|
||||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||||
|
Style="{StaticResource ButtonRevealStyle}"
|
||||||
ToolTipService.ToolTip="移除用户"/>
|
ToolTipService.ToolTip="移除用户"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
x:Class="Snap.Hutao.View.WelcomeView"
|
x:Class="Snap.Hutao.View.WelcomeView"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||||
@@ -32,6 +31,10 @@
|
|||||||
Style="{StaticResource BodyTextBlockStyle}"
|
Style="{StaticResource BodyTextBlockStyle}"
|
||||||
Text="你可以继续使用电脑,丝毫不受影响"/>
|
Text="你可以继续使用电脑,丝毫不受影响"/>
|
||||||
<ItemsControl Margin="0,0,0,32" ItemsSource="{Binding DownloadSummaries}">
|
<ItemsControl Margin="0,0,0,32" ItemsSource="{Binding DownloadSummaries}">
|
||||||
|
<ItemsControl.ItemContainerTransitions>
|
||||||
|
<EntranceThemeTransition IsStaggeringEnabled="False"/>
|
||||||
|
<AddDeleteThemeTransition/>
|
||||||
|
</ItemsControl.ItemContainerTransitions>
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Margin="0,8,0,0">
|
<StackPanel Margin="0,8,0,0">
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ internal class AvatarPropertyViewModel : Abstraction.ViewModel
|
|||||||
|
|
||||||
IBuffer buffer = await bitmap.GetPixelsAsync();
|
IBuffer buffer = await bitmap.GetPixelsAsync();
|
||||||
bool clipboardOpened = false;
|
bool clipboardOpened = false;
|
||||||
using (SoftwareBitmap softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(buffer, BitmapPixelFormat.Bgra8, bitmap.PixelWidth, bitmap.PixelHeight, BitmapAlphaMode.Ignore))
|
using (SoftwareBitmap softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(buffer, BitmapPixelFormat.Bgra8, bitmap.PixelWidth, bitmap.PixelHeight))
|
||||||
{
|
{
|
||||||
Color tintColor = (Color)Ioc.Default.GetRequiredService<App>().Resources["CompatBackgroundColor"];
|
Color tintColor = (Color)Ioc.Default.GetRequiredService<App>().Resources["CompatBackgroundColor"];
|
||||||
Bgra8 tint = Bgra8.FromColor(tintColor);
|
Bgra8 tint = Bgra8.FromColor(tintColor);
|
||||||
|
|||||||
@@ -2,9 +2,15 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Media.Imaging;
|
||||||
using Snap.Hutao.Model.Binding.Hutao;
|
using Snap.Hutao.Model.Binding.Hutao;
|
||||||
using Snap.Hutao.Service.Hutao;
|
using Snap.Hutao.Service.Hutao;
|
||||||
using Snap.Hutao.Web.Hutao.Model;
|
using Snap.Hutao.Web.Hutao.Model;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using Windows.Graphics.Imaging;
|
||||||
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel;
|
namespace Snap.Hutao.ViewModel;
|
||||||
|
|
||||||
@@ -31,6 +37,7 @@ internal class HutaoDatabaseViewModel : Abstraction.ViewModel
|
|||||||
this.hutaoCache = hutaoCache;
|
this.hutaoCache = hutaoCache;
|
||||||
|
|
||||||
OpenUICommand = new AsyncRelayCommand(OpenUIAsync);
|
OpenUICommand = new AsyncRelayCommand(OpenUIAsync);
|
||||||
|
ExportAsImageCommand = new AsyncRelayCommand<UIElement>(ExportAsImageAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -46,7 +53,7 @@ internal class HutaoDatabaseViewModel : Abstraction.ViewModel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 角色命座信息
|
/// 角色命座信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<ComplexAvatarConstellationInfo>? AvatarConstellationInfos { get => avatarConstellationInfos; set => avatarConstellationInfos = value; }
|
public List<ComplexAvatarConstellationInfo>? AvatarConstellationInfos { get => avatarConstellationInfos; set => SetProperty(ref avatarConstellationInfos, value); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 队伍出场
|
/// 队伍出场
|
||||||
@@ -63,6 +70,11 @@ internal class HutaoDatabaseViewModel : Abstraction.ViewModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ICommand OpenUICommand { get; }
|
public ICommand OpenUICommand { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出为图片命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand ExportAsImageCommand { get; }
|
||||||
|
|
||||||
private async Task OpenUIAsync()
|
private async Task OpenUIAsync()
|
||||||
{
|
{
|
||||||
if (await hutaoCache.InitializeForDatabaseViewModelAsync().ConfigureAwait(true))
|
if (await hutaoCache.InitializeForDatabaseViewModelAsync().ConfigureAwait(true))
|
||||||
@@ -74,4 +86,27 @@ internal class HutaoDatabaseViewModel : Abstraction.ViewModel
|
|||||||
Overview = hutaoCache.Overview;
|
Overview = hutaoCache.Overview;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ExportAsImageAsync(UIElement? element)
|
||||||
|
{
|
||||||
|
if (element == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetBitmap bitmap = new();
|
||||||
|
await bitmap.RenderAsync(element);
|
||||||
|
|
||||||
|
IBuffer buffer = await bitmap.GetPixelsAsync();
|
||||||
|
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||||
|
using (FileStream file = File.Create(Path.Combine(desktop, "hutao-database.png")))
|
||||||
|
{
|
||||||
|
using (IRandomAccessStream randomFileStream = file.AsRandomAccessStream())
|
||||||
|
{
|
||||||
|
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, randomFileStream);
|
||||||
|
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight, 96, 96, buffer.ToArray());
|
||||||
|
await encoder.FlushAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Snap.Hutao.Control.Extension;
|
||||||
using Snap.Hutao.Core.Database;
|
using Snap.Hutao.Core.Database;
|
||||||
using Snap.Hutao.Core.LifeCycle;
|
using Snap.Hutao.Core.LifeCycle;
|
||||||
using Snap.Hutao.Model.Binding.LaunchGame;
|
using Snap.Hutao.Model.Binding.LaunchGame;
|
||||||
@@ -13,6 +14,7 @@ using Snap.Hutao.Service.Abstraction;
|
|||||||
using Snap.Hutao.Service.Game;
|
using Snap.Hutao.Service.Game;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
using Snap.Hutao.Service.User;
|
using Snap.Hutao.Service.User;
|
||||||
|
using Snap.Hutao.View.Dialog;
|
||||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -37,12 +39,7 @@ internal class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
private readonly AppDbContext appDbContext;
|
private readonly AppDbContext appDbContext;
|
||||||
private readonly IMemoryCache memoryCache;
|
private readonly IMemoryCache memoryCache;
|
||||||
|
|
||||||
private readonly List<LaunchScheme> knownSchemes = new()
|
private readonly List<LaunchScheme> knownSchemes = LaunchScheme.KnownSchemes.ToList();
|
||||||
{
|
|
||||||
new LaunchScheme(name: "官方服 | 天空岛", channel: "1", subChannel: "1", launcherId: "18"),
|
|
||||||
new LaunchScheme(name: "渠道服 | 世界树", channel: "14", subChannel: "0", launcherId: "17"),
|
|
||||||
new LaunchScheme(name: "国际服 | 部分支持", channel: "1", subChannel: "0", launcherId: "unknown"),
|
|
||||||
};
|
|
||||||
|
|
||||||
private LaunchScheme? selectedScheme;
|
private LaunchScheme? selectedScheme;
|
||||||
private ObservableCollection<GameAccount>? gameAccounts;
|
private ObservableCollection<GameAccount>? gameAccounts;
|
||||||
@@ -301,30 +298,38 @@ internal class LaunchGameViewModel : Abstraction.ViewModel
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
gameService.SetMultiChannel(SelectedScheme);
|
if (gameService.SetMultiChannel(SelectedScheme))
|
||||||
|
{
|
||||||
|
// Channel changed, we need to change local file.
|
||||||
|
// Note that if channel changed successfully, the
|
||||||
|
// access level is already high enough.
|
||||||
|
await ThreadHelper.SwitchToMainThreadAsync();
|
||||||
|
LaunchGamePackageConvertDialog dialog = new();
|
||||||
|
await using (await dialog.BlockAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
Progress<Service.Game.Package.PackageReplaceStatus> progress = new(s => dialog.Description = s.Description);
|
||||||
|
await gameService.ReplaceGameResourceAsync(SelectedScheme, progress).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedGameAccount != null)
|
||||||
|
{
|
||||||
|
if (!gameService.SetGameAccount(SelectedGameAccount))
|
||||||
|
{
|
||||||
|
infoBarService.Warning("切换账号失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveSetting();
|
||||||
|
|
||||||
|
LaunchConfiguration configuration = new(IsExclusive, IsFullScreen, IsBorderless, ScreenWidth, ScreenHeight, IsElevated && UnlockFps, TargetFps);
|
||||||
|
await gameService.LaunchAsync(configuration).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (GameFileOperationException ex)
|
||||||
{
|
{
|
||||||
infoBarService.Warning("找不到游戏配置文件 config.ini");
|
infoBarService.Error(ex);
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException)
|
|
||||||
{
|
|
||||||
infoBarService.Warning("无法读取或保存配置文件,请以管理员模式启动胡桃。");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SelectedGameAccount != null)
|
|
||||||
{
|
|
||||||
if (!gameService.SetGameAccount(SelectedGameAccount))
|
|
||||||
{
|
|
||||||
infoBarService.Warning("切换账号失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveSetting();
|
|
||||||
|
|
||||||
LaunchConfiguration configuration = new(IsExclusive, IsFullScreen, IsBorderless, ScreenWidth, ScreenHeight, IsElevated && UnlockFps, TargetFps);
|
|
||||||
await gameService.LaunchAsync(configuration).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DetectGameAccountAsync()
|
private async Task DetectGameAccountAsync()
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ internal class TestViewModel : Abstraction.ViewModel
|
|||||||
ShowAdoptCalculatorDialogCommand = new AsyncRelayCommand(ShowAdoptCalculatorDialogAsync);
|
ShowAdoptCalculatorDialogCommand = new AsyncRelayCommand(ShowAdoptCalculatorDialogAsync);
|
||||||
DangerousLoginMihoyoBbsCommand = new AsyncRelayCommand(DangerousLoginMihoyoBbsAsync);
|
DangerousLoginMihoyoBbsCommand = new AsyncRelayCommand(DangerousLoginMihoyoBbsAsync);
|
||||||
DownloadStaticFileCommand = new AsyncRelayCommand(DownloadStaticFileAsync);
|
DownloadStaticFileCommand = new AsyncRelayCommand(DownloadStaticFileAsync);
|
||||||
|
HutaoDatabasePresentCommand = new RelayCommand(HutaoDatabasePresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,6 +50,11 @@ internal class TestViewModel : Abstraction.ViewModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ICommand DownloadStaticFileCommand { get; }
|
public ICommand DownloadStaticFileCommand { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 胡桃数据库呈现命令
|
||||||
|
/// </summary>
|
||||||
|
public ICommand HutaoDatabasePresentCommand { get; }
|
||||||
|
|
||||||
private async Task ShowCommunityGameRecordDialogAsync()
|
private async Task ShowCommunityGameRecordDialogAsync()
|
||||||
{
|
{
|
||||||
// ContentDialog must be created by main thread.
|
// ContentDialog must be created by main thread.
|
||||||
@@ -115,4 +121,11 @@ internal class TestViewModel : Abstraction.ViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HutaoDatabasePresent()
|
||||||
|
{
|
||||||
|
Ioc.Default
|
||||||
|
.GetRequiredService<Service.Navigation.INavigationService>()
|
||||||
|
.Navigate<View.Page.HutaoDatabasePresentPage>(Service.Navigation.INavigationAwaiter.Default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -65,11 +65,7 @@ internal class WelcomeViewModel : ObservableObject
|
|||||||
})).ConfigureAwait(true);
|
})).ConfigureAwait(true);
|
||||||
|
|
||||||
serviceProvider.GetRequiredService<IMessenger>().Send(new Message.WelcomeStateCompleteMessage());
|
serviceProvider.GetRequiredService<IMessenger>().Send(new Message.WelcomeStateCompleteMessage());
|
||||||
|
StaticResource.FulfillAllContracts();
|
||||||
// Complete StaticResourceContracts
|
|
||||||
LocalSetting.Set(SettingKeys.StaticResourceV1Contract, true);
|
|
||||||
LocalSetting.Set(SettingKeys.StaticResourceV2Contract, true);
|
|
||||||
LocalSetting.Set(SettingKeys.StaticResourceV3Contract, true);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -115,6 +111,11 @@ internal class WelcomeViewModel : ObservableObject
|
|||||||
downloadSummaries.TryAdd("Talent", new(serviceProvider, "命之座图标更新", "Talent"));
|
downloadSummaries.TryAdd("Talent", new(serviceProvider, "命之座图标更新", "Talent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StaticResource.IsContractUnfulfilled(SettingKeys.StaticResourceV4Contract))
|
||||||
|
{
|
||||||
|
downloadSummaries.TryAdd("AvatarIcon", new(serviceProvider, "角色图标更新", "AvatarIcon"));
|
||||||
|
}
|
||||||
|
|
||||||
return downloadSummaries.Select(x => x.Value);
|
return downloadSummaries.Select(x => x.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -316,13 +316,11 @@ internal static class ApiEndpoints
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动器资源
|
/// 启动器资源
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="launcherId">启动器Id</param>
|
/// <param name="scheme">启动方案</param>
|
||||||
/// <param name="channel">通道</param>
|
|
||||||
/// <param name="subChannel">子通道</param>
|
|
||||||
/// <returns>启动器资源字符串</returns>
|
/// <returns>启动器资源字符串</returns>
|
||||||
public static string SdkStaticLauncherResource(string launcherId, string channel, string subChannel)
|
public static string SdkStaticLauncherResource(Model.Binding.LaunchGame.LaunchScheme scheme)
|
||||||
{
|
{
|
||||||
return $"{SdkStaticLauncherApi}/resource?key=eYd89JmJ&launcher_id={launcherId}&channel_id={channel}&sub_channel_id={subChannel}";
|
return $"{SdkStaticLauncherApi}/resource?key={scheme.Key}&launcher_id={scheme.LauncherId}&channel_id={scheme.Channel}&sub_channel_id={scheme.SubChannel}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/content?filter_adv=true&key=eYd89JmJ&language=zh-cn&launcher_id=18
|
// https://sdk-static.mihoyo.com/hk4e_cn/mdk/launcher/api/content?filter_adv=true&key=eYd89JmJ&language=zh-cn&launcher_id=18
|
||||||
|
|||||||
@@ -23,8 +23,24 @@ internal static class ApiOsEndpoints
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region SdkStaticLauncherApi
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动器资源
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scheme">启动方案</param>
|
||||||
|
/// <returns>启动器资源字符串</returns>
|
||||||
|
public static string SdkOsStaticLauncherResource(Model.Binding.LaunchGame.LaunchScheme scheme)
|
||||||
|
{
|
||||||
|
return $"{SdkOsStaticLauncherApi}/resource?key={scheme.Key}&launcher_id={scheme.LauncherId}&channel_id={scheme.Channel}&sub_channel_id={scheme.SubChannel}";
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Hosts | Queries
|
#region Hosts | Queries
|
||||||
private const string Hk4eApiOs = "https://hk4e-api-os.hoyoverse.com";
|
private const string Hk4eApiOs = "https://hk4e-api-os.hoyoverse.com";
|
||||||
private const string Hk4eApiOsGachaInfoApi = $"{Hk4eApiOs}/event/gacha_info/api";
|
private const string Hk4eApiOsGachaInfoApi = $"{Hk4eApiOs}/event/gacha_info/api";
|
||||||
|
|
||||||
|
private const string SdkOsStatic = "https://sdk-os-static.mihoyo.com";
|
||||||
|
private const string SdkOsStaticLauncherApi = $"{SdkOsStatic}/hk4e_global/mdk/launcher/api";
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -45,10 +45,10 @@ public class GameResource
|
|||||||
public List<NameMd5> DeprecatedPackages { get; set; } = default!;
|
public List<NameMd5> DeprecatedPackages { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 渠道服sdk
|
/// 渠道服 sdk
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("sdk")]
|
[JsonPropertyName("sdk")]
|
||||||
public object? Sdk { get; set; }
|
public Sdk? Sdk { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 过期的单个文件
|
/// 过期的单个文件
|
||||||
|
|||||||
@@ -38,8 +38,14 @@ public class Package : PathMd5
|
|||||||
[JsonPropertyName("voice_packs")]
|
[JsonPropertyName("voice_packs")]
|
||||||
public List<VoicePackage> VoicePacks { get; set; } = default!;
|
public List<VoicePackage> VoicePacks { get; set; } = default!;
|
||||||
|
|
||||||
// We don't want to support
|
/// <summary>
|
||||||
// decompressed_path & segments
|
/// 松散文件
|
||||||
|
/// 用于校验完整性
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("decompressed_path")]
|
||||||
|
public string DecompressedPath { get; set; } = default!;
|
||||||
|
|
||||||
|
// We don't want to support `segments` downloading
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包大小 bytes
|
/// 包大小 bytes
|
||||||
@@ -47,4 +53,4 @@ public class Package : PathMd5
|
|||||||
[JsonPropertyName("package_size")]
|
[JsonPropertyName("package_size")]
|
||||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
|
||||||
public long PackageSize { get; set; } = default!;
|
public long PackageSize { get; set; } = default!;
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,10 @@ internal class ResourceClient
|
|||||||
/// <returns>游戏资源</returns>
|
/// <returns>游戏资源</returns>
|
||||||
public async Task<Response<GameResource>> GetResourceAsync(LaunchScheme scheme, CancellationToken token = default)
|
public async Task<Response<GameResource>> GetResourceAsync(LaunchScheme scheme, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
string url = ApiEndpoints.SdkStaticLauncherResource(scheme.LauncherId, scheme.Channel, scheme.SubChannel);
|
string url = scheme.LauncherId == "10"
|
||||||
|
? ApiOsEndpoints.SdkOsStaticLauncherResource(scheme)
|
||||||
|
: ApiEndpoints.SdkStaticLauncherResource(scheme);
|
||||||
|
|
||||||
Response<GameResource>? response = await httpClient
|
Response<GameResource>? response = await httpClient
|
||||||
.TryCatchGetFromJsonAsync<Response<GameResource>>(url, options, logger, token)
|
.TryCatchGetFromJsonAsync<Response<GameResource>>(url, options, logger, token)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sdk
|
||||||
|
/// </summary>
|
||||||
|
public class Sdk : PathMd5
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 版本
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("version")]
|
||||||
|
public string Version { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 常量 sdk_pkg_version
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("pkg_version")]
|
||||||
|
public string PackageVersion { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("desc")]
|
||||||
|
public string Description { get; set; } = default!;
|
||||||
|
}
|
||||||
@@ -63,6 +63,11 @@ public enum KnownReturnCode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
LoginDataOutdated = -262,
|
LoginDataOutdated = -262,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 无效的 Key
|
||||||
|
/// </summary>
|
||||||
|
InvalidKey = -205,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 访问过于频繁
|
/// 访问过于频繁
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -118,6 +123,11 @@ public enum KnownReturnCode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
DataIsNotPublicForTheUser = 10102,
|
DataIsNotPublicForTheUser = 10102,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 实时便笺
|
||||||
|
/// </summary>
|
||||||
|
CODE10103 = 10103,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 实时便笺
|
/// 实时便笺
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user