remove CoreEnvironment

This commit is contained in:
Lightczx
2023-04-24 19:34:58 +08:00
parent 8ac410fb4d
commit 9a54df4163
39 changed files with 592 additions and 350 deletions

View File

@@ -61,4 +61,12 @@ public class CSharpLanguageFeatureTest
ValueB = 2,
ValueC = 3,
}
[TestMethod]
public void GetTwiceOnPropertyResultsSame()
{
Assert.AreEqual(UUID, UUID);
}
public static Guid UUID { get => Guid.NewGuid(); }
}

View File

@@ -21,19 +21,22 @@ namespace Snap.Hutao;
[SuppressMessage("", "SH001")]
public sealed partial class App : Application
{
private const string AppInstanceKey = "main";
private readonly ILogger<App> logger;
private readonly IServiceProvider serviceProvider;
/// <summary>
/// Initializes the singleton application object.
/// </summary>
/// <param name="logger">日志器</param>
public App(ILogger<App> logger)
/// <param name="serviceProvider">服务提供器</param>
public App(IServiceProvider serviceProvider)
{
// load app resource
// Load app resource
InitializeComponent();
this.logger = logger;
_ = new ExceptionRecorder(this, logger);
logger = serviceProvider.GetRequiredService<ILogger<App>>();
serviceProvider.GetRequiredService<ExceptionRecorder>().Record(this);
this.serviceProvider = serviceProvider;
}
/// <inheritdoc/>
@@ -42,7 +45,7 @@ public sealed partial class App : Application
try
{
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance firstInstance = AppInstance.FindOrRegisterForKey("main");
AppInstance firstInstance = AppInstance.FindOrRegisterForKey(AppInstanceKey);
if (firstInstance.IsCurrent)
{
@@ -51,10 +54,8 @@ public sealed partial class App : Application
firstInstance.Activated += Activation.Activate;
ToastNotificationManagerCompat.OnActivated += Activation.NotificationActivate;
logger.LogInformation("Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
logger.LogInformation("Cache folder : {folder}", ApplicationData.Current.LocalCacheFolder.Path);
JumpListHelper.ConfigureAsync().SafeForget(logger);
LogDiagnosticInformation();
PostLaunchAsync().SafeForget(logger);
}
else
{
@@ -69,4 +70,18 @@ public sealed partial class App : Application
Process.GetCurrentProcess().Kill();
}
}
private static async Task PostLaunchAsync()
{
await JumpListHelper.ConfigureAsync().ConfigureAwait(false);
}
private void LogDiagnosticInformation()
{
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
logger.LogInformation("Snap Hutao FamilyName: {name}", hutaoOptions.FamilyName);
logger.LogInformation("Snap Hutao Version: {version}", hutaoOptions.Version);
logger.LogInformation("Snap Hutao LocalCache: {folder}", hutaoOptions.LocalCache);
}
}

View File

@@ -1,167 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Win32;
using Snap.Hutao.Core.Json;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System.Collections.Immutable;
using System.IO;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization.Metadata;
using Windows.ApplicationModel;
namespace Snap.Hutao.Core;
/// <summary>
/// 核心环境参数
/// </summary>
[HighQuality]
internal static class CoreEnvironment
{
/// <summary>
/// 米游社请求UA
/// </summary>
public const string HoyolabUA = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/{HoyolabXrpcVersion}";
/// <summary>
/// Hoyolab请求UA
/// </summary>
public const string HoyolabOsUA = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBSOversea/{HoyolabOsXrpcVersion}";
/// <summary>
/// 米游社移动端请求UA
/// </summary>
public const string HoyolabMobileUA = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/{HoyolabXrpcVersion}";
/// <summary>
/// Hoyolab 移动端请求UA
/// </summary>
public const string HoyolabOsMobileUA = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBSOversea/{HoyolabOsXrpcVersion}";
/// <summary>
/// 米游社 Rpc 版本
/// </summary>
public const string HoyolabXrpcVersion = "2.49.1";
/// <summary>
/// Hoyolab Rpc 版本
/// </summary>
public const string HoyolabOsXrpcVersion = "2.30.0";
/// <summary>
/// 盐
/// </summary>
// https://github.com/UIGF-org/Hoyolab.Salt
public static readonly ImmutableDictionary<SaltType, string> DynamicSecretSalts = new Dictionary<SaltType, string>()
{
[SaltType.K2] = "egBrFMO1BPBG0UX5XOuuwMRLZKwTVKRV",
[SaltType.LK2] = "DG8lqMyc9gquwAUFc7zBS62ijQRX9XF7",
[SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
[SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
[SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
// This SALT is not reliable
[SaltType.OSK2] = "6cqshh5dhw73bzxn20oexa9k516chk7s",
}.ToImmutableDictionary();
/// <summary>
/// 默认的Json序列化选项
/// </summary>
public static readonly JsonSerializerOptions JsonOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
{
Modifiers =
{
JsonTypeInfoResolvers.ResolveEnumType,
},
},
};
/// <summary>
/// 当前版本
/// </summary>
public static readonly Version Version;
/// <summary>
/// 标准UA
/// </summary>
public static readonly string CommonUA;
/// <summary>
/// 数据文件夹
/// </summary>
public static readonly string DataFolder;
/// <summary>
/// 包家族名称
/// </summary>
public static readonly string FamilyName;
/// <summary>
/// 米游社设备Id
/// </summary>
public static readonly string HoyolabDeviceId;
/// <summary>
/// 胡桃设备Id
/// </summary>
public static readonly string HutaoDeviceId;
/// <summary>
/// 安装位置
/// </summary>
public static readonly string InstalledLocation;
private const string CryptographyKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\";
private const string MachineGuidValue = "MachineGuid";
static CoreEnvironment()
{
DataFolder = GetDatafolderPath();
Version = Package.Current.Id.Version.ToVersion();
FamilyName = Package.Current.Id.FamilyName;
InstalledLocation = Package.Current.InstalledLocation.Path;
CommonUA = $"Snap Hutao/{Version}";
// simply assign a random guid
HoyolabDeviceId = Guid.NewGuid().ToString();
HutaoDeviceId = GetUniqueUserID();
}
private static string GetUniqueUserID()
{
string userName = Environment.UserName;
object? machineGuid = Registry.GetValue(CryptographyKey, MachineGuidValue, userName);
return Convert.ToMd5HexString($"{userName}{machineGuid}");
}
private static string GetDatafolderPath()
{
string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty);
if (string.IsNullOrEmpty(preferredPath))
{
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
#if RELEASE
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
#else
// 使得迁移能正常生成
string folderName = "Hutao";
#endif
string path = Path.GetFullPath(Path.Combine(myDocument, folderName));
Directory.CreateDirectory(path);
return path;
}
else
{
return preferredPath;
}
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
namespace Snap.Hutao.Core.DependencyInjection;
/// <summary>
/// 依赖注入
/// </summary>
internal static class DependencyInjection
{
/// <summary>
/// 初始化依赖注入
/// </summary>
/// <returns>服务提供器</returns>
public static ServiceProvider Initialize()
{
ServiceProvider serviceProvider = new ServiceCollection()
// Microsoft extension
.AddLogging(builder => builder.AddDebug())
.AddMemoryCache()
// Hutao extensions
.AddJsonOptions()
.AddDatabase()
.AddInjections()
.AddHttpClients()
// Discrete services
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
.BuildServiceProvider(true);
Ioc.Default.ConfigureServices(serviceProvider);
return serviceProvider;
}
}

View File

@@ -2,8 +2,12 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Json;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Service;
using System.Diagnostics;
using System.Globalization;
using Windows.Globalization;
namespace Snap.Hutao.Core.DependencyInjection;
@@ -20,7 +24,7 @@ internal static class IocConfiguration
/// <returns>可继续操作的集合</returns>
public static IServiceCollection AddJsonOptions(this IServiceCollection services)
{
return services.AddSingleton(CoreEnvironment.JsonOptions);
return services.AddSingleton(JsonOptions.Default);
}
/// <summary>
@@ -30,28 +34,51 @@ internal static class IocConfiguration
/// <returns>可继续操作的集合</returns>
public static IServiceCollection AddDatabase(this IServiceCollection services)
{
string dbFile = System.IO.Path.Combine(CoreEnvironment.DataFolder, "Userdata.db");
return services.AddDbContext<AppDbContext>(AddDbContextCore);
}
/// <summary>
/// 初始化语言
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <returns>服务提供器,用于链式调用</returns>
public static IServiceProvider InitializeCulture(this IServiceProvider serviceProvider)
{
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
appOptions.PreviousCulture = CultureInfo.CurrentCulture;
CultureInfo cultureInfo = appOptions.CurrentCulture;
CultureInfo.CurrentCulture = cultureInfo;
CultureInfo.CurrentUICulture = cultureInfo;
ApplicationLanguages.PrimaryLanguageOverride = cultureInfo.Name;
return serviceProvider;
}
private static void AddDbContextCore(IServiceProvider provider, DbContextOptionsBuilder builder)
{
HutaoOptions hutaoOptions = provider.GetRequiredService<HutaoOptions>();
string dbFile = System.IO.Path.Combine(hutaoOptions.DataFolder, "Userdata.db");
string sqlConnectionString = $"Data Source={dbFile}";
// temporarily create a context
// Temporarily create a context
using (AppDbContext context = AppDbContext.Create(sqlConnectionString))
{
if (context.Database.GetPendingMigrations().Any())
{
#if DEBUG
Debug.WriteLine("[Debug] Performing AppDbContext Migrations");
Debug.WriteLine("[Database] Performing AppDbContext Migrations");
#endif
context.Database.Migrate();
}
}
return services.AddDbContext<AppDbContext>(builder =>
{
builder
builder
#if DEBUG
.EnableSensitiveDataLogging()
.EnableSensitiveDataLogging()
#endif
.UseSqlite(sqlConnectionString);
});
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
.UseSqlite(sqlConnectionString);
}
}

View File

@@ -24,10 +24,12 @@ internal static partial class IocHttpClientConfiguration
/// 默认配置
/// </summary>
/// <param name="client">配置后的客户端</param>
private static void DefaultConfiguration(HttpClient client)
private static void DefaultConfiguration(IServiceProvider serviceProvider, HttpClient client)
{
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.CommonUA);
client.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
}
/// <summary>
@@ -37,11 +39,11 @@ internal static partial class IocHttpClientConfiguration
private static void XRpcConfiguration(HttpClient client)
{
client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA);
client.DefaultRequestHeaders.UserAgent.ParseAdd(HoyolabOptions.UserAgent);
client.DefaultRequestHeaders.Accept.ParseAdd(ApplicationJson);
client.DefaultRequestHeaders.Add("x-rpc-app_version", CoreEnvironment.HoyolabXrpcVersion);
client.DefaultRequestHeaders.Add("x-rpc-app_version", HoyolabOptions.XrpcVersion);
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId);
}
/// <summary>
@@ -51,13 +53,13 @@ internal static partial class IocHttpClientConfiguration
private static void XRpc2Configuration(HttpClient client)
{
client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabUA);
client.DefaultRequestHeaders.UserAgent.ParseAdd(HoyolabOptions.UserAgent);
client.DefaultRequestHeaders.Accept.ParseAdd(ApplicationJson);
client.DefaultRequestHeaders.Add("x-rpc-aigis", string.Empty);
client.DefaultRequestHeaders.Add("x-rpc-app_id", "bll8iq97cem8");
client.DefaultRequestHeaders.Add("x-rpc-app_version", CoreEnvironment.HoyolabXrpcVersion);
client.DefaultRequestHeaders.Add("x-rpc-app_version", HoyolabOptions.XrpcVersion);
client.DefaultRequestHeaders.Add("x-rpc-client_type", "2");
client.DefaultRequestHeaders.Add("x-rpc-device_id", CoreEnvironment.HoyolabDeviceId);
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId);
client.DefaultRequestHeaders.Add("x-rpc-game_biz", "bbs_cn");
client.DefaultRequestHeaders.Add("x-rpc-sdk_version", "1.3.1.2");
}
@@ -70,7 +72,7 @@ internal static partial class IocHttpClientConfiguration
private static void XRpc3Configuration(HttpClient client)
{
client.Timeout = Timeout.InfiniteTimeSpan;
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreEnvironment.HoyolabOsUA);
client.DefaultRequestHeaders.UserAgent.ParseAdd(HoyolabOptions.UserAgentOversea);
client.DefaultRequestHeaders.Accept.ParseAdd(ApplicationJson);
client.DefaultRequestHeaders.Add("x-rpc-app_version", "1.5.0");
client.DefaultRequestHeaders.Add("x-rpc-client_type", "4");

View File

@@ -0,0 +1,40 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections;
using System.Text;
namespace Snap.Hutao.Core.ExceptionService;
/// <summary>
/// 异常格式化
/// </summary>
internal sealed class ExceptionFormat
{
private const string SectionSeparator = "----------------------------------------";
/// <summary>
/// 格式化异常
/// </summary>
/// <param name="exception">异常</param>
/// <returns>格式化后的异常</returns>
public static string Format(Exception exception)
{
StringBuilder builder = new();
builder.AppendLine("Exception Data:");
foreach (DictionaryEntry entry in exception.Data)
{
builder
.Append(entry.Key)
.Append(':')
.Append(entry.Value)
.Append(StringLiterals.CRLF);
}
builder.AppendLine(SectionSeparator);
builder.Append(exception.ToString());
return builder.ToString();
}
}

View File

@@ -2,8 +2,6 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using System.Collections;
using System.Text;
namespace Snap.Hutao.Core.ExceptionService;
@@ -11,50 +9,56 @@ namespace Snap.Hutao.Core.ExceptionService;
/// 异常记录器
/// </summary>
[HighQuality]
[Injection(InjectAs.Singleton)]
internal sealed class ExceptionRecorder
{
private readonly ILogger logger;
private readonly ILogger<ExceptionRecorder> logger;
private readonly IServiceProvider serviceProvider;
/// <summary>
/// 构造一个新的异常记录器
/// </summary>
/// <param name="application">应用程序</param>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="logger">日志器</param>
public ExceptionRecorder(Application application, ILogger logger)
public ExceptionRecorder(IServiceProvider serviceProvider)
{
this.logger = logger;
logger = serviceProvider.GetRequiredService<ILogger<ExceptionRecorder>>();
this.serviceProvider = serviceProvider;
}
application.UnhandledException += OnAppUnhandledException;
application.DebugSettings.BindingFailed += OnXamlBindingFailed;
application.DebugSettings.XamlResourceReferenceFailed += OnXamlResourceReferenceFailed;
/// <summary>
/// 记录应用程序异常
/// </summary>
/// <param name="app">应用程序</param>
public void Record(Application app)
{
app.UnhandledException += OnAppUnhandledException;
app.DebugSettings.BindingFailed += OnXamlBindingFailed;
app.DebugSettings.XamlResourceReferenceFailed += OnXamlResourceReferenceFailed;
}
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
#if RELEASE
#pragma warning disable VSTHRD002
Ioc.Default.GetRequiredService<Web.Hutao.HomaLogUploadClient>().UploadLogAsync(e.Exception).GetAwaiter().GetResult();
serviceProvider
.GetRequiredService<Web.Hutao.HomaLogUploadClient>()
.UploadLogAsync(serviceProvider, e.Exception)
.GetAwaiter()
.GetResult();
#pragma warning restore VSTHRD002
#endif
StringBuilder dataDetailBuilder = new();
foreach (DictionaryEntry entry in e.Exception.Data)
{
string key = $"{entry.Key}";
string value = $"{entry.Value}";
dataDetailBuilder.Append(key).Append(':').Append(value).Append("\r\n");
}
logger.LogError(e.Exception, "未经处理的异常\r\n{detail}", dataDetailBuilder.ToString());
logger.LogError("未经处理的全局异常:\r\n{detail}", ExceptionFormat.Format(e.Exception));
}
private void OnXamlBindingFailed(object? sender, BindingFailedEventArgs e)
{
logger.LogCritical("XAML绑定失败: {message}", e.Message);
logger.LogCritical("XAML 绑定失败:{message}", e.Message);
}
private void OnXamlResourceReferenceFailed(DebugSettings sender, XamlResourceReferenceFailedEventArgs e)
{
logger.LogCritical("XAML资源引用失败: {message}", e.Message);
logger.LogCritical("XAML 资源引用失败:{message}", e.Message);
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Options;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System.Collections.Immutable;
namespace Snap.Hutao.Core;
/// <summary>
/// 米游社选项
/// </summary>
[Injection(InjectAs.Singleton)]
internal sealed class HoyolabOptions : IOptions<HoyolabOptions>
{
/// <summary>
/// 米游社请求UA
/// </summary>
public const string UserAgent = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/{XrpcVersion}";
/// <summary>
/// Hoyolab 请求UA
/// </summary>
public const string UserAgentOversea = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBSOversea/{XrpcVersionOversea}";
/// <summary>
/// 米游社移动端请求UA
/// </summary>
public const string MobileUserAgent = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/{XrpcVersion}";
/// <summary>
/// Hoyolab 移动端请求UA
/// </summary>
public const string MobileUserAgentOversea = $"Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBSOversea/{XrpcVersionOversea}";
/// <summary>
/// 米游社 Rpc 版本
/// </summary>
public const string XrpcVersion = "2.49.1";
/// <summary>
/// Hoyolab Rpc 版本
/// </summary>
public const string XrpcVersionOversea = "2.30.0";
// https://github.com/UIGF-org/Hoyolab.Salt
private static readonly ImmutableDictionary<SaltType, string> SaltsInner = new Dictionary<SaltType, string>()
{
[SaltType.K2] = "egBrFMO1BPBG0UX5XOuuwMRLZKwTVKRV",
[SaltType.LK2] = "DG8lqMyc9gquwAUFc7zBS62ijQRX9XF7",
[SaltType.X4] = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
[SaltType.X6] = "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
[SaltType.PROD] = "JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS",
// This SALT is not reliable
[SaltType.OSK2] = "6cqshh5dhw73bzxn20oexa9k516chk7s",
}.ToImmutableDictionary();
private static string? deviceId;
/// <summary>
/// 米游社设备Id
/// </summary>
public static string DeviceId { get => deviceId ??= Guid.NewGuid().ToString(); }
/// <summary>
/// 盐
/// </summary>
public static ImmutableDictionary<SaltType, string> Salts { get => SaltsInner; }
/// <inheritdoc/>
public HoyolabOptions Value { get => this; }
}

View File

@@ -0,0 +1,102 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Options;
using Microsoft.Win32;
using Snap.Hutao.Core.Setting;
using System.IO;
using Windows.ApplicationModel;
using Windows.Storage;
namespace Snap.Hutao.Core;
/// <summary>
/// 胡桃选项
/// 存储环境相关的选项
/// </summary>
[Injection(InjectAs.Singleton)]
internal sealed class HutaoOptions : IOptions<HutaoOptions>
{
/// <summary>
/// 构造一个新的胡桃选项
/// </summary>
public HutaoOptions()
{
DataFolder = GetDataFolderPath();
LocalCache = ApplicationData.Current.LocalCacheFolder.Path;
InstalledLocation = Package.Current.InstalledLocation.Path;
FamilyName = Package.Current.Id.FamilyName;
Version = Package.Current.Id.Version.ToVersion();
UserAgent = $"Snap Hutao/{Version}";
DeviceId = GetUniqueUserId();
}
/// <summary>
/// 当前版本
/// </summary>
public Version Version { get; }
/// <summary>
/// 标准UA
/// </summary>
public string UserAgent { get; }
/// <summary>
/// 数据文件夹路径
/// </summary>
public string DataFolder { get; }
/// <summary>
/// 安装位置
/// </summary>
public string InstalledLocation { get; }
/// <summary>
/// 本地缓存
/// </summary>
public string LocalCache { get; }
/// <summary>
/// 包家族名称
/// </summary>
public string FamilyName { get; }
/// <summary>
/// 设备Id
/// </summary>
public string DeviceId { get; }
/// <inheritdoc/>
public HutaoOptions Value { get => this; }
private static string GetDataFolderPath()
{
string preferredPath = LocalSetting.Get(SettingKeys.DataFolderPath, string.Empty);
if (!string.IsNullOrEmpty(preferredPath) && Directory.Exists(preferredPath))
{
return preferredPath;
}
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
#if RELEASE
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
#else
// 使得迁移能正常生成
string folderName = "Hutao";
#endif
string path = Path.GetFullPath(Path.Combine(myDocument, folderName));
Directory.CreateDirectory(path);
return path;
}
private static string GetUniqueUserId()
{
string userName = Environment.UserName;
object? machineGuid = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\", "MachineGuid", userName);
return Convert.ToMd5HexString($"{userName}{machineGuid}");
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text.Encodings.Web;
using System.Text.Json.Serialization.Metadata;
namespace Snap.Hutao.Core.Json;
/// <summary>
/// Json 选项
/// </summary>
internal static class JsonOptions
{
/// <summary>
/// 默认的Json序列化选项
/// </summary>
public static readonly JsonSerializerOptions Default = new()
{
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
{
Modifiers =
{
JsonTypeInfoResolvers.ResolveEnumType,
},
},
};
}

View File

@@ -32,9 +32,8 @@ internal static class JsonTypeInfoResolvers
if (property.AttributeProvider is System.Reflection.ICustomAttributeProvider provider)
{
object[] attributes = provider.GetCustomAttributes(JsonEnumAttributeType, false);
if (attributes.Length == 1)
if (attributes.SingleOrDefault() is JsonEnumAttribute attr)
{
JsonEnumAttribute attr = (JsonEnumAttribute)attributes[0];
property.CustomConverter = attr.CreateConverter(property);
}
}

View File

@@ -28,4 +28,6 @@ internal static class StringLiterals
/// False
/// </summary>
public const string False = "False";
public const string CRLF = "\r\n";
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 任务上下文
/// </summary>
internal interface ITaskContext
{
/// <summary>
/// 在主线程上同步等待执行操作
/// </summary>
/// <param name="action">操作</param>
void InvokeOnMainThread(Action action);
/// <summary>
/// 异步切换到 后台线程
/// </summary>
/// <remarks>使用 <see cref="SwitchToMainThreadAsync"/> 异步切换到 主线程</remarks>
/// <returns>等待体</returns>
ThreadPoolSwitchOperation SwitchToBackgroundAsync();
/// <summary>
/// 异步切换到 主线程
/// </summary>
/// <remarks>使用 <see cref="SwitchToBackgroundAsync"/> 异步切换到 后台线程</remarks>
/// <returns>等待体</returns>
DispatherQueueSwitchOperation SwitchToMainThreadAsync();
}

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Dispatching;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 任务上下文
/// </summary>
[Injection(InjectAs.Singleton, typeof(ITaskContext))]
internal sealed class TaskContext : ITaskContext
{
private readonly DispatcherQueue dispatcherQueue;
/// <summary>
/// 构造一个新的任务上下文
/// </summary>
public TaskContext()
{
dispatcherQueue = DispatcherQueue.GetForCurrentThread();
DispatcherQueueSynchronizationContext context = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(context);
}
/// <inheritdoc/>
public ThreadPoolSwitchOperation SwitchToBackgroundAsync()
{
return default;
}
/// <inheritdoc/>
public DispatherQueueSwitchOperation SwitchToMainThreadAsync()
{
return new(dispatcherQueue!);
}
/// <inheritdoc/>
public void InvokeOnMainThread(Action action)
{
if (dispatcherQueue!.HasThreadAccess)
{
action();
}
else
{
dispatcherQueue.Invoke(action);
}
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Dispatching;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Core.Threading;
@@ -9,21 +8,20 @@ namespace Snap.Hutao.Core.Threading;
/// <summary>
/// 线程帮助类
/// </summary>
[Obsolete("Use TaskContext instead")]
internal static class ThreadHelper
{
/// <summary>
/// 主线程队列
/// </summary>
private static volatile DispatcherQueue? dispatcherQueue;
private static volatile ITaskContext taskContext;
/// <summary>
/// 初始化
/// </summary>
public static void Initialize()
public static void Initialize(ITaskContext taskContext)
{
dispatcherQueue = DispatcherQueue.GetForCurrentThread();
DispatcherQueueSynchronizationContext context = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(context);
ThreadHelper.taskContext = taskContext;
}
/// <summary>
@@ -34,7 +32,7 @@ internal static class ThreadHelper
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ThreadPoolSwitchOperation SwitchToBackgroundAsync()
{
return default;
return taskContext.SwitchToBackgroundAsync();
}
/// <summary>
@@ -45,7 +43,7 @@ internal static class ThreadHelper
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DispatherQueueSwitchOperation SwitchToMainThreadAsync()
{
return new(dispatcherQueue!);
return taskContext.SwitchToMainThreadAsync();
}
/// <summary>
@@ -55,13 +53,6 @@ internal static class ThreadHelper
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InvokeOnMainThread(Action action)
{
if (dispatcherQueue!.HasThreadAccess)
{
action();
}
else
{
dispatcherQueue.Invoke(action);
}
taskContext.InvokeOnMainThread(action);
}
}

View File

@@ -11,9 +11,9 @@ using Snap.Hutao.Message;
using Snap.Hutao.Service;
using Snap.Hutao.Win32;
using System.IO;
using Windows.Win32.Foundation;
using Windows.Graphics;
using Windows.UI;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using static Windows.Win32.PInvoke;
@@ -30,7 +30,6 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
private readonly WindowOptions<TWindow> options;
private readonly IServiceProvider serviceProvider;
private readonly ILogger<ExtendedWindow<TWindow>> logger;
private readonly WindowSubclass<TWindow> subclass;
private ExtendedWindow(TWindow window, FrameworkElement titleBar, IServiceProvider serviceProvider)
@@ -38,7 +37,6 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
options = new(window, titleBar);
subclass = new(options);
logger = serviceProvider.GetRequiredService<ILogger<ExtendedWindow<TWindow>>>();
this.serviceProvider = serviceProvider;
InitializeWindow();
@@ -63,8 +61,10 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<FlyoutOpenCloseMessag
private void InitializeWindow()
{
options.AppWindow.Title = string.Format(SH.AppNameAndVersion, CoreEnvironment.Version);
options.AppWindow.SetIcon(Path.Combine(CoreEnvironment.InstalledLocation, "Assets/Logo.ico"));
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
options.AppWindow.Title = string.Format(SH.AppNameAndVersion, hutaoOptions.Version);
options.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
ExtendsContentIntoTitleBar();
Persistence.RecoverOrInit(options);

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Json;
namespace Snap.Hutao.Model.Entity.Configuration;
@@ -18,8 +18,8 @@ internal sealed class JsonTextValueConverter<TProperty> : ValueConverter<TProper
/// </summary>
public JsonTextValueConverter()
: base(
obj => JsonSerializer.Serialize(obj, CoreEnvironment.JsonOptions),
str => JsonSerializer.Deserialize<TProperty>(str, CoreEnvironment.JsonOptions)!)
obj => JsonSerializer.Serialize(obj, JsonOptions.Default),
str => JsonSerializer.Deserialize<TProperty>(str, JsonOptions.Default)!)
{
}
}

View File

@@ -47,15 +47,17 @@ internal sealed class UIAFInfo
/// <summary>
/// 构造一个新的专用 UIAF 信息
/// </summary>
/// <param name="uid">uid</param>
/// <param name="serviceProvider">服务提供器</param>
/// <returns>专用 UIAF 信息</returns>
public static UIAFInfo Create()
public static UIAFInfo Create(IServiceProvider serviceProvider)
{
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
return new()
{
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
ExportApp = SH.AppName,
ExportAppVersion = CoreEnvironment.Version.ToString(),
ExportAppVersion = hutaoOptions.Version.ToString(),
UIAFVersion = UIAF.CurrentVersion,
};
}

View File

@@ -60,17 +60,20 @@ internal sealed class UIGFInfo
/// <summary>
/// 构造一个新的专用 UIGF 信息
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="uid">uid</param>
/// <returns>专用 UIGF 信息</returns>
public static UIGFInfo Create(string uid)
public static UIGFInfo Create(IServiceProvider serviceProvider, string uid)
{
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
return new()
{
Uid = uid,
Language = "zh-cn",
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
ExportApp = SH.AppName,
ExportAppVersion = CoreEnvironment.Version.ToString(),
ExportAppVersion = hutaoOptions.Version.ToString(),
UIGFVersion = UIGF.CurrentVersion,
};
}

View File

@@ -60,17 +60,20 @@ internal sealed class UIIFInfo
/// <summary>
/// 构造一个新的专用 UIGF 信息
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="uid">uid</param>
/// <returns>专用 UIGF 信息</returns>
public static UIIFInfo Create(string uid)
public static UIIFInfo Create(IServiceProvider serviceProvider, string uid)
{
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
return new()
{
Uid = uid,
Language = "zh-cn",
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
ExportApp = "胡桃",
ExportAppVersion = CoreEnvironment.Version.ToString(),
ExportAppVersion = hutaoOptions.Version.ToString(),
UIIFVersion = UIIF.CurrentVersion,
};
}

View File

@@ -11,6 +11,8 @@ namespace Snap.Hutao.Model.Metadata;
[HighQuality]
internal sealed class ParameterFormat : IFormatProvider, ICustomFormatter
{
private static readonly Lazy<ParameterFormat> LazyFormat = new();
/// <summary>
/// 格式化
/// </summary>
@@ -20,7 +22,7 @@ internal sealed class ParameterFormat : IFormatProvider, ICustomFormatter
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Format(string str, object param)
{
return string.Format(new ParameterFormat(), str, param);
return string.Format(LazyFormat.Value, str, param);
}
/// <inheritdoc/>

View File

@@ -1,16 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Snap.Hutao.Service;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Globalization;
using WinRT;
[assembly:InternalsVisibleTo("Snap.Hutao.Test")]
// Visible to test project.
[assembly: InternalsVisibleTo("Snap.Hutao.Test")]
namespace Snap.Hutao;
@@ -20,67 +17,37 @@ namespace Snap.Hutao;
[SuppressMessage("", "SH001")]
public static partial class Program
{
private static readonly ApplicationInitializationCallback AppInitializationCallback = InitializeApp;
[LibraryImport("Microsoft.ui.xaml.dll")]
private static partial void XamlCheckProcessRequirements();
[STAThread]
private static void Main(string[] args)
{
_ = args;
System.Diagnostics.Debug.WriteLine($"[Arguments]:{args}");
XamlCheckProcessRequirements();
ComWrappersSupport.InitializeComWrappers();
// by adding the using statement, we can dispose the injected services when we closing
using (ServiceProvider serviceProvider = InitializeDependencyInjection())
// By adding the using statement, we can dispose the injected services when we closing
using (ServiceProvider serviceProvider = DependencyInjection.Initialize())
{
InitializeCulture(serviceProvider);
serviceProvider.InitializeCulture();
// In a Desktop app this runs a message pump internally,
// and does not return until the application shuts down.
Application.Start(InitializeApp);
Application.Start(AppInitializationCallback);
Control.ScopedPage.DisposePreviousScope();
}
}
private static void InitializeApp(ApplicationInitializationCallbackParams param)
{
ThreadHelper.Initialize();
_ = Ioc.Default.GetRequiredService<App>();
}
IServiceProvider serviceProvider = Ioc.Default;
private static void InitializeCulture(IServiceProvider serviceProvider)
{
AppOptions appOptions = serviceProvider.GetRequiredService<AppOptions>();
appOptions.PreviousCulture = CultureInfo.CurrentCulture;
CultureInfo cultureInfo = appOptions.CurrentCulture;
CultureInfo.CurrentCulture = cultureInfo;
CultureInfo.CurrentUICulture = cultureInfo;
ApplicationLanguages.PrimaryLanguageOverride = cultureInfo.Name;
}
private static ServiceProvider InitializeDependencyInjection()
{
ServiceProvider services = new ServiceCollection()
// Microsoft extension
.AddLogging(builder => builder.AddDebug())
.AddMemoryCache()
// Hutao extensions
.AddJsonOptions()
.AddDatabase()
.AddInjections()
.AddHttpClients()
// Discrete services
.AddSingleton<IMessenger>(WeakReferenceMessenger.Default)
.BuildServiceProvider(true);
Ioc.Default.ConfigureServices(services);
return services;
ITaskContext taskContext = serviceProvider.GetRequiredService<ITaskContext>();
ThreadHelper.Initialize(taskContext);
_ = serviceProvider.GetRequiredService<App>();
}
}

View File

@@ -28,19 +28,22 @@ internal sealed class AchievementService : IAchievementService
private readonly ILogger<AchievementService> logger;
private readonly DbCurrent<AchievementArchive, Message.AchievementArchiveChangedMessage> dbCurrent;
private readonly AchievementDbOperation achievementDbOperation;
private readonly IServiceProvider serviceProvider;
private ObservableCollection<AchievementArchive>? archiveCollection;
/// <summary>
/// 构造一个新的成就服务
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="appDbContext">数据库上下文</param>
/// <param name="logger">日志器</param>
/// <param name="messenger">消息器</param>
public AchievementService(AppDbContext appDbContext, ILogger<AchievementService> logger, IMessenger messenger)
public AchievementService(IServiceProvider serviceProvider, AppDbContext appDbContext, ILogger<AchievementService> logger, IMessenger messenger)
{
this.appDbContext = appDbContext;
this.logger = logger;
this.serviceProvider = serviceProvider;
dbCurrent = new(appDbContext.AchievementArchives, messenger);
achievementDbOperation = new(appDbContext);
@@ -174,7 +177,7 @@ internal sealed class AchievementService : IAchievementService
UIAF uigf = new()
{
Info = UIAFInfo.Create(),
Info = UIAFInfo.Create(serviceProvider),
List = list,
};

View File

@@ -9,6 +9,7 @@ namespace Snap.Hutao.Service;
/// <summary>
/// 应用程序选项
/// 存储服务相关的选项
/// </summary>
[Injection(InjectAs.Singleton)]
internal sealed class AppOptions : DbStoreOptions

View File

@@ -15,14 +15,16 @@ namespace Snap.Hutao.Service.GachaLog;
internal sealed class GachaLogExportService : IGachaLogExportService
{
private readonly AppDbContext appDbContext;
private readonly IServiceProvider serviceProvider;
/// <summary>
/// 构造一个新的祈愿记录导出服务
/// </summary>
/// <param name="appDbContext">数据库上下文</param>
public GachaLogExportService(AppDbContext appDbContext)
public GachaLogExportService(IServiceProvider serviceProvider, AppDbContext appDbContext)
{
this.appDbContext = appDbContext;
this.serviceProvider = serviceProvider;
}
/// <inheritdoc/>
@@ -37,7 +39,7 @@ internal sealed class GachaLogExportService : IGachaLogExportService
UIGF uigf = new()
{
Info = UIGFInfo.Create(archive.Uid),
Info = UIGFInfo.Create(serviceProvider, archive.Uid),
List = list,
};

View File

@@ -21,16 +21,19 @@ internal sealed class PackageConverter
{
private readonly JsonSerializerOptions options;
private readonly HttpClient httpClient;
private readonly IServiceProvider serviceProvider;
/// <summary>
/// 构造一个新的游戏文件转换器
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="options">Json序列化选项</param>
/// <param name="httpClient">http客户端</param>
public PackageConverter(JsonSerializerOptions options, HttpClient httpClient)
public PackageConverter(IServiceProvider serviceProvider, HttpClient httpClient)
{
this.options = options;
options = serviceProvider.GetRequiredService<JsonSerializerOptions>();
this.httpClient = httpClient;
this.serviceProvider = serviceProvider;
}
/// <summary>
@@ -228,7 +231,8 @@ internal sealed class PackageConverter
}
// Cache folder
string cacheFolder = Path.Combine(Core.CoreEnvironment.DataFolder, "ServerCache");
Core.HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<Core.HutaoOptions>();
string cacheFolder = Path.Combine(hutaoOptions.DataFolder, "ServerCache");
// 执行下载与移动操作
foreach (ItemOperationInfo info in operations)

View File

@@ -47,6 +47,7 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
/// <param name="logger">日志器</param>
/// <param name="memoryCache">内存缓存</param>
public MetadataService(
Core.HutaoOptions hutaoOptions,
IInfoBarService infoBarService,
IHttpClientFactory httpClientFactory,
JsonSerializerOptions options,
@@ -60,7 +61,7 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
httpClient = httpClientFactory.CreateClient(nameof(MetadataService));
locale = GetTextMapLocale();
metadataFolderPath = Path.Combine(Core.CoreEnvironment.DataFolder, "Metadata", locale);
metadataFolderPath = Path.Combine(hutaoOptions.DataFolder, "Metadata", locale);
Directory.CreateDirectory(metadataFolderPath);
}

View File

@@ -69,7 +69,7 @@
TextWrapping="Wrap"/>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingAboutHeader}"/>
<clw:SettingsCard
Description="{Binding AppVersion}"
Description="{Binding HutaoOptions.Version}"
Header="{shcm:ResourceString Name=AppName}"
HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"/>
<clw:SettingsCard
@@ -82,7 +82,7 @@
ActionIcon="{shcm:FontIcon Glyph=&#xE8C8;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCopyDeviceIdAction}"
Command="{Binding CopyDeviceIdCommand}"
Description="{Binding DeviceId}"
Description="{Binding HutaoOptions.DeviceId}"
Header="{shcm:ResourceString Name=ViewPageSettingDeviceIdHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE975;}"
IsClickEnabled="True"/>

View File

@@ -26,11 +26,18 @@ internal sealed partial class TitleView : UserControl
[SuppressMessage("", "CA1822")]
public string Title
{
get
{
Core.HutaoOptions hutaoOptions = Ioc.Default.GetRequiredService<Core.HutaoOptions>();
string format =
#if DEBUG
get => string.Format(SH.AppDevNameAndVersion, Core.CoreEnvironment.Version);
SH.AppDevNameAndVersion;
#else
get => string.Format(SH.AppNameAndVersion, Core.CoreEnvironment.Version);
SH.AppNameAndVersion;
#endif
return string.Format(format, hutaoOptions.Version);
}
}
/// <summary>

View File

@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Entity.Database;
using Windows.Storage;
@@ -52,12 +53,14 @@ internal sealed class ExperimentalFeaturesViewModel : ObservableObject
private Task OpenCacheFolderAsync()
{
return Launcher.LaunchFolderAsync(ApplicationData.Current.LocalCacheFolder).AsTask();
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
return Launcher.LaunchFolderPathAsync(hutaoOptions.LocalCache).AsTask();
}
private Task OpenDataFolderAsync()
{
return Launcher.LaunchFolderPathAsync(Core.CoreEnvironment.DataFolder).AsTask();
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
return Launcher.LaunchFolderPathAsync(hutaoOptions.DataFolder).AsTask();
}
private async Task DangerousDeleteUsersAsync()

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.Input;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Core;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Core.LifeCycle;
@@ -67,6 +68,7 @@ internal sealed class SettingViewModel : Abstraction.ViewModel
Experimental = serviceProvider.GetRequiredService<ExperimentalFeaturesViewModel>();
Options = serviceProvider.GetRequiredService<AppOptions>();
UserOptions = serviceProvider.GetRequiredService<HutaoUserOptions>();
HutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
this.serviceProvider = serviceProvider;
selectedCulture = cultures.FirstOrDefault(c => c.Value == Options.CurrentCulture.Name);
@@ -82,15 +84,6 @@ internal sealed class SettingViewModel : Abstraction.ViewModel
NavigateToHutaoPassportCommand = new RelayCommand(NavigateToHutaoPassport);
}
/// <summary>
/// 版本
/// </summary>
[SuppressMessage("", "CA1822")]
public string AppVersion
{
get => Core.CoreEnvironment.Version.ToString();
}
/// <summary>
/// WebView2 版本
/// </summary>
@@ -100,20 +93,16 @@ internal sealed class SettingViewModel : Abstraction.ViewModel
get => Core.WebView2Helper.Version;
}
/// <summary>
/// 设备Id
/// </summary>
[SuppressMessage("", "CA1822")]
public string DeviceId
{
get => Core.CoreEnvironment.HutaoDeviceId;
}
/// <summary>
/// 应用程序设置
/// </summary>
public AppOptions Options { get; }
/// <summary>
/// 胡桃选项
/// </summary>
public HutaoOptions HutaoOptions { get; }
/// <summary>
/// 胡桃用户选项
/// </summary>
@@ -317,7 +306,7 @@ internal sealed class SettingViewModel : Abstraction.ViewModel
try
{
Clipboard.SetText(DeviceId);
Clipboard.SetText(HutaoOptions.DeviceId);
infoBarService.Success(SH.ViewModelSettingCopyDeviceIdSuccess);
}
catch (COMException ex)

View File

@@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Notifications;
using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.IO;
@@ -146,7 +147,8 @@ internal sealed class WelcomeViewModel : ObservableObject
public DownloadSummary(IServiceProvider serviceProvider, string fileName)
{
httpClient = serviceProvider.GetRequiredService<HttpClient>();
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Core.CoreEnvironment.CommonUA);
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(hutaoOptions.UserAgent);
this.serviceProvider = serviceProvider;

View File

@@ -15,29 +15,29 @@ internal static class CoreWebView2Extension
/// <summary>
/// 设置 移动端UA
/// </summary>
/// <param name="webView">webview2</param>
/// <param name="webView">webView2</param>
/// <returns>链式调用的WebView2</returns>
public static CoreWebView2 SetMobileUserAgent(this CoreWebView2 webView)
{
webView.Settings.UserAgent = Core.CoreEnvironment.HoyolabMobileUA;
webView.Settings.UserAgent = Core.HoyolabOptions.MobileUserAgent;
return webView;
}
/// <summary>
/// 设置 移动端OsUA
/// </summary>
/// <param name="webView">webview2</param>
/// <param name="webView">webView2</param>
/// <returns>链式调用的WebView2</returns>
public static CoreWebView2 SetMobileOverseaUserAgent(this CoreWebView2 webView)
{
webView.Settings.UserAgent = Core.CoreEnvironment.HoyolabOsMobileUA;
webView.Settings.UserAgent = Core.HoyolabOptions.MobileUserAgentOversea;
return webView;
}
/// <summary>
/// 设置WebView2的Cookie
/// </summary>
/// <param name="webView">webview2</param>
/// <param name="webView">webView2</param>
/// <param name="cookieToken">CookieToken</param>
/// <param name="lToken">LToken</param>
/// <param name="sToken">SToken</param>

View File

@@ -87,8 +87,8 @@ internal class MiHoYoJSInterface
Data = new Dictionary<string, string>()
{
{ "x-rpc-client_type", "5" },
{ "x-rpc-device_id", Core.CoreEnvironment.HoyolabDeviceId },
{ "x-rpc-app_version", Core.CoreEnvironment.HoyolabXrpcVersion },
{ "x-rpc-device_id", Core.HoyolabOptions.DeviceId },
{ "x-rpc-app_version", Core.HoyolabOptions.XrpcVersion },
},
};
}
@@ -120,7 +120,7 @@ internal class MiHoYoJSInterface
/// <returns>响应</returns>
public virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV1(JsParam param)
{
string salt = Core.CoreEnvironment.DynamicSecretSalts[SaltType.LK2];
string salt = Core.HoyolabOptions.Salts[SaltType.LK2];
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string r = GetRandomString();
string check = Core.Convert.ToMd5HexString($"salt={salt}&t={t}&r={r}").ToLowerInvariant();
@@ -151,7 +151,7 @@ internal class MiHoYoJSInterface
public virtual JsResult<Dictionary<string, string>> GetDynamicSecrectV2(JsParam<DynamicSecrect2Playload> param)
{
// TODO: Salt X4 for hoyolab user
string salt = Core.CoreEnvironment.DynamicSecretSalts[SaltType.X4];
string salt = Core.HoyolabOptions.Salts[SaltType.X4];
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
int r = GetRandom();
string b = param.Payload.Body;

View File

@@ -35,8 +35,8 @@ internal sealed class SignInJSInterfaceOversea : MiHoYoJSInterface
Data = new Dictionary<string, string>()
{
{ "x-rpc-client_type", "2" },
{ "x-rpc-device_id", Core.CoreEnvironment.HoyolabDeviceId },
{ "x-rpc-app_version", Core.CoreEnvironment.HoyolabOsXrpcVersion },
{ "x-rpc-device_id", Core.HoyolabOptions.DeviceId },
{ "x-rpc-app_version", Core.HoyolabOptions.XrpcVersionOversea },
},
};
}

View File

@@ -26,8 +26,8 @@ internal sealed class SignInJsInterface : MiHoYoJSInterface
Data = new Dictionary<string, string>()
{
{ "x-rpc-client_type", "2" },
{ "x-rpc-device_id", Core.CoreEnvironment.HoyolabDeviceId },
{ "x-rpc-app_version", Core.CoreEnvironment.HoyolabXrpcVersion },
{ "x-rpc-device_id", Core.HoyolabOptions.DeviceId },
{ "x-rpc-app_version", Core.HoyolabOptions.XrpcVersion },
},
};
}

View File

@@ -33,7 +33,7 @@ internal sealed class DynamicSecretHandler : DelegatingHandler
private static async Task ProcessRequestWithOptionsAsync(HttpRequestMessage request, DynamicSecretCreationOptions options, CancellationToken token)
{
string salt = Core.CoreEnvironment.DynamicSecretSalts[options.SaltType];
string salt = Core.HoyolabOptions.Salts[options.SaltType];
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core;
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Hutao.Log;
using Snap.Hutao.Web.Response;
@@ -29,11 +30,12 @@ internal sealed class HomaLogUploadClient
/// <summary>
/// 上传日志
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="exception">异常</param>
/// <returns>任务</returns>
public async Task<string?> UploadLogAsync(Exception exception)
public async Task<string?> UploadLogAsync(IServiceProvider serviceProvider, Exception exception)
{
HutaoLog log = BuildFromException(exception);
HutaoLog log = BuildFromException(serviceProvider, exception);
Response<string>? a = await httpClient
.TryCatchPostAsJsonAsync<HutaoLog, Response<string>>(HutaoEndpoints.HutaoLogUpload, log)
@@ -41,13 +43,15 @@ internal sealed class HomaLogUploadClient
return a?.Data;
}
private static HutaoLog BuildFromException(Exception exception)
private static HutaoLog BuildFromException(IServiceProvider serviceProvider, Exception exception)
{
HutaoOptions hutaoOptions = serviceProvider.GetRequiredService<HutaoOptions>();
return new()
{
Id = Core.CoreEnvironment.HutaoDeviceId,
Id = hutaoOptions.DeviceId,
Time = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
Info = exception.ToString(),
Info = Core.ExceptionService.ExceptionFormat.Format(exception),
};
}
}