Compare commits

..

24 Commits

Author SHA1 Message Date
DismissedLight
7c03ce3486 1.7.11 hotfix package 2023-10-18 19:54:55 +08:00
DismissedLight
83e187ea9e fix launch args 2023-10-18 19:54:55 +08:00
Lightczx
d86232f413 fix #1028 2023-10-18 19:54:55 +08:00
Masterain
4e6691ac51 Update FUNDING.yml 2023-10-18 19:54:55 +08:00
Masterain
84ad39b192 Update FUNDING.yml 2023-10-18 19:54:55 +08:00
DismissedLight
ce50fc41e0 1.7.10 package 2023-10-17 22:14:50 +08:00
Masterain
1d71048f56 New Crowdin updates (#1019) 2023-10-17 22:14:50 +08:00
DismissedLight
08cf823156 auto select existed account when detecting 2023-10-17 22:14:50 +08:00
Lightczx
cca65635a6 investigate into CoreWindow 2023-10-17 14:52:38 +08:00
Lightczx
7caeb17788 use reference in picker factory 2023-10-17 14:51:41 +08:00
Lightczx
b11b90e9f1 re-impl #1020 2023-10-16 11:03:41 +08:00
DismissedLight
58643a60b5 fix bugs 2023-10-15 16:14:22 +08:00
DismissedLight
a29b487c26 impl #1020 2023-10-15 16:14:13 +08:00
DismissedLight
1bd6023e0a fix issues 2023-10-15 12:30:44 +08:00
DismissedLight
579173d464 webview lifetime 2023-10-14 19:29:28 +08:00
DismissedLight
9ed53e8c34 fix spiralabyss damage rank empty crash 2023-10-14 17:26:06 +08:00
DismissedLight
830556a043 fix ICoreWebView2_13 not supported 2023-10-14 00:12:14 +08:00
DismissedLight
7ba27e184f ignore invalid launch schemes 2023-10-13 23:17:08 +08:00
DismissedLight
9aa6a2b57b fix invalid geetest url crash 2023-10-13 22:53:32 +08:00
DismissedLight
5773902f4a fix #1001 2023-10-13 22:36:51 +08:00
DismissedLight
06c5bcad3e fix #1013 #1012 #1011 2023-10-13 21:42:36 +08:00
Lightczx
c0165c57fd support Send on sync context 2023-10-13 15:27:46 +08:00
DismissedLight
4e57520115 Update AvatarIds.cs 2023-10-12 22:55:35 +08:00
Masterain
17c3480dae New Crowdin updates (#1006) 2023-10-12 22:10:54 +08:00
48 changed files with 725 additions and 457 deletions

4
.github/FUNDING.yml vendored
View File

@@ -1,8 +1,8 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: [DGP-Studio]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
open_collective: snaphutao
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry

View File

@@ -1,6 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json",
"allowMarshaling": true,
"useSafeHandles": false,
"emitSingleFile": true
"useSafeHandles": false
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Runtime.InteropServices;
using Windows.UI.Core;
using Windows.Win32.Foundation;
namespace Snap.Hutao.Win32;
internal static class UnsafePInvoke
{
private enum WINDOW_TYPE : uint
{
IMMERSIVE_BODY,
IMMERSIVE_DOCK,
IMMERSIVE_HOSTED,
IMMERSIVE_TEST,
IMMERSIVE_BODY_ACTIVE,
IMMERSIVE_DOCK_ACTIVE,
NOT_IMMERSIVE,
}
[DllImport("Windows.UI.dll", CharSet = CharSet.None, EntryPoint = "#1500", ExactSpelling = false, SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern HRESULT PrivateCreateCoreWindow(WINDOW_TYPE WindowType, PWSTR pWindowTitle, int x, int y, uint uWidth, uint uHeight, uint dwAttributes, HWND hOwnerWindow, Guid riid, out nint ppv);
public static unsafe CoreWindow PrivateCreateCoreWindow(string title, HWND hOwnerWindow)
{
fixed(char* pTitle = title)
{
PrivateCreateCoreWindow(WINDOW_TYPE.NOT_IMMERSIVE, pTitle, 0, 0, 400, 400, 0, hOwnerWindow, typeof(ICoreWindow).GUID, out nint thisPtr);
return CoreWindow.FromAbi(thisPtr);
}
}
}

View File

@@ -10,8 +10,8 @@ internal struct ContentDialogHideToken : IDisposable, IAsyncDisposable
private readonly ContentDialog contentDialog;
private readonly ITaskContext taskContext;
private bool disposed = false;
private bool disposing = false;
private bool disposed = false;
public ContentDialogHideToken(ContentDialog contentDialog, ITaskContext taskContext)
{

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Windows.ApplicationModel.Resources;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service;
using System.Globalization;

View File

@@ -29,11 +29,14 @@ internal sealed partial class ExceptionRecorder
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
serviceProvider
ValueTask<string?> task = serviceProvider
.GetRequiredService<Web.Hutao.Log.HomaLogUploadClient>()
.UploadLogAsync(e.Exception)
.GetAwaiter()
.GetResult();
.UploadLogAsync(e.Exception);
if (!task.IsCompleted)
{
task.GetAwaiter().GetResult();
}
logger.LogError("未经处理的全局异常:\r\n{Detail}", ExceptionFormat.Format(e.Exception));
}

View File

@@ -15,7 +15,7 @@ internal sealed class RuntimeEnvironmentException : Exception
/// </summary>
/// <param name="message">消息</param>
/// <param name="innerException">内部错误</param>
public RuntimeEnvironmentException(string message, Exception innerException)
public RuntimeEnvironmentException(string message, Exception? innerException)
: base($"{message}\n{innerException.Message}", innerException)
{
}

View File

@@ -14,27 +14,27 @@ namespace Snap.Hutao.Core.ExceptionService;
[System.Diagnostics.StackTraceHidden]
internal static class ThrowHelper
{
/// <summary>
/// 操作取消
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="OperationCanceledException">操作取消异常</exception>
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)
public static ArgumentException Argument(string message, string? paramName)
{
throw new OperationCanceledException(message, inner);
throw new ArgumentException(message, paramName);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static DatabaseCorruptedException DatabaseCorrupted(string message, Exception? inner)
{
throw new DatabaseCorruptedException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static GameFileOperationException GameFileOperation(string message, Exception? inner)
{
throw new GameFileOperationException(message, inner);
}
/// <summary>
/// 无效操作
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="InvalidOperationException">无效操作异常</exception>
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static InvalidOperationException InvalidOperation(string message, Exception? inner = default)
@@ -42,71 +42,38 @@ internal static class ThrowHelper
throw new InvalidOperationException(message, inner);
}
/// <summary>
/// 游戏文件操作失败
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="GameFileOperationException">文件操作失败</exception>
public static GameFileOperationException GameFileOperation(string message, Exception inner)
{
throw new GameFileOperationException(message, inner);
}
/// <summary>
/// 包转换错误
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="PackageConvertException">包转换错误异常</exception>
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static PackageConvertException PackageConvert(string message, Exception inner)
{
throw new PackageConvertException(message, inner);
}
/// <summary>
/// 用户数据损坏
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="UserdataCorruptedException">数据损坏</exception>
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static UserdataCorruptedException UserdataCorrupted(string message, Exception inner)
{
throw new UserdataCorruptedException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static DatabaseCorruptedException DatabaseCorrupted(string message, Exception inner)
{
throw new DatabaseCorruptedException(message, inner);
}
/// <summary>
/// 运行环境异常
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="RuntimeEnvironmentException">环境异常</exception>
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static RuntimeEnvironmentException RuntimeEnvironment(string message, Exception inner)
{
throw new RuntimeEnvironmentException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static NotSupportedException NotSupported()
{
throw new NotSupportedException();
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)
{
throw new OperationCanceledException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static PackageConvertException PackageConvert(string message, Exception? inner)
{
throw new PackageConvertException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static RuntimeEnvironmentException RuntimeEnvironment(string message, Exception? inner)
{
throw new RuntimeEnvironmentException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static UserdataCorruptedException UserdataCorrupted(string message, Exception? inner)
{
throw new UserdataCorruptedException(message, inner);
}
}

View File

@@ -49,7 +49,14 @@ internal sealed partial class ShellLinkInterop : IShellLinkInterop
string target = Path.Combine(desktop, $"{SH.AppNameAndVersion.Format(runtimeOptions.Version)}.lnk");
IPersistFile persistFile = (IPersistFile)shellLink;
persistFile.Save(target, false);
try
{
persistFile.Save(target, false);
}
catch (UnauthorizedAccessException)
{
return false;
}
return true;
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Dispatching;
using System.Runtime.ExceptionServices;
namespace Snap.Hutao.Core.Threading;
@@ -18,15 +19,35 @@ internal static class DispatcherQueueExtension
/// <param name="action">执行的回调</param>
public static void Invoke(this DispatcherQueue dispatcherQueue, Action action)
{
using (ManualResetEventSlim blockEvent = new())
if (dispatcherQueue.HasThreadAccess)
{
action();
return;
}
ExceptionDispatchInfo? exceptionDispatchInfo = null;
using (ManualResetEventSlim blockEvent = new(false))
{
dispatcherQueue.TryEnqueue(() =>
{
action();
blockEvent.Set();
try
{
action();
}
catch (Exception ex)
{
ExceptionDispatchInfo.Capture(ex);
}
finally
{
blockEvent.Set();
}
});
blockEvent.Wait();
#pragma warning disable CA1508
exceptionDispatchInfo?.Throw();
#pragma warning restore CA1508
}
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Dispatching;
namespace Snap.Hutao.Core.Threading;
internal sealed class DispatcherQueueSynchronizationContextSendSupport : SynchronizationContext
{
private readonly DispatcherQueue dispatcherQueue;
public DispatcherQueueSynchronizationContextSendSupport(DispatcherQueue dispatcherQueue)
{
this.dispatcherQueue = dispatcherQueue;
}
public override void Post(SendOrPostCallback d, object? state)
{
ArgumentNullException.ThrowIfNull(d);
dispatcherQueue.TryEnqueue(() => d(state));
}
public override void Send(SendOrPostCallback d, object? state)
{
ArgumentNullException.ThrowIfNull(d);
dispatcherQueue.Invoke(() => d(state));
}
public override SynchronizationContext CreateCopy()
{
return new DispatcherQueueSynchronizationContextSendSupport(dispatcherQueue);
}
}

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.Threading;
[Injection(InjectAs.Singleton, typeof(ITaskContext))]
internal sealed class TaskContext : ITaskContext
{
private readonly DispatcherQueueSynchronizationContext dispatcherQueueSynchronizationContext;
private readonly DispatcherQueueSynchronizationContextSendSupport synchronizationContext;
private readonly DispatcherQueue dispatcherQueue;
/// <summary>
@@ -20,8 +20,8 @@ internal sealed class TaskContext : ITaskContext
public TaskContext()
{
dispatcherQueue = DispatcherQueue.GetForCurrentThread();
dispatcherQueueSynchronizationContext = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(dispatcherQueueSynchronizationContext);
synchronizationContext = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
}
/// <inheritdoc/>
@@ -39,18 +39,11 @@ internal sealed class TaskContext : ITaskContext
/// <inheritdoc/>
public void InvokeOnMainThread(Action action)
{
if (dispatcherQueue.HasThreadAccess)
{
action();
}
else
{
dispatcherQueue.Invoke(action);
}
dispatcherQueue.Invoke(action);
}
public IProgress<T> CreateProgressForMainThread<T>(Action<T> handler)
{
return new DispatcherQueueProgress<T>(handler, dispatcherQueueSynchronizationContext);
return new DispatcherQueueProgress<T>(handler, synchronizationContext);
}
}

View File

@@ -8,9 +8,11 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service;
using Snap.Hutao.Win32;
using System.IO;
using Windows.Graphics;
using Windows.UI;
using Windows.UI.Core;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.UI.WindowsAndMessaging;
@@ -55,6 +57,7 @@ internal sealed class WindowController
ExtendsContentIntoTitleBar();
RecoverOrInitWindowSize();
CoreWindow coreWindow = UnsafePInvoke.PrivateCreateCoreWindow("Snap Hutao CoreWindow", options.Hwnd);
UpdateImmersiveDarkMode(options.TitleBar, default!);
// appWindow.Show(true);

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT license.
using Snap.Hutao.Core;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Factory.Abstraction;
using Windows.Storage.Pickers;
using WinRT.Interop;
@@ -16,7 +18,7 @@ internal sealed partial class PickerFactory : IPickerFactory
{
private const string AnyType = "*";
private readonly MainWindow mainWindow;
private readonly ICurrentWindowReference currentWindow;
/// <inheritdoc/>
public FileOpenPicker GetFileOpenPicker(PickerLocationId location, string commitButton, params string[] fileTypes)
@@ -78,7 +80,10 @@ internal sealed partial class PickerFactory : IPickerFactory
{
// Create a folder picker.
T picker = new();
InitializeWithWindow.Initialize(picker, mainWindow.WindowOptions.Hwnd);
if (currentWindow.Window is IWindowOptionsSource optionsSource)
{
InitializeWithWindow.Initialize(picker, optionsSource.WindowOptions.Hwnd);
}
return picker;
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.InteropServices;
namespace Snap.Hutao.Model.InterChange.GachaLog;
/// <summary>
@@ -49,20 +51,35 @@ internal sealed class UIGF
/// <summary>
/// 列表物品是否正常
/// </summary>
/// <param name="itemId">首个出错的Id</param>
/// <param name="id">首个出错的Id</param>
/// <returns>是否正常</returns>
public bool IsMajor2Minor2OrLowerListValid([NotNullWhen(false)] out long itemId)
public bool IsMajor2Minor2OrLowerListValid([NotNullWhen(false)] out long id)
{
foreach (UIGFItem item in List)
foreach (ref readonly UIGFItem item in CollectionsMarshal.AsSpan(List))
{
if (item.ItemType != SH.ModelInterchangeUIGFItemTypeAvatar && item.ItemType != SH.ModelInterchangeUIGFItemTypeWeapon)
{
itemId = item.Id;
id = item.Id;
return false;
}
}
itemId = 0;
id = 0;
return true;
}
public bool IsMajor2Minor3OrHigherListValid([NotNullWhen(false)] out long id)
{
foreach (ref readonly UIGFItem item in CollectionsMarshal.AsSpan(List))
{
if (string.IsNullOrEmpty(item.ItemId))
{
id = item.Id;
return false;
}
}
id = 0;
return true;
}
}

View File

@@ -91,6 +91,8 @@ internal static class AvatarIds
public static readonly AvatarId Freminet = 10000085;
public static readonly AvatarId Wriothesley = 10000086;
public static readonly AvatarId Neuvillette = 10000087;
public static readonly AvatarId Charlotte = 10000088;
public static readonly AvatarId Furina = 10000089;
/// <summary>
/// 检查该角色是否为主角

View File

@@ -12,7 +12,7 @@
<Identity
Name="60568DGPStudio.SnapHutao"
Publisher="CN=35C8E923-85DF-49A7-9172-B39DC6312C52"
Version="1.7.9.0" />
Version="1.7.11.0" />
<Properties>
<DisplayName>Snap Hutao</DisplayName>

View File

@@ -2418,6 +2418,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级 的本地化字符串。
/// </summary>
internal static string ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed {
get {
return ResourceManager.GetString("ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed", resourceCulture);
}
}
/// <summary>
/// 查找类似 养成计划 的本地化字符串。
/// </summary>
@@ -3777,6 +3786,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 保存游戏路径失败 的本地化字符串。
/// </summary>
internal static string ViewModelSettingSetGamePathDatabaseFailedTitle {
get {
return ResourceManager.GetString("ViewModelSettingSetGamePathDatabaseFailedTitle", resourceCulture);
}
}
/// <summary>
/// 查找类似 用户 [{0}] 添加成功 的本地化字符串。
/// </summary>

View File

@@ -959,6 +959,9 @@
<data name="ViewControlStatisticsSegmentedItemContentStatistics" xml:space="preserve">
<value>Statistics</value>
</data>
<data name="ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed" xml:space="preserve">
<value>The current version of WebView2 does not support management configuration, continue to use may cause abnormalities, please upgrade as soon as possible</value>
</data>
<data name="ViewCultivationHeader" xml:space="preserve">
<value>Dev Plan</value>
</data>
@@ -1412,6 +1415,9 @@
<data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve">
<value>Set data directory successfully. Restart to apply changes.</value>
</data>
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
<value>Failed to save game path</value>
</data>
<data name="ViewModelUserAdded" xml:space="preserve">
<value>User [{0}] added successfully</value>
</data>
@@ -1938,7 +1944,7 @@
<value>Server</value>
</data>
<data name="ViewPageLaunchGameSwitchSchemeWarning" xml:space="preserve">
<value>版本更新前需要提前转换至与启动器匹配的服务器</value>
<value>You need to convert to a server that matches the launcher before updating the version</value>
</data>
<data name="ViewPageLaunchGameUnlockFpsDescription" xml:space="preserve">
<value>Please turn off V-Sync in the game settings. You may need a high-performance graphic card to support a high frame rate limit.</value>

View File

@@ -169,10 +169,10 @@
<value>権限不足のため、一時ファイルを作成できませんでした</value>
</data>
<data name="CoreJumpListHelperLaunchGameItemDisplayName" xml:space="preserve">
<value>ゲームスタート</value>
<value>ゲームランチャー</value>
</data>
<data name="CoreScheduleTaskHelperDailyNoteRefreshTaskDescription" xml:space="preserve">
<value>胡桃がリアルタイムノートを更新しています | 編集や削除をしないでください</value>
<value>胡桃がリアルタイムノートを更新するために使用するタスクです。編集や削除をしないでください!</value>
</data>
<data name="CoreThreadingSemaphoreSlimDisposed" xml:space="preserve">
<value>セマフォが解放され、操作がキャンセルされました</value>
@@ -211,10 +211,10 @@
<value>{0:f2} 回目</value>
</data>
<data name="ModelBindingGachaTypedWishSummaryMaxOrangePullFormat" xml:space="preserve">
<value>最も引けなかった回数: {0} 回</value>
<value>最も遅い回数: {0} 回</value>
</data>
<data name="ModelBindingGachaTypedWishSummaryMinOrangePullFormat" xml:space="preserve">
<value>最も早く引けた回数: {0} 回</value>
<value>最も早回数: {0} 回</value>
</data>
<data name="ModelBindingGachaWishBaseTotalCountFormat" xml:space="preserve">
<value>{0} 回目</value>
@@ -295,7 +295,7 @@
<comment>Need EXACT same string in game</comment>
</data>
<data name="ModelIntrinsicBodyTypeBoy" xml:space="preserve">
<value>少年</value>
<value>ショタ</value>
</data>
<data name="ModelIntrinsicBodyTypeGirl" xml:space="preserve">
<value>少女</value>
@@ -426,58 +426,58 @@
<value>この階層のボス</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupA" xml:space="preserve">
<value>A组不同的组同时在场各自分波独立</value>
<value>グループA: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupAWave1" xml:space="preserve">
<value>A组第一波不同的组同时在场各自分波独立</value>
<value>グループA ウェーブ1: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupAWave2" xml:space="preserve">
<value>A组第二波不同的组同时在场各自分波独立</value>
<value>グループA ウェーブ2: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupAWave3" xml:space="preserve">
<value>A组第三波不同的组同时在场各自分波独立</value>
<value>グループA ウェーブ3: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupB" xml:space="preserve">
<value>B组不同的组同时在场各自分波独立</value>
<value>グループB: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupBWave1" xml:space="preserve">
<value>B组第一波不同的组同时在场各自分波独立</value>
<value>グループB ウェーブ1: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupBWave2" xml:space="preserve">
<value>B组第二波不同的组同时在场各自分波独立</value>
<value>グループB ウェーブ2: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupBWave3" xml:space="preserve">
<value>B组第三波不同的组同时在场各自分波独立</value>
<value>グループB ウェーブ3: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupC" xml:space="preserve">
<value>C组不同的组同时在场各自分波独立</value>
<value>グループC: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupCWave1" xml:space="preserve">
<value>C组第一波不同的组同时在场各自分波独立</value>
<value>グループC ウェーブ1: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupCWave2" xml:space="preserve">
<value>C组第二波不同的组同时在场各自分波独立</value>
<value>グループC ウェーブ2: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupCWave3" xml:space="preserve">
<value>C组第三波不同的组同时在场各自分波独立</value>
<value>グループC ウェーブ3: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupD" xml:space="preserve">
<value>D组不同的组同时在场各自分波独立</value>
<value>グループD: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupDWave1" xml:space="preserve">
<value>D组第一波不同的组同时在场各自分波独立</value>
<value>グループD ウェーブ1: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupDWave2" xml:space="preserve">
<value>D组第二波不同的组同时在场各自分波独立</value>
<value>グループD ウェーブ2: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeGroupDWave3" xml:space="preserve">
<value>D组第三波不同的组同时在场各自分波独立</value>
<value>グループD ウェーブ3: 異なるグループが同時に存在し、それぞれ独立したウェーブがある</value>
</data>
<data name="ModelMetadataTowerWaveTypeIndependent" xml:space="preserve">
<value>与其他怪物独立</value>
<value>他の敵とは独立</value>
</data>
<data name="ModelMetadataTowerWaveTypeSuppressed" xml:space="preserve">
<value>暂时没有分波信息</value>
<value>ウェーブ情報がありません</value>
</data>
<data name="ModelMetadataTowerWaveTypeWave1" xml:space="preserve">
<value>ウェーブ 1: すべての敵を倒すと、次のウェーブの敵が出現する。</value>
@@ -498,13 +498,13 @@
<value>キャラクターラインナップを更新する</value>
</data>
<data name="ModelNameValueDefaultName" xml:space="preserve">
<value>データありません</value>
<value>データありません</value>
</data>
<data name="ModelWeaponAffixFormat" xml:space="preserve">
<value>精錬ランク{0}</value>
</data>
<data name="MustSelectUserAndUid" xml:space="preserve">
<value>必须先选择一个用户与角色</value>
<value>ユーザーとUIDを選択する必要があります</value>
</data>
<data name="ServiceAchievementImportResultFormat" xml:space="preserve">
<value>{0} つのアチーブメントを追加 | {1} つのアチーブメントを更新 |{2} つのアチーブメントを削除</value>
@@ -645,10 +645,10 @@
<value>育成計画を複数選択しています</value>
</data>
<data name="ServiceDailyNoteNotifierActionLaunchGameButton" xml:space="preserve">
<value>スタート</value>
<value>ゲームを起動</value>
</data>
<data name="ServiceDailyNoteNotifierActionLaunchGameDismiss" xml:space="preserve">
<value>わかりました</value>
<value>了解</value>
</data>
<data name="ServiceDailyNoteNotifierAttribution" xml:space="preserve">
<value>リクエストエラー</value>
@@ -675,7 +675,7 @@
<value>現在の洞天宝銭:{0}</value>
</data>
<data name="ServiceDailyNoteNotifierMultiValueReached" xml:space="preserve">
<value>多个提醒项达到设定值</value>
<value>複数の通知項目が設定値に達しました</value>
</data>
<data name="ServiceDailyNoteNotifierResin" xml:space="preserve">
<value>天然樹脂</value>
@@ -690,7 +690,7 @@
<value>参量物質変化器</value>
</data>
<data name="ServiceDailyNoteNotifierTransformerAdaptiveHint" xml:space="preserve">
<value>まもなく完成</value>
<value>準備完了</value>
</data>
<data name="ServiceDailyNoteNotifierTransformerHint" xml:space="preserve">
<value>参量物質変化器は使用可能</value>
@@ -720,7 +720,7 @@
<value>無効なアイテムが含まれてます、Id{0}</value>
</data>
<data name="ServiceGachaLogUrlProviderAuthkeyRequestFailed" xml:space="preserve">
<value>请求验证密钥失败</value>
<value>認証キーのリクエストに失敗しました。</value>
</data>
<data name="ServiceGachaLogUrlProviderCachePathInvalid" xml:space="preserve">
<value>原神のパスの書き方に誤りがあるか、またはパスを正しく設定されていません。</value>
@@ -744,7 +744,7 @@
<value>Item Id{0} はサポートしていません</value>
</data>
<data name="ServiceGachaUIGFImportLanguageNotMatch" xml:space="preserve">
<value>UIGF ファイルの言語:{0} と胡桃の設定言語:{1} マッチングしません。言語を切り替えて再度試してください</value>
<value>UIGF ファイルの言語:{0} と胡桃の設定言語:{1} マッチングしません。言語を切り替えて再度試してください</value>
</data>
<data name="ServiceGameDetectGameAccountMultiMatched" xml:space="preserve">
<value>一致するアカウントが複数見つかりました。重複しているアカウントを削除してください。</value>
@@ -753,25 +753,25 @@
<value>ゲームのリソース情報を確認</value>
</data>
<data name="ServiceGameFileOperationExceptionMessage" xml:space="preserve">
<value>游戏文件操作失败:{0}</value>
<value>ゲームファイルの操作に失敗しました。: {0}</value>
</data>
<data name="ServiceGameLaunchPhaseProcessExited" xml:space="preserve">
<value>ゲームプロセスが終了した</value>
<value>ゲームプロセスが終了しました</value>
</data>
<data name="ServiceGameLaunchPhaseProcessInitializing" xml:space="preserve">
<value>ゲームプロセス初期化してい</value>
<value>ゲームプロセス初期化しています</value>
</data>
<data name="ServiceGameLaunchPhaseProcessStarted" xml:space="preserve">
<value>ゲームプロセスが生成された</value>
<value>ゲームプロセスが生成されました</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockFpsFailed" xml:space="preserve">
<value>FPS上限解除失敗、プロセス終了させる</value>
<value>FPS上限解除失敗しました、プロセス終了します</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockFpsSucceed" xml:space="preserve">
<value>FPS上限解除成功</value>
</data>
<data name="ServiceGameLaunchPhaseUnlockingFps" xml:space="preserve">
<value>FPS上限解除を試み</value>
<value>FPS上限解除を試みています</value>
</data>
<data name="ServiceGameLaunchPhaseWaitingProcessExit" xml:space="preserve">
<value>プロセスが終了するまで待機中</value>
@@ -783,16 +783,16 @@
<value>Unity ログファイルが見つかりません</value>
</data>
<data name="ServiceGameLocatorUnityLogGamePathNotFound" xml:space="preserve">
<value>Unity ログファイル内にゲームパスが見つかりません</value>
<value>Unity ログファイル内にゲームパスが見つかりません</value>
</data>
<data name="ServiceGamePackageConvertMoveFileBackupFormat" xml:space="preserve">
<value>バックアップ:{0}</value>
</data>
<data name="ServiceGamePackageConvertMoveFileRenameFormat" xml:space="preserve">
<value>リネーム:{0} を:{1} </value>
<value>リネーム:{0} を:{1} </value>
</data>
<data name="ServiceGamePackageConvertMoveFileRestoreFormat" xml:space="preserve">
<value>復元する{0}</value>
<value>置換{0}</value>
</data>
<data name="ServiceGamePackageRenameDataFolderFailed" xml:space="preserve">
<value>データフォルダの名前を変更できませんでした</value>
@@ -804,13 +804,13 @@
<value>Package Versionを取得できません</value>
</data>
<data name="ServiceGamePackageRequestScatteredFileFailed" xml:space="preserve">
<value>下载客户端文件失败:{0}</value>
<value>クライアントファイルのダウンロードに失敗しました。: {0}</value>
</data>
<data name="ServiceGamePathLocateFailed" xml:space="preserve">
<value>ゲームパスが見つかりません、設定にて変更してください</value>
<value>ゲームパスが見つかりません、設定変更してください</value>
</data>
<data name="ServiceGameRegisteryInteropLongPathsDisabled" xml:space="preserve">
<value>未开启长路径功能,无法设置注册表键值</value>
<value>長いパスのサポートがオフになっているため、レジストリキーを編集できません。</value>
</data>
<data name="ServiceGameRegisteryInteropPowershellNotFound" xml:space="preserve">
<value>PowerShellのインストールディレクトリが見つかりません</value>
@@ -822,22 +822,22 @@
<value>設定ファイルを読み取れない、または保存できませんでした。管理者モードで再試行してください。</value>
</data>
<data name="ServiceGameUnlockerFindModuleNoModuleFound" xml:space="preserve">
<value>在查找必要的模块时遇到问题:无法读取任何模块,可能是保护驱动已经加载完成,请重试</value>
<value>必要なモジュールの読み込みに失敗しました: モジュールの読み込みが出来ません。保護ドライバが読み込まれている可能性があります。もう一度お試しください。</value>
</data>
<data name="ServiceGameUnlockerFindModuleTimeLimitExeeded" xml:space="preserve">
<value>在查找必要的模块时遇到问题:查找模块超时,请重试</value>
<value>必要なモジュールの検索時にエラーが発生しました: タイムアウトしました。もう一度お試しください。</value>
</data>
<data name="ServiceGameUnlockerInterestedPatternNotFound" xml:space="preserve">
<value>在匹配内存时遇到问题:无法匹配到期望的内容</value>
<value>メモリパターンマッチングエラー: 予期しない値です。</value>
</data>
<data name="ServiceGameUnlockerReadModuleMemoryCopyVirtualMemoryFailed" xml:space="preserve">
<value>在读取必要的模块内存时遇到问题:无法将模块内存复制到指定位置</value>
<value>メモリへのモジュールの読み込みに失敗しました: 指定されたメモリ位置にモジュールをコピーできません。</value>
</data>
<data name="ServiceGameUnlockerReadProcessMemoryPointerAddressFailed" xml:space="preserve">
<value>在读取游戏进程内存时遇到问题:无法读取到指定地址的有效值</value>
<value>ゲームプロセスのメモリの読み取りに失敗しました: 指定されたアドレスで有効な値を読み取れませんでした。</value>
</data>
<data name="ServiceHutaoUserGachaLogExpiredAt" xml:space="preserve">
<value>祈願履歴のアップロード期限は\n{0:yyyy.MM.dd HH:mm:ss}</value>
<value>祈願履歴のアップロード期限は\n{0:yyyy.MM.dd HH:mm:ss}です。</value>
</data>
<data name="ServiceMetadataFileNotFound" xml:space="preserve">
<value>キャッシュされたメタデータファイルが見つかりませんでした</value>
@@ -846,34 +846,34 @@
<value>メタデータサービスが初期化されていないか、または初期化できませんでした</value>
</data>
<data name="ServiceMetadataParseFailed" xml:space="preserve">
<value>メタデータの検証ファイルを解析できなかった</value>
<value>メタデータの検証ファイルを解析できませんでした。</value>
</data>
<data name="ServiceMetadataRequestFailed" xml:space="preserve">
<value>メタデータの検証ファイルダウンロードできなかった</value>
<value>メタデータの検証ファイルダウンロードに失敗しました。</value>
</data>
<data name="ServiceMetadataVersionNotSupported" xml:space="preserve">
<value>胡桃のバージョンが古すぎるため、アップデートを推奨します。</value>
</data>
<data name="ServiceSignInClaimRewardFailedFormat" xml:space="preserve">
<value>ログインボーナスを獲得できなかった、{0}</value>
<value>ログインボーナスを獲得できませんでした。 {0}</value>
</data>
<data name="ServiceSignInInfoRequestFailed" xml:space="preserve">
<value>获取签到次数失败</value>
<value>サインイン日数の取得に失敗しました。</value>
</data>
<data name="ServiceSignInRewardListRequestFailed" xml:space="preserve">
<value>获取奖励列表失败</value>
<value>ログイン報酬リストの取得に失敗しました。</value>
</data>
<data name="ServiceSignInRiskVerificationFailed" xml:space="preserve">
<value>認証に失敗した、MiYouSheの原神コーナーでログインボーナスを獲得してください</value>
<value>認証に失敗しました、MiHoYo BBSの原神コーナーでログインボーナスを獲得してください</value>
</data>
<data name="ServiceSignInSuccessRewardFormat" xml:space="preserve">
<value>ログインボーナスを獲得した、{0}×{1}</value>
<value>ログインボーナスを獲得しました。{0}×{1}</value>
</data>
<data name="ServiceUIGFImportUnsupportedVersion" xml:space="preserve">
<value>サポートされていないUIGFバージョン</value>
</data>
<data name="ServiceUserCurrentMultiMatched" xml:space="preserve">
<value>ユーザー情報複数セレクトしています</value>
<value>ユーザー情報複数選択しています</value>
</data>
<data name="ServiceUserCurrentUpdateAndSaveFailed" xml:space="preserve">
<value>ユーザー {0} のステータスを保存できません</value>
@@ -959,6 +959,9 @@
<data name="ViewControlStatisticsSegmentedItemContentStatistics" xml:space="preserve">
<value>統計</value>
</data>
<data name="ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed" xml:space="preserve">
<value>現在インストールされているWebView2のバージョンは構成管理をサポートしていないため、エラーが発生する可能性があります。WebView2 コンポーネントを更新してください。</value>
</data>
<data name="ViewCultivationHeader" xml:space="preserve">
<value>育成計画</value>
</data>
@@ -1044,7 +1047,7 @@
<value>祈願記録をインポート</value>
</data>
<data name="ViewDialogGachaLogRefreshProgressAuthkeyTimeout" xml:space="preserve">
<value>祈願履歴Url無効、再度取得してください</value>
<value>祈願履歴Url無効です、再度取得してください</value>
</data>
<data name="ViewDialogGachaLogRefreshProgressDescription" xml:space="preserve">
<value>{0} を取得中</value>
@@ -1059,22 +1062,22 @@
<value>祈願履歴のURLを直接入力</value>
</data>
<data name="ViewDialogGeetestCustomUrlCompositInputHint" xml:space="preserve">
<value>请输入请求接口的 Url 复合模板</value>
<value>パラメーターを使用したリクエストURLの入力</value>
</data>
<data name="ViewDialogGeetestCustomUrlReturnDataDescription1" xml:space="preserve">
<value>接口需要返回形如上方所示的 Json 数据,多余的数据会被忽略</value>
<value>認証サービスは、上記のようなJsonデータを返す必要があります。この例以外のデータは無視されます。</value>
</data>
<data name="ViewDialogGeetestCustomUrlReturnDataDescription2" xml:space="preserve">
<value>"code" は 0 の場合のみ認証成功です。他の戻り値は認証失敗となります。</value>
</data>
<data name="ViewDialogGeetestCustomUrlReturnDataHeader" xml:space="preserve">
<value>結果を返す</value>
<value>戻り値</value>
</data>
<data name="ViewDialogGeetestCustomUrlSampleDescription1" xml:space="preserve">
<value>{0} はリクエストの際、gtに切り替えます</value>
<value>{0} はリクエストの際、gtに置き換えられます</value>
</data>
<data name="ViewDialogGeetestCustomUrlSampleDescription2" xml:space="preserve">
<value>{1} はリクエストの際、challengeに切り替えます</value>
<value>{1} はリクエストの際、challengeに置き換えられます</value>
</data>
<data name="ViewDialogGeetestCustomUrlSampleDescription3" xml:space="preserve">
<value>GETメソッドでリクエストの処理を行います。</value>
@@ -1083,7 +1086,7 @@
<value>例</value>
</data>
<data name="ViewDialogGeetestCustomUrlTitle" xml:space="preserve">
<value>配置无感验证接口</value>
<value>Geetest/CAPTCHA 認証APIの設定</value>
</data>
<data name="ViewDialogImportExportApp" xml:space="preserve">
<value>Appをエクスポート</value>
@@ -1101,7 +1104,7 @@
<value>UIAFバージョン</value>
</data>
<data name="ViewDialogImportUIGFExportListCount" xml:space="preserve">
<value>记录条数</value>
<value>レコード数</value>
</data>
<data name="ViewDialogImportUIGFExportUid" xml:space="preserve">
<value>UID</value>
@@ -1128,16 +1131,16 @@
<value>ユーザーデータを完全に削除しますか</value>
</data>
<data name="ViewDialogUserDocumentAction" xml:space="preserve">
<value>立即前往</value>
<value>すぐに移動</value>
</data>
<data name="ViewDialogUserDocumentDescription" xml:space="preserve">
<value>进入文档页面并按指示操作</value>
<value>ドキュメントのページへ移動し、指示に従って操作してください</value>
</data>
<data name="ViewDialogUserDocumentHeader" xml:space="preserve">
<value>ドキュメント</value>
</data>
<data name="ViewDialogUserInputPlaceholder" xml:space="preserve">
<value>STokenが含めているCookieを入力してください</value>
<value>STokenが含まれたCookieを入力してください</value>
</data>
<data name="ViewDialogUserTitle" xml:space="preserve">
<value>クッキーを設定</value>
@@ -1167,16 +1170,16 @@
<value>実行環境</value>
</data>
<data name="ViewGuideStepEnvironmentAfterInstallDescription" xml:space="preserve">
<value>安装完成后重启胡桃以查看是否正常生效</value>
<value>インストール完了後に胡桃を再起動し、動作を確認してください</value>
</data>
<data name="ViewGuideStepEnvironmentFontDescription1" xml:space="preserve">
<value>アイコンが文字化けが起きた場合は、ここに</value>
<value>上のアイコンが読み込めなかったり文字化けしている場合はこちら</value>
</data>
<data name="ViewGuideStepEnvironmentFontDescription2" xml:space="preserve">
<value>フォントを自動的にダウンロード、インストールします</value>
</data>
<data name="ViewGuideStepEnvironmentWebView2Description1" xml:space="preserve">
<value>WebView2 ランタイムが検出されませんアラートが出た場合は、ここに</value>
<value>WebView2 ランタイムが検出されませんアラートが出た場合はこちら</value>
</data>
<data name="ViewGuideStepEnvironmentWebView2Description2" xml:space="preserve">
<value>ランタイムを自動的にダウンロード、インストールします</value>
@@ -1197,13 +1200,13 @@
<value>ゲームランチャー</value>
</data>
<data name="ViewModelAchievementArchiveAdded" xml:space="preserve">
<value>アーカイブ [{0}] を作成されました</value>
<value>アーカイブ [{0}] を作成ました</value>
</data>
<data name="ViewModelAchievementArchiveAlreadyExists" xml:space="preserve">
<value>アーカイブ [{0}] はすでに使用されています。別の名前で作成してください</value>
</data>
<data name="ViewModelAchievementArchiveInvalidName" xml:space="preserve">
<value>無効な文字含むアーカイブ作成できません</value>
<value>無効な文字含むアーカイブ作成できません</value>
</data>
<data name="ViewModelAchievementExportFileType" xml:space="preserve">
<value>UIAF ファイル</value>
@@ -1224,7 +1227,7 @@
<value>素材リスト取得中、しばらくお待ちください</value>
</data>
<data name="ViewModelAvatarPropertyCalculateWeaponNull" xml:space="preserve">
<value>当前角色无法计算,请同步信息后再试</value>
<value>現在のキャラクターを計算できません。データの同期後に再試行してください。</value>
</data>
<data name="ViewModelAvatarPropertyEnkaApiUnavailable" xml:space="preserve">
<value>キャラクターデータサービス [Enka API] は現在利用できません。</value>
@@ -1236,7 +1239,7 @@
<value>データを取得中</value>
</data>
<data name="ViewModelAvatarPropertyOpenClipboardFail" xml:space="preserve">
<value>クリップボード読み込みできません</value>
<value>クリップボードから読み込みできません</value>
</data>
<data name="ViewModelAvatarPropertyShowcaseNotOpen" xml:space="preserve">
<value>キャラクターラインナップが未配置か非表示です。ゲーム内のプロフィール編集で設定してください</value>
@@ -1248,10 +1251,10 @@
<value>育成計画の追加に失敗しました</value>
</data>
<data name="ViewModelCultivationBatchAddCompletedFormat" xml:space="preserve">
<value>完了した:追加/更新:{0}、スキップ{1}</value>
<value>完了しました:追加/更新:{0}、スキップ{1}</value>
</data>
<data name="ViewModelCultivationBatchAddIncompletedFormat" xml:space="preserve">
<value>一部完了した:追加/更新:{0}、スキップ{1}</value>
<value>操作の一部に失敗しました:追加/更新:{0}、スキップ{1}</value>
</data>
<data name="ViewModelCultivationEntryAddSuccess" xml:space="preserve">
<value>選択中の育成計画に正常に追加されました</value>
@@ -1260,7 +1263,7 @@
<value>育成計画で新規作成及びセットしてから続けてください</value>
</data>
<data name="ViewModelCultivationProjectAdded" xml:space="preserve">
<value>追加完了</value>
<value>正常に追加されました。</value>
</data>
<data name="ViewModelCultivationProjectAlreadyExists" xml:space="preserve">
<value>育成計画は同じ名前を使えません。他の名前を使用してください。</value>
@@ -1296,7 +1299,7 @@
<value>指定された位置に保存しました</value>
</data>
<data name="ViewModelExportSuccessTitle" xml:space="preserve">
<value>エクスポート成功しました</value>
<value>エクスポート成功しました</value>
</data>
<data name="ViewModelExportWarningMessage" xml:space="preserve">
<value>書き込み処理でエラーが発生しました</value>
@@ -1317,7 +1320,7 @@
<value>UIGF バージョンが古いため、インポートできません</value>
</data>
<data name="ViewModelGachaLogImportWarningMessage2" xml:space="preserve">
<value>インポートデータにサポートされないアイテムが含まれてい</value>
<value>インポートデータにサポートされていないアイテムが含まれています</value>
</data>
<data name="ViewModelGachaLogImportWarningTitle" xml:space="preserve">
<value>インポート失敗</value>
@@ -1341,7 +1344,7 @@
<value>{0} を削除してよろしいでしょうか?</value>
</data>
<data name="ViewModelGachaLogRetrieveFromHutaoCloudProgress" xml:space="preserve">
<value>胡桃クラウドから祈願履歴を同期します</value>
<value>胡桃クラウド祈願履歴を同期します</value>
</data>
<data name="ViewModelGachaLogUploadToHutaoCloudProgress" xml:space="preserve">
<value>胡桃クラウドにアップロード中</value>
@@ -1359,7 +1362,7 @@
<value>アセットをダウンロード中、しばらくお待ちください</value>
</data>
<data name="ViewModelHutaoPassportEmailNotValidHint" xml:space="preserve">
<value>メールが正しい形式ではありません</value>
<value>メールアドレスが正しい形式ではありません</value>
</data>
<data name="ViewModelImportFromClipboardErrorTitle" xml:space="preserve">
<value>クリップボードのテキスト形式が正しくありません</value>
@@ -1374,7 +1377,7 @@
<value>インポート失敗</value>
</data>
<data name="ViewModelLaunchGameEnsureGameResourceFail" xml:space="preserve">
<value>サーバー切り替えできませんでした</value>
<value>サーバー切り替えできませんでした</value>
</data>
<data name="ViewModelLaunchGameMultiChannelReadFail" xml:space="preserve">
<value>ゲーム設定ファイル {0} の読み込みに失敗しました。ファイルが無いか、権限が不足している可能性があります。</value>
@@ -1389,7 +1392,7 @@
<value>アカウントの切り替えができませんでした</value>
</data>
<data name="ViewModelSettingActionComplete" xml:space="preserve">
<value>完了した</value>
<value>操作は完了しました</value>
</data>
<data name="ViewModelSettingClearWebCacheFail" xml:space="preserve">
<value>削除に失敗しました。ディレクトリのアクセス許可がありません。「管理者として実行」で再度試してください</value>
@@ -1404,13 +1407,16 @@
<value>コピーしました</value>
</data>
<data name="ViewModelSettingCreateDesktopShortcutFailed" xml:space="preserve">
<value>创建桌面快捷方式失败</value>
<value>デスクトップへのショートカット作成に失敗しました</value>
</data>
<data name="ViewModelSettingGeetestCustomUrlSucceed" xml:space="preserve">
<value>无感验证复合 Url 配置成功</value>
<value>非探知認証複合URLの構成に成功しました</value>
</data>
<data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve">
<value>データディレクトリセット完了、再起動して適用する</value>
<value>データディレクトリのリセット完了しました、再起動して変更を適用します</value>
</data>
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
<value>ゲームパスの保存に失敗しました</value>
</data>
<data name="ViewModelUserAdded" xml:space="preserve">
<value>ユーザー [{0}] を正常に追加しました</value>
@@ -1425,7 +1431,7 @@
<value>このCookieが無効のため、操作できません</value>
</data>
<data name="ViewModelUserRemoved" xml:space="preserve">
<value>ユーザー [{0}] 削除されました</value>
<value>ユーザー [{0}] 削除ました</value>
</data>
<data name="ViewModelUserUpdated" xml:space="preserve">
<value>ユーザー [{0}] の Cookie が更新されました。</value>
@@ -1473,7 +1479,7 @@
<value>アチーブメント、キャプション、バージョンまたは番号で検索</value>
</data>
<data name="ViewPageAchievementSortIncompletedItemsFirst" xml:space="preserve">
<value>优先未完成</value>
<value>未達成順にソート</value>
</data>
<data name="ViewPageAnnouncementActivity" xml:space="preserve">
<value>イベント</value>
@@ -1494,7 +1500,7 @@
<value>会心スコア</value>
</data>
<data name="ViewPageAvatarPropertyDefaultDescription" xml:space="preserve">
<value>旅人の仲間の情報まだ入手していない</value>
<value>旅人の仲間の情報まだ無いぞ!</value>
</data>
<data name="ViewPageAvatarPropertyExportAsImage" xml:space="preserve">
<value>画像をエクスポート</value>
@@ -1515,13 +1521,13 @@
<value>キャラクターラインナップの情報を同期</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabCalculate" xml:space="preserve">
<value>MiYouShe育成ツールから同期</value>
<value>MiHoYo BBS育成ツールから同期</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabCalculateDescription" xml:space="preserve">
<value>キャラクターの天賦情報を同期</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecord" xml:space="preserve">
<value>MiYouSheから所持キャラを同期する</value>
<value>MiHoYo BBSから所持キャラを同期</value>
</data>
<data name="ViewPageAvatarPropertyRefreshFromHoyolabGameRecordDescription" xml:space="preserve">
<value>キャラ天賦以外の情報を概ね同期</value>
@@ -1530,7 +1536,7 @@
<value>更新日時</value>
</data>
<data name="ViewPageAvatarPropertyScore" xml:space="preserve">
<value>評価する</value>
<value>評価スコア</value>
</data>
<data name="ViewPageAvatarPropertySecondaryProperties" xml:space="preserve">
<value>強化後付与のサブOP</value>
@@ -1548,10 +1554,10 @@
<value>続けるには、まず「育成計画」を立てよう</value>
</data>
<data name="ViewPageCultivationAddProjectDescription" xml:space="preserve">
<value>稍后可以前往其他页面添加养成计划项</value>
<value>育成計画の項目は後から他のページでも追加できます。</value>
</data>
<data name="ViewPageCultivationAvatarPropertyDescription" xml:space="preserve">
<value>添加我的角色与武器到养成计划</value>
<value>マイ キャラクターと武器を育成計画に追加します</value>
</data>
<data name="ViewPageCultivationCultivateEntry" xml:space="preserve">
<value>育成素材</value>
@@ -1611,16 +1617,16 @@
<value>カードを削除</value>
</data>
<data name="ViewPageDailyNoteResinDiscountUsed" xml:space="preserve">
<value>今週の消費半減回数は消化済</value>
<value>今週の消費半減は消化済</value>
</data>
<data name="ViewPageDailyNoteSettingAutoRefresh" xml:space="preserve">
<value>自動更新</value>
</data>
<data name="ViewPageDailyNoteSettingAutoRefreshDescription" xml:space="preserve">
<value>指定間隔でリアルタイムノート更新を設定する</value>
<value>指定間隔でリアルタイムノート更新します</value>
</data>
<data name="ViewPageDailyNoteSettingRefreshElevatedHint" xml:space="preserve">
<value>这些选项仅允许在非管理员模式下更改</value>
<value>これらの設定は管理者モードでない時のみ変更できます。</value>
</data>
<data name="ViewPageDailyNoteSettingRefreshHeader" xml:space="preserve">
<value>更新</value>
@@ -1632,7 +1638,7 @@
<value>おやすみモード</value>
</data>
<data name="ViewPageDailyNoteVerify" xml:space="preserve">
<value>验证当前用户与角色</value>
<value>現在のユーザーとUIDを確認する</value>
</data>
<data name="ViewPageGachaLogAggressiveRefresh" xml:space="preserve">
<value>すべて更新</value>
@@ -1656,16 +1662,16 @@
<value>このUidのクラウドバックアップを削除する</value>
</data>
<data name="ViewPageGachaLogHutaoCloudDeveloperHint" xml:space="preserve">
<value>デベロッパーアカウントは利用期限ありません</value>
<value>デベロッパーアカウントは利用期限ありません</value>
</data>
<data name="ViewPageGachaLogHutaoCloudNotAllowed" xml:space="preserve">
<value>胡桃クラウドのサービス有効期限が切れました。</value>
<value>胡桃クラウドのサービス期限が切れました。</value>
</data>
<data name="ViewPageGachaLogHutaoCloudRetrieve" xml:space="preserve">
<value>このUidのクラウドバックアップをダウンロードする</value>
</data>
<data name="ViewPageGachaLogHutaoCloudSpiralAbyssActivityDescription" xml:space="preserve">
<value>每期深渊首次上传可免费获得 3 天时长</value>
<value>各シーズンの深境螺旋の記録を初めてアップロードすると3日間のフリーライセンスが付与されます。</value>
</data>
<data name="ViewPageGachaLogHutaoCloudSpiralAbyssActivityHeader" xml:space="preserve">
<value>螺旋の記録をアップロード</value>
@@ -1704,13 +1710,13 @@
<value>現在のユーザーのCookieで祈願履歴を表示する</value>
</data>
<data name="ViewPageGachaLogRefreshByWebCache" xml:space="preserve">
<value>キャッシュの再読み込み</value>
<value>ゲーム内ブラウザキャッシュで更新</value>
</data>
<data name="ViewPageGachaLogRefreshByWebCacheDescription" xml:space="preserve">
<value>ゲーム内ブラウザのキャッシュで祈願履歴を更新</value>
</data>
<data name="ViewPageGachaLogRemoveArchiveAction" xml:space="preserve">
<value>のアーカイブを削除する</value>
<value>現在のアーカイブを削除する</value>
</data>
<data name="ViewPageGahcaLogPivotAvatar" xml:space="preserve">
<value>キャラクター</value>
@@ -1734,13 +1740,13 @@
<value>胡桃を {0} 回起動しました</value>
</data>
<data name="ViewPageHomeGreetingTextDefault" xml:space="preserve">
<value>テイワットへようこそ</value>
<value>テイワットへようこそ!</value>
</data>
<data name="ViewPageHomeGreetingTextEasterEgg" xml:space="preserve">
<value>あなたの言う通りだが、しかし「胡桃」はDGP Studioが開発した...</value>
</data>
<data name="ViewPageHomeGreetingTextEpic1" xml:space="preserve">
<value>ねぇ、旅人、今日はテイワットで{0} 日目の冒険だ</value>
<value>なあ、旅人、今日はテイワットで{0} 日目の冒険だぞ!</value>
</data>
<data name="ViewPageHomeLaunchGameSettingAction" xml:space="preserve">
<value>設定</value>
@@ -1821,7 +1827,7 @@
<value>パスワードを入力してください</value>
</data>
<data name="ViewPageHutaoPassportPasswordRequirementHint" xml:space="preserve">
<value>最低8文字以上</value>
<value>8文字以上必要です</value>
</data>
<data name="ViewPageHutaoPassportRegisterHeader" xml:space="preserve">
<value>アカウント作成</value>
@@ -1845,10 +1851,10 @@
<value>上級者向け設定</value>
</data>
<data name="ViewPageLaunchGameAppearanceBorderlessDescription" xml:space="preserve">
<value>将窗口创建为弹出窗口,不带框架</value>
<value>枠の無いポップアップウィンドウとして作成します。</value>
</data>
<data name="ViewPageLaunchGameAppearanceBorderlessHeader" xml:space="preserve">
<value>フレームレス</value>
<value>ボーダーレス</value>
</data>
<data name="ViewPageLaunchGameAppearanceExclusiveDescription" xml:space="preserve">
<value>ゲーム内ブラウザには対応していません。ウィンドウの切り替え操作などによりゲームが強制終了する可能性があります。</value>
@@ -1890,7 +1896,7 @@
<value>モニター</value>
</data>
<data name="ViewPageLaunchGameMultipleInstancesDescription" xml:space="preserve">
<value>同时运行多个游戏客户端</value>
<value>複数のゲームプロセスを同時に実行します。</value>
</data>
<data name="ViewPageLaunchGameMultipleInstancesHeader" xml:space="preserve">
<value>マルチクライアント</value>
@@ -1914,7 +1920,7 @@
<value>このアカウントは UID と紐付けしていません</value>
</data>
<data name="ViewPageLaunchGameSwitchAccountAttachUidToolTip" xml:space="preserve">
<value>绑定当前用户的角色</value>
<value>現在のユーザのUIDを連携する</value>
</data>
<data name="ViewPageLaunchGameSwitchAccountDescription" xml:space="preserve">
<value>ゲーム内でアカウントを切り替えたり、インターネット環境を変更した場合は再検出が必要です。</value>
@@ -1938,7 +1944,7 @@
<value>サーバー</value>
</data>
<data name="ViewPageLaunchGameSwitchSchemeWarning" xml:space="preserve">
<value>版本更新前需要提前转换至与启动器匹配的服务器</value>
<value>アップデートをする前に、ランチャーと同じサーバーに変更する必要があります。</value>
</data>
<data name="ViewPageLaunchGameUnlockFpsDescription" xml:space="preserve">
<value>ゲーム内のグラフィック設定で垂直同期を無効にしてください。フレームレートを上げるにはもっと高性能のグラフィックボードが必要となります。</value>
@@ -1953,13 +1959,13 @@
<value>有効</value>
</data>
<data name="ViewPageLoginHoyoverseUserHint" xml:space="preserve">
<value>HoYo Lab UIDを入力してください</value>
<value>HoYoLab UIDを入力してください</value>
</data>
<data name="ViewPageLoginMihoyoUserLoggedInAction" xml:space="preserve">
<value>ログインしました</value>
</data>
<data name="ViewPageLoginMihoyoUserTitle" xml:space="preserve">
<value>Hoyoverse通行証ログイン</value>
<value>MiHoYo BBS通行証ログイン</value>
</data>
<data name="ViewPageOpenScreenshotFolderAction" xml:space="preserve">
<value>スクリーンショットフォルダを開く</value>
@@ -1983,7 +1989,7 @@
<value>テーマ</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>イメージキャッシュはここに格納</value>
<value>イメージキャッシュはここに格納されます</value>
</data>
<data name="ViewPageSettingCacheFolderHeader" xml:space="preserve">
<value>イメージキャッシュフォルダを開く</value>
@@ -1995,7 +2001,7 @@
<value>新規作成</value>
</data>
<data name="ViewPageSettingCreateDesktopShortcutDescription" xml:space="preserve">
<value>デスクトップで常に「管理者として実行」のエイリアスを作成する</value>
<value>管理者として実行できるショートカットを作成します</value>
</data>
<data name="ViewPageSettingCreateDesktopShortcutHeader" xml:space="preserve">
<value>ショートカットを作成する</value>
@@ -2061,7 +2067,7 @@
<value>構成</value>
</data>
<data name="ViewPageSettingGeetestCustomUrlDescription" xml:space="preserve">
<value>配置当请求触发人机验证时使用的验证接口</value>
<value>ボット確認に使用される認証APIの設定</value>
</data>
<data name="ViewPageSettingGeetestCustomUrlHeader" xml:space="preserve">
<value>Geetest認証サービスの設定</value>
@@ -2100,7 +2106,7 @@
<value>胡桃アカウント</value>
</data>
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledDescription" xml:space="preserve">
<value>原神およびSnap Hutaoの利用規約を全て熟読し、その後『ゲームランチャー - 上級者向け設定』を有効にします。</value>
<value>原神およびSnap Hutaoの利用規約を全て熟読し、その後『ゲームランチャー - 上級者向け設定』を有効にします。</value>
</data>
<data name="ViewPageSettingIsAdvancedLaunchOptionsEnabledHeader" xml:space="preserve">
<value>上級者向け設定を有効にする</value>
@@ -2112,7 +2118,7 @@
<value>リセット</value>
</data>
<data name="ViewPageSettingResetStaticResourceDescription" xml:space="preserve">
<value>全ての画像リソースを削除し、すぐに再度ダウンロードす</value>
<value>全ての画像リソースを削除し、次の起動時に再度ダウンロードします</value>
</data>
<data name="ViewPageSettingResetStaticResourceHeader" xml:space="preserve">
<value>画像リソースをリセット</value>
@@ -2292,13 +2298,13 @@
<value>編成</value>
</data>
<data name="ViewSpiralAbyssRecordMonsterAttacksMonolith" xml:space="preserve">
<value>攻击地脉镇石</value>
<value>地脈石へ攻撃</value>
</data>
<data name="ViewSpiralAbyssRefresh" xml:space="preserve">
<value>データを再読み込み</value>
</data>
<data name="ViewSpiralAbyssRefreshDescription" xml:space="preserve">
<value>MiYouSheから深境螺旋の記録を同期</value>
<value>HoYoLABから深境螺旋の記録を同期</value>
</data>
<data name="ViewSpiralAbyssReveal" xml:space="preserve">
<value>出撃回数</value>
@@ -2388,16 +2394,16 @@
<value>武器一覧</value>
</data>
<data name="WebAnnouncementMatchPermanentActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|〓任务开放时间〓).*?\d\.\d版本更新(?:完|)后永久开放</value>
<value>(?:〓イベント期間〓|〓任務開始時間〓).*?\d\.\dバージョンアップ(?:完|)後常設オープン</value>
</data>
<data name="WebAnnouncementMatchPersistentActivityTime" xml:space="preserve">
<value>〓活动时间〓.*?\d\.\d版本期间持续开放</value>
<value>〓イベント期間〓.*?\d\.\d当バージョン期間オープン</value>
</data>
<data name="WebAnnouncementMatchTransientActivityTime" xml:space="preserve">
<value>(?:〓活动时间〓|祈愿时间|【上架时间】).*?(\d\.\d版本更新后).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
<value>(?:〓イベント期間〓|祈願期間|【開始日時】).*?(\d\.\dバージョンアップ完了後).*?~.*?&amp;lt;t class="t_(?:gl|lc)".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTime" xml:space="preserve">
<value>〓更新时间〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
<value>〓更新日時〓.+?&amp;lt;t class=\"t_(?:gl|lc)\".*?&amp;gt;(.*?)&amp;lt;/t&amp;gt;</value>
</data>
<data name="WebAnnouncementMatchVersionUpdateTitle" xml:space="preserve">
<value>Ver.\d\.\d 更新内容</value>
@@ -2424,16 +2430,16 @@
<value>すべての依頼を完了していません</value>
</data>
<data name="WebDailyNoteExtraTaskRewardNotTaken" xml:space="preserve">
<value>デイリー依頼の報酬はまだ受け取っていません</value>
<value>デイリー依頼の報酬はまだ受け取っていません</value>
</data>
<data name="WebDailyNoteExtraTaskRewardReceived" xml:space="preserve">
<value>デイリー依頼の報酬は受け取り済みです</value>
<value>デイリー依頼の報酬は受け取り済みです</value>
</data>
<data name="WebDailyNoteHomeCoinRecoveryFormat" xml:space="preserve">
<value>上限到達まで{0}{1:HH:mm}</value>
<value>上限到達まで{0} {1:HH:mm}</value>
</data>
<data name="WebDailyNoteHomeLocked" xml:space="preserve">
<value>洞天形態未開放</value>
<value>塵歌壺が未解放です</value>
</data>
<data name="WebDailyNoteRecoveryTimeDay0" xml:space="preserve">
<value>今日</value>
@@ -2451,7 +2457,7 @@
<value>天然樹脂は完全に回復しました</value>
</data>
<data name="WebDailyNoteResinRecoveryFormat" xml:space="preserve">
<value>{0} {1:HH:mm} 後すべて回復</value>
<value>{0} {1:HH:mm} 後すべて回復</value>
</data>
<data name="WebDailyNoteTransformerAppend" xml:space="preserve">
<value>後に再び使用することができます</value>
@@ -2469,7 +2475,7 @@
<value>未獲得</value>
</data>
<data name="WebDailyNoteTransformerNotObtainedDetail" xml:space="preserve">
<value>参量物質変化器未獲得</value>
<value>参量物質変化器を持っていません</value>
</data>
<data name="WebDailyNoteTransformerNotReached" xml:space="preserve">
<value>クールタイム中</value>
@@ -2484,7 +2490,7 @@
<value>{0} 秒</value>
</data>
<data name="WebDailyNoteVerificationFailed" xml:space="preserve">
<value>認証に失敗しました。「MiYouShe - 戦績ツール - リアルタイムノート」で確認し、認証を行ってください</value>
<value>認証に失敗しました。「MiHoYo BBS - 戦績ツール - リアルタイムノート」で確認し、認証を行ってください</value>
</data>
<data name="WebEnkaResponseStatusCode400" xml:space="preserve">
<value>UIDは正しくありません</value>

View File

@@ -959,6 +959,9 @@
<data name="ViewControlStatisticsSegmentedItemContentStatistics" xml:space="preserve">
<value>统计</value>
</data>
<data name="ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed" xml:space="preserve">
<value>当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级</value>
</data>
<data name="ViewCultivationHeader" xml:space="preserve">
<value>육성 계획</value>
</data>
@@ -1412,6 +1415,9 @@
<data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve">
<value>데이터 경로를 설정했습니다. 변경 사항을 적용하기 위해 재시작합니다</value>
</data>
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
<value>保存游戏路径失败</value>
</data>
<data name="ViewModelUserAdded" xml:space="preserve">
<value>사용자 [{0}]가 정상적으로 추가되었습니다</value>
</data>

View File

@@ -959,6 +959,9 @@
<data name="ViewControlStatisticsSegmentedItemContentStatistics" xml:space="preserve">
<value>统计</value>
</data>
<data name="ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed" xml:space="preserve">
<value>当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级</value>
</data>
<data name="ViewCultivationHeader" xml:space="preserve">
<value>养成计划</value>
</data>
@@ -1412,6 +1415,9 @@
<data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve">
<value>设置数据目录成功,重启以应用更改</value>
</data>
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
<value>保存游戏路径失败</value>
</data>
<data name="ViewModelUserAdded" xml:space="preserve">
<value>用户 [{0}] 添加成功</value>
</data>

View File

@@ -959,6 +959,9 @@
<data name="ViewControlStatisticsSegmentedItemContentStatistics" xml:space="preserve">
<value>統計</value>
</data>
<data name="ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed" xml:space="preserve">
<value>当前 WebView2 版本不支持管理配置,继续使用可能会导致异常,请尽快升级</value>
</data>
<data name="ViewCultivationHeader" xml:space="preserve">
<value>養成計劃</value>
</data>
@@ -1412,6 +1415,9 @@
<data name="ViewModelSettingSetDataFolderSuccess" xml:space="preserve">
<value>設置數據目錄成功,重啓以應用更改</value>
</data>
<data name="ViewModelSettingSetGamePathDatabaseFailedTitle" xml:space="preserve">
<value>保存游戏路径失败</value>
</data>
<data name="ViewModelUserAdded" xml:space="preserve">
<value>用戶 [{0}] 新增成功</value>
</data>

View File

@@ -105,8 +105,12 @@ internal static class SummaryHelper
/// <returns>分数</returns>
public static float GetPercentSubAffixScore(in ReliquarySubAffixId appendId)
{
// 圣遗物相同类型副词条强化档位一共为 4 档
// 恰好为 70% 80% 90% 100%
// 圣遗物相同类型副词条强化档位一共为 4/3/2
// 五星 为 70% 80% 90% 100%
// 四星 为 70% 80% 90% 100%
// 三星 为 70% 80% 90% 100%
// 二星 为 70% 85% 100%
// 二星 为 80% 100%
// 通过计算与最大属性的 Id 差来决定当前副词条的强化档位
uint maxId = GetAffixMaxId(appendId);
uint delta = maxId - appendId;
@@ -119,7 +123,11 @@ internal static class SummaryHelper
(5 or 4 or 3, 3) => 70F,
(2, 0) => 100F,
(2, 1) => 80F,
(2, 1) => 85F,
(2, 2) => 70F,
(1, 0) => 100F,
(1, 1) => 80F,
_ => throw Must.NeverHappen($"Unexpected AppendId: {appendId.Value} Delta: {delta}"),
};

View File

@@ -35,10 +35,28 @@ internal sealed partial class UIGFImportService : IUIGFImportService
// v2.3+ support any locale
// v2.2 only support matched locale
// v2.1 only support CHS
if (version is UIGFVersion.Major2Minor2OrLower && !metadataOptions.IsCurrentLocale(uigf.Info.Language))
if (version is UIGFVersion.Major2Minor2OrLower)
{
string message = SH.ServiceGachaUIGFImportLanguageNotMatch.Format(uigf.Info.Language, metadataOptions.LanguageCode);
ThrowHelper.InvalidOperation(message, null);
if (!metadataOptions.IsCurrentLocale(uigf.Info.Language))
{
string message = SH.ServiceGachaUIGFImportLanguageNotMatch.Format(uigf.Info.Language, metadataOptions.LanguageCode);
ThrowHelper.InvalidOperation(message);
}
if (!uigf.IsMajor2Minor2OrLowerListValid(out long id))
{
string message = SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(id);
ThrowHelper.InvalidOperation(message);
}
}
if (version is UIGFVersion.Major2Minor3OrHigher)
{
if (!uigf.IsMajor2Minor3OrHigherListValid(out long id))
{
string message = SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(id);
ThrowHelper.InvalidOperation(message);
}
}
GachaArchiveOperation.GetOrAdd(gachaLogDbService, taskContext, uigf.Info.Uid, archives, out GachaArchive? archive);

View File

@@ -49,6 +49,13 @@ internal readonly struct ChannelOptions
ConfigFilePath = configFilePath;
}
public ChannelOptions(ChannelType channel, SubChannelType subChannel, bool isOversea)
{
Channel = channel;
SubChannel = subChannel;
IsOversea = isOversea;
}
/// <summary>
/// 配置文件未找到
/// </summary>
@@ -65,4 +72,9 @@ internal readonly struct ChannelOptions
{
return $"[ChannelType:{Channel}] [SubChannel:{SubChannel}] [IsOversea: {IsOversea}]";
}
public override int GetHashCode()
{
return HashCode.Combine(Channel, SubChannel, IsOversea);
}
}

View File

@@ -14,7 +14,7 @@ internal sealed class GameFileOperationException : Exception
/// </summary>
/// <param name="message">消息</param>
/// <param name="innerException">内部错误</param>
public GameFileOperationException(string message, Exception innerException)
public GameFileOperationException(string message, Exception? innerException)
: base(SH.ServiceGameFileOperationExceptionMessage.Format(message), innerException)
{
}

View File

@@ -281,7 +281,7 @@ internal sealed partial class GameService : IGameService
}
/// <inheritdoc/>
public async ValueTask DetectGameAccountAsync()
public async ValueTask<GameAccount?> DetectGameAccountAsync()
{
ArgumentNullException.ThrowIfNull(gameAccounts);
@@ -318,7 +318,11 @@ internal sealed partial class GameService : IGameService
gameAccounts.Add(account);
}
}
return account;
}
return default;
}
/// <inheritdoc/>

View File

@@ -26,11 +26,7 @@ internal interface IGameService
/// <param name="uid">uid</param>
void AttachGameAccountToUid(GameAccount gameAccount, string uid);
/// <summary>
/// 检测并尝试添加游戏内账户
/// </summary>
/// <returns>任务</returns>
ValueTask DetectGameAccountAsync();
ValueTask<GameAccount?> DetectGameAccountAsync();
/// <summary>
/// 异步获取游戏路径

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using System.Collections.Immutable;
namespace Snap.Hutao.Service.Game;
internal static class IgnoredInvalidChannelOptions
{
private static readonly ImmutableHashSet<ChannelOptions> InvalidOptions = new HashSet<ChannelOptions>()
{
new(ChannelType.Bili, SubChannelType.Official, true),
}.ToImmutableHashSet();
public static bool Contains(in ChannelOptions options)
{
return InvalidOptions.Contains(options);
}
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Game.Package;
internal sealed class PackageConvertException : Exception
{
/// <inheritdoc cref="Exception(string?, Exception?)"/>
public PackageConvertException(string message, Exception innerException)
public PackageConvertException(string message, Exception? innerException)
: base(message, innerException)
{
}

View File

@@ -26,7 +26,10 @@ internal static class ProcessInterop
/// <returns>初始化后的游戏进程</returns>
public static Process InitializeGameProcess(LaunchOptions options, string gamePath)
{
Must.Argument(!(options.IsBorderless && options.IsExclusive), "无边框与独占全屏选项无法同时生效");
// https://docs.unity.cn/cn/current/Manual/PlayerCommandLineArguments.html
// https://docs.unity3d.com/2017.4/Documentation/Manual/CommandLineArguments.html
string commandLine = new CommandLineBuilder()
.AppendIf("-popupwindow", options.IsBorderless)
.AppendIf("-window-mode", options.IsExclusive, "exclusive")

View File

@@ -3,6 +3,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Control.Theme;
using Snap.Hutao.Web.Bridge;
@@ -131,9 +132,10 @@ internal sealed partial class AnnouncementContentViewer : UserControl
private void OnUnloaded(object sender, RoutedEventArgs e)
{
WebView.CoreWebView2.WebMessageReceived -= webMessageReceivedHandler;
Loaded -= loadEventHandler;
Unloaded -= unloadEventHandler;
if (WebView is { CoreWebView2: CoreWebView2 coreWebView2 })
{
coreWebView2.WebMessageReceived -= webMessageReceivedHandler;
}
}
private async ValueTask LoadAnnouncementAsync()
@@ -141,7 +143,7 @@ internal sealed partial class AnnouncementContentViewer : UserControl
try
{
await WebView.EnsureCoreWebView2Async();
WebView.CoreWebView2.DisableDevToolsOnReleaseBuild();
WebView.CoreWebView2.DisableDevToolsForReleaseBuild();
WebView.CoreWebView2.WebMessageReceived += webMessageReceivedHandler;
await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition);

View File

@@ -2,16 +2,16 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Content;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Message;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Bridge;
using WinRT;
using WinRT.Interop;
namespace Snap.Hutao.View.Control;
@@ -19,22 +19,22 @@ namespace Snap.Hutao.View.Control;
internal partial class WebViewer : UserControl, IRecipient<UserChangedMessage>
{
private readonly IServiceProvider serviceProvider;
private readonly IInfoBarService infoBarService;
private readonly RoutedEventHandler loadEventHandler;
private readonly RoutedEventHandler unloadEventHandler;
private MiHoYoJSInterface? jsInterface;
private bool isInitializingOrInitialized;
public WebViewer()
{
InitializeComponent();
serviceProvider = Ioc.Default;
infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
serviceProvider.GetRequiredService<IMessenger>().Register(this);
loadEventHandler = OnLoaded;
unloadEventHandler = OnUnloaded;
Loaded += loadEventHandler;
Unloaded += unloadEventHandler;
}
public void Receive(UserChangedMessage message)
@@ -48,16 +48,17 @@ internal partial class WebViewer : UserControl, IRecipient<UserChangedMessage>
InitializeAsync().SafeForget();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Loaded -= loadEventHandler;
Unloaded -= unloadEventHandler;
}
private async ValueTask InitializeAsync()
{
if (isInitializingOrInitialized)
{
return;
}
isInitializingOrInitialized = true;
await WebView.EnsureCoreWebView2Async();
WebView.CoreWebView2.DisableDevToolsOnReleaseBuild();
WebView.CoreWebView2.DisableDevToolsForReleaseBuild();
RefreshWebview2Content();
}
@@ -86,13 +87,22 @@ internal partial class WebViewer : UserControl, IRecipient<UserChangedMessage>
string source = SourceProvider.GetSource(userAndUid);
if (!string.IsNullOrEmpty(source))
{
await coreWebView2.Profile.ClearBrowsingDataAsync();
try
{
await coreWebView2.Profile.ClearBrowsingDataAsync();
}
catch (InvalidCastException)
{
infoBarService.Warning(SH.ViewControlWebViewerCoreWebView2ProfileQueryInterfaceFailed);
await coreWebView2.DeleteCookiesAsync(userAndUid.IsOversea).ConfigureAwait(true);
}
CoreWebView2Navigator navigator = new(coreWebView2);
await navigator.NavigateAsync("about:blank").ConfigureAwait(true);
coreWebView2.SetCookie(user.CookieToken, user.LToken, user.SToken);
_ = userAndUid.User.IsOversea ? coreWebView2.SetMobileOverseaUserAgent() : coreWebView2.SetMobileUserAgent();
coreWebView2
.SetCookie(user.CookieToken, user.LToken, userAndUid.IsOversea)
.SetMobileUserAgent(userAndUid.IsOversea);
jsInterface?.Detach();
jsInterface = SourceProvider.CreateJsInterface(serviceProvider, coreWebView2, userAndUid);
@@ -101,7 +111,7 @@ internal partial class WebViewer : UserControl, IRecipient<UserChangedMessage>
}
else
{
serviceProvider.GetRequiredService<IInfoBarService>().Warning(SH.MustSelectUserAndUid);
infoBarService.Warning(SH.MustSelectUserAndUid);
}
}
}

View File

@@ -18,7 +18,7 @@ internal interface ISupportLoginByWebView
{
await webView2.EnsureCoreWebView2Async();
await webView2.CoreWebView2.DeleteCookiesAsync(cookie).ConfigureAwait(true);
webView2.CoreWebView2.DisableDevToolsOnReleaseBuild();
webView2.CoreWebView2.DisableDevToolsForReleaseBuild();
webView2.CoreWebView2.DisableAutoCompletion();
webView2.CoreWebView2.Navigate(navigate);
@@ -33,7 +33,7 @@ internal interface ISupportLoginByWebView
{
(UserOptionResult result, string nickname) = await serviceProvider
.GetRequiredService<IUserService>()
.ProcessInputCookieAsync(cookie, false)
.ProcessInputCookieAsync(cookie, isOversea)
.ConfigureAwait(false);
serviceProvider.GetRequiredService<INavigationService>().GoBack();

View File

@@ -148,4 +148,4 @@ internal sealed partial class LoginHoyoverseUserPage : Microsoft.UI.Xaml.Control
[JsonPropertyName("weblogin_token")]
public string WebLoginToken { get; set; } = default!;
}
}
}

View File

@@ -22,10 +22,13 @@
<StackPanel Margin="16,16,16,16" Spacing="{StaticResource SettingsCardSpacing}">
<Border cw:Effects.Shadow="{ThemeResource CompatCardShadow}">
<Grid Height="280" Style="{ThemeResource GridCardStyle}">
<Image
VerticalAlignment="Center"
Source="ms-appx:///Resource/BlurBackground.png"
Stretch="Fill"/>
<Border CornerRadius="{ThemeResource ControlCornerRadius}">
<Image
VerticalAlignment="Center"
Source="ms-appx:///Resource/BlurBackground.png"
Stretch="Fill"/>
</Border>
<Grid Background="{ThemeResource SystemControlBackgroundAltMediumBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
@@ -74,29 +77,31 @@
Margin="0,4"
Text="Copyright © 2022 - 2023 DGP Studio. All Rights Reserved."
TextWrapping="Wrap"/>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingAboutHeader}"/>
<cwc:SettingsCard
<cwc:SettingsExpander
Description="{Binding HutaoOptions.Version}"
Header="{shcm:ResourceString Name=AppName}"
HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"/>
HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"
IsExpanded="True">
<cwc:SettingsExpander.Items>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE8C8;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCopyDeviceIdAction}"
Command="{Binding CopyDeviceIdCommand}"
Description="{Binding HutaoOptions.DeviceId}"
Header="{shcm:ResourceString Name=ViewPageSettingDeviceIdHeader}"
IsClickEnabled="True"/>
<cwc:SettingsCard Description="{Binding HutaoOptions.WebView2Version}" Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"/>
</cwc:SettingsExpander.Items>
</cwc:SettingsExpander>
<cwc:SettingsCard
Command="{Binding NavigateToHutaoPassportCommand}"
Description="{Binding UserOptions.UserName}"
Header="{shcm:ResourceString Name=ViewPageSettingHutaoPassportHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE716;}"
IsClickEnabled="True"/>
<cwc:SettingsCard
ActionIcon="{shcm:FontIcon Glyph=&#xE8C8;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingCopyDeviceIdAction}"
Command="{Binding CopyDeviceIdCommand}"
Description="{Binding HutaoOptions.DeviceId}"
Header="{shcm:ResourceString Name=ViewPageSettingDeviceIdHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xE975;}"
IsClickEnabled="True"/>
<cwc:SettingsCard
Description="{Binding HutaoOptions.WebView2Version}"
Header="{shcm:ResourceString Name=ViewPageSettingWebview2Header}"
HeaderIcon="{shcm:FontIcon Glyph=&#xECAA;}"/>
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{shcm:ResourceString Name=ViewPageSettingGeetestVerificationHeader}"/>
<cwc:SettingsCard
@@ -141,7 +146,8 @@
<cwc:SettingsExpander
Description="{shcm:ResourceString Name=ViewpageSettingHomeCardDescription}"
Header="{shcm:ResourceString Name=ViewpageSettingHomeCardHeader}"
HeaderIcon="{shcm:FontIcon Glyph=&#xEE40;}">
HeaderIcon="{shcm:FontIcon Glyph=&#xEE40;}"
IsExpanded="True">
<cwc:SettingsExpander.Items>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewpageSettingHomeCardItemLaunchGameHeader}">
<ToggleSwitch
@@ -238,7 +244,7 @@
IsClickEnabled="True"/>
<cwc:SettingsCard
Margin="0,4,0,0"
Margin="0,3,0,0"
ActionIcon="{shcm:FontIcon Glyph=&#xE76C;}"
ActionIconToolTip="{shcm:ResourceString Name=ViewPageSettingStorageOpenAction}"
Command="{Binding OpenCacheFolderCommand}"
@@ -276,7 +282,7 @@
</StackPanel>
</cwc:SettingsCard>
<InfoBar
Margin="0,4,0,0"
Margin="0,3,0,0"
IsClosable="False"
IsOpen="True"
Message="{shcm:ResourceString Name=ViewPageSettingDangerousHint}"

View File

@@ -14,6 +14,7 @@ using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using Windows.Storage.Pickers;
namespace Snap.Hutao.ViewModel.GachaLog;
@@ -115,21 +116,6 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
return false;
}
private static bool CanImport(UIGFVersion version, UIGF uigf, out long id)
{
id = 0;
if (version == UIGFVersion.Major2Minor3OrHigher)
{
return true;
}
else if (version == UIGFVersion.Major2Minor2OrLower && uigf.IsMajor2Minor2OrLowerListValid(out id))
{
return true;
}
return false;
}
[Command("RefreshByWebCacheCommand")]
private Task RefreshByWebCacheAsync()
{
@@ -159,7 +145,23 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
RefreshStrategy strategy = IsAggressiveRefresh ? RefreshStrategy.AggressiveMerge : RefreshStrategy.LazyMerge;
GachaLogRefreshProgressDialog dialog = await contentDialogFactory.CreateInstanceAsync<GachaLogRefreshProgressDialog>().ConfigureAwait(false);
ContentDialogHideToken hideToken = await dialog.BlockAsync(taskContext).ConfigureAwait(false);
ContentDialogHideToken hideToken;
try
{
hideToken = await dialog.BlockAsync(taskContext).ConfigureAwait(false);
}
catch (COMException ex)
{
if (ex.HResult == unchecked((int)0x80000019))
{
infoBarService.Error(ex);
return;
}
throw;
}
IProgress<GachaLogFetchStatus> progress = taskContext.CreateProgressForMainThread<GachaLogFetchStatus>(dialog.OnReport);
bool authkeyValid;
@@ -221,7 +223,7 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
}
else
{
await contentDialogFactory.CreateForConfirmAsync(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage).ConfigureAwait(false);
infoBarService.Error(SH.ViewModelImportWarningTitle, SH.ViewModelImportWarningMessage);
}
}
}
@@ -349,50 +351,42 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
private async ValueTask<bool> TryImportUIGFInternalAsync(UIGF uigf)
{
if (uigf.IsCurrentVersionSupported(out UIGFVersion version))
{
GachaLogImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync<GachaLogImportDialog>(uigf).ConfigureAwait(false);
if (await importDialog.GetShouldImportAsync().ConfigureAwait(false))
{
if (CanImport(version, uigf, out long itemId))
{
await taskContext.SwitchToMainThreadAsync();
ContentDialog dialog = await contentDialogFactory.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogImportProgress).ConfigureAwait(true);
try
{
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{
await gachaLogService.ImportFromUIGFAsync(uigf).ConfigureAwait(false);
}
}
catch (InvalidOperationException ex)
{
// 导入物品中存在无效的项
infoBarService.Error(ex);
return false;
}
catch (FormatException ex)
{
infoBarService.Error(ex);
return false;
}
infoBarService.Success(SH.ViewModelGachaLogImportComplete);
await taskContext.SwitchToMainThreadAsync();
await SetSelectedArchiveAndUpdateStatisticsAsync(gachaLogService.CurrentArchive, true).ConfigureAwait(false);
return true;
}
else
{
infoBarService.Warning(SH.ViewModelGachaLogImportWarningTitle, SH.ServiceGachaLogUIGFImportItemInvalidFormat.Format(itemId));
}
}
}
else
if (!uigf.IsCurrentVersionSupported(out UIGFVersion version))
{
infoBarService.Warning(SH.ViewModelGachaLogImportWarningTitle, SH.ViewModelGachaLogImportWarningMessage);
return false;
}
return false;
GachaLogImportDialog importDialog = await contentDialogFactory.CreateInstanceAsync<GachaLogImportDialog>(uigf).ConfigureAwait(false);
if (!await importDialog.GetShouldImportAsync().ConfigureAwait(false))
{
return false;
}
await taskContext.SwitchToMainThreadAsync();
ContentDialog dialog = await contentDialogFactory.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogImportProgress).ConfigureAwait(true);
try
{
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{
await gachaLogService.ImportFromUIGFAsync(uigf).ConfigureAwait(false);
}
}
catch (InvalidOperationException ex)
{
// 语言不匹配/导入物品中存在无效的项
infoBarService.Error(ex);
return false;
}
catch (FormatException ex)
{
infoBarService.Error(ex);
return false;
}
infoBarService.Success(SH.ViewModelGachaLogImportComplete);
await taskContext.SwitchToMainThreadAsync();
await SetSelectedArchiveAndUpdateStatisticsAsync(gachaLogService.CurrentArchive, true).ConfigureAwait(false);
return true;
}
}

View File

@@ -34,12 +34,12 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
public const string DesiredUid = nameof(DesiredUid);
private readonly IContentDialogFactory contentDialogFactory;
private readonly LaunchStatusOptions launchStatusOptions;
private readonly INavigationService navigationService;
private readonly IInfoBarService infoBarService;
private readonly LaunchOptions launchOptions;
private readonly LaunchStatusOptions launchStatusOptions;
private readonly RuntimeOptions hutaoOptions;
private readonly ResourceClient resourceClient;
private readonly LaunchOptions launchOptions;
private readonly RuntimeOptions hutaoOptions;
private readonly IUserService userService;
private readonly ITaskContext taskContext;
private readonly IGameService gameService;
@@ -125,8 +125,11 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
}
catch (InvalidOperationException)
{
// 后台收集
throw new NotSupportedException($"不支持的 MultiChannel: {options}");
if (!IgnoredInvalidChannelOptions.Contains(options))
{
// 后台收集
throw new NotSupportedException($"不支持的 MultiChannel: {options}");
}
}
}
else
@@ -233,7 +236,15 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel
{
try
{
await gameService.DetectGameAccountAsync().ConfigureAwait(false);
GameAccount? account = await gameService.DetectGameAccountAsync().ConfigureAwait(false);
// If user canceled the operation, the return is null,
// and thus we should not set SelectedAccount
if (account is not null)
{
await taskContext.SwitchToMainThreadAsync();
SelectedGameAccount = account;
}
}
catch (UserdataCorruptedException ex)
{

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.AppLifecycle;
@@ -126,7 +127,15 @@ internal sealed partial class SettingViewModel : Abstraction.ViewModel
if (isOk)
{
await taskContext.SwitchToMainThreadAsync();
Options.GamePath = path;
try
{
Options.GamePath = path;
}
catch (SqliteException ex)
{
// 文件夹权限不足,无法写入数据库
infoBarService.Error(ex, SH.ViewModelSettingSetGamePathDatabaseFailedTitle);
}
}
}

View File

@@ -18,11 +18,6 @@ internal sealed class SpiralAbyssView : IEntityOnly<SpiralAbyssEntry?>,
{
private readonly SpiralAbyssEntry? entity;
/// <summary>
/// 构造一个新的深渊视图
/// </summary>
/// <param name="entity">实体</param>
/// <param name="idAvatarMap">Id角色映射</param>
private SpiralAbyssView(SpiralAbyssEntry entity, SpiralAbyssMetadataContext context)
: this(context.IdScheduleMap[entity.ScheduleId], context)
{
@@ -32,12 +27,12 @@ internal sealed class SpiralAbyssView : IEntityOnly<SpiralAbyssEntry?>,
TotalBattleTimes = spiralAbyss.TotalBattleTimes;
TotalStar = spiralAbyss.TotalStar;
MaxFloor = spiralAbyss.MaxFloor;
Reveals = spiralAbyss.RevealRank.SelectList(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId]));
Defeat = spiralAbyss.DefeatRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
Damage = spiralAbyss.DamageRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
TakeDamage = spiralAbyss.TakeDamageRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
NormalSkill = spiralAbyss.NormalSkillRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
EnergySkill = spiralAbyss.EnergySkillRank.Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
Reveals = ToRankAvatars(spiralAbyss.RevealRank, context);
Defeat = ToRankAvatar(spiralAbyss.DefeatRank, context);
Damage = ToRankAvatar(spiralAbyss.DamageRank, context);
TakeDamage = ToRankAvatar(spiralAbyss.TakeDamageRank, context);
NormalSkill = ToRankAvatar(spiralAbyss.NormalSkillRank, context);
EnergySkill = ToRankAvatar(spiralAbyss.EnergySkillRank, context);
Engaged = true;
foreach (Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Floor webFloor in spiralAbyss.Floors)
@@ -143,4 +138,15 @@ internal sealed class SpiralAbyssView : IEntityOnly<SpiralAbyssEntry?>,
return new(meta, context);
}
}
private static List<RankAvatar> ToRankAvatars(List<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Rank> ranks, SpiralAbyssMetadataContext context)
{
return ranks.Where(r => r.AvatarId != 0U).Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).ToList();
}
private static RankAvatar? ToRankAvatar(List<Web.Hoyolab.Takumi.GameRecord.SpiralAbyss.Rank> ranks, SpiralAbyssMetadataContext context)
{
return ranks.Where(r => r.AvatarId != 0U).Select(r => new RankAvatar(r.Value, context.IdAvatarMap[r.AvatarId])).SingleOrDefault();
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Web.Hoyolab;
using System.Diagnostics;
namespace Snap.Hutao.Web.Bridge;
@@ -14,7 +13,7 @@ namespace Snap.Hutao.Web.Bridge;
internal static class CoreWebView2Extension
{
[Conditional("RELEASE")]
public static void DisableDevToolsOnReleaseBuild(this CoreWebView2 webView)
public static void DisableDevToolsForReleaseBuild(this CoreWebView2 webView)
{
CoreWebView2Settings settings = webView.Settings;
settings.AreBrowserAcceleratorKeysEnabled = false;
@@ -38,71 +37,4 @@ internal static class CoreWebView2Extension
manager.DeleteCookie(item);
}
}
/// <summary>
/// 设置 移动端UA
/// </summary>
/// <param name="webView">webView2</param>
/// <returns>链式调用的WebView2</returns>
public static CoreWebView2 SetMobileUserAgent(this CoreWebView2 webView)
{
webView.Settings.UserAgent = HoyolabOptions.MobileUserAgent;
return webView;
}
/// <summary>
/// 设置 移动端OsUA
/// </summary>
/// <param name="webView">webView2</param>
/// <returns>链式调用的WebView2</returns>
public static CoreWebView2 SetMobileOverseaUserAgent(this CoreWebView2 webView)
{
webView.Settings.UserAgent = HoyolabOptions.MobileUserAgentOversea;
return webView;
}
/// <summary>
/// 设置WebView2的Cookie
/// </summary>
/// <param name="webView">webView2</param>
/// <param name="cookieToken">CookieToken</param>
/// <param name="lToken">LToken</param>
/// <param name="sToken">SToken</param>
/// <param name="isOversea">是否为国际服,用于改变 cookie domain</param>
/// <returns>链式调用的WebView2</returns>
public static CoreWebView2 SetCookie(this CoreWebView2 webView, Cookie? cookieToken = null, Cookie? lToken = null, Cookie? sToken = null, bool isOversea = false)
{
CoreWebView2CookieManager cookieManager = webView.CookieManager;
if (cookieToken is not null)
{
cookieManager
.AddMihoyoCookie(Cookie.ACCOUNT_ID, cookieToken, isOversea)
.AddMihoyoCookie(Cookie.COOKIE_TOKEN, cookieToken, isOversea);
}
if (lToken is not null)
{
cookieManager
.AddMihoyoCookie(Cookie.LTUID, lToken, isOversea)
.AddMihoyoCookie(Cookie.LTOKEN, lToken, isOversea);
}
if (sToken is not null)
{
cookieManager
.AddMihoyoCookie(Cookie.MID, sToken, isOversea)
.AddMihoyoCookie(Cookie.STUID, sToken, isOversea)
.AddMihoyoCookie(Cookie.STOKEN, sToken, isOversea);
}
return webView;
}
private static CoreWebView2CookieManager AddMihoyoCookie(this CoreWebView2CookieManager manager, string name, Cookie cookie, bool isOversea = false)
{
string domain = isOversea ? ".hoyolab.com" : ".mihoyo.com";
manager.AddOrUpdateCookie(manager.CreateCookie(name, cookie[name], domain, "/"));
return manager;
}
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Web.Bridge;
internal static class HoyolabCoreWebView2Extension
{
public static ValueTask DeleteCookiesAsync(this CoreWebView2 webView, bool isOversea)
{
return webView.DeleteCookiesAsync(isOversea ? ".hoyolab.com" : ".mihoyo.com");
}
public static CoreWebView2 SetMobileUserAgent(this CoreWebView2 webView, bool isOversea)
{
return isOversea
? webView.SetMobileUserAgentOversea()
: webView.SetMobileUserAgentChinese();
}
/// <summary>
/// 设置 移动端UA
/// </summary>
/// <param name="webView">webView2</param>
/// <returns>链式调用的WebView2</returns>
public static CoreWebView2 SetMobileUserAgentChinese(this CoreWebView2 webView)
{
webView.Settings.UserAgent = HoyolabOptions.MobileUserAgent;
return webView;
}
/// <summary>
/// 设置 移动端OsUA
/// </summary>
/// <param name="webView">webView2</param>
/// <returns>链式调用的WebView2</returns>
public static CoreWebView2 SetMobileUserAgentOversea(this CoreWebView2 webView)
{
webView.Settings.UserAgent = HoyolabOptions.MobileUserAgentOversea;
return webView;
}
/// <summary>
/// 设置WebView2的Cookie
/// </summary>
/// <param name="webView">webView2</param>
/// <param name="cookieToken">CookieToken</param>
/// <param name="lToken">LToken</param>
/// <param name="isOversea">是否为国际服,用于改变 cookie domain</param>
/// <returns>链式调用的WebView2</returns>
public static CoreWebView2 SetCookie(this CoreWebView2 webView, Cookie? cookieToken = null, Cookie? lToken = null, bool isOversea = false)
{
CoreWebView2CookieManager cookieManager = webView.CookieManager;
if (cookieToken is not null)
{
cookieManager
.AddMihoyoCookie(Cookie.ACCOUNT_ID, cookieToken, isOversea)
.AddMihoyoCookie(Cookie.COOKIE_TOKEN, cookieToken, isOversea);
}
if (lToken is not null)
{
cookieManager
.AddMihoyoCookie(Cookie.LTUID, lToken, isOversea)
.AddMihoyoCookie(Cookie.LTOKEN, lToken, isOversea);
}
return webView;
}
private static CoreWebView2CookieManager AddMihoyoCookie(this CoreWebView2CookieManager manager, string name, Cookie cookie, bool isOversea = false)
{
string domain = isOversea ? ".hoyolab.com" : ".mihoyo.com";
manager.AddOrUpdateCookie(manager.CreateCookie(name, cookie[name], domain, "/"));
return manager;
}
}

View File

@@ -39,19 +39,20 @@ internal class MiHoYoJSInterface
document.querySelector('body').appendChild(st);
""";
private readonly SemaphoreSlim webMessageSemaphore = new(1);
private readonly Guid interfaceId = Guid.NewGuid();
private readonly IServiceProvider serviceProvider;
private readonly UserAndUid userAndUid;
private CoreWebView2 webView;
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly ILogger<MiHoYoJSInterface> logger;
private readonly SemaphoreSlim webMessageSemaphore = new(1);
private readonly TypedEventHandler<CoreWebView2, CoreWebView2WebMessageReceivedEventArgs> webMessageReceivedEventHandler;
private readonly TypedEventHandler<CoreWebView2, CoreWebView2DOMContentLoadedEventArgs> domContentLoadedEventHandler;
private readonly TypedEventHandler<CoreWebView2, CoreWebView2NavigationStartingEventArgs> navigationStartingEventHandler;
private CoreWebView2 webView;
public MiHoYoJSInterface(CoreWebView2 webView, UserAndUid userAndUid)
{
// 由于Webview2 的作用域特殊性,我们在此处直接使用根服务
@@ -374,14 +375,18 @@ internal class MiHoYoJSInterface
await taskContext.SwitchToMainThreadAsync();
try
{
return await webView.ExecuteScriptAsync(js);
if (webView is not null)
{
return await webView.ExecuteScriptAsync(js);
}
}
catch (COMException)
{
// COMException (0x8007139F): 组或资源的状态不是执行请求操作的正确状态。 (0x8007139F)
// webview is disposing or disposed
return string.Empty;
}
return string.Empty;
}
private async void OnWebMessageReceived(CoreWebView2 webView2, CoreWebView2WebMessageReceivedEventArgs args)

View File

@@ -57,7 +57,7 @@ internal sealed partial class SignInClientOversea : ISignInClient
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(ApiOsEndpoints.SignInRewardSign)
.SetUserCookie(userAndUid, CookieType.CookieToken)
.PostJson(new SignInData(userAndUid.Uid, false));
.PostJson(new SignInData(userAndUid.Uid, true));
Response<SignInResult>? resp = await builder
.TryCatchSendAsync<Response<SignInResult>>(httpClient, logger, token)
@@ -73,7 +73,7 @@ internal sealed partial class SignInClientOversea : ISignInClient
.SetRequestUri(ApiOsEndpoints.SignInRewardSign)
.SetUserCookie(userAndUid, CookieType.CookieToken)
.SetXrpcChallenge(challenge, validate)
.PostJson(new SignInData(userAndUid.Uid, false));
.PostJson(new SignInData(userAndUid.Uid, true));
resp = await verifiedBuilder
.TryCatchSendAsync<Response<SignInResult>>(httpClient, logger, token)

View File

@@ -22,13 +22,23 @@ internal sealed partial class HomaGeetestClient
{
string template = appOptions.GeetestCustomCompositeUrl;
if (string.IsNullOrEmpty(template))
string url;
try
{
url = template.Format(gt, challenge);
}
catch (FormatException)
{
return GeetestResponse.InternalFailure;
}
if (string.IsNullOrEmpty(template) || !Uri.TryCreate(url, UriKind.Absolute, out Uri? uri))
{
return GeetestResponse.InternalFailure;
}
HttpRequestMessageBuilder builder = httpRequestMessageBuilderFactory.Create()
.SetRequestUri(template.Format(gt, challenge))
.SetRequestUri(uri)
.Get();
GeetestResponse? resp = await builder

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Net.Http;
using System.Text;
@@ -66,7 +67,7 @@ internal abstract class HttpContentSerializer : IHttpContentSerializer, IHttpCon
The content to be serialized does not match the specified type.
Expected an instance of the class "{contentType.FullName}", but got "{actualContentType.FullName}".
""";
throw new ArgumentException(message, nameof(contentType));
ThrowHelper.Argument(message, nameof(contentType));
}
// The contentType is optional. In that case, try to get the type on our own.

View File

@@ -33,6 +33,11 @@ internal static class HttpRequestMessageBuilderExtension
logger.LogWarning(ex, RequestErrorMessage);
return default;
}
catch (HttpContentSerializationException ex)
{
logger.LogWarning(ex, RequestErrorMessage);
return default;
}
catch (SocketException ex)
{
logger.LogWarning(ex, RequestErrorMessage);