fix launch scheme detection

This commit is contained in:
Lightczx
2023-05-10 13:44:09 +08:00
parent 404cd9d705
commit 42a19239e6
32 changed files with 416 additions and 158 deletions

View File

@@ -7,12 +7,17 @@
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug As Fake Elevated|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<None Remove="stylecop.json" />
</ItemGroup>

View File

@@ -7,6 +7,7 @@
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
</PropertyGroup>
<ItemGroup>

View File

@@ -16,6 +16,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Test", "Snap.Hut
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug As Fake Elevated|Any CPU = Debug As Fake Elevated|Any CPU
Debug As Fake Elevated|arm64 = Debug As Fake Elevated|arm64
Debug As Fake Elevated|x64 = Debug As Fake Elevated|x64
Debug As Fake Elevated|x86 = Debug As Fake Elevated|x86
Debug|Any CPU = Debug|Any CPU
Debug|arm64 = Debug|arm64
Debug|x64 = Debug|x64
@@ -26,6 +30,18 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.Deploy.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.Deploy.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.Deploy.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.Deploy.0 = Debug As Fake Elevated|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.ActiveCfg = Debug|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.Build.0 = Debug|x64
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.Deploy.0 = Debug|x64
@@ -50,6 +66,14 @@ Global
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.ActiveCfg = Release|x86
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Build.0 = Release|x86
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Deploy.0 = Release|x86
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.ActiveCfg = Debug|Any CPU
@@ -66,6 +90,14 @@ Global
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|arm64.ActiveCfg = Debug|Any CPU

View File

@@ -64,7 +64,7 @@ public sealed partial class App : Application
Process.GetCurrentProcess().Kill();
}
}
catch (Exception)
catch
{
// AppInstance.GetCurrent() calls failed
Process.GetCurrentProcess().Kill();
@@ -80,8 +80,8 @@ public sealed partial class App : Application
{
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);
logger.LogInformation("FamilyName: {name}", hutaoOptions.FamilyName);
logger.LogInformation("Version: {version}", hutaoOptions.Version);
logger.LogInformation("LocalCache: {folder}", hutaoOptions.LocalCache);
}
}

View File

@@ -11,13 +11,15 @@ using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.Service.Notification;
using System.Diagnostics;
using System.Security.Principal;
using Windows.ApplicationModel;
#if !DEBUG_AS_FAKE_ELEVATED
using System.Security.Principal;
#endif
namespace Snap.Hutao.Core.LifeCycle;
/// <summary>
/// 激活处理器
/// 激活
/// </summary>
[HighQuality]
internal static class Activation
@@ -58,16 +60,15 @@ internal static class Activation
/// <returns>是否提升了权限</returns>
public static bool GetElevated()
{
if (Debugger.IsAttached)
{
return true;
}
#if DEBUG_AS_FAKE_ELEVATED
return true;
#else
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
{
WindowsPrincipal principal = new(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
#endif
}
/// <summary>

View File

@@ -7,6 +7,7 @@ namespace Snap.Hutao.Core.Setting;
/// 设置键
/// </summary>
[HighQuality]
[SuppressMessage("", "SA1124")]
internal static class SettingKeys
{
/// <summary>

View File

@@ -29,5 +29,8 @@ internal static class StringLiterals
/// </summary>
public const string False = "False";
/// <summary>
/// CRLF 换行符
/// </summary>
public const string CRLF = "\r\n";
}

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.Threading;
/// 等待此类型对象后上下文会被切换至主线程
/// </summary>
[HighQuality]
internal readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, IAwaiter
internal readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueueSwitchOperation>, ICriticalAwaiter
{
private readonly DispatcherQueue dispatherQueue;
@@ -44,6 +44,15 @@ internal readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQue
/// <inheritdoc/>
public void OnCompleted(Action continuation)
{
dispatherQueue.TryEnqueue(() => continuation());
dispatherQueue.TryEnqueue(continuation.Invoke);
}
/// <inheritdoc/>
public void UnsafeOnCompleted(Action continuation)
{
using (ExecutionContext.SuppressFlow())
{
dispatherQueue.TryEnqueue(continuation.Invoke);
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.Threading;
/// 线程池切换操作
/// 等待此类型对象后上下文会被切换至线程池线程
/// </summary>
internal readonly struct ThreadPoolSwitchOperation : IAwaitable<ThreadPoolSwitchOperation>, IAwaiter, ICriticalAwaiter
internal readonly struct ThreadPoolSwitchOperation : IAwaitable<ThreadPoolSwitchOperation>, ICriticalAwaiter
{
private static readonly WaitCallback WaitCallbackRunAction = RunAction;
private readonly DispatcherQueue dispatherQueue;
@@ -27,9 +27,8 @@ internal readonly struct ThreadPoolSwitchOperation : IAwaitable<ThreadPoolSwitch
/// <inheritdoc/>
public bool IsCompleted
{
get =>
// 如果已经处于后台就不再切换新的线程
!dispatherQueue.HasThreadAccess;
// 如果已经处于后台就不再切换新的线程
get => !dispatherQueue.HasThreadAccess;
}
/// <inheritdoc/>

View File

@@ -22,12 +22,13 @@ internal static class Persistence
/// <summary>
/// 设置窗体位置
/// </summary>
/// <param name="window">选项窗口param>
/// <typeparam name="TWindow">窗体类型</typeparam>
/// <param name="window">选项窗口param>
public static void RecoverOrInit<TWindow>(TWindow window)
where TWindow : Window, IWindowOptionsSource
{
WindowOptions options = window.WindowOptions;
// Set first launch size.
double scale = GetScaleForWindowHandle(options.Hwnd);
SizeInt32 transformedSize = options.InitSize.Scale(scale);

View File

@@ -29,7 +29,7 @@ internal sealed class WindowSubclass<TWindow> : IDisposable
/// <summary>
/// 构造一个新的窗体子类管理器
/// </summary>
/// <param name="options">选项</param>
/// <param name="window">窗口</param>
public WindowSubclass(TWindow window)
{
this.window = window;

View File

@@ -1,12 +1,12 @@
{
"profiles": {
"Snap.Hutao (Package)": {
"Snap.Hutao": {
"commandName": "MsixPackage",
"nativeDebugging": false,
"doNotLaunchApp": false,
"allowLocalNetworkLoopbackProperty": true
},
"Snap.Hutao (Unpackaged)": {
"[Unpackaged] Snap.Hutao": {
"commandName": "Project"
}
}

View File

@@ -3552,6 +3552,51 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 胡桃已经为你启动了 {0} 次游戏 的本地化字符串。
/// </summary>
internal static string ViewPageHomeGreetingTextCommon1 {
get {
return ResourceManager.GetString("ViewPageHomeGreetingTextCommon1", resourceCulture);
}
}
/// <summary>
/// 查找类似 你已经启动了胡桃 {0} 次 的本地化字符串。
/// </summary>
internal static string ViewPageHomeGreetingTextCommon2 {
get {
return ResourceManager.GetString("ViewPageHomeGreetingTextCommon2", resourceCulture);
}
}
/// <summary>
/// 查找类似 旅行者,欢迎来到提瓦特大陆! 的本地化字符串。
/// </summary>
internal static string ViewPageHomeGreetingTextDefault {
get {
return ResourceManager.GetString("ViewPageHomeGreetingTextDefault", resourceCulture);
}
}
/// <summary>
/// 查找类似 你说的对,但是《胡桃》是由 DGP Studio 自主研发的一款... 的本地化字符串。
/// </summary>
internal static string ViewPageHomeGreetingTextEasterEgg {
get {
return ResourceManager.GetString("ViewPageHomeGreetingTextEasterEgg", resourceCulture);
}
}
/// <summary>
/// 查找类似 呐,旅行者,这是你来到提瓦特大陆的第 {0} 天哦 的本地化字符串。
/// </summary>
internal static string ViewPageHomeGreetingTextEpic1 {
get {
return ResourceManager.GetString("ViewPageHomeGreetingTextEpic1", resourceCulture);
}
}
/// <summary>
/// 查找类似 设置 的本地化字符串。
/// </summary>

View File

@@ -1995,4 +1995,19 @@
<data name="ServiceMetadataFileNotFound" xml:space="preserve">
<value>无法找到缓存的元数据文件</value>
</data>
<data name="ViewPageHomeGreetingTextDefault" xml:space="preserve">
<value>旅行者,欢迎来到提瓦特大陆!</value>
</data>
<data name="ViewPageHomeGreetingTextCommon1" xml:space="preserve">
<value>胡桃已经为你启动了 {0} 次游戏</value>
</data>
<data name="ViewPageHomeGreetingTextCommon2" xml:space="preserve">
<value>你已经启动了胡桃 {0} 次</value>
</data>
<data name="ViewPageHomeGreetingTextEasterEgg" xml:space="preserve">
<value>你说的对,但是《胡桃》是由 DGP Studio 自主研发的一款...</value>
</data>
<data name="ViewPageHomeGreetingTextEpic1" xml:space="preserve">
<value>呐,旅行者,这是你来到提瓦特大陆的第 {0} 天哦</value>
</data>
</root>

View File

@@ -16,14 +16,14 @@ namespace Snap.Hutao.Service;
[Injection(InjectAs.Singleton)]
internal sealed class AppOptions : DbStoreOptions
{
private readonly List<NameValue<BackdropType>> backdropTypes = new()
private static readonly List<NameValue<BackdropType>> SupportedBackdropTypes = new()
{
new("Acrylic", BackdropType.Acrylic),
new("Mica", BackdropType.Mica),
new("MicaAlt", BackdropType.MicaAlt),
};
private readonly List<NameValue<string>> cultures = new()
private static readonly List<NameValue<string>> SupportedCultures = new()
{
ToNameValue(CultureInfo.GetCultureInfo("zh-Hans")),
ToNameValue(CultureInfo.GetCultureInfo("zh-Hant")),
@@ -40,7 +40,7 @@ internal sealed class AppOptions : DbStoreOptions
/// <summary>
/// 构造一个新的应用程序选项
/// </summary>
/// <param name="serviceScopeFactory">服务范围工厂</param>
/// <param name="serviceProvider">服务提供器</param>
public AppOptions(IServiceProvider serviceProvider)
: base(serviceProvider)
{
@@ -67,7 +67,7 @@ internal sealed class AppOptions : DbStoreOptions
/// <summary>
/// 所有支持的背景样式
/// </summary>
public List<NameValue<BackdropType>> BackdropTypes { get => backdropTypes; }
public List<NameValue<BackdropType>> BackdropTypes { get => SupportedBackdropTypes; }
/// <summary>
/// 背景类型 默认 Mica
@@ -81,7 +81,7 @@ internal sealed class AppOptions : DbStoreOptions
/// <summary>
/// 所有支持的语言
/// </summary>
public List<NameValue<string>> Cultures { get => cultures; }
public List<NameValue<string>> Cultures { get => SupportedCultures; }
/// <summary>
/// 初始化前的语言

View File

@@ -107,23 +107,24 @@ internal sealed partial class GameService : IGameService
}
/// <inheritdoc/>
public MultiChannel GetMultiChannel()
public ChannelOptions GetChannelOptions()
{
string gamePath = appOptions.GamePath;
string configPath = Path.Combine(Path.GetDirectoryName(gamePath) ?? string.Empty, ConfigFileName);
bool isOversea = string.Equals(Path.GetFileName(gamePath), GenshinImpactFileName, StringComparison.OrdinalIgnoreCase);
if (!File.Exists(configPath))
{
return new(null, null, configPath);
return ChannelOptions.FileNotFound(isOversea, configPath);
}
using (FileStream stream = File.OpenRead(configPath))
{
IEnumerable<IniParameter> parameters = IniSerializer.Deserialize(stream).ToList().OfType<IniParameter>();
List<IniParameter> parameters = IniSerializer.Deserialize(stream).OfType<IniParameter>().ToList();
string? channel = parameters.FirstOrDefault(p => p.Key == "channel")?.Value;
string? subChannel = parameters.FirstOrDefault(p => p.Key == "sub_channel")?.Value;
return new(channel, subChannel);
return new(channel, subChannel, isOversea);
}
}

View File

@@ -42,7 +42,7 @@ internal interface IGameService
/// 获取多通道值
/// </summary>
/// <returns>多通道值</returns>
MultiChannel GetMultiChannel();
ChannelOptions GetChannelOptions();
/// <summary>
/// 游戏是否正在运行

View File

@@ -35,7 +35,7 @@ internal sealed class LaunchOptions : DbStoreOptions
/// <summary>
/// 构造一个新的启动游戏选项
/// </summary>
/// <param name="serviceScopeFactory">服务范围工厂</param>
/// <param name="serviceProvider">服务提供器</param>
public LaunchOptions(IServiceProvider serviceProvider)
: base(serviceProvider)
{

View File

@@ -0,0 +1,96 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Service.Game;
/// <summary>
/// 方案列表部分
/// </summary>
internal sealed partial class LaunchScheme
{
private const int SdkStaticLauncherChineseId = 18;
private const int SdkStaticLauncherBilibiliId = 17;
private const int SdkStaticLauncherGlobalId = 10;
private const string SdkStaticLauncherChineseKey = "eYd89JmJ";
private const string SdkStaticLauncherBilibiliKey = "KAtdSsoQ";
private const string SdkStaticLauncherGlobalKey = "gcStgarh";
private static readonly LaunchScheme ServerChineseChannelOfficialSubChannelOfficial = new()
{
LauncherId = SdkStaticLauncherChineseId,
Key = SdkStaticLauncherChineseKey,
Channel = ChannelType.Official,
SubChannel = SubChannelType.Official,
IsOversea = false,
};
private static readonly LaunchScheme ServerChineseChannelOfficialSubChannelNoTapTap = new()
{
LauncherId = SdkStaticLauncherChineseId,
Key = SdkStaticLauncherChineseKey,
Channel = ChannelType.Official,
SubChannel = SubChannelType.NoTapTap,
IsOversea = false,
};
private static readonly LaunchScheme ServerChineseChannelBilibiliSubChannelDefault = new()
{
LauncherId = SdkStaticLauncherBilibiliId,
Key = SdkStaticLauncherBilibiliKey,
Channel = ChannelType.Bili,
SubChannel = SubChannelType.Default,
IsOversea = false,
};
private static readonly LaunchScheme ServerGlobalChannelOfficialSubChannelDefault = new()
{
LauncherId = SdkStaticLauncherGlobalId,
Key = SdkStaticLauncherGlobalKey,
Channel = ChannelType.Official,
SubChannel = SubChannelType.Default,
IsOversea = false,
};
private static readonly LaunchScheme ServerGlobalChannelOfficialSubChannelEpic = new()
{
LauncherId = SdkStaticLauncherGlobalId,
Key = SdkStaticLauncherGlobalKey,
Channel = ChannelType.Official,
SubChannel = SubChannelType.Epic,
IsOversea = false,
};
private static readonly LaunchScheme ServerGlobalChannelOfficialSubChannelGoogle = new()
{
LauncherId = SdkStaticLauncherGlobalId,
Key = SdkStaticLauncherGlobalKey,
Channel = ChannelType.Official,
SubChannel = SubChannelType.Google,
IsOversea = false,
};
/// <summary>
/// 获取已知的启动方案
/// </summary>
/// <returns>已知的启动方案</returns>
public static List<LaunchScheme> GetKnownSchemes()
{
return new List<LaunchScheme>()
{
// 官服
ServerChineseChannelOfficialSubChannelOfficial,
ServerChineseChannelOfficialSubChannelNoTapTap,
// 渠道服
ServerChineseChannelBilibiliSubChannelDefault,
// 国际服
ServerGlobalChannelOfficialSubChannelDefault,
ServerGlobalChannelOfficialSubChannelEpic,
ServerGlobalChannelOfficialSubChannelGoogle,
};
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Immutable;
namespace Snap.Hutao.Service.Game;
@@ -10,70 +9,8 @@ namespace Snap.Hutao.Service.Game;
/// 启动方案
/// </summary>
[HighQuality]
internal sealed class LaunchScheme
internal sealed partial class LaunchScheme
{
// TODO: fix detection
/// <summary>
/// 已知的启动方案
/// </summary>
public static readonly ImmutableList<LaunchScheme> KnownSchemes = new List<LaunchScheme>()
{
// 官服
new()
{
LauncherId = "18",
Key = "eYd89JmJ",
Channel = ChannelType.Official,
SubChannel = SubChannelType.Official,
IsOversea = false,
},
new()
{
LauncherId = "18",
Key = "eYd89JmJ",
Channel = ChannelType.Official,
SubChannel = SubChannelType.NoTapTap,
IsOversea = false,
},
// 渠道服
new()
{
LauncherId = "17",
Key = "KAtdSsoQ",
Channel = ChannelType.Bili,
SubChannel = SubChannelType.Default,
IsOversea = false,
},
// 国际服
new()
{
LauncherId = "10",
Key = "gcStgarh",
Channel = ChannelType.Official,
SubChannel = SubChannelType.Default,
IsOversea = true,
},
new()
{
LauncherId = "10",
Key = "gcStgarh",
Channel = ChannelType.Official,
SubChannel = SubChannelType.Epic,
IsOversea = true,
},
new()
{
LauncherId = "10",
Key = "gcStgarh",
Channel = ChannelType.Official,
SubChannel = SubChannelType.Google,
IsOversea = true,
},
}.ToImmutableList();
/// <summary>
/// 显示名称
/// </summary>
@@ -104,7 +41,7 @@ internal sealed class LaunchScheme
/// <summary>
/// 启动器 Id
/// </summary>
public string LauncherId { get; private set; } = default!;
public int LauncherId { get; private set; }
/// <summary>
/// API Key
@@ -121,7 +58,7 @@ internal sealed class LaunchScheme
/// </summary>
/// <param name="multiChannel">多通道</param>
/// <returns>是否相等</returns>
public bool MultiChannelEqual(in MultiChannel multiChannel)
public bool MultiChannelEqual(in ChannelOptions multiChannel)
{
return Channel == multiChannel.Channel && SubChannel == multiChannel.SubChannel;
}

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.Service.Game;
/// 多通道
/// </summary>
[HighQuality]
internal readonly struct MultiChannel
internal readonly struct ChannelOptions
{
/// <summary>
/// 通道
@@ -21,6 +21,11 @@ internal readonly struct MultiChannel
/// </summary>
public readonly SubChannelType SubChannel;
/// <summary>
/// 是否为国际服
/// </summary>
public readonly bool IsOversea;
/// <summary>
/// 配置文件路径 当不为 null 时则存在文件读写问题
/// </summary>
@@ -31,12 +36,30 @@ internal readonly struct MultiChannel
/// </summary>
/// <param name="channel">通道</param>
/// <param name="subChannel">子通道</param>
/// <param name="isOversea">是否为国际服</param>
/// <param name="configFilePath">配置文件路径</param>
public MultiChannel(string? channel, string? subChannel, string? configFilePath = null)
public ChannelOptions(string? channel, string? subChannel, bool isOversea, string? configFilePath = null)
{
Channel = string.IsNullOrEmpty(channel) ? ChannelType.Default : Enum.Parse<ChannelType>(channel);
SubChannel = string.IsNullOrEmpty(subChannel) ? SubChannelType.Default : Enum.Parse<SubChannelType>(subChannel);
_ = Enum.TryParse(channel, out Channel);
_ = Enum.TryParse(subChannel, out SubChannel);
IsOversea = isOversea;
ConfigFilePath = configFilePath;
}
/// <summary>
/// 配置文件未找到
/// </summary>
/// <param name="isOversea">是否为国际服</param>
/// <param name="configFilePath">配置文件期望路径</param>
/// <returns>选项</returns>
public static ChannelOptions FileNotFound(bool isOversea, string configFilePath)
{
return new(null, null, isOversea, configFilePath);
}
/// <inheritdoc/>
public override string ToString()
{
return $"[ChannelType:{Channel}] [SubChannel:{SubChannel}] [IsOversea: {IsOversea}]";
}
}

View File

@@ -35,6 +35,7 @@
<ApplicationIcon>Assets\Logo.ico</ApplicationIcon>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<AppxBundlePlatforms>x64</AppxBundlePlatforms>
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
</PropertyGroup>
<!-- Included Files -->

View File

@@ -132,6 +132,7 @@
TextWrapping="NoWrap"/>
<StackPanel
Grid.Column="1"
Margin="0,0,-12,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock
@@ -142,7 +143,7 @@
Visibility="{Binding ElementName=DetailExpander, Path=IsExpanded, Converter={StaticResource BoolToVisibilityRevertConverter}}"/>
<shcp:PanelSelector
x:Name="ItemsPanelSelector"
Margin="6,0"
Margin="6,0,0,0"
Current="Grid"/>
</StackPanel>
</Grid>

View File

@@ -2,7 +2,6 @@
x:Class="Snap.Hutao.View.Page.AnnouncementPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:clw="using:CommunityToolkit.Labs.WinUI"
xmlns:cwu="using:CommunityToolkit.WinUI.UI"
xmlns:cwua="using:CommunityToolkit.WinUI.UI.Animations"
xmlns:cwub="using:CommunityToolkit.WinUI.UI.Behaviors"
@@ -16,10 +15,10 @@
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shv="using:Snap.Hutao.ViewModel"
xmlns:shvca="using:Snap.Hutao.View.Card"
xmlns:shvco="using:Snap.Hutao.View.Control"
d:DataContext="{d:DesignInstance shv:AnnouncementViewModel}"
xmlns:shvh="using:Snap.Hutao.ViewModel.Home"
d:DataContext="{d:DesignInstance shvh:AnnouncementViewModel}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<mxi:Interaction.Behaviors>
@@ -160,8 +159,8 @@
<TextBlock
Margin="16,16,16,0"
Style="{StaticResource TitleTextBlockStyle}"
Text="Greeting Text"/>
<TextBlock Margin="16,0,16,0" Text="账号邮箱@xx.com"/>
Text="{Binding GreetingText}"/>
<TextBlock Margin="16,0,16,0" Text="{Binding UserOptions.UserName}"/>
<cwucont:AdaptiveGridView
Margin="16,16,0,0"
HorizontalAlignment="Stretch"

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Control;
using Snap.Hutao.ViewModel;
using Snap.Hutao.ViewModel.Home;
namespace Snap.Hutao.View.Page;

View File

@@ -158,7 +158,7 @@
Header="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudSpiralAbyssActivityHeader}"
IsClickEnabled="True"/>
<clw:SettingsCard
Command="{Binding HutaoCloudViewModel.NavigateToAfdianSKuCommand}"
Command="{Binding HutaoCloudViewModel.NavigateToAfdianSkuCommand}"
Description="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudAfdianPurchaseDescription}"
Header="{shcm:ResourceString Name=ViewPageGachaLogHutaoCloudAfdianPurchaseHeader}"
IsClickEnabled="True"/>

View File

@@ -1,37 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
namespace Snap.Hutao.ViewModel;
/// <summary>
/// 公告视图模型
/// </summary>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Scoped)]
internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
{
private readonly IAnnouncementService announcementService;
private AnnouncementWrapper? announcement;
/// <summary>
/// 公告
/// </summary>
public AnnouncementWrapper? Announcement { get => announcement; set => SetProperty(ref announcement, value); }
/// <inheritdoc/>
protected override async Task OpenUIAsync()
{
try
{
Announcement = await announcementService.GetAnnouncementsAsync(CancellationToken).ConfigureAwait(true);
}
catch (OperationCanceledException)
{
}
}
}

View File

@@ -133,8 +133,8 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
.Navigate<View.Page.SpiralAbyssRecordPage>(INavigationAwaiter.Default);
}
[Command("NavigateToAfdianSKuCommand")]
private async Task NavigateToAfdianSKuAsync()
[Command("NavigateToAfdianSkuCommand")]
private async Task NavigateToAfdianSkuAsync()
{
await Windows.System.Launcher.LaunchUriAsync(new(@"ms-windows-store://pdp/?productid=9PH4NXJ2JN52"));
}

View File

@@ -47,7 +47,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
/// <summary>
/// 已知的服务器方案
/// </summary>
public List<LaunchScheme> KnownSchemes { get => LaunchScheme.KnownSchemes.ToList(); }
public List<LaunchScheme> KnownSchemes { get => LaunchScheme.GetKnownSchemes(); }
/// <summary>
/// 当前选择的服务器方案
@@ -108,22 +108,24 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
{
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
MultiChannel multi = gameService.GetMultiChannel();
if (string.IsNullOrEmpty(multi.ConfigFilePath))
ChannelOptions options = gameService.GetChannelOptions();
if (string.IsNullOrEmpty(options.ConfigFilePath))
{
try
{
SelectedScheme = KnownSchemes.Single(scheme => scheme.MultiChannelEqual(multi));
SelectedScheme = KnownSchemes
.Where(scheme => scheme.IsOversea == options.IsOversea)
.Single(scheme => scheme.MultiChannelEqual(options));
}
catch (InvalidOperationException)
{
// 后台收集
throw new NotSupportedException($"不支持的 MultiChannel: [ChannelType:{multi.Channel}] [SubChannel:{multi.SubChannel}]");
// 后台收集
throw new NotSupportedException($"不支持的 MultiChannel: {options}");
}
}
else
{
infoBarService.Warning(string.Format(SH.ViewModelLaunchGameMultiChannelReadFail, multi.ConfigFilePath));
infoBarService.Warning(string.Format(SH.ViewModelLaunchGameMultiChannelReadFail, options.ConfigFilePath));
}
ObservableCollection<GameAccount> accounts = gameService.GameAccountCollection;

View File

@@ -0,0 +1,84 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Web.Hoyolab.Hk4e.Common.Announcement;
namespace Snap.Hutao.ViewModel.Home;
/// <summary>
/// 公告视图模型
/// </summary>
[HighQuality]
[ConstructorGenerated]
[Injection(InjectAs.Scoped)]
internal sealed partial class AnnouncementViewModel : Abstraction.ViewModel
{
private readonly IAnnouncementService announcementService;
private readonly HutaoUserOptions hutaoUserOptions;
private readonly ITaskContext taskContext;
private AnnouncementWrapper? announcement;
private string greetingText = SH.ViewPageHomeGreetingTextDefault;
/// <summary>
/// 公告
/// </summary>
public AnnouncementWrapper? Announcement { get => announcement; set => SetProperty(ref announcement, value); }
/// <summary>
/// 用户选项
/// </summary>
public HutaoUserOptions UserOptions { get => hutaoUserOptions; }
/// <summary>
/// 欢迎语
/// </summary>
public string GreetingText { get => greetingText; set => SetProperty(ref greetingText, value); }
/// <inheritdoc/>
protected override async Task OpenUIAsync()
{
try
{
AnnouncementWrapper announcement = await announcementService.GetAnnouncementsAsync(CancellationToken).ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
Announcement = announcement;
UpdateGreetingText();
}
catch (OperationCanceledException)
{
}
}
private void UpdateGreetingText()
{
// TODO avatar birthday override.
int rand = Random.Shared.Next(0, 1000);
if (rand >= 0 && rand < 6)
{
GreetingText = SH.ViewPageHomeGreetingTextEasterEgg;
}
else if (rand >= 6 && rand < 57)
{
// TODO: retrieve days
GreetingText = string.Format(SH.ViewPageHomeGreetingTextEpic1, 0);
}
else if (rand >= 57 && rand < 1000)
{
rand = Random.Shared.Next(0, 2);
if (rand == 0)
{
// TODO: impl game launch times
GreetingText = string.Format(SH.ViewPageHomeGreetingTextCommon1, 0);
}
else if (rand == 1)
{
GreetingText = string.Format(SH.ViewPageHomeGreetingTextCommon2, LocalSetting.Get(SettingKeys.LaunchTimes, 0));
}
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.Home;
/// <summary>
/// 欢迎语等级
/// </summary>
internal enum GreetingTier
{
/// <summary>
/// 默认
/// </summary>
Default,
/// <summary>
/// 普通
/// </summary>
Common,
/// <summary>
/// 稀有
/// </summary>
Rare,
/// <summary>
/// 史诗
/// </summary>
Epic,
/// <summary>
/// 传说
/// </summary>
Legendary,
}

View File

@@ -44,7 +44,11 @@ internal static class StructMarshal
public static unsafe Windows.UI.Color Color(uint value)
{
Unsafe.SkipInit(out Windows.UI.Color color);
*(uint*)&color = BinaryPrimitives.ReverseEndianness(value);
// *(uint*)&color = BinaryPrimitives.ReverseEndianness(value);
// How .NET store struct in BE host?
Span<byte> colorSpan = new(&color, sizeof(uint));
BinaryPrimitives.WriteUInt32BigEndian(colorSpan, value);
return color;
}