diff --git a/.github/ISSUE_TEMPLATE/bug1.yml b/.github/ISSUE_TEMPLATE/bug1.yml index 68cd2ba..af235f1 100644 --- a/.github/ISSUE_TEMPLATE/bug1.yml +++ b/.github/ISSUE_TEMPLATE/bug1.yml @@ -12,7 +12,7 @@ body: required: false - label: "**我已尝试使用 HMCL 启动,HMCL 没有出现问题。** 如果 HMCL 也无法启动就不是 PCL 导致的问题,请 **不要** 提交反馈。[下载 HMCL](https://hmcl.huangyuhui.net/download)" required: true - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" required: true - type: textarea id: "yml-2" diff --git a/.github/ISSUE_TEMPLATE/bug2.yml b/.github/ISSUE_TEMPLATE/bug2.yml index de636f0..0490f61 100644 --- a/.github/ISSUE_TEMPLATE/bug2.yml +++ b/.github/ISSUE_TEMPLATE/bug2.yml @@ -12,7 +12,7 @@ body: required: false - label: "我知晓大多数此类问题都是网络环境不佳导致的,但我确实认为我的问题可能是 PCL 导致的,和网络环境无关。" required: true - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" required: true - type: textarea id: "yml-2" diff --git a/.github/ISSUE_TEMPLATE/bug9.yml b/.github/ISSUE_TEMPLATE/bug9.yml index be20097..73980a7 100644 --- a/.github/ISSUE_TEMPLATE/bug9.yml +++ b/.github/ISSUE_TEMPLATE/bug9.yml @@ -8,7 +8,7 @@ body: label: "检查项" description: "请逐个检查下列项目,并勾选确认。" options: - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" required: true - type: textarea id: "yml-2" diff --git a/.github/ISSUE_TEMPLATE/ch.yml b/.github/ISSUE_TEMPLATE/ch.yml index 0ddd693..fc3ee5d 100644 --- a/.github/ISSUE_TEMPLATE/ch.yml +++ b/.github/ISSUE_TEMPLATE/ch.yml @@ -8,9 +8,9 @@ body: label: "检查项" description: "请逐个检查下列项目,并勾选确认。" options: - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一建议未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一建议未被提交过。" required: true - - label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。" + - label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。" required: true - type: textarea id: "yml-2" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 082755e..3a6eed8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,14 +1,14 @@ blank_issues_enabled: false contact_links: - name: 主页预设反馈 - url: https://github.com/Hex-Dragon/PCL2/discussions/categories/自定义主页 + url: https://github.com/Meloong-Git/PCL/discussions/categories/自定义主页 about: 提交与预设的主页(设置 → 个性化 → 主页预设)中的具体内容相关的反馈 - name: 帮助文档反馈 url: https://github.com/LTCatt/PCL2Help/issues about: 提交与 PCL 帮助文档(更多 → 帮助)中的具体内容相关的反馈 - name: 提问 - url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E6%8F%90%E9%97%AE + url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE about: 我想问一些 PCL 相关的问题…… - name: 讨论 - url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E8%AE%A8%E8%AE%BA + url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E8%AE%A8%E8%AE%BA about: 我想讨论一些 PCL 相关的事情…… diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index e27452b..f434a0a 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -8,9 +8,9 @@ body: label: "检查项" description: "请逐个检查下列项目,并勾选确认。" options: - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一提案未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一提案未被提交过。" required: true - - label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。" + - label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。" required: true - label: "我知晓还没做的新功能真的太多了,忙不过来,所以新功能提案几乎不会被处理,也不建议再提交新功能提案 qwq……" required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbebe6a..b6de4af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,6 @@ jobs: - name: Update Help run: | - $workpath = [System.Environment]::CurrentDirectory Set-Location .. git clone https://github.com/LTCatt/PCL2Help.git -b master --single-branch --depth 1 Set-Location PCL2Help @@ -52,8 +51,8 @@ jobs: - name: Replace run: | - (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public Const OAuthClientId As String = ""', 'Public Const OAuthClientId As String = "${{ secrets.CLIENT_ID }}"' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb" - (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public Const CurseForgeAPIKey As String = ""', 'Public Const CurseForgeAPIKey As String = "${{ secrets.CURSEFORGE_API_KEY }}"' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb" + (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "")', 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "${{ secrets.CLIENT_ID }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb" + (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "")', 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "${{ secrets.CURSEFORGE_API_KEY }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb" (gc "Plain Craft Launcher 2\Modules\Base\ModBase.vb") -replace 'Public Const CommitHash As String = ""', 'Public Const CommitHash As String = "${{ github.sha }}"' | Out-File "Plain Craft Launcher 2\Modules\Base\ModBase.vb" - name: Build diff --git a/LICENCE b/LICENCE index f9e821b..c09c900 100644 --- a/LICENCE +++ b/LICENCE @@ -44,6 +44,6 @@ 这些规则主要是为了阻止恶意的使用和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧! 如果你只是参考了一小段代码,署个名就行啦,不用担心。 -如果对具体细节有疑问,欢迎在 https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E6%8F%90%E9%97%AE 发帖询问! +如果对具体细节有疑问,欢迎在 https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE 发帖询问! 多谢大家啦! diff --git a/Plain Craft Launcher 2/App.config b/Plain Craft Launcher 2/App.config index 8d134eb..0e1a473 100644 --- a/Plain Craft Launcher 2/App.config +++ b/Plain Craft Launcher 2/App.config @@ -30,6 +30,10 @@ + + + + diff --git a/Plain Craft Launcher 2/Application.xaml b/Plain Craft Launcher 2/Application.xaml index a24bad7..3d5488f 100644 --- a/Plain Craft Launcher 2/Application.xaml +++ b/Plain Craft Launcher 2/Application.xaml @@ -1,6 +1,7 @@  + diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb index 5997359..b527334 100644 --- a/Plain Craft Launcher 2/Application.xaml.vb +++ b/Plain Craft Launcher 2/Application.xaml.vb @@ -140,6 +140,7 @@ WaitRetry: ServicePointManager.Expect100Continue = True ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12 ServicePointManager.DefaultConnectionLimit = 1024 + ServicePointManager.UseNagleAlgorithm = False '计时 Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms") ApplicationStartTick = GetTimeTick() @@ -181,53 +182,29 @@ WaitRetry: End Sub '动态 DLL 调用 - Private Shared AssemblyNAudio As Assembly - Private Shared AssemblyJson As Assembly - Private Shared AssemblyDialog As Assembly - Private Shared AssemblyImazenWebp As Assembly - Private Shared ReadOnly AssemblyNAudioLock As New Object - Private Shared ReadOnly AssemblyJsonLock As New Object - Private Shared ReadOnly AssemblyDialogLock As New Object - Private Shared ReadOnly AssemblyImazenWebpLock As New Object Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean - Public Shared Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly - If args.Name.StartsWithF("NAudio") Then - SyncLock AssemblyNAudioLock - If AssemblyNAudio Is Nothing Then - Log("[Start] 加载 DLL:NAudio") - AssemblyNAudio = Assembly.Load(GetResources("NAudio")) - End If - Return AssemblyNAudio - End SyncLock - ElseIf args.Name.StartsWithF("Newtonsoft.Json") Then - SyncLock AssemblyJsonLock - If AssemblyJson Is Nothing Then - Log("[Start] 加载 DLL:Json") - AssemblyJson = Assembly.Load(GetResources("Json")) - End If - Return AssemblyJson - End SyncLock - ElseIf args.Name.StartsWithF("Ookii.Dialogs.Wpf") Then - SyncLock AssemblyDialogLock - If AssemblyDialog Is Nothing Then - Log("[Start] 加载 DLL:Dialogs") - AssemblyDialog = Assembly.Load(GetResources("Dialogs")) - End If - Return AssemblyDialog - End SyncLock - ElseIf args.Name.StartsWithF("Imazen.WebP") Then - SyncLock AssemblyImazenWebpLock - If AssemblyImazenWebp Is Nothing Then - Log("[Start] 加载 DLL:Imazen.WebP") - AssemblyImazenWebp = Assembly.Load(GetResources("Imazen_WebP")) - SetDllDirectory(PathPure.TrimEnd("\")) + Public Shared Function AssemblyResolve(sender As Object, Args As ResolveEventArgs) As Assembly + '缓存 + Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "System.Net.Http.Formatting"} + Static Locks As New Dictionary(Of String, Object)(StringComparer.Ordinal) + Static LoadedAssembly As New Dictionary(Of String, Assembly)(StringComparer.Ordinal) + '查找对应的 DLL + Dim Prefix As String = Prefixes.FirstOrDefault(Function(p) Args.Name.StartsWithF(p)) + If Prefix Is Nothing Then Return Nothing + '加载 DLL + If Not Locks.ContainsKey(Prefix) Then Locks(Prefix) = New Object() + SyncLock Locks(Prefix) + If Not LoadedAssembly.ContainsKey(Prefix) Then + Log($"[Start] 加载 DLL:{Prefix}") + LoadedAssembly(Prefix) = Assembly.Load(GetResources(Prefix)) + 'WebP 特判 + If Prefix = "Imazen.WebP" Then + SetDllDirectory(PathPure.TrimEnd("\"c)) WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64")) End If - Return AssemblyImazenWebp - End SyncLock - Else - Return Nothing - End If + End If + Return LoadedAssembly(Prefix) + End SyncLock End Function '切换窗口 diff --git a/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb b/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb new file mode 100644 index 0000000..b145b27 --- /dev/null +++ b/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb @@ -0,0 +1,187 @@ +' Author: uye (owner of the MaaAssistantArknights team) +' Original Source: MaaAssistantArknights project - https://github.com/MaaAssistantArknights/MaaAssistantArknights +' License: Apache License 2.0 (this file only) +' +' This file is originally developed for MaaAssistantArknights and licensed under AGPL v3.0. +' As the original author and copyright holder, +' I hereby re-license this file under the Apache License 2.0 for use in the Plain Craft Launcher project. +' +' This file is contributed under the spirit of good faith and open cooperation. +' It does not implement any core launcher functions of PCL. +' +' Description: +' Provides a WPF clipboard handling fix to avoid OpenClipboard exceptions +' in TextBox, RichTextBox, and DataGrid under Windows focus issues or clipboard hooks. +' +' Date: 2025-07-03 + +Imports System.Windows +Imports System.Windows.Controls +Imports System.Windows.Input +Imports System.Windows.Documents +Imports System.Linq + +Namespace Controls.Behaviors + Public NotInheritable Class ClipboardInterceptor + Private Sub New() + End Sub + + Public Shared ReadOnly EnableSafeClipboardProperty As DependencyProperty = + DependencyProperty.RegisterAttached("EnableSafeClipboard", GetType(Boolean), GetType(ClipboardInterceptor), + New PropertyMetadata(False, AddressOf OnEnableSafeClipboardChanged)) + + Public Shared Sub SetEnableSafeClipboard(element As DependencyObject, value As Boolean) + element.SetValue(EnableSafeClipboardProperty, value) + End Sub + + Public Shared Function GetEnableSafeClipboard(element As DependencyObject) As Boolean + Return CBool(element.GetValue(EnableSafeClipboardProperty)) + End Function + + Private Shared Sub OnEnableSafeClipboardChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) + If TypeOf d Is TextBox AndAlso CBool(e.NewValue) Then + AddCommandBindingsToTextBox(DirectCast(d, TextBox)) + ElseIf TypeOf d Is RichTextBox AndAlso CBool(e.NewValue) Then + AddCommandBindingsToRichTextBox(DirectCast(d, RichTextBox)) + ElseIf TypeOf d Is DataGrid AndAlso CBool(e.NewValue) Then + AddCommandBindingsToDataGrid(DirectCast(d, DataGrid)) + End If + End Sub + + Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox) + tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox)) + tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox)) + tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox)) + End Sub + + Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox) + rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyRichTextBox)) + rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutRichTextBox)) + rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteRichTextBox)) + End Sub + + Private Shared Sub AddCommandBindingsToDataGrid(dg As DataGrid) + dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid)) + End Sub + + Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim tb = TryCast(sender, TextBox) + If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(tb.SelectedText, True) + Catch + End Try + + e.Handled = True + End Sub + + Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim tb = TryCast(sender, TextBox) + If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(tb.SelectedText, True) + Catch + End Try + + tb.SelectedText = String.Empty + e.Handled = True + End Sub + + Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim tb = TryCast(sender, TextBox) + If tb Is Nothing Then Return + + If Forms.Clipboard.ContainsText() Then + Dim pasteText = Forms.Clipboard.GetText() + Dim start = tb.SelectionStart + + tb.SelectedText = pasteText + tb.CaretIndex = start + pasteText.Length + tb.SelectionLength = 0 + End If + + e.Handled = True + End Sub + + Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim rtb = TryCast(sender, RichTextBox) + If rtb Is Nothing Then Return + + Dim textRange = New TextRange(rtb.Selection.Start, rtb.Selection.End) + If String.IsNullOrEmpty(textRange.Text) Then Return + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(textRange.Text, True) + Catch + End Try + + e.Handled = True + End Sub + + Private Shared Sub OnCutRichTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim rtb = TryCast(sender, RichTextBox) + If rtb Is Nothing Then Return + + Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End) + If String.IsNullOrEmpty(selection.Text) Then Return + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(selection.Text, True) + Catch + End Try + + selection.Text = String.Empty + e.Handled = True + End Sub + + Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim rtb = TryCast(sender, RichTextBox) + If rtb Is Nothing Then Return + + If Not Forms.Clipboard.ContainsText() Then Return + + Dim pasteText = Forms.Clipboard.GetText() + Dim selection = rtb.Selection + + selection.Text = pasteText + + Dim caretPos = selection.End + rtb.CaretPosition = caretPos + rtb.Selection.Select(caretPos, caretPos) + + e.Handled = True + End Sub + + Private Shared Sub OnCopyDataGrid(sender As Object, e As ExecutedRoutedEventArgs) + Dim dg = TryCast(sender, DataGrid) + If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return + + Dim sb = New System.Text.StringBuilder() + Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item) + + For Each row In rowGroups + Dim rowText = String.Join(vbTab, row.Select(Function(cell) + Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock) + Return If(tb IsNot Nothing, tb.Text, "") + End Function)) + sb.AppendLine(rowText) + Next + + Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf) + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(sbStr, True) + Catch + End Try + + e.Handled = True + End Sub + End Class +End Namespace diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb index decffd4..444bcb9 100644 --- a/Plain Craft Launcher 2/Controls/MyCard.vb +++ b/Plain Craft Launcher 2/Controls/MyCard.vb @@ -90,7 +90,7 @@ MainGrid.Children.Add(MainSwap) End If '改变默认的折叠 - If IsSwaped AndAlso SwapControl IsNot Nothing Then + If IsSwapped AndAlso SwapControl IsNot Nothing Then MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0)) '取消由于高度变化被迫触发的高度动画 Dim RawUseAnimation As Boolean = UseAnimation @@ -224,7 +224,7 @@ Private ActualUsedHeight As Double '回滚实际高度(例如 NaN) Private Sub MySizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged If Not UseAnimation Then Return - Dim DeltaHeight As Double = If(IsSwaped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height + Dim DeltaHeight As Double = If(IsSwapped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height '卡片的进入时动画已被页面通用切换动画替代 If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Return StartHeightAnimation(DeltaHeight, e.PreviousSize.Height, False) @@ -269,18 +269,18 @@ Sub() IsHeightAnimating = False Height = ActualUsedHeight - If IsSwaped AndAlso SwapControl IsNot Nothing Then SwapControl.Visibility = Visibility.Collapsed + If IsSwapped AndAlso SwapControl IsNot Nothing Then SwapControl.Visibility = Visibility.Collapsed End Sub,, True)) AniStart(AnimList, "MyCard Height " & Uuid) IsHeightAnimating = True - ActualUsedHeight = If(IsSwaped, SwapedHeight, Height) + ActualUsedHeight = If(IsSwapped, SwapedHeight, Height) Height = PreviousHeight End Sub ''' ''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。 ''' Public Sub TriggerForceResize() - Height = If(IsSwaped, SwapedHeight, Double.NaN) + Height = If(IsSwapped, SwapedHeight, Double.NaN) AniStop("MyCard Height " & Uuid) IsHeightAnimating = False End Sub @@ -300,26 +300,45 @@ ''' ''' 是否已被折叠。 ''' - Public Property IsSwaped As Boolean + Public Property IsSwapped As Boolean Get - Return _IsSwaped + Return _IsSwapped End Get Set(value As Boolean) - If _IsSwaped = value Then Return - _IsSwaped = value + If _IsSwapped = value Then Return + _IsSwapped = value If SwapControl Is Nothing Then Return '展开 - If Not IsSwaped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title) + If Not IsSwapped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title) '若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行 If Not IsLoaded Then Return '更新高度 SwapControl.Visibility = Visibility.Visible TriggerForceResize() '改变箭头 - AniStart(AaRotateTransform(MainSwap, If(_IsSwaped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True) + AniStart(AaRotateTransform(MainSwap, If(_IsSwapped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True) End Set End Property - Private _IsSwaped As Boolean = False + Private _IsSwapped As Boolean = False + ''' + ''' 是否已被折叠。(已过时,请使用 IsSwapped) + ''' + + Public Property IsSwaped As Boolean + Get + Return GetValue(IsSwapedProperty) + End Get + Set(value As Boolean) + SetValue(IsSwapedProperty, value) + End Set + End Property + Public Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False, AddressOf OnIsSwapedPropertyChanged)) + Private Shared Sub OnIsSwapedPropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) + Dim self = TryCast(d, MyCard) + If self IsNot Nothing Then + self.IsSwapped = CType(e.NewValue, Boolean) + End If + End Sub Public Property SwapLogoRight As Boolean = False Private IsMouseDown As Boolean = False @@ -328,8 +347,8 @@ Public Const SwapedHeight As Integer = 40 Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown Dim Pos As Double = Mouse.GetPosition(Me).Y - If Not IsSwaped AndAlso - (SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判 + If Not IsSwapped AndAlso + (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判 IsMouseDown = True End Sub Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp @@ -337,8 +356,8 @@ IsMouseDown = False Dim Pos As Double = Mouse.GetPosition(Me).Y - If Not IsSwaped AndAlso - (SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判 + If Not IsSwapped AndAlso + (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判 Dim ee = New RouteEventArgs(True) RaiseEvent PreviewSwap(Me, ee) @@ -347,8 +366,8 @@ Return End If - IsSwaped = Not IsSwaped - Log("[Control] " & If(IsSwaped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title)) + IsSwapped = Not IsSwapped + Log("[Control] " & If(IsSwapped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title)) RaiseEvent Swap(Me, ee) End Sub Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave diff --git a/Plain Craft Launcher 2/Controls/MyTextBox.vb b/Plain Craft Launcher 2/Controls/MyTextBox.vb index 9149737..e847a58 100644 --- a/Plain Craft Launcher 2/Controls/MyTextBox.vb +++ b/Plain Craft Launcher 2/Controls/MyTextBox.vb @@ -264,7 +264,7 @@ If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画 '有动画 AniStart({ - AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime), + AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime), AaColor(Me, BackgroundProperty, BackColorName, AnimationTime) }, "MyTextBox Color " & Uuid) Else diff --git a/Plain Craft Launcher 2/Controls/MyTextButton.vb b/Plain Craft Launcher 2/Controls/MyTextButton.vb index b7e3a73..687ab88 100644 --- a/Plain Craft Launcher 2/Controls/MyTextButton.vb +++ b/Plain Craft Launcher 2/Controls/MyTextButton.vb @@ -57,7 +57,7 @@ Private Const AnimationTimeIn As Integer = 100 Private Const AnimationTimeOut As Integer = 200 Private ColorName As String - Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp + Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp '判断当前颜色 Dim ForeName As String Dim Time As Integer diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index b9a8d1d..f1befdc 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -1,4 +1,5 @@ Imports System.ComponentModel +Imports System.Windows.Interop Public Class FormMain @@ -10,6 +11,19 @@ Public Class FormMain Dim FeatureList As New List(Of KeyValuePair(Of Integer, String)) '统计更新日志条目 #If BETA Then + If LastVersion < 367 Then 'Release 2.10.6 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:启用 MCIM 社区资源镜像源,以缓解社区资源难以下载的问题")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:正版登录出错时无法给出正确的错误信息")) + FeatureCount += 9 + BugCount += 9 + End If + If LastVersion < 365 Then 'Release 2.10.5 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源时,会单独记忆每种资源上次下载到的文件夹,以防混淆")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:网络底层框架与下载稳定性")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法启动一部分 LabyMod 和 GTNH 客户端")) + FeatureCount += 22 + BugCount += 26 + End If If LastVersion < 361 Then 'Release 2.10.3 FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法安装部分使用老版本 PCL 导出的整合包")) End If @@ -82,6 +96,25 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- + If LastVersion < 366 Then 'Snapshot 2.10.6 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:启用 MCIM 社区资源镜像源,以缓解社区资源难以下载的问题")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:正版登录出错时无法给出正确的错误信息")) + FeatureCount += 9 + BugCount += 9 + End If + If LastVersion < 364 Then 'Snapshot 2.10.5 + If LastVersion >= 363 Then + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法添加新的正版账号")) + BugCount += 1 + End If + End If + If LastVersion < 363 Then 'Snapshot 2.10.4 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源时,会单独记忆每种资源上次下载到的文件夹,以防混淆")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:网络底层框架与下载稳定性")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法启动一部分 LabyMod 和 GTNH 客户端")) + FeatureCount += 22 + BugCount += 26 + End If If LastVersion < 362 Then 'Snapshot 2.10.3 FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法安装部分使用老版本 PCL 导出的整合包")) End If @@ -225,7 +258,7 @@ Public Class FormMain If Not Setup.IsUnset("LaunchArgumentIndie") Then Log("[Start] 从老 PCL 迁移版本隔离") Setup.Set("LaunchArgumentIndieV2", Setup.Get("LaunchArgumentIndie")) - ElseIf Not Setup.IsUnset("WindowHeight") Then + ElseIf Not Setup.IsUnset("LaunchVersionSelect") Then Log("[Start] 从老 PCL 升级,但此前未调整版本隔离,使用老的版本隔离默认值") Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndie")) Else @@ -242,13 +275,18 @@ Public Class FormMain '加载 UI InitializeComponent() Opacity = 0 - ''开启管理员权限下的文件拖拽,但下列代码也没用(#2531) - 'If IsAdmin() Then - ' Log("[Start] PCL 正以管理员权限运行") - ' ChangeWindowMessageFilter(&H233, 1) - ' ChangeWindowMessageFilter(&H4A, 1) - ' ChangeWindowMessageFilter(&H49, 1) - 'End If + '开启管理员权限下的文件拖拽 + If IsAdmin() Then + Static Helper As New DragHelper + AddHandler SourceInitialized, + Sub() + Dim WpfHelper As New WindowInteropHelper(Me) + Helper.HwndIntPtrSource = HwndSource.FromHwnd(WpfHelper.Handle) + Helper.AddHook() + End Sub + AddHandler Closing, Sub() Helper.RemoveDragHook() + AddHandler Helper.DragDrop, Sub() FileDrag(Helper.DropFilePaths) + End If '切换到首页 If Not IsNothing(FrmLaunchLeft.Parent) Then FrmLaunchLeft.SetValue(ContentPresenter.ContentProperty, Nothing) If Not IsNothing(FrmLaunchRight.Parent) Then FrmLaunchRight.SetValue(ContentPresenter.ContentProperty, Nothing) @@ -365,7 +403,9 @@ Public Class FormMain Catch ex As Exception Log(ex, "清理自动更新文件失败") End Try - End Sub, "Start Loader", ThreadPriority.Lowest) + '上报 + Telemetry("启动") + End Sub, "初始化", ThreadPriority.Lowest) Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms") End Sub @@ -540,7 +580,7 @@ Public Class FormMain If ReturnCode = ProcessReturnValues.Exception Then If Not IsLogShown Then FeedbackInfo() - Log("请在 https://github.com/Hex-Dragon/PCL2/issues 提交错误报告,以便于作者解决此问题!") + Log("请在 https://github.com/Meloong-Git/PCL/issues 提交错误报告,以便于作者解决此问题!") IsLogShown = True ShellOnly(Path & "PCL\Log1.txt") End If @@ -591,11 +631,12 @@ Public Class FormMain '按键事件 Private Sub FormMain_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown If e.IsRepeat Then Return - '调用弹窗:回车选择第一个,Esc 选择最后一个 - If PanMsg.Children.Count > 0 Then + '修复按下 Alt 后误认为弹出系统菜单导致的冻结 + If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True + '在有弹窗时:回车选择第一个,Esc 选择最后一个 + If PanMsg.Children.Any Then If e.Key = Key.Enter Then CType(PanMsg.Children(0), Object).Btn1_Click() - Return ElseIf e.Key = Key.Escape Then Dim Msg As Object = PanMsg.Children(0) If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then @@ -605,9 +646,14 @@ Public Class FormMain Else Msg.Btn1_Click() End If - Return End If + Return End If + + '========================== + ' 在没有弹窗时:继续检查…… + '========================== + '按 ESC 返回上一级 If e.Key = Key.Escape Then TriggerPageBack() '更改隐藏版本可见性 @@ -641,8 +687,6 @@ Public Class FormMain FrmLaunchLeft.LaunchButtonClick() End If End If - '修复按下 Alt 后误认为弹出系统菜单导致的冻结 - If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True End Sub Private Sub FormMain_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseDown '鼠标侧键返回上一级 @@ -707,6 +751,7 @@ Public Class FormMain End Sub Private Sub FrmMain_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop Try + ShowWindowToTop() If e.Data.GetDataPresent(DataFormats.Text) Then '获取文本 Try @@ -845,7 +890,7 @@ Public Class FormMain '错误报告分析 Try Log("[System] 尝试进行错误报告分析") - Dim Analyzer As New CrashAnalyzer(GetUuid()) + Dim Analyzer As New CrashAnalyzer Analyzer.Import(FilePath) If Not Analyzer.Prepare() Then Exit Try Analyzer.Analyze() diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index 407855a..b4ef294 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -1,6 +1,6 @@ Imports System.Globalization Imports System.IO.Compression -Imports System.Reflection +Imports System.Net.Sockets Imports System.Runtime.CompilerServices Imports System.Security.Cryptography Imports System.Security.Principal @@ -13,13 +13,13 @@ Public Module ModBase #Region "声明" '下列版本信息由更新器自动修改 - Public Const VersionBaseName As String = "2.10.3" '不含分支前缀的显示用版本名 - Public Const VersionStandardCode As String = "2.10.3." & VersionBranchCode '标准格式的四段式版本号 + Public Const VersionBaseName As String = "2.10.6" '不含分支前缀的显示用版本名 + Public Const VersionStandardCode As String = "2.10.6." & VersionBranchCode '标准格式的四段式版本号 Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换 #If BETA Then - Public Const VersionCode As Integer = 361 'Release + Public Const VersionCode As Integer = 367 'Release #Else - Public Const VersionCode As Integer = 362 'Snapshot + Public Const VersionCode As Integer = 366 'Snapshot #End If '自动生成的版本信息 Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName @@ -791,8 +791,8 @@ Public Module ModBase Public Sub CopyFile(FromPath As String, ToPath As String) Try '还原文件路径 - If Not FromPath.Contains(":\") Then FromPath = Path & FromPath - If Not ToPath.Contains(":\") Then ToPath = Path & ToPath + FromPath = ShortenPath(If(FromPath.Contains(":\"), FromPath, Path & FromPath)) + ToPath = ShortenPath(If(ToPath.Contains(":\"), ToPath, Path & ToPath)) '如果复制同一个文件则跳过 If FromPath = ToPath Then Return '确保目录存在 @@ -861,7 +861,7 @@ Public Module ModBase ''' 是否将文件内容追加到当前文件,而不是覆盖它。 Public Sub WriteFile(FilePath As String, Text As String, Optional Append As Boolean = False, Optional Encoding As Encoding = Nothing) '处理相对路径 - If Not FilePath.Contains(":\") Then FilePath = Path & FilePath + FilePath = ShortenPath(If(FilePath.Contains(":\"), FilePath, Path & FilePath)) '确保目录存在 Directory.CreateDirectory(GetPathFromFullPath(FilePath)) '写入文件 @@ -886,7 +886,7 @@ Public Module ModBase ''' 是否将文件内容追加到当前文件,而不是覆盖它。 Public Sub WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False) '处理相对路径 - If Not FilePath.Contains(":\") Then FilePath = Path & FilePath + FilePath = ShortenPath(If(FilePath.Contains(":\"), FilePath, Path & FilePath)) '确保目录存在 Directory.CreateDirectory(GetPathFromFullPath(FilePath)) '写入文件 @@ -1294,11 +1294,11 @@ Re: ''' Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing, Optional ProgressIncrementHandler As Action(Of Double) = Nothing) - Directory.CreateDirectory(DestDirectory) + Directory.CreateDirectory(ShortenPath(DestDirectory)) If CompressFilePath.EndsWithF(".gz", True) Then '以 gz 方式解压 Dim stream As New GZipStream(New FileStream(CompressFilePath, FileMode.Open, FileAccess.ReadWrite), CompressionMode.Decompress) - Dim decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", ""), FileMode.OpenOrCreate, FileAccess.Write) + Dim decompressedFile As New FileStream(ShortenPath(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", "")), FileMode.OpenOrCreate, FileAccess.Write) Dim data As Integer = stream.ReadByte() While data <> -1 decompressedFile.WriteByte(data) @@ -1309,10 +1309,10 @@ Re: Else '以 zip 方式解压 Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030"))) - Dim TotalCount As Integer = Archive.Entries.Count + Dim TotalCount As Long = Archive.Entries.Count For Each Entry As ZipArchiveEntry In Archive.Entries - If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / TotalCount) - Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName) + If ProgressIncrementHandler IsNot Nothing AndAlso TotalCount > 0 Then ProgressIncrementHandler(1 / TotalCount) + Dim DestinationPath As String = ShortenPath(IO.Path.Combine(DestDirectory, Entry.FullName)) If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then Continue For '不创建空文件夹 Else @@ -1387,10 +1387,10 @@ RetryDir: ToPath = ToPath.Replace("/", "\") If Not ToPath.EndsWithF("\") Then ToPath &= "\" Dim AllFiles = EnumerateFiles(FromPath).ToList - Dim FileCount As Integer = AllFiles.Count + Dim FileCount As Long = AllFiles.Count For Each File In AllFiles CopyFile(File.FullName, File.FullName.Replace(FromPath, ToPath)) - If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / FileCount) + If ProgressIncrementHandler IsNot Nothing AndAlso FileCount > 0 Then ProgressIncrementHandler(1 / FileCount) Next End Sub ''' @@ -1449,22 +1449,8 @@ RetryDir: IsInner = True Loop - '常见错误(记得同时修改下面的) - Dim CommonReason As String = Nothing - If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then - CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。" - ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then - CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。" - ElseIf TypeOf InnerEx Is OutOfMemoryException Then - CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。" - ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then - CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。" - ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", - "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) DescList.Any(Function(l) l.Contains(s))) Then - CommonReason = "你的网络环境不佳,导致难以连接到服务器。请稍后重试,或使用 VPN 以改善网络环境。" - End If - '构造输出信息 + Dim CommonReason As String = AnalyzeCommonReason(InnerEx, DescList) If CommonReason Is Nothing Then Return DescList.Join(vbCrLf) Else @@ -1492,22 +1478,8 @@ RetryDir: DescList = DescList.Distinct.ToList Dim Desc As String = Join(DescList, vbCrLf & "→ ") - '常见错误(记得同时修改上面的) - Dim CommonReason As String = Nothing - If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then - CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。" - ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then - CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。" - ElseIf TypeOf InnerEx Is OutOfMemoryException Then - CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。" - ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then - CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。" - ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", - "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) Desc.Contains(s)) Then - CommonReason = "你的网络环境不佳,导致难以连接到服务器。请稍后重试,或使用 VPN 以改善网络环境。" - End If - '构造输出信息 + Dim CommonReason As String = AnalyzeCommonReason(InnerEx, DescList) If CommonReason IsNot Nothing Then Return CommonReason & "详细错误:" & DescList.First Else @@ -1515,6 +1487,24 @@ RetryDir: Return Join(DescList, " ← ") End If End Function + Private Function AnalyzeCommonReason(InnerEx As Exception, DescList As List(Of String)) As String + If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then + Return "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。" + ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then + Return "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。" + ElseIf TypeOf InnerEx Is OutOfMemoryException Then + Return "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。" + ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then + Return "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。" + ElseIf TypeOf InnerEx Is SocketException AndAlso DescList.Any(Function(l) l.Contains("WSAStartup")) Then + Return "请尝试卸载中国移动云盘,然后再试。" + ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", + "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) DescList.Any(Function(l) l.Contains(s))) Then + Return "你的网络环境不佳,导致难以连接到服务器。请稍后重试,或使用 VPN 以改善网络环境。" + Else + Return Nothing + End If + End Function ''' ''' 返回一个枚举对应的字符串。 @@ -1551,12 +1541,12 @@ RetryDir: ''' ''' 获取 JSON 对象。 ''' - Public Function GetJson(Data As String) + Public Function GetJson(Data As String) As JToken Try Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local}) Catch ex As Exception Dim Length As Integer = If(Data, "").Length - Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data)) + Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data), ex) End Try End Function @@ -2131,6 +2121,19 @@ RetryDir: End Function End Class + ''' + ''' 将二维数组转换为字典。 + ''' + Public Function ToDictionary(Of T)(arr As T(,)) As Dictionary(Of T, T) + Dim result As New Dictionary(Of T, T) + If arr.Length = 0 Then Return result + If arr.GetLength(1) <> 2 Then Throw New ArgumentException("数组必须为两列,第一列为 Key,第二列为 Value。") + For i As Integer = 0 To arr.GetLength(0) - 1 + result(arr(i, 0)) = arr(i, 1) + Next + Return result + End Function + ''' ''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。 ''' @@ -2179,10 +2182,10 @@ RetryDir: End Function ''' - ''' 判断当前系统语言是否为 zh-CN。 + ''' 判断当前系统语言是否为中文。 ''' Public Function IsSystemLanguageChinese() As Boolean - Return CultureInfo.CurrentCulture.Name = "zh-CN" OrElse CultureInfo.CurrentUICulture.Name = "zh-CN" + Return CultureInfo.CurrentCulture.Name.StartsWithF("zh-") OrElse CultureInfo.CurrentUICulture.Name.StartsWithF("zh-") End Function Private Uuid As Integer = 1 @@ -2225,6 +2228,9 @@ NextElement: Next i Return ResultArray End Function + Public Function Any(Arr As UIElementCollection) As Boolean + Return Arr?.Count > 0 + End Function ''' ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。 @@ -2462,7 +2468,7 @@ NextElement: ''' Public Sub RunInThread(Action As Action) If RunInUi() Then - RunInNewThread(Action, "Runtime Invoke " & GetUuid() & "#") + RunInNewThread(Action, "Invoke " & GetUuid()) Else Action() End If @@ -2625,10 +2631,6 @@ Retry: ''' 相对增减控件的左边距。 ''' Public Sub DeltaLeft(control As FrameworkElement, newValue As Double) - '安全性检查 - DebugAssert(Not Double.IsNaN(newValue)) - DebugAssert(Not Double.IsInfinity(newValue)) - If TypeOf control Is Window Then '窗口改变 CType(control, Window).Left += newValue @@ -2640,8 +2642,6 @@ Retry: Case HorizontalAlignment.Right control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right - newValue, control.Margin.Bottom) 'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom) - Case Else - DebugAssert(False) End Select End If End Sub @@ -2649,17 +2649,12 @@ Retry: ''' 设置控件的左边距。(仅针对置左控件) ''' Public Sub SetLeft(control As FrameworkElement, newValue As Double) - DebugAssert(control.HorizontalAlignment = HorizontalAlignment.Left) control.Margin = New Thickness(newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom) End Sub ''' ''' 相对增减控件的上边距。 ''' Public Sub DeltaTop(control As FrameworkElement, newValue As Double) - '安全性检查 - DebugAssert(Not Double.IsNaN(newValue)) - DebugAssert(Not Double.IsInfinity(newValue)) - If TypeOf control Is Window Then '窗口改变 CType(control, Window).Top += newValue @@ -2671,11 +2666,8 @@ Retry: Case VerticalAlignment.Bottom control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, control.Margin.Bottom - newValue) 'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom) - Case Else - DebugAssert(False) End Select End If - 'If Double.IsNaN(newValue) OrElse Double.IsInfinity(newValue) Then Return '安全性检查 'Select Case control.VerticalAlignment ' Case VerticalAlignment.Top, VerticalAlignment.Stretch, VerticalAlignment.Center @@ -2689,7 +2681,6 @@ Retry: ''' 设置控件的顶边距。(仅针对置上控件) ''' Public Sub SetTop(control As FrameworkElement, newValue As Double) - DebugAssert(control.VerticalAlignment = VerticalAlignment.Top) control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom) End Sub @@ -2760,11 +2751,11 @@ Retry: Using Reader As New XamlXmlReader(Stream) While Reader.Read() For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider)} - If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 类型。") - If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 值。") + If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 类型。") + If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 值。") Next For Each BlackListMember In {"Code", "FactoryMethod", "Static"} - If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListMember} 成员。") + If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListMember} 成员。") Next End While End Using @@ -2922,7 +2913,7 @@ Retry: '处理错误会导致再次调用 Log() 导致无限循环 '输出日志 - Dim AppendText As String = "[" & GetTimeNow() & "] " & Text & vbCrLf '减轻同步锁占用 + Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Text}{vbCrLf}" '减轻同步锁占用 If ModeDebug Then SyncLock LogListLock LogList.Append(AppendText) @@ -2973,6 +2964,13 @@ Retry: End If End Select + '上报 + If Level >= LogLevel.Hint Then + Telemetry("PCL 错误", + "Level", GetStringFromEnum(Level), + "Title", FilterUserName(FilterAccessToken(Title, "*"), "*"), + "Exception", FilterUserName(FilterAccessToken(Text, "*"), "*")) + End If End Sub ''' ''' 输出错误信息。 @@ -2986,7 +2984,7 @@ Retry: Dim ExFull As String = Desc & ":" & GetExceptionDetail(Ex) '输出日志 - Dim AppendText As String = "[" & GetTimeNow() & "] " & Desc & ":" & GetExceptionDetail(Ex, True) & vbCrLf '减轻同步锁占用 + Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{GetExceptionDetail(Ex, True)}{vbCrLf}" '减轻同步锁占用 If ModeDebug Then SyncLock LogListLock LogList.Append(AppendText) @@ -3039,6 +3037,13 @@ Retry: End If End Select + '上报 + If Level >= LogLevel.Hint Then + Telemetry("PCL 异常", + "Level", GetStringFromEnum(Level), + "Title", FilterUserName(FilterAccessToken(Title, "*"), "*"), + "Exception", FilterUserName(FilterAccessToken(ExFull, "*"), "*")) + End If End Sub '反馈 @@ -3048,7 +3053,7 @@ Retry: If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then OpenExplorer(Path & "PCL\Log1.txt") End If - OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/") + OpenWebsite("https://github.com/Meloong-Git/PCL/issues/") End Sub Public Function CanFeedback(ShowHint As Boolean) As Boolean If False.Equals(PageSetupSystem.IsLauncherNewest) Then @@ -3075,9 +3080,23 @@ Retry: "文件位置:" & Path) End Sub - '断言 - Public Sub DebugAssert(Exp As Boolean) - If Not Exp Then Throw New Exception("断言命中") + '遥测 + Public Sub Telemetry([Event] As String, ParamArray Datas As String()) + If Not Setup.Get("SystemSystemTelemetry") Then Return '用户关闭了遥测 + If Not ClsBaseUrl.StartsWithF("http") Then Return '开源版没有设置遥测地址 + RunInNewThread( + Sub() + Try + Log($"[System] 匿名数据上报:{[Event]}") + Dim Url As String = $"{ClsBaseUrl}&Event={WebUtility.UrlEncode([Event])}" + For i = 0 To Datas.Length - 1 Step 2 + Url &= "&" & WebUtility.UrlEncode(Datas(i)) & "=" & WebUtility.UrlEncode(Datas(i + 1).Replace(vbCrLf, vbLf)) + Next + NetRequestByClient(Url, MakeLog:=False) + Catch + Log("[System] 匿名数据上报失败", LogLevel.Debug) + End Try + End Sub, "Telemetry", ThreadPriority.Lowest) End Sub '获取当前的堆栈信息 diff --git a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb index c0bb4c8..4719587 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb @@ -339,7 +339,7 @@ [Error] = ex State = LoadState.Failed End Try - End Sub) With {.Name = Name, .Priority = ThreadPriority} + End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority} LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted End Sub Public Overrides Sub Abort() diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb index f93903a..6539356 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb @@ -1,102 +1,41 @@ -Public Module ModNet - Public Const NetDownloadEnd As String = ".PCLDownloading" +Imports System.Net.Http.Headers +Imports System.Threading.Tasks +Imports CacheCow.Client + +Public Module ModNet + +#Region "网络请求" ''' - ''' 测试 Ping。失败则返回 -1。 + ''' 发送一次网络请求并获取返回内容。 ''' - Public Function Ping(Ip As String, Optional Timeout As Integer = 10000, Optional MakeLog As Boolean = True) As Integer - Dim PingResult As NetworkInformation.PingReply + Public Function NetRequestByClient(Url As String, Optional Method As HttpMethod = Nothing, + Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*", + Optional Timeout As Integer = 25000, Optional Headers As String(,) = Nothing, + Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False, Optional MakeLog As Boolean = True) As String + If Method Is Nothing Then Method = HttpMethod.Get + If MakeLog Then Log("[Net] 发起网络请求(" & Method.Method & "," & Url & "),最大超时 " & Timeout) Try - PingResult = (New NetworkInformation.Ping).Send(Ip) + Dim HeaderDictionary = If(Headers, {}).ToDictionary + If ContentType IsNot Nothing Then HeaderDictionary("Content-Type") = ContentType + If Accept IsNot Nothing Then HeaderDictionary("Accept") = Accept + Return If(Encoding, Encoding.UTF8).GetString(SendRequest(Url, Method, Content, HeaderDictionary, Timeout:=Timeout, Encoding:=Encoding, UseBrowserUserAgent:=UseBrowserUserAgent)) + Catch ex As ThreadInterruptedException + Throw Catch ex As Exception - If MakeLog Then Log("[Net] Ping " & Ip & " 失败:" & ex.Message) - Return -1 + If MakeLog Then Log(ex, "网络请求失败", LogLevel.Developer) + Throw End Try - If PingResult.Status = NetworkInformation.IPStatus.Success Then - If MakeLog Then Log("[Net] Ping " & Ip & " 结束:" & PingResult.RoundtripTime & "ms") - Return PingResult.RoundtripTime - Else - If MakeLog Then Log("[Net] Ping " & Ip & " 失败") - Return -1 - End If End Function - ''' - ''' 以 WebClient 获取网页源代码。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。 + ''' 发送网络请求并获取返回内容。 + ''' 会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。 ''' - ''' 网页的 Url。 - ''' 网页的编码,通常为 UTF-8。 - Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Optional Accept As String = "application/json, text/javascript, */*; q=0.01", Optional UseBrowserUserAgent As Boolean = False) As String - Dim RetryCount As Integer = 0 - Dim RetryException As Exception = Nothing - Dim StartTime As Long = GetTimeTick() - Try -Retry: - Select Case RetryCount - Case 0 '正常尝试 - Return NetGetCodeByClient(Url, Encoding, 10000, Accept, UseBrowserUserAgent) - Case 1 '慢速重试 - Thread.Sleep(500) - Return NetGetCodeByClient(Url, Encoding, 30000, Accept, UseBrowserUserAgent) - Case Else '快速重试 - If GetTimeTick() - StartTime > 5500 Then - '若前两次加载耗费 5 秒以上,才进行重试 - Thread.Sleep(500) - Return NetGetCodeByClient(Url, Encoding, 4000, Accept, UseBrowserUserAgent) - Else - Throw RetryException - End If - End Select - Catch ex As Exception - Select Case RetryCount - Case 0 - RetryException = ex - RetryCount += 1 - GoTo Retry - Case 1 - RetryCount += 1 - GoTo Retry - Case Else - Throw - End Select - End Try - End Function - Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Timeout As Integer, Accept As String, Optional UseBrowserUserAgent As Boolean = False) As String - Url = SecretCdnSign(Url) - Log("[Net] 获取客户端网络结果:" & Url & ",最大超时 " & Timeout) - Dim Request As CookieWebClient - Dim res As HttpWebResponse = Nothing - Dim HttpStream As Stream = Nothing - Try - Request = New CookieWebClient With { - .Encoding = Encoding, - .Timeout = Timeout - } - Request.Headers("Accept") = Accept - Request.Headers("Accept-Language") = "en-US,en;q=0.5" - Request.Headers("X-Requested-With") = "XMLHttpRequest" - SecretHeadersSign(Url, Request, UseBrowserUserAgent) - Return Request.DownloadString(Url) - Catch ex As Exception - If ex.GetType.Equals(GetType(WebException)) AndAlso CType(ex, WebException).Status = WebExceptionStatus.Timeout Then - Throw New TimeoutException("连接服务器超时(" & Url & ")", ex) - Else - Throw New WebException("获取结果失败," & ex.Message & "(" & Url & ")", ex) - End If - Finally - If Not IsNothing(HttpStream) Then HttpStream.Dispose() - If Not IsNothing(res) Then res.Dispose() - End Try - End Function - - ''' - ''' 以 WebRequest 获取网页源代码或 Json。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。 - ''' - ''' 网页的 Url。 - ''' 网页的编码,通常为 UTF-8。 ''' 如果第一次尝试失败,换用的备用 URL。 - Public Function NetGetCodeByRequestRetry(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", - Optional IsJson As Boolean = False, Optional BackupUrl As String = Nothing, Optional UseBrowserUserAgent As Boolean = False) + Public Function NetRequestByClientRetry(Url As String, Optional Method As HttpMethod = Nothing, Optional BackupUrl As String = Nothing, + Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*", + Optional Headers As String(,) = Nothing, + Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False) As String Dim RetryCount As Integer = 0 Dim RetryException As Exception = Nothing Dim StartTime As Long = GetTimeTick() @@ -104,15 +43,15 @@ Retry: Retry: Select Case RetryCount Case 0 '正常尝试 - Return NetGetCodeByRequestOnce(Url, Encode, 10000, IsJson, Accept, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 10000, Headers, Encoding, UseBrowserUserAgent) Case 1 '慢速重试 Thread.Sleep(500) - Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 30000, IsJson, Accept, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 30000, Headers, Encoding, UseBrowserUserAgent) Case Else '快速重试 If GetTimeTick() - StartTime > 5500 Then '若前两次加载耗费 5 秒以上,才进行重试 Thread.Sleep(500) - Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 4000, IsJson, Accept, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 4000, Headers, Encoding, UseBrowserUserAgent) Else Throw RetryException End If @@ -134,20 +73,22 @@ Retry: End Try End Function ''' - ''' 以 WebRequest 获取网页源代码或 Json。会逐渐生成 4 个尝试线程,并在 60s 后超时。 + ''' 生成数个线程,同时发送网络请求并获取返回内容。 + ''' 每个线程开始之前会略有延迟。 ''' - ''' 网页的 Url。 - ''' 网页的编码,通常为 UTF-8。 - Public Function NetGetCodeByRequestMultiple(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False) + Public Function NetRequestByClientMultiple(Url As String, Optional Method As HttpMethod = Nothing, Optional ThreadCount As Integer = 3, + Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*", + Optional Headers As String(,) = Nothing, Optional Timeout As Integer = 25000, + Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False) As String Dim Threads As New List(Of Thread) Dim RequestResult = Nothing Dim RequestEx As Exception = Nothing Dim FailCount As Integer = 0 - For i = 1 To 4 + For i = 1 To ThreadCount Dim th As New Thread( Sub() Try - RequestResult = NetGetCodeByRequestOnce(Url, Encode, 30000, IsJson, Accept) + RequestResult = NetRequestByClient(Url, Method, Content, ContentType, Accept, Timeout, Headers, Encoding, UseBrowserUserAgent) Catch ex As Exception FailCount += 1 RequestEx = ex @@ -168,7 +109,7 @@ RequestFinished: Catch End Try Return RequestResult - ElseIf FailCount = 4 Then + ElseIf FailCount = ThreadCount Then Try For Each th In Threads If th.IsAlive Then th.Interrupt() @@ -179,124 +120,71 @@ RequestFinished: End If Thread.Sleep(20) Loop - Throw New Exception("未知错误") - End Function - Public Function NetGetCodeByRequestOnce(Url As String, Optional Encode As Encoding = Nothing, Optional Timeout As Integer = 30000, Optional IsJson As Boolean = False, Optional Accept As String = "", Optional UseBrowserUserAgent As Boolean = False) - If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求") - Url = SecretCdnSign(Url) - Log($"[Net] 获取网络结果:{Url},超时 {Timeout}ms{If(IsJson, ",要求 json", "")}") - Dim Request As HttpWebRequest = WebRequest.Create(Url) - Dim Result As New List(Of Byte) - Try - If Url.StartsWithF("https", True) Then Request.ProtocolVersion = HttpVersion.Version11 - Request.Timeout = Timeout - Request.Accept = Accept - SecretHeadersSign(Url, Request, UseBrowserUserAgent) - Using res As HttpWebResponse = Request.GetResponse() - Using HttpStream As Stream = res.GetResponseStream() - HttpStream.ReadTimeout = Timeout - Dim HttpData As Byte() = New Byte(16384) {} - Using Reader As New StreamReader(HttpStream, If(Encode, Encoding.UTF8)) - Dim ResultString As String = Reader.ReadToEnd - Return If(IsJson, GetJson(ResultString), ResultString) - End Using - End Using - End Using - Catch ex As ThreadInterruptedException - Throw - Catch ex As Exception - If TypeOf ex Is WebException AndAlso CType(ex, WebException).Status = WebExceptionStatus.Timeout Then - Throw New TimeoutException($"获取结果失败({CType(ex, WebException).Status},{ex.Message},{Url})", ex) - Else - Throw New WebException($"获取结果失败({If(TypeOf ex Is WebException, CType(ex, WebException).Status & ",", "")}{ex.Message},{Url})", ex) - End If - Finally - Request.Abort() - End Try + Return Nothing End Function ''' - ''' 以多线程下载网页文件的方式获取网页源代码。 + ''' 以多线程下载网页文件的方式获取内容。 + ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。 ''' - ''' 网页的 Url。 - Public Function NetGetCodeByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String - Dim Temp As String = RequestTaskTempFolder() & "download.txt" - Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)}) - Try - NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(" & Url & ")") - NetGetCodeByLoader = ReadFile(Temp) - File.Delete(Temp) - Finally - NewTask.Abort() - End Try + Public Function NetRequestByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String + Return NetRequestByLoader({Url}, Timeout, IsJson, UseBrowserUserAgent) End Function ''' - ''' 以多线程下载网页文件的方式获取网页源代码。 + ''' 以多线程下载网页文件的方式获取内容。 + ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。 ''' - ''' 网页的 Url 列表。 - Public Function NetGetCodeByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String + Public Function NetRequestByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String Dim Temp As String = RequestTaskTempFolder() & "download.txt" Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)}) Try NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(第一下载源:" & Urls.First & ")") - NetGetCodeByLoader = ReadFile(Temp) + NetRequestByLoader = ReadFile(Temp) File.Delete(Temp) Finally NewTask.Abort() End Try End Function +#End Region + +#Region "文件下载" + ''' - ''' 使用 WebClient 从网络中下载文件。这不能下载 CDN 中的文件。 + ''' 通过网络请求直接下载小文件,若文件已存在将被覆盖。 + ''' 不建议用于下载大文件。 ''' - ''' 网络 Url。 - ''' 下载的本地地址。 - Public Sub NetDownloadByClient(Url As String, LocalFile As String, Optional UseBrowserUserAgent As Boolean = False) - Log("[Net] 直接下载文件:" & Url) - '初始化 + Public Sub NetDownloadByClient(Url As String, LocalPath As String) + Log($"[Net] 通过网络请求直接下载小文件:{Url} → {LocalPath}") + '初始化文件路径 Try - '建立目录 - Directory.CreateDirectory(GetPathFromFullPath(LocalFile)) - '尝试删除原文件 - File.Delete(LocalFile) + Directory.CreateDirectory(GetPathFromFullPath(LocalPath)) + File.Delete(LocalPath) Catch ex As Exception - Throw New WebException($"预处理下载文件路径失败({LocalFile})", ex) + Throw New Exception($"预处理下载文件路径失败({LocalPath})", ex) End Try '下载 - Using Client As New WebClient - Try - SecretHeadersSign(Url, Client, UseBrowserUserAgent) - Client.DownloadFile(Url, LocalFile) - Catch ex As Exception - File.Delete(LocalFile) - Throw New WebException($"直接下载文件失败({Url})", ex) - End Try - End Using - End Sub - - ''' - ''' 简单的多线程下载文件。可以下载 CDN 中的文件。 - ''' - ''' 文件的 Url。 - ''' 下载的本地地址。 - Public Sub NetDownloadByLoader(Url As String, LocalFile As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) - Dim NewTask As New LoaderDownload("文件下载 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, LocalFile, Check, UseBrowserUserAgent)}) Try - NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress) + File.WriteAllBytes(LocalPath, SendRequest(Url, HttpMethod.Get)) Catch ex As Exception - Throw New WebException($"多线程直接下载文件失败({Url})", ex) - Finally - NewTask.Abort() + File.Delete(LocalPath) + Throw New WebException($"通过网络请求直接下载小文件失败({LocalPath})", ex) End Try End Sub ''' - ''' 简单的多线程下载文件。可以下载 CDN 中的文件。 + ''' 通过多线程下载引擎下载文件。 + ''' + Public Sub NetDownloadByLoader(Url As String, LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) + NetDownloadByLoader({Url}, LocalPath, LoaderToSyncProgress, Check, UseBrowserUserAgent) + End Sub + ''' + ''' 通过多线程下载引擎下载文件。 ''' ''' 文件的 Url 列表。 - ''' 下载的本地地址。 - Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalFile As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) - Dim NewTask As New LoaderDownload("文件下载 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, LocalFile, Check, UseBrowserUserAgent)}) + ''' 下载的本地地址。 + Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) + Dim NewTask As New LoaderDownload($"文件下载 {GetUuid()}#", New List(Of NetFile) From {New NetFile(Urls, LocalPath, Check, UseBrowserUserAgent)}) Try NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress) Catch ex As Exception @@ -306,184 +194,89 @@ RequestFinished: End Try End Sub - ''' - ''' 发送一个网络请求并获取返回内容,会重试三次并在最长 45s 后超时。 - ''' - ''' 请求的服务器地址。 - ''' 请求方式(POST 或 GET)。 - ''' 请求的内容。 - ''' 请求的套接字类型。 - ''' 当返回 40x 时不重试。 - Public Function NetRequestRetry(Url As String, Method As String, Data As Object, ContentType As String, Optional DontRetryOnRefused As Boolean = True, Optional Headers As Dictionary(Of String, String) = Nothing) As String - Dim RetryCount As Integer = 0 - Dim RetryException As Exception = Nothing - Dim StartTime As Long = GetTimeTick() - Try -Retry: - Select Case RetryCount - Case 0 '正常尝试 - Return NetRequestOnce(Url, Method, Data, ContentType, 15000, Headers) - Case 1 '慢速重试 - Thread.Sleep(500) - Return NetRequestOnce(Url, Method, Data, ContentType, 25000, Headers) - Case Else '快速重试 - If GetTimeTick() - StartTime > 5500 Then - '若前两次加载耗费 5 秒以上,才进行重试 - Thread.Sleep(500) - Return NetRequestOnce(Url, Method, Data, ContentType, 4000, Headers) - Else - Throw RetryException - End If - End Select - Catch ex As ThreadInterruptedException - Throw - Catch ex As Exception - If ex.InnerException IsNot Nothing AndAlso ex.InnerException.Message.Contains("(40") AndAlso DontRetryOnRefused Then Throw - Select Case RetryCount - Case 0 - If ModeDebug Then Log(ex, "[Net] 网络请求第一次失败(" & Url & ")") - RetryException = ex - RetryCount += 1 - GoTo Retry - Case 1 - If ModeDebug Then Log(ex, "[Net] 网络请求第二次失败(" & Url & ")") - RetryCount += 1 - GoTo Retry - Case Else - Throw - End Select - End Try - End Function - ''' - ''' 同时发送多个网络请求并要求返回内容。 - ''' - Public Function NetRequestMultiple(Url As String, Method As String, Data As Object, ContentType As String, Optional RequestCount As Integer = 4, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True) - Dim Threads As New List(Of Thread) - Dim RequestResult = Nothing - Dim RequestEx As Exception = Nothing - Dim FailCount As Integer = 0 - For i = 1 To RequestCount - Dim th As New Thread( - Sub() - Try - RequestResult = NetRequestOnce(Url, Method, Data, ContentType, 30000, Headers, MakeLog) - Catch ex As Exception - FailCount += 1 - RequestEx = ex - End Try - End Sub) - th.Start() - Threads.Add(th) - Thread.Sleep(i * 250) - If RequestResult IsNot Nothing Then GoTo RequestFinished - Next - Do While True - If RequestResult IsNot Nothing Then -RequestFinished: - For Each th In Threads - If th.IsAlive Then th.Interrupt() - Next - Return RequestResult - ElseIf FailCount = RequestCount Then - For Each th In Threads - If th.IsAlive Then th.Interrupt() - Next - Throw RequestEx - End If - Thread.Sleep(20) - Loop - Throw New Exception("未知错误") - End Function - ''' - ''' 发送一次网络请求并获取返回内容。 - ''' - Public Function NetRequestOnce(Url As String, Method As String, Data As Object, ContentType As String, Optional Timeout As Integer = 25000, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True, Optional UseBrowserUserAgent As Boolean = False) As String +#End Region + +#Region "基础请求引擎" + + '基础请求函数 + Private Function SendRequest(Url As String, Method As HttpMethod, + Optional Content As Object = Nothing, Optional Headers As Dictionary(Of String, String) = Nothing, + Optional UseBrowserUserAgent As Boolean = False, Optional Timeout As Integer = 25000, Optional Encoding As Encoding = Nothing) As Byte() If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求") - Url = SecretCdnSign(Url) - If MakeLog Then Log("[Net] 发起网络请求(" & Method & "," & Url & "),最大超时 " & Timeout) - Dim DataStream As Stream = Nothing - Dim Resp As WebResponse = Nothing - Dim Req As HttpWebRequest + Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing, Response As HttpResponseMessage = Nothing Try - Req = WebRequest.Create(Url) - Req.Method = Method - Dim SendData As Byte() - If TypeOf Data Is Byte() Then - SendData = Data - Else - SendData = New UTF8Encoding(False).GetBytes(Data.ToString) + Static Client As HttpClient = ClientExtensions.CreateClient(New FileCacheStore.FileStore(PathTemp & "Cache/Http/"), New HttpClientHandler With { + .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None, + .UseCookies = False '不设为 False 就不能从 Header 手动传入 Cookies + }) + Url = SecretCdnSign(Url) + Request = New HttpRequestMessage(Method, Url) + '写入 Content + If Content IsNot Nothing AndAlso Request.Method <> HttpMethod.Get AndAlso Request.Method <> HttpMethod.Head Then + Dim Bytes As Byte() = If(TypeOf Content Is Byte(), Content, If(Encoding, Encoding.UTF8).GetBytes(Content.ToString)) + Request.Content = New ByteArrayContent(Bytes) + Request.Content.Headers.ContentLength = Bytes.Length End If + '写入 Headers If Headers IsNot Nothing Then - For Each Pair In Headers - Req.Headers.Add(Pair.Key, Pair.Value) + For Each Header In Headers + If Header.Key.ToLower = "content-type" Then + Request.Content?.Headers.TryAddWithoutValidation(Header.Key, Header.Value) + Else + Request.Headers.Add(Header.Key, Header.Value) + End If Next End If - Req.ContentType = ContentType - Req.Timeout = Timeout - SecretHeadersSign(Url, Req, UseBrowserUserAgent) - If Url.StartsWithF("https", True) Then Req.ProtocolVersion = HttpVersion.Version11 - If Method = "POST" OrElse Method = "PUT" Then - Req.ContentLength = SendData.Length - DataStream = Req.GetRequestStream() - DataStream.WriteTimeout = Timeout - DataStream.ReadTimeout = Timeout - DataStream.Write(SendData, 0, SendData.Length) - DataStream.Close() - End If - Resp = Req.GetResponse() - DataStream = Resp.GetResponseStream() - DataStream.WriteTimeout = Timeout - DataStream.ReadTimeout = Timeout - Using Reader As New StreamReader(DataStream) - Return Reader.ReadToEnd() + SecretHeadersSign(Url, Request, UseBrowserUserAgent) + '发送请求 + CancelToken = New CancellationTokenSource(Timeout) + Response = Client.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult() + Dim ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult() + Dim ResponseBytes As Byte() + Using Stream As New MemoryStream + ResponseStream.CopyToAsync(Stream, 81920, CancelToken.Token).GetAwaiter().GetResult() + ResponseBytes = Stream.ToArray() End Using + '输出 + If Response.IsSuccessStatusCode Then + Return ResponseBytes + Else + Dim ResponseMessage = If(Encoding, Encoding.UTF8).GetString(ResponseBytes) + Throw New ResponsedWebException( + $"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url}" & + If(String.IsNullOrEmpty(ResponseMessage), "", vbCrLf & ResponseMessage), ResponseMessage) + End If Catch ex As ThreadInterruptedException Throw - Catch ex As WebException - If ex.Status = WebExceptionStatus.Timeout Then - ex = New WebException($"连接服务器超时,请检查你的网络环境是否良好({ex.Message},{Url})", ex) - Else - '获取请求失败的返回 - Dim Res As String = "" - Try - If ex.Response Is Nothing Then Exit Try - DataStream = ex.Response.GetResponseStream() - DataStream.WriteTimeout = Timeout - DataStream.ReadTimeout = Timeout - Using Reader As New StreamReader(DataStream) - Res = Reader.ReadToEnd() - End Using - Catch - End Try - If Res = "" Then - ex = New WebException($"网络请求失败({ex.Status},{ex.Message},{Url})", ex) - Else - ex = New ResponsedWebException($"服务器返回错误({ex.Status},{ex.Message},{Url}){vbCrLf}{Res}", Res, ex) - End If - End If - If MakeLog Then Log(ex, "NetRequestOnce 失败", LogLevel.Developer) - Throw ex + Catch ex As ResponsedWebException + Throw + Catch ex As TaskCanceledException 'CancellationTokenSource 超时 + Throw New WebException($"连接服务器超时,请检查你的网络环境是否良好({Method}, {Url})", WebExceptionStatus.Timeout) Catch ex As Exception - ex = New WebException("网络请求失败(" & Url & ")", ex) - If MakeLog Then Log(ex, "NetRequestOnce 失败", LogLevel.Developer) - Throw ex + Throw New Exception($"网络请求出现意外异常({Method}, {Url})", ex) Finally - If DataStream IsNot Nothing Then DataStream.Dispose() - If Resp IsNot Nothing Then Resp.Dispose() + Request?.Dispose() + CancelToken?.Dispose() + Response?.Dispose() End Try End Function + Public Class ResponsedWebException Inherits WebException ''' ''' 远程服务器给予的回复。 ''' Public Overloads Property Response As String - Public Sub New(Message As String, Response As String, InnerException As Exception) - MyBase.New(Message, InnerException) + Public Sub New(Message As String, Response As String) + MyBase.New(Message) Me.Response = Response End Sub End Class +#End Region + +#Region "多线程下载引擎" + ''' ''' 最大线程数。 ''' @@ -516,7 +309,10 @@ RequestFinished: Public Url As String Public FailCount As Integer Public Ex As Exception - Public Thread As NetThread + ''' + ''' 若该下载源正在进行强制单线程下载,标记这个唯一的线程。 + ''' + Public SingleThread As NetThread Public IsFailed As Boolean Public Overrides Function ToString() As String Return Url @@ -585,7 +381,7 @@ RequestFinished: ''' 下载线程。 ''' Public Class NetThread - Implements IEnumerable(Of NetThread) + Implements IEnumerable(Of NetThread), IEquatable(Of NetThread) ''' ''' 对应的下载任务。 @@ -644,7 +440,7 @@ RequestFinished: SyncLock Task.LockChain If NextThread Is Nothing Then If Task.IsUnknownSize Then - Return 5 * 1024 * 1024 * 1024L '5G + Return 1000 * 1000 * 1000 * 1000L '约 1T Else Return Task.FileSize - 1 End If @@ -718,6 +514,19 @@ RequestFinished: ''' Public Source As NetSource + '允许进行 UUID 比较 + Public Overloads Function Equals(other As NetThread) As Boolean Implements IEquatable(Of NetThread).Equals + Return other IsNot Nothing AndAlso Uuid = other.Uuid + End Function + Public Overrides Function Equals(obj As Object) As Boolean + Return Equals(TryCast(obj, NetThread)) + End Function + Public Shared Operator =(left As NetThread, right As NetThread) As Boolean + Return EqualityComparer(Of NetThread).Default.Equals(left, right) + End Operator + Public Shared Operator <>(left As NetThread, right As NetThread) As Boolean + Return Not left = right + End Operator End Class ''' ''' 下载单个文件。 @@ -743,12 +552,17 @@ RequestFinished: ''' Public SourcesOnce As New SafeList(Of NetSource) ''' + ''' 仅当合并失败或首次下载失败时,会将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载。 + ''' 这一策略可以兼容多个下载源中的一部分返回错误的文件的情况,以及部分在多线程下载时会抽风的源。 + ''' + Private Retried As Boolean = False + ''' ''' 获取从某个源开始,第一个可用的源。 ''' Private Function GetSource(Optional Id As Integer = 0) As NetSource If Id >= Sources.Count OrElse Id < 0 Then Id = 0 SyncLock LockSource - If Not IsSourceFailed(False) Then + If HasAvailableSource(False) Then '存在多线程可用源 Dim CurrentSource As NetSource = Sources(Id) While CurrentSource.IsFailed @@ -759,7 +573,7 @@ RequestFinished: Return CurrentSource ElseIf SourcesOnce.Any Then '仅存在单线程可用源 - Return SourcesOnce(0) + Return SourcesOnce.First Else '没有可用源 Return Nothing @@ -769,14 +583,12 @@ RequestFinished: ''' ''' 是否已经没有可用源了。 ''' - Public Function IsSourceFailed(Optional AllowOnceSource As Boolean = True) As Boolean - If AllowOnceSource AndAlso SourcesOnce.Any Then Return False + Public Function HasAvailableSource(Optional AllowOnceSource As Boolean = True) As Boolean SyncLock LockSource - For Each Source As NetSource In Sources - If Not Source.IsFailed Then Return False - Next + If AllowOnceSource AndAlso SourcesOnce.Any Then Return True + If Sources.Any(Function(s) Not s.IsFailed) Then Return True End SyncLock - Return True + Return False End Function ''' @@ -820,9 +632,9 @@ RequestFinished: End Get End Property ''' - ''' 为不需要分割的小文件进行临时存储。 + ''' 为不需要分割的小文件在内存中提供临时存储。 ''' - Private SmailFileCache As Queue(Of Byte) + Private Cache As MemoryStream ''' ''' 文件的已下载大小。 @@ -957,7 +769,7 @@ RequestFinished: Try '条件检测 - If NetTaskThreadCount >= NetTaskThreadLimit OrElse IsSourceFailed() OrElse + If NetTaskThreadCount >= NetTaskThreadLimit OrElse Not HasAvailableSource() OrElse (IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing If State >= NetState.Merge OrElse State = NetState.WaitForCheck Then Return Nothing SyncLock LockState @@ -969,20 +781,12 @@ RequestFinished: SyncLock LockChain '获取线程起点与下载源 '不分割 - If IsNoSplit Then GoTo Capture - '单线程 - If IsSourceFailed(False) Then - '确认没有其他线程正使用此点 - If SourcesOnce(0).Thread IsNot Nothing AndAlso SourcesOnce(0).Thread.State <> NetState.Error Then Return Nothing - '占用此点 -Capture: - SmailFileCache = Nothing - Threads = Nothing - NetManager.DownloadDone -= DownloadDone - SyncLock LockDone - DownloadDone = 0 - End SyncLock - SpeedLastDone = 0 + If IsNoSplit Then GoTo StartSingleThreadDownload + '只剩下单线程可用点 + If Not HasAvailableSource(False) Then + If SourcesOnce.First.SingleThread IsNot Nothing AndAlso SourcesOnce.First.SingleThread.State <> NetState.Error Then Return Nothing +StartSingleThreadDownload: + Reset() State = NetState.Get End If '首个开始点 @@ -1003,7 +807,7 @@ Capture: '是否禁用多线程,以及规定碎片大小 Dim TargetUrl As String = GetSource().Url If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("bmclapi") OrElse TargetUrl.Contains("github.com") OrElse - TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") Then Return Nothing + TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing '寻找最大碎片 'FUTURE: 下载引擎重做,计算下载源平均链接时间和线程下载速度,按最高时间节省来开启多线程 Dim FilePieceMax As NetThread = Threads @@ -1020,7 +824,7 @@ StartThread: '构建线程 Dim ThreadUuid As Integer = GetUuid() If Not Tasks.Any() Then Return Nothing '由于中断,已没有可用任务 - Th = New Thread(AddressOf Thread) With {.Name = $"NetTask {Tasks(0).Uuid}/{Uuid} Download {ThreadUuid}#", .Priority = ThreadPriority.BelowNormal} + Th = New Thread(AddressOf Thread) With {.Name = $"下载 {Tasks(0).Uuid}/{Uuid}/{ThreadUuid}#", .Priority = ThreadPriority.BelowNormal} ThreadInfo = New NetThread With {.Uuid = ThreadUuid, .DownloadStart = StartPosition, .Thread = Th, .Source = StartSource, .Task = Me, .State = NetState.WaitForDownload} '链表处理 If ThreadInfo.IsFirstThread OrElse Threads Is Nothing Then @@ -1040,7 +844,7 @@ StartThread: NetTaskThreadCount += 1 End SyncLock SyncLock LockSource - If IsSourceFailed(False) Then SourcesOnce(0).Thread = ThreadInfo + If Not HasAvailableSource(False) Then SourcesOnce.First.SingleThread = ThreadInfo End SyncLock Th.Start(ThreadInfo) Return ThreadInfo @@ -1053,293 +857,278 @@ StartThread: ''' ''' 每个下载线程执行的代码。 ''' - Private Sub Thread(Info As NetThread) - If ModeDebug OrElse Info.DownloadStart = 0 Then Log("[Download] " & LocalName & " " & Info.Uuid & "#:开始,起始点 " & Info.DownloadStart & "," & Info.Source.Url) - Dim HttpRequest As HttpWebRequest + Private Sub Thread(Th As NetThread) + If ModeDebug OrElse Th.DownloadStart = 0 Then Log($"[Download] {LocalName}:开始,起始点 {Th.DownloadStart},{Th.Source.Url}") Dim ResultStream As Stream = Nothing '部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟 '延迟过长会导致某些特别慢的链接迟迟不被掐死 - Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Info.Source.FailCount), 30000) - Info.State = NetState.Connect + Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Th.Source.FailCount), 30000) + Th.State = NetState.Connect Try Dim HttpDataCount As Integer = 0 - If SourcesOnce.Contains(Info.Source) AndAlso Not Info.Equals(Info.Source.Thread) Then GoTo SourceBreak - '请求头 - HttpRequest = WebRequest.Create(Info.Source.Url) - If Info.Source.Url.StartsWithF("https", True) Then HttpRequest.ProtocolVersion = HttpVersion.Version11 - 'HttpRequest.Proxy = Nothing 'new WebProxy(Ip, Port) - HttpRequest.Timeout = Timeout - HttpRequest.AddRange(Info.DownloadStart) - SecretHeadersSign(Info.Source.Url, HttpRequest, UseBrowserUserAgent) + If SourcesOnce.Contains(Th.Source) AndAlso Th <> Th.Source.SingleThread Then GoTo SourceBreak + Dim HttpRequest As New HttpRequestMessage(HttpMethod.Get, Th.Source.Url) + SecretHeadersSign(Th.Source.Url, HttpRequest, UseBrowserUserAgent) + If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New RangeHeaderValue(Th.DownloadStart, Nothing) Dim ContentLength As Long = 0 - Using HttpResponse As HttpWebResponse = HttpRequest.GetResponse() - If State = NetState.Error Then GoTo SourceBreak '快速中断 - If ModeDebug AndAlso HttpResponse.ResponseUri.OriginalString <> Info.Source.Url Then - Log($"[Download] {LocalName} {Info.Uuid}#:重定向至 {HttpResponse.ResponseUri.OriginalString}") - End If - ''从响应头获取文件名 - 'If Info.IsFirstThread Then - ' Dim FileName As String = GetFileNameFromResponse(HttpResponse) - ' If ModeDebug Then Log($"[Download] {LocalName} {Info.Uuid}#:远程文件名:{If(FileName, "未提供")}") - ' If FileName IsNot Nothing AndAlso LocalName = "待定" Then - ' LocalName = FileName - ' Log($"[Download] {LocalName} {Info.Uuid}#:从响应头获取到文件名") - ' End If - 'End If - '文件大小校验 - ContentLength = HttpResponse.ContentLength - If ContentLength = -1 Then - If FileSize > 1 Then - If Info.DownloadStart = 0 Then - Log($"[Download] {LocalName} {Info.Uuid}#:文件大小未知,但已从其他下载源获取,不作处理") - Else - Log($"[Download] {LocalName} {Info.Uuid}#:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持") - GoTo NotSupportRange - End If - Else - FileSize = -1 : IsUnknownSize = True - Log($"[Download] {LocalName} {Info.Uuid}#:文件大小未知") - End If - ElseIf ContentLength < 0 Then - Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。") - ElseIf Info.IsFirstThread Then - If Check IsNot Nothing Then - If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then - Throw New Exception($"文件大小不足,获取结果为 {ContentLength},要求至少为 {Check.MinSize}。") - End If - If ContentLength <> Check.ActualSize AndAlso Check.ActualSize > 0 Then - Throw New Exception($"文件大小不一致,获取结果为 {ContentLength},要求必须为 {Check.ActualSize}。") - End If - End If - FileSize = ContentLength : IsUnknownSize = False - Log($"[Download] {LocalName} {Info.Uuid}#:文件大小 {ContentLength}({GetString(ContentLength)})") - '若文件大小大于 50 M,进行剩余磁盘空间校验 - If ContentLength > 50 * 1024 * 1024 Then - For Each Drive As DriveInfo In DriveInfo.GetDrives - Dim DriveName As String = Drive.Name.First.ToString - Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) + - If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0) - If Drive.TotalFreeSpace < RequiredSpace Then - Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" & - If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", "")) - End If - Next - End If - ElseIf FileSize < 0 Then - Throw New Exception("非首线程运行时,尚未获取文件大小") - ElseIf Info.DownloadStart > 0 AndAlso ContentLength = FileSize Then -NotSupportRange: - SyncLock LockSource - If SourcesOnce.Contains(Info.Source) Then - GoTo SourceBreak - Else - SourcesOnce.Add(Info.Source) - End If - End SyncLock - Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Info.DownloadStart},预期 ContentLength 为 {FileSize - Info.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") - ElseIf Not FileSize - Info.DownloadStart = ContentLength Then - Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Info.DownloadStart},预期 ContentLength 为 {FileSize - Info.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") - End If - 'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}") - Info.State = NetState.Get - SyncLock LockState - If State < NetState.Get Then State = NetState.Get - End SyncLock - '创建缓存文件 - If IsNoSplit Then - Info.Temp = Nothing - SmailFileCache = New Queue(Of Byte) - Else - Info.Temp = $"{PathTemp}Download\{Uuid}_{Info.Uuid}_{RandomInteger(0, 999999)}.tmp" - ResultStream = New FileStream(Info.Temp, FileMode.Create, FileAccess.Write, FileShare.Read) - End If - '开始下载 - Using HttpStream = HttpResponse.GetResponseStream() - HttpStream.ReadTimeout = Timeout - If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000)) - Dim HttpData As Byte() = New Byte(16384) {} - HttpDataCount = HttpStream.Read(HttpData, 0, 16384) - While (IsUnknownSize OrElse Info.DownloadUndone > 0) AndAlso '判断是否下载完成 - HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merge AndAlso (Not Info.Source.IsFailed OrElse Info.Equals(Info.Source.Thread)) - '限速 - While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0 - Threading.Thread.Sleep(16) - End While - Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Info.DownloadUndone)) - SyncLock NetTaskSpeedLimitLeftLock - If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount - End SyncLock - Dim DeltaTime = GetTimeTick() - Info.LastReceiveTime - If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值 - If RealDataCount > 0 Then - '有数据 - If Info.DownloadDone = 0 Then - '第一次接受到数据 - Info.State = NetState.Download - SyncLock LockState - If State < NetState.Download Then State = NetState.Download - End SyncLock - SyncLock LockCount - ConnectCount += 1 - ConnectTime += GetTimeTick() - Info.InitTime - End SyncLock - End If - SyncLock LockCount - Info.Source.FailCount = 0 - For Each Task In Tasks - Task.FailCount = 0 - Next - End SyncLock - NetManager.DownloadDone += RealDataCount - SyncLock LockDone - DownloadDone += RealDataCount - End SyncLock - Info.DownloadDone += RealDataCount - If IsNoSplit Then - If HttpData.Count = RealDataCount Then - 'SmailFileCache.AddRange(HttpData) - For Each B In HttpData - SmailFileCache.Enqueue(B) - Next - Else - 'SmailFileCache.AddRange(HttpData.ToList.GetRange(0, RealDataCount)) - For i = 0 To RealDataCount - 1 - SmailFileCache.Enqueue(HttpData(i)) - Next - End If + Using CancelToken As New CancellationTokenSource(Timeout) + Static Client As New HttpClient(New HttpClientHandler With { + .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None + }) + Using Response As HttpResponseMessage = Client.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult() + If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查 + If State = NetState.Error Then GoTo SourceBreak '快速中断 + If ModeDebug AndAlso Response.RequestMessage.RequestUri.ToString <> Th.Source.Url Then Log($"[Download] {LocalName}:重定向至 {Response.RequestMessage.RequestUri}") + '文件大小校验 + ContentLength = Response.Content.Headers.ContentLength.GetValueOrDefault(-1) + If ContentLength = -1 Then + If FileSize > 1 Then + If Th.DownloadStart = 0 Then + Log($"[Download] {LocalName}:文件大小未知,但已从其他下载源获取,不作处理") Else - ResultStream.Write(HttpData, 0, RealDataCount) + Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持") + GoTo NotSupportRange End If - '检查速度是否过慢 - If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1.5s,且速度小于 1.5K/s - Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。") - End If - Info.LastReceiveTime = GetTimeTick() - '已完成 - If Info.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While - ElseIf Info.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then - '无数据,且已超时 - Throw New TimeoutException("操作超时,无数据。") + Else + FileSize = -1 : IsUnknownSize = True + Log($"[Download] {LocalName}:文件大小未知") End If - HttpDataCount = HttpStream.Read(HttpData, 0, 16384) - End While + ElseIf ContentLength < 0 Then + Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。") + ElseIf Th.IsFirstThread Then + If Check IsNot Nothing Then + If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then + Throw New Exception($"文件大小不足,获取结果为 {ContentLength},要求至少为 {Check.MinSize}。") + End If + If ContentLength <> Check.ActualSize AndAlso Check.ActualSize > 0 Then + Throw New Exception($"文件大小不一致,获取结果为 {ContentLength},要求必须为 {Check.ActualSize}。") + End If + End If + FileSize = ContentLength : IsUnknownSize = False + Log($"[Download] {LocalName}:文件大小 {ContentLength}({GetString(ContentLength)})") + '若文件大小大于 50 M,进行剩余磁盘空间校验 + If ContentLength > 50 * 1024 * 1024 Then + For Each Drive As DriveInfo In DriveInfo.GetDrives + Dim DriveName As String = Drive.Name.First.ToString + Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) + + If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0) + If Drive.TotalFreeSpace < RequiredSpace Then + Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" & + If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", "")) + End If + Next + End If + ElseIf FileSize < 0 Then + Throw New Exception("非首线程运行时,尚未获取文件大小") + ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then +NotSupportRange: + SyncLock LockSource + If SourcesOnce.Contains(Th.Source) Then + GoTo SourceBreak + Else + SourcesOnce.Add(Th.Source) + End If + End SyncLock + Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") + ElseIf Not FileSize - Th.DownloadStart = ContentLength Then + Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") + End If + 'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}") + Th.State = NetState.Get + SyncLock LockState + If State < NetState.Get Then State = NetState.Get + End SyncLock + '创建缓存文件 + If IsNoSplit Then + Th.Temp = Nothing + Cache = New MemoryStream + ResultStream = Cache + Else + Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp" + ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read) + End If + '开始下载 + Using ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult() + If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000)) + Dim ResponseBytes As Byte() = New Byte(16384) {} + HttpDataCount = ResponseStream.ReadAsync(ResponseBytes, 0, 16384, CancelToken.Token).GetAwaiter().GetResult() + While (IsUnknownSize OrElse Th.DownloadUndone > 0) AndAlso '判断是否下载完成 + HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merge AndAlso (Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th) + '限速 + While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0 + Threading.Thread.Sleep(16) + End While + Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Th.DownloadUndone)) + SyncLock NetTaskSpeedLimitLeftLock + If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount + End SyncLock + Dim DeltaTime = GetTimeTick() - Th.LastReceiveTime + If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值 + If RealDataCount > 0 Then + '有数据 + If Th.DownloadDone = 0 Then + '第一次接受到数据 + Th.State = NetState.Download + SyncLock LockState + If State < NetState.Download Then State = NetState.Download + End SyncLock + SyncLock LockCount + ConnectCount += 1 + ConnectTime += GetTimeTick() - Th.InitTime + End SyncLock + End If + SyncLock LockCount + Th.Source.FailCount = 0 + For Each Task In Tasks + Task.FailCount = 0 + Next + End SyncLock + NetManager.DownloadDone += RealDataCount + SyncLock LockDone + DownloadDone += RealDataCount + End SyncLock + Th.DownloadDone += RealDataCount + ResultStream.Write(ResponseBytes, 0, RealDataCount) + '检查速度是否过慢 + If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 1.5s,且速度小于 1.5K/s + Th.Source.SingleThread Is Nothing Then '并非单线程下载 + Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。") + End If + Th.LastReceiveTime = GetTimeTick() + '已完成 + If Th.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While + ElseIf Th.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then + '无数据,且已超时 + Throw New TimeoutException("操作超时,无数据。") + End If + HttpDataCount = ResponseStream.Read(ResponseBytes, 0, 16384) + End While + End Using End Using End Using SourceBreak: - If State = NetState.Error OrElse Info.Source.IsFailed OrElse (Info.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then + If State = NetState.Error OrElse (Th.Source.IsFailed AndAlso Th.Source.SingleThread <> Th) OrElse (Th.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then '被外部中断 - Info.State = NetState.Error - Log($"[Download] {LocalName} {Info.Uuid}#:中断") - ElseIf HttpDataCount = 0 AndAlso Info.DownloadUndone > 0 AndAlso Not IsUnknownSize Then + Th.State = NetState.Error + Log($"[Download] {LocalName}:中断") + ElseIf HttpDataCount = 0 AndAlso Th.DownloadUndone > 0 AndAlso Not IsUnknownSize Then '服务器无返回数据 - Throw New Exception($"返回的 ContentLength 过多:ContentLength 为 {ContentLength},但获取到的总数据量仅为 {Info.DownloadDone}(全文件总数据量 {DownloadDone})") + Throw New Exception($"返回的 ContentLength 过多:ContentLength 为 {ContentLength},但获取到的总数据量仅为 {Th.DownloadDone}(全文件总数据量 {DownloadDone})") Else '本线程完成 - Info.State = NetState.Finish - If ModeDebug Then Log($"[Download] {LocalName} {Info.Uuid}#:完成,已下载 {Info.DownloadDone}") + Th.State = NetState.Finish + If ModeDebug Then Log($"[Download] {LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})") End If Catch ex As Exception - '状态变更 - SyncLock LockCount - Info.Source.FailCount += 1 - For Each Task In Tasks - Task.FailCount += 1 - Next - End SyncLock - Dim IsTimeoutString As String = GetExceptionSummary(ex).ToLower.Replace(" ", "") - Dim IsTimeout As Boolean = IsTimeoutString.Contains("由于连接方在一段时间后没有正确答复或连接的主机没有反应") OrElse - IsTimeoutString.Contains("超时") OrElse IsTimeoutString.Contains("timeout") OrElse IsTimeoutString.Contains("timedout") - Log("[Download] " & LocalName & " " & Info.Uuid & If(IsTimeout, "#:超时(" & (Timeout * 0.001) & "s)", "#:出错," & GetExceptionDetail(ex))) - Info.State = NetState.Error - ''使用该下载源的线程是否没有速度 - ''下载超时也会导致没有速度,容易误判下载失败,所以已弃用此方法 - 'Dim IsNoSpeed As Boolean = True - 'SyncLock LockChain - ' If Threads IsNot Nothing Then - ' For Each Thread As NetThread In Threads - ' If Thread.Source.Id = Info.Source.Id AndAlso Thread.Speed > 0 Then - ' IsNoSpeed = False - ' Exit For - ' End If - ' Next - ' End If - 'End SyncLock - Info.Source.Ex = ex - '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传) - If ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse - ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse - (ex.Message.Contains("(403)") AndAlso Not Info.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403,所以不应因此禁用下载源 - (Info.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse - Info.Source.FailCount > NetTaskThreadLimit + 2 Then - Dim IsThisFail As Boolean = False - SyncLock LockSource - If Info.Source.Thread IsNot Nothing AndAlso Info.Source.Thread.Equals(Info) Then - '单线程下,本线程出错 - SourcesOnce.RemoveAt(0) - GoTo Wrong - ElseIf Not Info.Source.IsFailed Then - '多线程下,本线程出错 -Wrong: - Info.Source.IsFailed = True - IsThisFail = True - End If - End SyncLock - '本线程引发下载源被禁用 - If IsThisFail Then - Log($"[Download] {LocalName} {Info.Uuid}#:下载源被禁用({Info.Source.Id}):{Info.Source.Url}") - Log(ex, "下载源 " & Info.Source.Id & " 已被禁用", If(ex.Message.Contains("不支持分段下载") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug)) - If IsSourceFailed() Then - '没有可用源 - Log("[Download] 文件 " & LocalName & " 已无可用下载源") - Dim ExampleEx As Exception = Nothing - SyncLock LockSource - For Each Source As NetSource In Sources - Log("[Download] 已禁用的下载源:" & Source.Url) - If Source.Ex IsNot Nothing Then - ExampleEx = Source.Ex - Log(Source.Ex, "下载源禁用原因", LogLevel.Developer) - End If - Next - End SyncLock - Fail(ExampleEx) - ElseIf ex.Message.Contains("空间不足") Then - '没有空间 - Fail(ex) - End If - End If - End If - '首线程错误 - If FileSize = -2 Then - SyncLock LockChain - Threads = Nothing - End SyncLock - End If + Log($"[Download] {LocalName}:出错,{If(TypeOf ex Is TaskCanceledException, $"已超时({Timeout}ms)", GetExceptionDetail(ex))}") + SourceFail(Th, ex, False) Finally - If ResultStream IsNot Nothing Then ResultStream.Dispose() SyncLock NetTaskThreadCountLock NetTaskThreadCount -= 1 End SyncLock - '可能在没有下载完的时候开始合并文件了,这造成了大多数合并失败 - If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge() + '合并 + If Not IsNoSplit Then ResultStream?.Dispose() + If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso + State < NetState.Merge AndAlso Th.State <> NetState.Error Then Merge(Th) End Try End Sub - ''' - ''' 从 HTTP 响应头中获取文件名。 - ''' 如果没有,返回 Nothing。 - ''' - Private Function GetFileNameFromResponse(response As HttpWebResponse) As String - Dim header As String = response.Headers("Content-Disposition") - If String.IsNullOrEmpty(header) Then Return Nothing - 'attachment; filename="filename.ext" - If Not header.Contains("filename=") Then Return Nothing - Return header.AfterLast("filename=").Trim(""""c, " "c).BeforeFirst(";") - End Function + Private Sub SourceFail(Th As NetThread, ex As Exception, IsMergeFailure As Boolean) + '状态变更 + SyncLock LockCount + Th.Source.FailCount += 1 + For Each Task In Tasks + Task.FailCount += 1 + Next + End SyncLock + Th.State = NetState.Error + Th.Source.Ex = ex + '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传) + If IsMergeFailure OrElse + ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse + ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse + (ex.Message.Contains("(403)") AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403,所以不应因此禁用下载源 + (Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then + '当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理 + Dim IsThisFail As Boolean = False + SyncLock LockSource + If Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th Then + IsThisFail = True + Th.Source.IsFailed = True + End If + End SyncLock + '……后续处理 + If IsThisFail Then + Log($"[Download] {LocalName}:下载源被禁用({Th.Source.Id}):{Th.Source.Url}") + Log(ex, $"{If(SourcesOnce.FirstOrDefault?.SingleThread Is Nothing, "", "单线程")}下载源 {Th.Source.Id} 已被禁用", + If(ex.Message.Contains("不支持分段下载") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug)) + SyncLock LockSource + SourcesOnce.Remove(Th.Source) + End SyncLock + If ex.Message.Contains("空间不足") Then + '硬盘空间不足:强制失败 + Fail(ex) + ElseIf HasAvailableSource() AndAlso Not IsMergeFailure Then + '当前源失败,但还有下载源:正常地继续执行 + ElseIf Not Retried AndAlso Sources.Count > 1 Then + '合并失败或首次下载失败,未重试:将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载 + Hint($"{LocalName}:文件下载失败,正在自动重试……", If(ModeDebug, HintType.Critical, HintType.Info)) '在调试模式下给出红色提示 + Retried = True + SyncLock LockSource + SourcesOnce.Clear() + For Each Source In Sources + SourcesOnce.Add(Source) + Source.IsFailed = True + Next + End SyncLock + Reset() + SyncLock LockState + State = NetState.WaitForDownload + End SyncLock + ElseIf HasAvailableSource() AndAlso IsMergeFailure Then + '合并失败且单个源失败:继续下一个源 + Reset() + SyncLock LockState + State = NetState.WaitForDownload + End SyncLock + Else + '失败 + Log($"[Download] {LocalName}:已无可用下载源,下载失败") + Dim ExampleEx As Exception = Nothing + SyncLock LockSource + For Each Source As NetSource In Sources + Log("[Download] 已禁用的下载源:" & Source.Url) + If Source.Ex IsNot Nothing Then + ExampleEx = Source.Ex + Log(Source.Ex, "下载源禁用原因", LogLevel.Developer) + End If + Next + End SyncLock + Fail(ExampleEx) + End If + End If + End If + '清理当前已下载的内容 + If FileSize = -2 Then Reset() + End Sub + Private Sub Reset() + FileSize = -2 + Cache?.Dispose() : Cache = Nothing + SyncLock LockChain + Threads = Nothing + End SyncLock + NetManager.DownloadDone -= DownloadDone + SyncLock LockDone + DownloadDone = 0 + End SyncLock + SpeedLastDone = 0 + End Sub + + '最终收束事件 - '下载文件的最终收束事件 ''' ''' 下载完成。合并文件。 ''' - Private Sub Merge() + Private Sub Merge(Th As NetThread) '状态判断 SyncLock LockState If State < NetState.Merge Then @@ -1349,7 +1138,6 @@ Wrong: End If End SyncLock Dim RetryCount As Integer = 0 - Dim MergeFile As Stream = Nothing, AddWriter As BinaryWriter = Nothing Try Retry: SyncLock LockChain @@ -1359,12 +1147,9 @@ Retry: '合并文件 If IsNoSplit Then '仅有一个线程,从缓存中输出 - If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从缓存输出文件,长度:" & SmailFileCache.Count) - MergeFile = New FileStream(LocalPath, FileMode.Create) - AddWriter = New BinaryWriter(MergeFile) - AddWriter.Write(SmailFileCache.ToArray) - AddWriter.Dispose() : AddWriter = Nothing - MergeFile.Dispose() : MergeFile = Nothing + If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从内存输出文件") + Cache.Position = 0 + WriteFile(LocalPath, Cache) ElseIf Threads.DownloadDone = DownloadDone AndAlso Threads.Temp IsNot Nothing Then '仅有一个文件,直接复制 If ModeDebug Then Log($"[Download] {LocalName}:下载结束,仅有一个文件,无需合并") @@ -1372,40 +1157,37 @@ Retry: Else '有多个线程,合并 If ModeDebug Then Log($"[Download] {LocalName}:下载结束,开始合并文件") - MergeFile = New FileStream(LocalPath, FileMode.Create) - AddWriter = New BinaryWriter(MergeFile) - For Each Thread As NetThread In Threads - If Thread.DownloadDone = 0 OrElse Thread.Temp Is Nothing Then Continue For - Using fs As New FileStream(Thread.Temp, FileMode.Open, FileAccess.Read, FileShare.Read) - Using TempReader As New BinaryReader(fs) - AddWriter.Write(TempReader.ReadBytes(Thread.DownloadDone)) - End Using + Using MergeFile As New FileStream(LocalPath, FileMode.Create) + Using AddWriter As New BinaryWriter(MergeFile) + For Each Thread As NetThread In Threads + If Thread.DownloadDone = 0 OrElse Thread.Temp Is Nothing Then Continue For + Using fs As New FileStream(Thread.Temp, FileMode.Open, FileAccess.Read, FileShare.Read) + Using TempReader As New BinaryReader(fs) + AddWriter.Write(TempReader.ReadBytes(Thread.DownloadDone)) + End Using + End Using + Next End Using - Next - AddWriter.Dispose() : AddWriter = Nothing - MergeFile.Dispose() : MergeFile = Nothing - End If - '写入大小要求 - If Not IsUnknownSize AndAlso Check IsNot Nothing Then - If Check.ActualSize = -1 Then - Check.ActualSize = FileSize - ElseIf Check.ActualSize <> FileSize Then - Throw New Exception($"文件大小不一致:任务要求为 {Check.ActualSize} B,网络获取结果为 {FileSize}B") - End If + End Using End If '检查文件 Dim CheckResult As String = Check?.Check(LocalPath) + If CheckResult Is Nothing AndAlso Not IsUnknownSize AndAlso Check IsNot Nothing Then + If Check.ActualSize = -1 Then + CheckResult = (New FileChecker With {.ActualSize = FileSize}).Check(LocalPath) '不修改原始的 Checker,以免污染原始实例 + ElseIf Check.ActualSize <> FileSize Then + CheckResult = $"文件大小不一致:任务校验要求为 {Check.ActualSize},请求结果为 {FileSize}" + End If + End If If CheckResult IsNot Nothing Then Log($"[Download] {LocalName} 文件校验失败,下载线程细节:") - For Each Th As NetThread In Threads - Log($"[Download] {Th.Uuid}#,状态 {GetStringFromEnum(Th.State)},范围 {Th.DownloadStart}~{Th.DownloadStart + Th.DownloadDone},完成 {Th.DownloadDone},剩余 {Th.DownloadUndone}") + For Each T As NetThread In Threads + Log($"[Download] - {T.Uuid}#,状态 {GetStringFromEnum(T.State)},范围 {T.DownloadStart}~{T.DownloadEnd},已下载 {T.DownloadDone},未下载 {T.DownloadUndone}") Next Throw New Exception(CheckResult) End If '后处理 - If IsNoSplit Then - SmailFileCache = Nothing - Else + If Not IsNoSplit Then For Each Thread As NetThread In Threads If Thread.Temp IsNot Nothing Then File.Delete(Thread.Temp) Next @@ -1413,20 +1195,18 @@ Retry: Finish() End SyncLock Catch ex As Exception - Log(ex, "合并文件出错(" & LocalName & ")") - If MergeFile IsNot Nothing Then - MergeFile.Dispose() : MergeFile = Nothing - End If - If AddWriter IsNot Nothing Then - AddWriter.Dispose() : AddWriter = Nothing - End If - '重试 - If RetryCount <= 3 Then - Threading.Thread.Sleep(RandomInteger(500, 1000)) - RetryCount += 1 + RetryCount += 1 + Log(ex, $"合并文件出错,第 {RetryCount} 次尝试({LocalName})") + '重新尝试合并 + If RetryCount < 3 Then + Threading.Thread.Sleep(500 * RetryCount) GoTo Retry End If - Fail(ex) + '失败,禁用当前下载源并重启下载 + If File.Exists(LocalPath) Then File.Delete(LocalPath) + SourceFail(Th, ex, True) + Finally + Cache?.Dispose() End Try End Sub ''' @@ -1439,7 +1219,7 @@ Retry: '凉凉 State = NetState.Error End SyncLock - InterruptAndDelete() + AbortInternal() For Each Task In Tasks Task.OnFileFail(Me) Next @@ -1447,24 +1227,24 @@ Retry: ''' ''' 下载中断。 ''' - Public Sub Abort(CausedByTask As LoaderDownload) + Public Sub Abort(AbortedTask As LoaderDownload) '从特定任务中移除,如果它还属于其他任务,则继续下载 - Tasks.Remove(CausedByTask) + Tasks.Remove(AbortedTask) If Tasks.Any Then Return '确认中断 SyncLock LockState If State >= NetState.Finish Then Return State = NetState.Error End SyncLock - InterruptAndDelete() + AbortInternal() End Sub - Private Sub InterruptAndDelete() + Private Sub AbortInternal() On Error Resume Next - If File.Exists(LocalPath) Then File.Delete(LocalPath) + Reset() SyncLock NetManager.LockRemain NetManager.FileRemain -= 1 - Log($"[Download] {LocalName}:状态 {State},剩余文件 {NetManager.FileRemain}") End SyncLock + Log($"[Download] {LocalName}:已终止,当前状态 {State}") End Sub '状态改变接口 @@ -1478,8 +1258,8 @@ Retry: End SyncLock SyncLock NetManager.LockRemain NetManager.FileRemain -= 1 - If PrintLog Then Log("[Download] " & LocalName & ":已完成,剩余文件 " & NetManager.FileRemain) End SyncLock + If PrintLog Then Log($"[Download] {LocalName}:已完成") For Each Task In Tasks Task.OnFileFinish(Me) Next @@ -1610,7 +1390,7 @@ NextElement: Source.IsFailed = True End If Next - If File.IsSourceFailed() Then Throw New ArgumentException("输入的下载链接不正确!") + If Not File.HasAvailableSource() Then Throw New ArgumentException("输入的下载链接不正确!") If Not File.LocalPath.ToLower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确!") If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径!") '文件夹检测 @@ -1640,20 +1420,20 @@ NextElement: If FilesInThread.Count = FilesPerThread Then Dim FilesToRun As New List(Of NetFile) FilesToRun.AddRange(FilesInThread) - RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid) + RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "下载 文件复制 " & Uuid) FilesInThread.Clear() End If Next If FilesInThread.Any Then Dim FilesToRun As New List(Of NetFile) FilesToRun.AddRange(FilesInThread) - RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid) + RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "下载 文件复制 " & Uuid) FilesInThread.Clear() End If Catch ex As Exception OnFail(New List(Of Exception) From {ex}) End Try - End Sub, "NetTask " & Uuid & " Main") + End Sub, "L/下载 " & Uuid) End Sub Private Sub StartCopy(Files As List(Of NetFile), FolderList As List(Of String)) Try @@ -1741,23 +1521,28 @@ Retry: SyncLock LockState If State > LoadState.Loading Then Return If ExList Is Nothing OrElse Not ExList.Any() Then ExList = New List(Of Exception) From {New Exception("未知错误!")} - '寻找第一个不是 404 的下载源 - Dim UsefulExs = ExList.Where(Function(e) Not e.Message.Contains("(404)")).ToList - [Error] = If(UsefulExs.Any, UsefulExs(0), ExList(0)) + '寻找有效的错误信息 + Dim UsefulExs = ExList.Where(Function(e) TypeOf e IsNot TaskCanceledException AndAlso TypeOf e IsNot ThreadInterruptedException).ToList + [Error] = If(UsefulExs.FirstOrDefault, ExList.FirstOrDefault) '获取实际失败的文件 For Each File In Files - If File.State = NetState.Error Then - [Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf & Join( - File.Sources.Select(Function(s) If(s.Ex Is Nothing, s.Url, s.Ex.Message & "(" & s.Url & ")")), vbCrLf), [Error]) - Exit For - End If + If File.State <> NetState.Error Then Continue For + Dim Detail As String = Join(File.Sources.Select(Function(s) $"{If(s.Ex?.Message, "无错误信息。")}({s.Url})"), vbCrLf) + [Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf & + "各下载源的错误如下:" & vbCrLf & Detail, + [Error]) + '上报 + Telemetry("文件下载失败", + "FileName", File.LocalName, + "Exception", Detail) + Exit For Next '在设置 Error 对象后再更改为失败,避免 WaitForExit 无法捕获错误 State = LoadState.Failed End SyncLock '中断所有文件 For Each TaskFile In Files - If TaskFile.State < NetState.Merge Then TaskFile.State = NetState.Error + TaskFile.Abort(Me) Next '在退出同步锁后再进行日志输出 Dim ErrOutput As New List(Of String) @@ -1997,9 +1782,8 @@ Retry: Files(File.LocalPath) = File SyncLock LockRemain FileRemain += 1 - If ModeDebug Then Log("[Download] " & File.LocalName & ":已替换列表,剩余文件 " & FileRemain) - 'FileRemainList.Add(File.LocalPath) End SyncLock + If ModeDebug Then Log($"[Download] {File.LocalName}:重新加入下载列表") Else '该文件正在下载中 '将当前文件替换成下载中的文件,即两个任务指向同一个文件 @@ -2012,9 +1796,8 @@ Retry: Files.Add(File.LocalPath, File) SyncLock LockRemain FileRemain += 1 - If ModeDebug Then Log("[Download] " & File.LocalName & ":已加入列表,剩余文件 " & FileRemain) - 'FileRemainList.Add(File.LocalPath) End SyncLock + If ModeDebug Then Log($"[Download] {File.LocalName}:已加入下载列表") End If Task.Files(i) = File '回设 Next @@ -2037,4 +1820,26 @@ Retry: Return False End Function +#End Region + + ''' + ''' 测试 Ping。失败则返回 -1。 + ''' + Public Function Ping(Ip As String, Optional Timeout As Integer = 10000, Optional MakeLog As Boolean = True) As Integer + Dim PingResult As NetworkInformation.PingReply + Try + PingResult = (New NetworkInformation.Ping).Send(Ip) + Catch ex As Exception + If MakeLog Then Log("[Net] Ping " & Ip & " 失败:" & ex.Message) + Return -1 + End Try + If PingResult.Status = NetworkInformation.IPStatus.Success Then + If MakeLog Then Log("[Net] Ping " & Ip & " 结束:" & PingResult.RoundtripTime & "ms") + Return PingResult.RoundtripTime + Else + If MakeLog Then Log("[Net] Ping " & Ip & " 失败") + Return -1 + End If + End Function + End Module diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb index 08d07aa..c62beb9 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb @@ -1076,7 +1076,7 @@ Retry: Try '获取工程列表 Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl) - Dim RequestResult As JObject = DlModRequest(CurseForgeUrl, IsJson:=True) + Dim RequestResult As JObject = DlModRequest(CurseForgeUrl) Task.Progress += 0.2 Dim ProjectList As New List(Of CompProject) For Each JsonEntry As JObject In RequestResult("data") @@ -1107,7 +1107,7 @@ Retry: Sub() Try Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl) - Dim RequestResult As JObject = DlModRequest(ModrinthUrl, IsJson:=True) + Dim RequestResult As JObject = DlModRequest(ModrinthUrl) Task.Progress += 0.2 Dim ProjectList As New List(Of CompProject) For Each JsonEntry As JObject In RequestResult("hits") @@ -1137,6 +1137,11 @@ Retry: If ModrinthThread IsNot Nothing Then ModrinthThread.Join() If Task.IsAborted Then Return + '筛除不是 Forge 的 Mod + If IsOldForgeRequest Then + RawResults = RawResults.Where(Function(p) Not p.ModLoaders.Any() OrElse p.ModLoaders.Contains(CompModLoaderType.Forge)).ToList + End If + '确保存在结果 Storage.ErrorMessage = Nothing If Not RawResults.Any() Then @@ -1167,11 +1172,6 @@ Retry: ModrinthThread?.Interrupt() End Try - '筛除不是 Forge 的 Mod - If IsOldForgeRequest Then - RawResults = RawResults.Where(Function(p) Not p.ModLoaders.Any() OrElse p.ModLoaders.Contains(CompModLoaderType.Forge)).ToList - End If - #End Region #Region "提取非重复项,存储于 RealResults" @@ -1396,7 +1396,7 @@ Retry: 'DownloadAddress Dim Url = Data("downloadUrl").ToString If Url = "" Then Url = $"https://media.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}" - Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码 + Url = Url.Replace(FileName, WebUtility.UrlEncode(FileName)) '对文件名进行编码 DownloadUrls = HandleCurseForgeDownloadUrls(Url) '对脑残 CurseForge 的下载地址进行多种修正 DownloadUrls.AddRange(DownloadUrls.Select(Function(u) DlSourceModGet(u)).ToList) '添加镜像源,这个写法是为了让镜像源排在后面 DownloadUrls = DownloadUrls.Distinct.ToList '最终去重 @@ -1492,9 +1492,8 @@ Retry: Return { Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."), Url.Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."), - Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://media."), Url.Replace("-service.overwolf.wtf", ".forgecdn.net"), - Url.Replace("://edge.", "://media."), + Url.Replace("://media.", "://edge."), Url }.Distinct.ToList End Function @@ -1591,27 +1590,40 @@ Retry: If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存 TargetProject = CompProjectCache(ProjectId) ElseIf FromCurseForge Then 'CurseForge - TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True)("data")) + TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId)("data")) Else 'Modrinth - TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId, IsJson:=True)) + TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId)) End If '获取工程对象的文件列表 If Not CompFilesCache.ContainsKey(ProjectId) Then '有缓存也不能直接返回,这时候前置可能没获取(#5173) Log("[Comp] 开始获取文件列表:" & ProjectId) - Dim ResultJsonArray As JArray + Dim ResultJsonArray As JArray = Nothing If FromCurseForge Then 'CurseForge - 'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522) - ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=10000", IsJson:=True)("data") - '之前只请求一部分文件的方法备份如下: - 'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件 - ' ResultJsonArray = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json"))("data") - 'Else '否则使用全部文件 - ' ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999", IsJson:=True)("data") - 'End If + For RetryCount As Integer = 0 To 2 + Dim ResultJson As JObject = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=" & + (10000 + RetryCount)) '每次重试多请求一个文件,以避免触发 CDN 缓存 + 'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522) + '之前只请求一部分文件的方法备份如下: + 'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件 + ' ResultJsonArray = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json")("data") + 'Else '否则使用全部文件 + ' ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999")("data") + 'End If + If ResultJson("pagination")("resultCount").ToObject(Of Integer) = ResultJson("pagination")("totalCount").ToObject(Of Integer) Then + ResultJsonArray = ResultJson("data") + Exit For + ElseIf RetryCount < 2 Then + Log($"[Comp] CurseForge 返回的文件列表存在缺失,即将进行第 {RetryCount + 1} 次重试", LogLevel.Debug) '#6224 + Log($"[Comp] 返回的原始内容如下:{vbCrLf}{ResultJson}") + Else + Log($"[Comp] CurseForge 返回的文件列表存在缺失,返回的原始内容如下:{vbCrLf}{ResultJson}") + Throw New Exception("CurseForge 返回的文件列表存在缺失") + End If + Next Else 'Modrinth - ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version", IsJson:=True) + ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version") End If CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)). Where(Function(a) a.Available).ToList. @@ -1625,10 +1637,10 @@ Retry: Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置:{Join(UndoneDeps, ",")}") Dim Projects As JArray If TargetProject.FromCurseForge Then - Projects = GetJson(DlModRequest("https://api.curseforge.com/v1/mods", - "POST", "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json"))("data") + Projects = DlModRequest("https://api.curseforge.com/v1/mods", + HttpMethod.Post, "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json")("data") Else - Projects = DlModRequest($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]", IsJson:=True) + Projects = DlModRequest($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]") End If For Each Project In Projects Dim Unused As New CompProject(Project) '在 New 的时候会添加缓存以便之后读取 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb index 33fe597..fc72957 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb @@ -2,7 +2,9 @@ '构造函数 Private TempFolder As String - Public Sub New(UUID As Integer) + Private TargetVersion As McVersion + Public Sub New(Optional TargetVersion As McVersion = Nothing) + Me.TargetVersion = TargetVersion '构建文件结构 TempFolder = RequestTaskTempFolder() Directory.CreateDirectory(TempFolder & "Temp\") @@ -339,6 +341,7 @@ Extracted: ''' 导致崩溃的原因枚举。 ''' Private Enum CrashReason + Java虚拟机参数有误 Mod文件被解压 MixinBootstrap缺失 内存不足 @@ -482,10 +485,12 @@ Done: '崩溃报告分析,高优先级 If LogCrash IsNot Nothing Then If LogCrash.Contains("Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass") Then AppendReason(CrashReason.Java版本过高) + If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)}) End If '游戏日志分析 If LogMc IsNot Nothing Then + If LogMc.Contains("Unrecognized option:") Then AppendReason(CrashReason.Java虚拟机参数有误) If LogMc.Contains("Found multiple arguments for option fml.forgeVersion, but you asked for only one") Then AppendReason(CrashReason.版本Json中存在多个Forge) If LogMc.Contains("The driver does not appear to support OpenGL") Then AppendReason(CrashReason.显卡不支持OpenGL) If LogMc.Contains("java.lang.ClassCastException: java.base/jdk") Then AppendReason(CrashReason.使用JDK) @@ -508,13 +513,13 @@ Done: If LogMc.Contains("Couldn't set pixel format") Then AppendReason(CrashReason.显卡驱动不支持导致无法设置像素格式) If LogMc.Contains("java.lang.OutOfMemoryError") OrElse LogMc.Contains("an out of memory error") Then AppendReason(CrashReason.内存不足) If LogMc.Contains("java.lang.RuntimeException: Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders.") Then AppendReason(CrashReason.ShadersMod与OptiFine同时安装) - If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容) + If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") OrElse LogMc.Contains("java.lang.NoSuchMethodError: 'void sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容) If LogMc.Contains("1282: Invalid operation") Then AppendReason(CrashReason.光影或资源包导致OpenGL1282错误) If LogMc.Contains("signer information does not match signer information of other classes in the same package") Then AppendReason(CrashReason.文件或内容校验失败, If(RegexSeek(LogMc, "(?<=class "")[^']+(?=""'s signer information)"), "").TrimEnd(vbCrLf)) If LogMc.Contains("Maybe try a lower resolution resourcepack?") Then AppendReason(CrashReason.材质过大或显卡配置不足) If LogMc.Contains("java.lang.NoSuchMethodError: net.minecraft.world.server.ChunkManager$ProxyTicketManager.shouldForceTicks(J)Z") AndAlso LogMc.Contains("OptiFine") Then AppendReason(CrashReason.OptiFine导致无法加载世界) If LogMc.Contains("Unsupported class file major version") Then AppendReason(CrashReason.Java版本不兼容) - If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") Then AppendReason(CrashReason.NightConfig的Bug) + If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") AndAlso Not CrashReasons.ContainsKey(CrashReason.Mod配置文件导致游戏崩溃) Then AppendReason(CrashReason.NightConfig的Bug) If LogMc.Contains("Cannot find launch target fmlclient, unable to launch") Then AppendReason(CrashReason.Forge安装不完整) If LogMc.Contains("Invalid paths argument, contained no existing paths") AndAlso LogMc.Contains("libraries\net\minecraftforge\fmlcore") Then AppendReason(CrashReason.Forge安装不完整) If LogMc.Contains("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符) @@ -576,7 +581,6 @@ Done: End If If LogCrash.Contains("Multiple entries with same key: ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Multiple entries with same key: )[^=]+"), "").TrimEnd((vbCrLf & " ").ToCharArray))) If LogCrash.Contains("LoaderExceptionModCrash: Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=LoaderExceptionModCrash: Caught exception from )[^\n]+"), "").TrimEnd((vbCrLf & " ").ToCharArray))) - If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)}) End If End Sub @@ -856,12 +860,13 @@ NextStack: ''' ''' 弹出崩溃弹窗,并指导导出崩溃报告。 ''' - Public Sub Output(IsHandAnalyze As Boolean, Optional ExtraFiles As List(Of String) = Nothing) + Public Sub Output(IsManualAnalyze As Boolean, Optional ExtraFiles As List(Of String) = Nothing) '弹窗提示 FrmMain.ShowWindowToTop() - Select Case MyMsgBox(GetAnalyzeResult(IsHandAnalyze), If(IsHandAnalyze, "错误报告分析结果", "Minecraft 出现错误"), - "确定", If(IsHandAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsHandAnalyze, "", "导出错误报告"), - Button2Action:=If(IsHandAnalyze OrElse DirectFile Is Nothing, Nothing, + Dim Detail As String = GetAnalyzeResult(IsManualAnalyze) + Select Case MyMsgBox(Detail, If(IsManualAnalyze, "错误报告分析结果", "Minecraft 出现错误"), + "确定", If(IsManualAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsManualAnalyze, "", "导出错误报告"), + Button2Action:=If(IsManualAnalyze OrElse DirectFile Is Nothing, Nothing, Sub() '弹窗选择:查看日志 If File.Exists(DirectFile.Value.Key) Then @@ -918,6 +923,11 @@ NextStack: End Try OpenExplorer(FileAddress) End Select + '上报 + If IsManualAnalyze Then Return + Telemetry("Minecraft 崩溃", + "Version", If(TargetVersion Is Nothing, "null", TargetVersion.Version.ToString), + "Detail", FilterUserName(Detail, "*")) End Sub ''' ''' 获取崩溃分析的结果描述。 @@ -938,6 +948,8 @@ NextStack: For Each Reason In CrashReasons Dim Additional As List(Of String) = Reason.Value Select Case Reason.Key + Case CrashReason.Java虚拟机参数有误 + Results.Add("由于 Java 虚拟机参数有误,导致游戏无法继续运行。\n请检查高级启动选项中设置的 Java 虚拟机参数是否有误。") Case CrashReason.Mod文件被解压 Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。") Case CrashReason.内存不足 @@ -1018,9 +1030,14 @@ NextStack: End If Case CrashReason.特定实体导致崩溃 If Additional.Count = 1 Then - Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况:\n - 若正常运行,则是该实体导致出错,你或许需要使用一些方式删除此实体。\n - 若仍然出错,问题就可能来自其他原因……\h") + Dim entityInfo As String = Additional.First + If entityInfo.StartsWith("minecraft:player") Then + Results.Add("游戏似乎因为玩家实体 " & entityInfo & " 出现了问题。\n\n这通常是因为玩家数据损坏或世界存档问题导致的。\n请尝试以下解决方案:\n - 创建一个新世界,观察游戏是否正常运行\n - 如果新世界正常,可能需要删除或修复当前世界的玩家数据文件\n - 也可能是某个 Mod 与玩家实体处理存在冲突,尝试逐个禁用近期安装的 Mod\n - 如果问题持续存在,建议备份重要数据后重新创建世界\h") + Else + Results.Add("游戏似乎因为实体 " & entityInfo & " 出现了问题。\n\n请尝试以下解决方案:\n - 创建一个新世界,并尝试生成该类型的实体,观察游戏运行情况\n - 如果新世界中该实体正常,问题可能在于当前世界的数据损坏\n - 如果新世界中仍然崩溃,可能是某个 Mod 导致了实体处理异常\n - 尝试逐个禁用可能影响实体的 Mod(如生物类、AI 类 Mod)\n - 也可以尝试使用指令删除该坐标附近的实体\h") + End If Else - Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,观察游戏的运行情况:\n - 若正常运行,则是某些实体导致出错,你或许需要删除该世界。\n - 若仍然出错,问题就可能来自其他原因……\h") + Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n请尝试以下解决方案:\n - 创建一个新世界,观察游戏的运行情况\n - 如果新世界正常运行,则是当前世界的实体数据导致出错\n - 可以尝试使用指令清理异常实体\n - 如果问题持续存在,可能是某个处理实体的 Mod 存在问题\n - 建议逐个禁用近期安装的 Mod,特别是涉及生物、AI 或实体处理的 Mod\h") End If Case CrashReason.OptiFine与Forge不兼容 Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。") @@ -1102,7 +1119,7 @@ NextStack: If(Not Results.Any(Function(r) r.EndsWithF("\h")) OrElse IsHandAnalyze, "", vbCrLf & "如果要寻求帮助,请把错误报告文件发给对方,而不是发送这个窗口的照片或者截图。" & If(If(PageSetupSystem.IsLauncherNewest(), True), "", - vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 设置 → 启动器 → 检查更新 来更新 PCL。")) + vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 [设置 → 其他 → 启动器 → 检查更新] 更新 PCL。")) End Function End Class diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb index ef7e020..e6d517e 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb @@ -198,7 +198,7 @@ Private IsNewClientVersionHinted As Boolean = False Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult)) Dim StartTime As Long = GetTimeTick() - Dim Json As JObject = NetGetCodeByRequestRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", IsJson:=True) + Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json")) Try Dim Versions As JArray = Json("versions") If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")") @@ -235,7 +235,7 @@ ''' Public DlClientListBmclapiLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain) Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of String, DlClientListResult)) - Dim Json As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", IsJson:=True) + Dim Json As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json")) Try Dim Versions As JArray = Json("versions") If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")") @@ -377,7 +377,7 @@ ''' Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain) Private Sub DlOptiFineListOfficialMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult)) - Dim Result As String = NetGetCodeByClient("https://optifine.net/downloads", Encoding.Default) + Dim Result As String = NetRequestByClientRetry("https://optifine.net/downloads", Encoding:=Encoding.Default) If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") Try '获取所有版本信息 @@ -413,7 +413,7 @@ ''' Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain) Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult)) - Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/optifine/versionList", IsJson:=True) + Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList")) Try Dim Versions As New List(Of DlOptiFineListEntry) For Each Token As JObject In Json @@ -483,7 +483,7 @@ ''' Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain) Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult)) - Dim Result As String = NetGetCodeByRequestRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html", UseBrowserUserAgent:=True) + Dim Result As String = NetRequestByClientRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding:=Encoding.Default, Accept:="text/html", UseBrowserUserAgent:=True) If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") '获取所有版本信息 Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)") @@ -497,7 +497,7 @@ ''' Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain) Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult)) - Dim Result As String = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding.Default) + Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding:=Encoding.Default) If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") '获取所有版本信息 Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?") @@ -594,7 +594,7 @@ VersionName = Version Me.Version = New Version(Version) Me.Inherit = Inherit - FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch) + FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch) End Sub End Class @@ -629,7 +629,7 @@ Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry))) Dim Result As String Try - Result = NetGetCodeByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & + Result = NetRequestByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057 ".html", UseBrowserUserAgent:=True) Catch ex As Exception @@ -700,9 +700,8 @@ ''' Forge 版本列表,BMCLAPI。 ''' Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry))) - Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" & - Loader.Input.Replace("-", "_"), '兼容 Forge 1.7.10-pre4,#4057 - IsJson:=True) + Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" & + Loader.Input.Replace("-", "_"))) '兼容 Forge 1.7.10-pre4,#4057 Dim Versions As New List(Of DlForgeVersionEntry) Try Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input) @@ -836,8 +835,8 @@ Public DlNeoForgeListOfficialLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Official", AddressOf DlNeoForgeListOfficialMain) Private Sub DlNeoForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult)) '获取版本列表 JSON - Dim ResultLatest As String = NetGetCodeByLoader("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True) - Dim ResultLegacy As String = NetGetCodeByLoader("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True) + Dim ResultLatest As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge") + Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge") If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")") '解析 Try @@ -854,8 +853,8 @@ Public DlNeoForgeListBmclapiLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Bmclapi", AddressOf DlNeoForgeListBmclapiMain) Public Sub DlNeoForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult)) '获取版本列表 JSON - Dim ResultLatest As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True) - Dim ResultLegacy As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True) + Dim ResultLatest As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge") + Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge") If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")") '解析 Try @@ -960,7 +959,7 @@ ''' Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain) Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("https://dl.liteloader.com/versions/versions.json", IsJson:=True) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json")) Try Dim Json As JObject = Result("versions") Dim Versions As New List(Of DlLiteLoaderListEntry) @@ -988,7 +987,7 @@ ''' Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain) Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", IsJson:=True) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")) Try Dim Json As JObject = Result("versions") Dim Versions As New List(Of DlLiteLoaderListEntry) @@ -1059,7 +1058,7 @@ ''' Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain) Private Sub DlFabricListOfficialMain(Loader As LoaderTask(Of Integer, DlFabricListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("https://meta.fabricmc.net/v2/versions", IsJson:=True) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions")) Try Dim Output = New DlFabricListResult With {.IsOfficial = True, .SourceName = "Fabric 官方源", .Value = Result} If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项") @@ -1074,7 +1073,7 @@ ''' Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain) Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", IsJson:=True) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions")) Try Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result} If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项") @@ -1101,72 +1100,37 @@ #Region "DlMod | Mod 镜像源请求" ''' - ''' 对可能涉及 Mod 镜像源的请求进行处理,返回字符串或 JObject。 - ''' 调用 NetGetCodeByRequest,会进行重试。 - ''' - Public Function DlModRequest(Url As String, Optional IsJson As Boolean = False) As Object - Dim Urls As New List(Of KeyValuePair(Of String, Integer)) - Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - Urls.Add(New KeyValuePair(Of String, Integer)(Url, 20)) - 'Dim McimUrl As String = DlSourceModGet(Url) - 'If McimUrl <> Url Then - ' Select Case Setup.Get("ToolDownloadMod") - ' Case 0 - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Case 1 - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' Case Else - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' End Select - 'End If - Dim Exs As String = "" - For Each Source In Urls - Try - Return NetGetCodeByRequestOnce(Source.Key, Encode:=Encoding.UTF8, Timeout:=Source.Value * 1000, IsJson:=IsJson, UseBrowserUserAgent:=True) - Catch ex As Exception - Exs += ex.Message + vbCrLf - End Try - Next - Throw New Exception(Exs) - End Function - - ''' - ''' 对可能涉及 Mod 镜像源的请求进行处理。 + ''' 对可能涉及 Mod 镜像源的请求进行处理,返回 JToken。 ''' 调用 NetRequest,会进行重试。 ''' - Public Function DlModRequest(Url As String, Method As String, Data As String, ContentType As String) As String + Public Function DlModRequest(Url As String, Optional Method As HttpMethod = Nothing, + Optional Content As String = Nothing, Optional ContentType As String = Nothing) As JToken Dim Urls As New List(Of KeyValuePair(Of String, Integer)) - Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - Urls.Add(New KeyValuePair(Of String, Integer)(Url, 20)) - 'Dim McimUrl As String = DlSourceModGet(Url) - 'If McimUrl <> Url Then - ' Select Case Setup.Get("ToolDownloadMod") - ' Case 0 - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Case 1 - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' Case Else - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' End Select - 'End If + Dim McimUrl As String = DlSourceModGet(Url) + If McimUrl <> Url Then + Select Case Setup.Get("ToolDownloadMod") + Case 0 + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30)) + Case 1 + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) '至少 10s,要不然有时候远端服务器来不及完成 + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30)) + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30)) + Case Else + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30)) + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30)) + End Select + Else + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30)) + End If Dim Exs As String = "" For Each Source In Urls Try - Return NetRequestOnce(Source.Key, Method, Data, ContentType, Timeout:=Source.Value * 1000) + Return GetJson(NetRequestByClient(Source.Key, Method, Content, ContentType, Timeout:=Source.Value * 1000, Encoding:=Encoding.UTF8)) Catch ex As Exception Exs += ex.Message + vbCrLf End Try @@ -1269,7 +1233,14 @@ 'Mod 下载源 Public Function DlSourceModGet(Original As String) As String - Return Original + Return Original. + Replace("api.modrinth.com", "mod.mcimirror.top/modrinth"). + Replace("staging-api.modrinth.com", "mod.mcimirror.top/modrinth"). + Replace("cdn.modrinth.com", "mod.mcimirror.top"). + Replace("api.curseforge.com", "mod.mcimirror.top/curseforge"). + Replace("edge.forgecdn.net", "mod.mcimirror.top"). + Replace("mediafilez.forgecdn.net", "mod.mcimirror.top"). + Replace("media.forgecdn.net", "mod.mcimirror.top") End Function 'Loader 自动切换 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb index 9c41a4a..b5ea00a 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb @@ -117,6 +117,7 @@ If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败") If ModeDebug Then Log("[Java] Java 检查输出:" & PathFolder & "java.exe" & vbCrLf & Output) If Output.Contains("/lib/ext exists") Then Throw New ApplicationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试") + If Output.Contains("a fatal error") Then Throw New ApplicationException("无法运行该 Java,该 Java 或系统存在问题") '获取详细信息 Dim VersionString = If(RegexSeek(Output, "(?<=version "")[^""]+"), If(RegexSeek(Output, "(?<=openjdk )[0-9]+"), "")).Replace("_", ".").Split("-").First If VersionString.Split(".").Count > 4 Then VersionString = VersionString.Replace(".0.", ".") '#3493,VersionString = "21.0.2.0.2" @@ -459,10 +460,12 @@ NoUserJava: ''' 将 Java 按照适用性排序。 ''' Public Function JavaSorter(Left As JavaEntry, Right As JavaEntry) As Boolean + Dim PathInfo As New DirectoryInfo(ShortenPath(Path)) + Dim PathMcInfo As New DirectoryInfo(ShortenPath(PathMcFolder)) '1. 尽量在当前文件夹或当前 Minecraft 文件夹 Dim ProgramPathParent As String, MinecraftPathParent As String = "" - ProgramPathParent = If(New DirectoryInfo(Path).Parent, New DirectoryInfo(Path)).FullName - If PathMcFolder <> "" Then MinecraftPathParent = If(New DirectoryInfo(PathMcFolder).Parent, New DirectoryInfo(PathMcFolder)).FullName + ProgramPathParent = If(PathInfo.Parent, PathInfo).FullName + If PathMcFolder <> "" Then MinecraftPathParent = If(PathMcInfo.Parent, PathMcInfo).FullName If Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Not Right.PathFolder.StartsWithF(ProgramPathParent) Then Return True If Not Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Right.PathFolder.StartsWithF(ProgramPathParent) Then Return False If PathMcFolder <> "" Then @@ -529,6 +532,8 @@ NoUserJava: If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705) JavaSearchFolder(Disk.Name, JavaPreList, False) Next + '查找 .jdks 文件夹中的 Java + JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\.jdks\", JavaPreList, False, True) '查找 APPDATA 文件夹中的 Java JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\", JavaPreList, False) JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) & "\", JavaPreList, False) @@ -677,14 +682,14 @@ Wait: '若该目录有 Java,则加入结果 If File.Exists(Path & "javaw.exe") Then Results(Path) = Source '查找其下的所有文件夹 - '不应使用网易的 Java:https://github.com/Hex-Dragon/PCL2/issues/1279#issuecomment-2761489121 - Dim Keywords = {"java", "jdk", "env", "环境", "run", "软件", "jre", "mc", "dragon", + '不应使用网易的 Java:https://github.com/Meloong-Git/PCL/issues/1279#issuecomment-2761489121 + Dim Keywords = {"java", "jdk", "env", "环境", "run", "软件", "jre", "mc", "dragon", "bin", "soft", "cache", "temp", "corretto", "roaming", "users", "craft", "program", "世界", "net", "游戏", "oracle", "game", "file", "data", "jvm", "服务", "server", "客户", "client", "整合", "应用", "运行", "前置", "mojang", "官启", "新建文件夹", "eclipse", "microsoft", "hotspot", "runtime", "x86", "x64", "forge", "原版", "optifine", "官方", "启动", "hmcl", "mod", "高清", "download", "launch", "程序", "path", "version", "baka", "pcl", "zulu", "local", "packages", - "4297127d64ec6", "1.", "启动"} + "4297127d64ec6", "1.", "启动", "jbr-"} For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接 Dim SearchEntry = GetFolderNameFromPath(FolderInfo.Name).ToLower '用于搜索的字符串 @@ -745,7 +750,7 @@ Wait: Private LastJavaBaseDir As String = Nothing '用于在下载中断或失败时删除未完成下载的 Java 文件夹,防止残留只下了一半但 -version 能跑的 Java Private Sub JavaFileList(Loader As LoaderTask(Of Integer, List(Of NetFile))) Log("[Java] 开始获取 Java 下载信息") - Dim IndexFileStr As String = NetGetCodeByLoader(DlVersionListOrder( + Dim IndexFileStr As String = NetRequestByLoader(DlVersionListOrder( {"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}, {"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"} ), IsJson:=True) @@ -757,7 +762,7 @@ Wait: Dim Address As String = TargetEntry.Value("manifest")("url") Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}") '获取文件列表 - Dim ListFileStr As String = NetGetCodeByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True) + Dim ListFileStr As String = NetRequestByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True) LastJavaBaseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Key & "\" Dim Results As New List(Of NetFile) For Each File As JProperty In CType(GetJson(ListFileStr), JObject)("files") diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb index 14edb8a..393355b 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb @@ -66,7 +66,7 @@ Public Module ModLaunch ''' Public Sub McLaunchLog(Text As String) Text = FilterUserName(FilterAccessToken(Text, "*"), "*") - RunInUi(Sub() FrmLaunchRight.LabLog.Text += vbCrLf & "[" & GetTimeNow() & "] " & Text) + RunInUi(Sub() FrmLaunchRight.LabLog.Text += $"{vbCrLf}[{GetTimeNow()}] {Text}") Log("[Launch] " & Text) End Sub @@ -101,6 +101,8 @@ Public Module ModLaunch If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Critical) Throw End Try + Dim IsSavingBatch As Boolean = CurrentLaunchOptions?.SaveBatch IsNot Nothing + '正式加载 Try '构造主加载器 @@ -145,9 +147,15 @@ Public Module ModLaunch Select Case LaunchLoader.State Case LoadState.Finished Hint(McVersionCurrent.Name & " 启动成功!", HintType.Finish) + '上报 + If Not IsSavingBatch Then + Telemetry("Minecraft 启动成功", + "Version", McVersionCurrent.Version.ToString, + "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType))) + End If Case LoadState.Aborted If AbortHint Is Nothing Then - Hint(If(CurrentLaunchOptions?.SaveBatch Is Nothing, "已取消启动!", "已取消导出启动脚本!"), HintType.Info) + Hint(If(IsSavingBatch, "已取消导出启动脚本!", "已取消启动!"), HintType.Info) Else Hint(AbortHint, HintType.Finish) End If @@ -162,7 +170,7 @@ NextInner: If CurrentEx.Message.StartsWithF("$") Then '若有以 $ 开头的错误信息,则以此为准显示提示 '若错误信息为 $$,则不提示 - If Not CurrentEx.Message = "$$" Then MyMsgBox(CurrentEx.Message.TrimStart("$"), If(CurrentLaunchOptions?.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败")) + If Not CurrentEx.Message = "$$" Then MyMsgBox(CurrentEx.Message.TrimStart("$"), If(IsSavingBatch, "导出启动脚本失败", "启动失败")) Throw ElseIf CurrentEx.InnerException IsNot Nothing Then '检查下一级错误 @@ -172,8 +180,15 @@ NextInner: '没有特殊处理过的错误信息 McLaunchLog("错误:" & GetExceptionDetail(ex)) Log(ex, - If(CurrentLaunchOptions?.SaveBatch Is Nothing, "Minecraft 启动失败", "导出启动脚本失败"), LogLevel.Msgbox, - If(CurrentLaunchOptions?.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败")) + If(IsSavingBatch, "导出启动脚本失败", "Minecraft 启动失败"), LogLevel.Msgbox, + If(IsSavingBatch, "导出启动脚本失败", "启动失败")) + '上报 + If Not IsSavingBatch Then + Telemetry("Minecraft 启动失败", + "Version", McVersionCurrent.Version.ToString, + "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType)), + "Exception", FilterUserName(FilterAccessToken(GetExceptionDetail(ex), "*"), "*")) + End If Throw End If End Try @@ -684,11 +699,10 @@ LoginFinish: '发送登录请求 Dim RequestData As New JObject( New JProperty("accessToken", AccessToken), New JProperty("clientToken", ClientToken), New JProperty("requestUser", True)) - NetRequestRetry( - Url:=Data.Input.BaseUrl & "/validate", - Method:="POST", - Data:=RequestData.ToString(0), - Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}}, + NetRequestByClientRetry( + Data.Input.BaseUrl & "/validate", HttpMethod.Post, + Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None), + Headers:={{"Accept-Language", "zh-CN"}}, ContentType:="application/json; charset=utf-8") '没有返回值的 '将登录结果输出 Data.Output.AccessToken = AccessToken @@ -701,10 +715,9 @@ LoginFinish: End Sub Private Sub McLoginRequestRefresh(ByRef Data As LoaderTask(Of McLoginServer, McLoginResult), RequestUser As Boolean) McLaunchLog("刷新登录开始(Refresh, " & Data.Input.Token & ")") - Dim LoginJson As JObject = GetJson(NetRequestRetry( - Url:=Data.Input.BaseUrl & "/refresh", - Method:="POST", - Data:=New JObject( + Dim LoginJson As JObject = GetJson(NetRequestByClientRetry( + Data.Input.BaseUrl & "/refresh", HttpMethod.Post, + Content:=New JObject( New JProperty("selectedProfile", New JObject( New JProperty("name", Setup.Get($"Cache{Data.Input.Token}Name")), New JProperty("id", Setup.Get($"Cache{Data.Input.Token}Uuid")) @@ -712,7 +725,7 @@ LoginFinish: New JProperty("accessToken", Setup.Get($"Cache{Data.Input.Token}Access")), New JProperty("requestUser", True) ).ToString(Newtonsoft.Json.Formatting.None), - Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}}, + Headers:={{"Accept-Language", "zh-CN"}}, ContentType:="application/json; charset=utf-8")) '将登录结果输出 If LoginJson("selectedProfile") Is Nothing Then Throw New Exception("选择的角色 " & Setup.Get("Cache" & Data.Input.Token & "Name") & " 无效!") @@ -739,11 +752,10 @@ LoginFinish: New JProperty("username", Data.Input.UserName), New JProperty("password", Data.Input.Password), New JProperty("requestUser", True)) - Dim LoginJson As JObject = GetJson(NetRequestRetry( - Url:=Data.Input.BaseUrl & "/authenticate", - Method:="POST", - Data:=RequestData.ToString(0), - Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}}, + Dim LoginJson As JObject = GetJson(NetRequestByClientRetry( + Data.Input.BaseUrl & "/authenticate", HttpMethod.Post, + Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None), + Headers:={{"Accept-Language", "zh-CN"}}, ContentType:="application/json; charset=utf-8")) '检查登录结果 If LoginJson("availableProfiles").Count = 0 Then @@ -840,7 +852,7 @@ LoginFinish: If(Data.Input.UserName.Contains("@"), "", " - 登录账号应为邮箱或统一通行证账号,而非游戏角色 ID。" & vbCrLf) & " - 只注册了账号,但没有加入对应服务器。") End Select - ElseIf AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout") OrElse AllMessage.Contains("网络请求失败") Then + ElseIf AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout") OrElse AllMessage.Contains("网络请求失败") OrElse AllMessage.Contains("408") OrElse AllMessage.Contains("由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。") Then Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后重试,或使用加速器或 VPN 以改善网络环境。") ElseIf ex.Message.StartsWithF("$") Then Throw @@ -858,8 +870,9 @@ LoginFinish: '初始请求 Retry: McLaunchLog("开始微软登录步骤 1/6(原始登录)") - Dim PrepareJson As JObject = GetJson(NetRequestRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", "POST", - $"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access", "application/x-www-form-urlencoded")) + Dim PrepareJson As JObject = GetJson(NetRequestByClientRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", HttpMethod.Post, + Content:=$"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access", + ContentType:="application/x-www-form-urlencoded")) McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString) '弹窗 @@ -888,13 +901,22 @@ Retry: Dim Result As String Try - Result = NetRequestMultiple("https://login.live.com/oauth20_token.srf", "POST", - $"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access", - "application/x-www-form-urlencoded", 2) - Catch ex As Exception - If ex.Message.ContainsF("must sign in again", True) OrElse ex.Message.ContainsF("password expired", True) OrElse - (ex.Message.Contains("refresh_token") AndAlso ex.Message.Contains("is not valid")) Then '#269 + Result = NetRequestByClientMultiple("https://login.live.com/oauth20_token.srf", HttpMethod.Post, + Content:=$"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access", + ContentType:="application/x-www-form-urlencoded", + Headers:={{"Accept-Language", "en-US,en;q=0.5"}, {"X-Requested-With", "XMLHttpRequest"}}, + ThreadCount:=2) + Catch ex As ResponsedWebException + Dim Response = ex.Response + If Response.ContainsF("must sign in again", True) OrElse Response.ContainsF("password expired", True) OrElse + (Response.Contains("refresh_token") AndAlso Response.Contains("is not valid")) Then '#269 Return {"Relogin", ""} + ElseIf Response.Contains("Account security interrupt") Then + MyMsgBox("该账号由于安全问题无法登陆,请前往微软账户页获取更多信息。", "登录失败", "我知道了", IsWarn:=True) + Throw New Exception("$$") + ElseIf Response.Contains("service abuse") Then + MyMsgBox("非常抱歉,该账号已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True) + Throw New Exception("$$") Else Throw End If @@ -918,8 +940,8 @@ Retry: ""RelyingParty"": ""http://auth.xboxlive.com"", ""TokenType"": ""JWT"" }" - Dim Result As String = NetRequestMultiple("https://user.auth.xboxlive.com/user/authenticate", "POST", Request, "application/json", 3) - + Dim Result As String = NetRequestByClientMultiple("https://user.auth.xboxlive.com/user/authenticate", HttpMethod.Post, + Content:=Request, ContentType:="application/json") Dim ResultJson As JObject = GetJson(Result) Dim XBLToken As String = ResultJson("Token").ToString Return XBLToken @@ -940,21 +962,22 @@ Retry: }" Dim Result As String Try - Result = NetRequestMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", "POST", Request, "application/json", 3) - Catch ex As Net.WebException + Result = NetRequestByClientMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post, + Content:=Request, ContentType:="application/json") + Catch ex As ResponsedWebException '参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js - If ex.Message.Contains("2148916227") Then + If ex.Response.Contains("2148916227") Then MyMsgBox("该账号似乎已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True) Throw New Exception("$$") - ElseIf ex.Message.Contains("2148916233") Then + ElseIf ex.Response.Contains("2148916233") Then If MyMsgBox("你尚未注册 Xbox 账户,请在注册后再登录。", "登录提示", "注册", "取消") = 1 Then OpenWebsite("https://signup.live.com/signup") End If Throw New Exception("$$") - ElseIf ex.Message.Contains("2148916235") Then + ElseIf ex.Response.Contains("2148916235") Then MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请使用加速器或 VPN。", "登录失败", "我知道了") Throw New Exception("$$") - ElseIf ex.Message.Contains("2148916238") Then + ElseIf ex.Response.Contains("2148916238") Then If MyMsgBox("该账号年龄不足,你需要先修改出生日期,然后才能登录。" & vbCrLf & "该账号目前填写的年龄是否在 13 岁以上?", "登录提示", "13 岁以上", "12 岁以下", "我不知道") = 1 Then OpenWebsite("https://account.live.com/editprof.aspx") @@ -971,10 +994,15 @@ Retry: End If End Try - Dim ResultJson As JObject = GetJson(Result) - Dim XSTSToken As String = ResultJson("Token").ToString - Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString - Return {XSTSToken, UHS} + Try '这个 Try 块仅为了追踪 #6683 而添加 + Dim ResultJson As JObject = GetJson(Result) + Dim XSTSToken As String = ResultJson("Token").ToString + Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString + Return {XSTSToken, UHS} + Catch + Log("[Launch] 返回内容:" & vbCrLf & Result) + Throw + End Try End Function '微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken Private Function MsLoginStep4(Tokens As String()) As String @@ -983,14 +1011,15 @@ Retry: Dim Request As String = New JObject(New JProperty("identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}")).ToString(0) Dim Result As String Try - Result = NetRequestRetry("https://api.minecraftservices.com/authentication/login_with_xbox", "POST", Request, "application/json") + Result = NetRequestByClientRetry("https://api.minecraftservices.com/authentication/login_with_xbox", HttpMethod.Post, + Content:=Request, ContentType:="application/json") Catch ex As Net.WebException Dim Message As String = GetExceptionSummary(ex) If Message.Contains("(429)") Then - Log(ex, "微软登录第 5 步汇报 429") + Log(ex, "微软登录第 4 步汇报 429") Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!") ElseIf Message.Contains("(403)") Then - Log(ex, "微软登录第 5 步汇报 403") + Log(ex, "微软登录第 4 步汇报 403") Throw New Exception("$当前 IP 的登录尝试异常。" & vbCrLf & "如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!") Else Throw @@ -1005,7 +1034,8 @@ Retry: Private Sub MsLoginStep5(AccessToken As String) McLaunchLog("开始微软登录步骤 5/6") - Dim Result As String = NetRequestMultiple("https://api.minecraftservices.com/entitlements/mcstore", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}}) + Dim Result As String = NetRequestByClientMultiple("https://api.minecraftservices.com/entitlements/mcstore", + ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}) Try Dim ResultJson As JObject = GetJson(Result) If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then @@ -1016,7 +1046,7 @@ Retry: Throw New Exception("$$") End If Catch ex As Exception - Log(ex, "微软登录第 6 步异常:" & Result) + Log(ex, "微软登录第 5 步异常:" & Result) Throw End Try End Sub @@ -1026,14 +1056,15 @@ Retry: Dim Result As String Try - Result = NetRequestMultiple("https://api.minecraftservices.com/minecraft/profile", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}}) + Result = NetRequestByClientMultiple("https://api.minecraftservices.com/minecraft/profile", + ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}) Catch ex As Net.WebException Dim Message As String = GetExceptionSummary(ex) If Message.Contains("(429)") Then - Log(ex, "微软登录第 7 步汇报 429") + Log(ex, "微软登录第 6 步汇报 429") Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!") ElseIf Message.Contains("(404)") Then - Log(ex, "微软登录第 7 步汇报 404") + Log(ex, "微软登录第 6 步汇报 404") RunInNewThread( Sub() Select Case MyMsgBox("请先创建 Minecraft 玩家档案,然后再重新登录。", "登录失败", "创建档案", "取消") @@ -1099,7 +1130,7 @@ Retry: If Len(Uuid) = 32 Then Return Uuid '从官网获取 Try - Dim GotJson As JObject = NetGetCodeByRequestRetry("https://api.mojang.com/users/profiles/minecraft/" & Name, IsJson:=True) + Dim GotJson As JObject = GetJson(NetRequestByClientRetry("https://api.mojang.com/users/profiles/minecraft/" & Name)) If GotJson Is Nothing Then Throw New FileNotFoundException("正版玩家档案不存在(" & Name & ")") Uuid = If(GotJson("id"), "") Catch ex As Exception @@ -1205,6 +1236,12 @@ Retry: End If End If + 'LiteLoader 检测 + If McVersionCurrent.Version.HasLiteLoader AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本 + '最高 Java 8 + MaxVer = If(New Version(1, 8, 999, 999) < MaxVer, New Version(1, 8, 999, 999), MaxVer) + End If + '统一通行证检测 If Setup.Get("LoginType") = McLoginType.Nide Then '至少 Java 8u101 @@ -1349,23 +1386,13 @@ Retry: McLaunchLog("新版 Game 参数获取成功") End If '编码参数(#4700、#5892、#5909) + If McLaunchJavaSelected.VersionCode >= 18 Then 'Dfile.encoding 需要放在 Dstdout.encoding 后面(#6934) + If Not Arguments.Contains("-Dfile.encoding=") Then Arguments = "-Dfile.encoding=COMPAT " & Arguments + End If If McLaunchJavaSelected.VersionCode > 8 Then If Not Arguments.Contains("-Dstdout.encoding=") Then Arguments = "-Dstdout.encoding=UTF-8 " & Arguments If Not Arguments.Contains("-Dstderr.encoding=") Then Arguments = "-Dstderr.encoding=UTF-8 " & Arguments End If - If McLaunchJavaSelected.VersionCode >= 18 Then - If Not Arguments.Contains("-Dfile.encoding=") Then Arguments = "-Dfile.encoding=COMPAT " & Arguments - End If - '替换参数 - Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader) - If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then - '若自定义信息为空,则去掉该部分 - Arguments = Arguments.Replace(" --versionType ${version_type}", "") - ReplaceArguments("${version_type}") = """""" - End If - For Each entry As KeyValuePair(Of String, String) In ReplaceArguments - Arguments = Arguments.Replace(entry.Key, If(entry.Value.Contains(" ") OrElse entry.Value.Contains(":\"), """" & entry.Value & """", entry.Value)) - Next 'MJSB Arguments = Arguments.Replace(" -Dos.name=Windows 10", " -Dos.name=""Windows 10""") '全屏 @@ -1374,31 +1401,47 @@ Retry: For Each Arg In CurrentLaunchOptions.ExtraArgs Arguments += " " & Arg.Trim Next + '自定义 + Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent) + Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame) + '进行参数替换 + Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader) + If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then + '若自定义信息为空,则去掉该部分 + Arguments = Arguments.Replace(" --versionType ${version_type}", "") + ReplaceArguments("${version_type}") = """""" + End If + Dim FinalArguments As String = "" + For Each Argument In Arguments.Split(" ") + For Each Entry As KeyValuePair(Of String, String) In ReplaceArguments + Argument = Argument.Replace(Entry.Key, Entry.Value) + Next + If (Argument.Contains(" ") OrElse Argument.Contains(":\")) AndAlso Not Argument.EndsWithF("""") Then Argument = $"""{Argument}""" + FinalArguments += Argument & " " + Next + FinalArguments = FinalArguments.TrimEnd '进服 Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp) If Server.Length > 0 Then If McVersionCurrent.ReleaseTime > New Date(2023, 4, 4) Then 'QuickPlay - Arguments += $" --quickPlayMultiplayer ""{Server}""" + FinalArguments += $" --quickPlayMultiplayer ""{Server}""" Else '老版本 If Server.Contains(":") Then '包含端口号 - Arguments += " --server " & Server.Split(":")(0) & " --port " & Server.Split(":")(1) + FinalArguments += $" --server {Server.Split(":")(0)} --port {Server.Split(":")(1)}" Else '不包含端口号 - Arguments += " --server " & Server & " --port 25565" + FinalArguments += $" --server {Server} --port 25565" End If If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Critical) End If End If - '自定义 - Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent) - Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame) '输出 McLaunchLog("Minecraft 启动参数:") - McLaunchLog(Arguments) - McLaunchArgument = Arguments + McLaunchLog(FinalArguments) + McLaunchArgument = FinalArguments End Sub 'Jvm 部分(第一段) @@ -1424,11 +1467,12 @@ Retry: End If 'Authlib-Injector If McLoginLoader.Output.Type = "Auth" Then + If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#) Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy, "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录 Setup.Get("VersionServerAuthServer", McVersionCurrent)) Try - Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8) + Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8) DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server & " -Dauthlibinjector.side=client" & " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response))) @@ -1493,11 +1537,12 @@ NextVersion: End If 'Authlib-Injector If McLoginLoader.Output.Type = "Auth" Then + If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#) Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy, "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录 Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent)) Try - Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8) + Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8) DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server & " -Dauthlibinjector.side=client" & " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response))) @@ -1531,10 +1576,8 @@ NextVersion: DeDuplicateDataList.Add(CurrentEntry.Trim.Replace("McEmu= ", "McEmu=")) Next - '#3511 的清理 - DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") - '去重 + DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") '#3511 的清理 Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ") '添加 MainClass @@ -1671,6 +1714,7 @@ NextVersion: GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken) GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken) GameArguments.Add("${user_type}", "msa") '#1221 + GameArguments.Add("${primary_jar}", Version.Path & Version.Name & ".jar") '#6942 '窗口尺寸参数 Dim GameSize As Size diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb index d532ff2..6c11868 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb @@ -169,8 +169,8 @@ Public Module ModMinecraft _McVersionCurrent = value '由于有可能是 Nothing,导致无法初始化,才得这样弄一圈 _McVersionLast = value If value Is Nothing Then Return - '重置缓存的 Mod 文件夹 - PageDownloadCompDetail.CachedFolder = Nothing + '重置缓存的下载文件夹 + PageDownloadCompDetail.CachedFolder.Clear() '统一通行证重判 If AniControlEnabled = 0 AndAlso Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso @@ -440,6 +440,33 @@ VersionSearchFinish: ''' Public ReleaseTime As New Date(1970, 1, 1, 15, 0, 0) + ''' + ''' 获取该版本的 JSON 路径。 + ''' + Public Function GetJsonPath() As String + Dim JsonPath As String = Path & Name & ".json" + If Not File.Exists(JsonPath) Then + '尝试寻找 JSON 文件 + JsonPath = Nothing + For Each JsonCandidatePath In Directory.GetFiles(Path, "*.json") + Try + Dim JsonCandidate As JObject = GetJson(ReadFile(JsonCandidatePath)) + If Not JsonCandidate.ContainsKey("mainClass") Then Continue For + If Not JsonCandidate.ContainsKey("type") Then Continue For + If Not JsonCandidate.ContainsKey("id") Then Continue For + Catch + End Try + JsonPath = JsonCandidatePath + Exit For + Next + If JsonPath Is Nothing Then + Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json") + Else + Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug) + End If + End If + Return JsonPath + End Function ''' ''' 该版本的 Json 文本。 ''' @@ -452,17 +479,7 @@ VersionSearchFinish: Return TrimedJson.StartsWithF("{") AndAlso TrimedJson.EndsWithF("}") End Function If _JsonText Is Nothing Then - Dim JsonPath As String = Path & Name & ".json" - If Not File.Exists(JsonPath) Then - '如果文件夹下只有一个 JSON 文件,则将其作为版本 JSON - Dim JsonFiles As String() = Directory.GetFiles(Path, "*.json") - If JsonFiles.Count = 1 Then - JsonPath = JsonFiles(0) - Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug) - Else - Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json") - End If - End If + Dim JsonPath As String = GetJsonPath() _JsonText = ReadFile(JsonPath) '如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试 If Not FastJsonCheck(_JsonText) Then @@ -540,9 +557,15 @@ Recheck: '继续循环 If Inherit.InheritVersion = InheritVersion Then Throw New Exception("版本依赖项出现嵌套:" & InheritVersion) InheritVersion = Inherit.InheritVersion - '合并 + '合并 Libraries 项:子版本放在前面,父版本放在后面(5978#:如果多个 jar 包中含有相同的类,Java 8 和之前的版本按照 -cp 指定的顺序选择第一个) + Dim CurrentLib As JArray = _JsonObject("libraries").DeepClone() + For Each LibToken In Inherit.JsonObject("libraries") + CurrentLib.Add(LibToken) + Next + '合并其他项:子版本优先于父版本 Inherit.JsonObject.Merge(_JsonObject) _JsonObject = Inherit.JsonObject + _JsonObject("libraries") = CurrentLib GoTo Recheck End If Catch ex As Exception @@ -580,20 +603,18 @@ Recheck: Get If Not JsonVersionInited Then JsonVersionInited = True - If File.Exists(Path & Name & ".jar") Then - Try - Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json") - If VersionJson IsNot Nothing Then - Using VersionJsonStream As New StreamReader(VersionJson.Open) - _JsonVersion = GetJson(VersionJsonStream.ReadToEnd) - End Using - End If + Try + If Not File.Exists($"{Path}{Name}.jar") Then Exit Try + Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json") + If VersionJson Is Nothing Then Exit Try + Using VersionJsonStream As New StreamReader(VersionJson.Open) + _JsonVersion = GetJson(VersionJsonStream.ReadToEnd) End Using - Catch ex As Exception - Log(ex, "从版本 jar 中读取 version.json 失败") - End Try - End If + End Using + Catch ex As Exception + Log(ex, $"从版本 jar 中读取 version.json 失败({Path}{Name}.jar)") + End Try End If Return _JsonVersion End Get @@ -968,7 +989,7 @@ ExitDataLoad: If ToString = "" Then Return "原版 " & McName Else - Return McName & ToString & If(ModeDebug, " (" & SortCode & "#)", "") + Return McName & ToString End If End Function @@ -1538,7 +1559,7 @@ OnLoaded: ''' 要求玩家选择一个皮肤文件,并进行相关校验。 ''' Public Function McSkinSelect() As McSkinInfo - Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.webp)|*.png;*.jpg;*.webp", "选择皮肤文件") + Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.jpeg;*.webp)|*.png;*.jpg;*.jpeg;*.webp", "选择皮肤文件") '验证有效性 If FileName = "" Then Return New McSkinInfo With {.IsVaild = False} @@ -1589,7 +1610,7 @@ OnLoaded: Case Else Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null")) End Select - Dim SkinString = NetGetCodeByRequestRetry(Url & Uuid) + Dim SkinString = NetRequestByClientRetry(Url & Uuid) If SkinString = "" Then Throw New Exception("皮肤返回值为空,可能是未设置自定义皮肤的用户") '处理皮肤地址 Dim SkinValue As String @@ -1610,7 +1631,8 @@ OnLoaded: If SkinJson("textures") Is Nothing OrElse SkinJson("textures")("skin") Is Nothing OrElse SkinJson("textures")("skin")("url") Is Nothing Then Throw New Exception("用户未设置自定义皮肤") Else - SkinValue = SkinJson("textures")("skin")("url").ToString.Replace("http:", "https:") + Dim SkinUrl As String = SkinJson("textures")("skin")("url").ToString + SkinValue = If(SkinUrl.Contains("minecraft.net/"), SkinUrl.Replace("http://", "https://"), SkinUrl) End If '保存缓存 WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid, SkinValue) @@ -1627,9 +1649,9 @@ OnLoaded: Dim FileAddress As String = PathTemp & "Cache\Skin\" & GetHash(Address) & ".png" SyncLock McSkinDownloadLock If Not File.Exists(FileAddress) Then - NetDownloadByClient(Address, FileAddress & NetDownloadEnd) + NetDownloadByClient(Address, FileAddress & ".PCLDownloading") File.Delete(FileAddress) - FileSystem.Rename(FileAddress & NetDownloadEnd, FileAddress) + FileSystem.Rename(FileAddress & ".PCLDownloading", FileAddress) Log("[Minecraft] 皮肤下载成功:" & FileAddress) End If Return FileAddress @@ -1772,61 +1794,60 @@ OnLoaded: '获取当前支持库列表 Log("[Minecraft] 获取支持库列表:" & Version.Name) - McLibListGet = McLibListGetWithJson(Version.JsonObject) - If Not IncludeVersionJar Then Return McLibListGet + Dim Result = McLibListGetWithJson(Version.JsonObject) '需要添加原版 Jar - Dim RealVersion As McVersion - Dim RequiredJar As String = Version.JsonObject("jar")?.ToString - If Version.IsHmclFormatJson OrElse RequiredJar Is Nothing Then - 'HMCL 项直接使用自身的 Jar - '根据 Inherit 获取最深层版本 - Dim OriginalVersion As McVersion = Version - '1.17+ 的 Forge 不寻找 Inherit - If Not ((Version.Version.HasForge OrElse Version.Version.HasNeoForge) AndAlso Version.Version.McCodeMain >= 17) Then - Do Until OriginalVersion.InheritVersion = "" - If OriginalVersion.InheritVersion = OriginalVersion.Name Then Exit Do - OriginalVersion = New McVersion(PathMcFolder & "versions\" & OriginalVersion.InheritVersion & "\") - Loop + If IncludeVersionJar Then + Dim RealVersion As McVersion + Dim RequiredJar As String = Version.JsonObject("jar")?.ToString + If Version.IsHmclFormatJson OrElse RequiredJar Is Nothing Then + 'HMCL 项直接使用自身的 Jar + '根据 Inherit 获取最深层版本 + Dim OriginalVersion As McVersion = Version + '1.17+ 的 Forge 不寻找 Inherit + If Not ((Version.Version.HasForge OrElse Version.Version.HasNeoForge) AndAlso Version.Version.McCodeMain >= 17) Then + Do Until OriginalVersion.InheritVersion = "" + If OriginalVersion.InheritVersion = OriginalVersion.Name Then Exit Do + OriginalVersion = New McVersion(PathMcFolder & "versions\" & OriginalVersion.InheritVersion & "\") + Loop + End If + '需要新建对象,否则后面的 Check 会导致 McVersionCurrent 的 State 变回 Original + '复现:启动一个 Snapshot 版本 + RealVersion = New McVersion(OriginalVersion.Path) + Else + 'Json 已提供 Jar 字段,使用该字段的信息 + RealVersion = New McVersion(RequiredJar) End If - '需要新建对象,否则后面的 Check 会导致 McVersionCurrent 的 State 变回 Original - '复现:启动一个 Snapshot 版本 - RealVersion = New McVersion(OriginalVersion.Path) - Else - 'Json 已提供 Jar 字段,使用该字段的信息 - RealVersion = New McVersion(RequiredJar) + Dim ClientUrl As String, ClientSHA1 As String + '判断需求的版本是否存在 + '不能调用 RealVersion.Check(),可能会莫名其妙地触发 CheckPermission 正被另一进程使用,导致误判前置不存在 + If Not File.Exists(RealVersion.Path & RealVersion.Name & ".json") Then + RealVersion = Version + Log("[Minecraft] 可能缺少前置版本 " & RealVersion.Name & ",找不到对应的 json 文件", LogLevel.Debug) + End If + '获取详细下载信息 + If RealVersion.JsonObject("downloads") IsNot Nothing AndAlso RealVersion.JsonObject("downloads")("client") IsNot Nothing Then + ClientUrl = RealVersion.JsonObject("downloads")("client")("url") + ClientSHA1 = RealVersion.JsonObject("downloads")("client")("sha1") + Else + ClientUrl = Nothing + ClientSHA1 = Nothing + End If + '把所需的原版 Jar 添加进去 + Result.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1}) End If - Dim ClientUrl As String, ClientSHA1 As String - '判断需求的版本是否存在 - '不能调用 RealVersion.Check(),可能会莫名其妙地触发 CheckPermission 正被另一进程使用,导致误判前置不存在 - If Not File.Exists(RealVersion.Path & RealVersion.Name & ".json") Then - RealVersion = Version - Log("[Minecraft] 可能缺少前置版本 " & RealVersion.Name & ",找不到对应的 json 文件", LogLevel.Debug) - End If - '获取详细下载信息 - If RealVersion.JsonObject("downloads") IsNot Nothing AndAlso RealVersion.JsonObject("downloads")("client") IsNot Nothing Then - ClientUrl = RealVersion.JsonObject("downloads")("client")("url") - ClientSHA1 = RealVersion.JsonObject("downloads")("client")("sha1") - Else - ClientUrl = Nothing - ClientSHA1 = Nothing - End If - '把所需的原版 Jar 添加进去 - McLibListGet.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1}) + Return Result End Function ''' ''' 获取 Minecraft 某一版本忽视继承的支持库列表,即结果中没有继承项。 ''' Public Function McLibListGetWithJson(JsonObject As JObject, Optional KeepSameNameDifferentVersionResult As Boolean = False, Optional CustomMcFolder As String = Nothing) As List(Of McLibToken) CustomMcFolder = If(CustomMcFolder, PathMcFolder) - Dim BasicArray As New List(Of McLibToken) - - '添加基础 Json 项 - Dim AllLibs As JArray = JsonObject("libraries") '转换为 LibToken - For Each Library As JObject In AllLibs.Children + Dim BasicArray As New List(Of McLibToken) + For Each Library As JObject In CType(JsonObject("libraries"), JArray).Children '清理 null 项(BakaXL 会把没有的项序列化为 null,但会被 Newtonsoft 转换为 JValue,导致 Is Nothing = false;这导致了 #409) For i = Library.Properties.Count - 1 To 0 Step -1 @@ -1945,8 +1966,7 @@ OnLoaded: Try Log("[Minecraft] 开始获取统一通行证下载信息") '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/ - DownloadInfo = GetJson(NetGetCodeByLoader({ - "https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)}, IsJson:=True)) + DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version))) Catch ex As Exception Log(ex, "获取统一通行证下载信息失败") End Try @@ -1968,10 +1988,9 @@ OnLoaded: '获取下载信息 Try Log("[Minecraft] 开始获取 Authlib-Injector 下载信息") - DownloadInfo = GetJson(NetGetCodeByLoader({ + DownloadInfo = GetJson(NetRequestByClientRetry( "https://authlib-injector.yushi.moe/artifact/latest.json", - "https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json" - }, IsJson:=True)) + BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json")) Catch ex As Exception Log(ex, "获取 Authlib-Injector 下载信息失败") End Try @@ -2016,12 +2035,16 @@ OnLoaded: '获取 For Each Token As McLibToken In Libs '检查文件 - Dim Checker As New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1) + Dim Checker As FileChecker + If Token.Name.ContainsF("labymod") Then + Checker = New FileChecker '不检查 LabyMod 的文件,它们提供的文件校验信息是错的(#3225) + Else + Checker = New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1) + End If If Checker.Check(Token.LocalPath) Is Nothing Then Continue For '文件不符合,添加下载 Dim Urls As New List(Of String) - If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then - '特判修复 Forge 部分 universal 文件缺失 URL(#5455) + If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then '特判修复 Forge 部分 universal 文件缺失 URL(#5455) Token.Url = "https://maven.minecraftforge.net" & Token.LocalPath.Replace(CustomMcFolder & "libraries", "").Replace("\", "/") End If If Token.Url IsNot Nothing Then @@ -2193,16 +2216,16 @@ OnLoaded: Dim LocalPath As String If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToObject(Of Boolean) Then 'Remap - LocalPath = Version.PathIndie & "resources\" & File.Name.Replace("/", "\") + LocalPath = $"{Version.PathIndie}resources\{File.Name}" ElseIf Json("virtual") IsNot Nothing AndAlso Json("virtual").ToObject(Of Boolean) Then 'Virtual - LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\") + LocalPath = $"{PathMcFolder}assets\virtual\legacy\{File.Name}" Else '正常 - LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString + LocalPath = $"{PathMcFolder}assets\objects\{Left(File.Value("hash").ToString, 2)}\{File.Value("hash")}" End If Result.Add(New McAssetsToken With { - .LocalPath = LocalPath, + .LocalPath = LocalPath.Replace("/", "\"), .SourcePath = File.Name, .Hash = File.Value("hash").ToString, .Size = File.Value("size").ToString @@ -2392,7 +2415,7 @@ NextEntry: Public Function FilterUserName(Raw As String, FilterChar As Char) As String If Raw.Contains(":\Users\") Then For Each Token In RegexSearch(Raw, "(?<=:\\Users\\)[^\\]+") - Raw = Raw.Replace(Token, New String(FilterChar, Token.Count)) + Raw = Raw.Replace("\" & Token, "\" & New String(FilterChar, Token.Count)) Next End If Return Raw diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb index bb9b3d5..cc634fa 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb @@ -1,7 +1,7 @@ Imports System.IO.Compression Public Module ModMod - Private Const LocalModCacheVersion As Integer = 7 + Private Const LocalModCacheVersion As Integer = 8 Public Class McMod @@ -688,7 +688,7 @@ Finished: ''' Public ReadOnly Property CanUpdate As Boolean Get - Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso ChangelogUrls.Any() + Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso Not Setup.Get("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version) AndAlso ChangelogUrls.Any() End Get End Property @@ -946,8 +946,8 @@ Finished: Try '步骤 1:获取 Hash 与对应的工程 ID Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList() - Dim ModrinthVersion = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files", "POST", - $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject) + Dim ModrinthVersion As JObject = DlModRequest("https://api.modrinth.com/v2/version_files", HttpMethod.Post, + $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json") Log($"[Mod] 从 Modrinth 获取到 {ModrinthVersion.Count} 个本地 Mod 的对应信息") '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系 If ModrinthVersion.Count = 0 Then Return @@ -967,9 +967,8 @@ Finished: Log($"[Mod] 需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息") '步骤 3:获取工程信息 If Not ModrinthMapping.Any() Then Return - Dim ModrinthProject = CType(GetJson(DlModRequest( - $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]", - "GET", "", "application/json")), JArray) + Dim ModrinthProject As JArray = DlModRequest( + $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]") For Each ProjectJson In ModrinthProject Dim Project As New CompProject(ProjectJson) For Each Entry In ModrinthMapping(Project.Id) @@ -978,9 +977,9 @@ Finished: Next Log($"[Mod] 已从 Modrinth 获取本地 Mod 信息,继续获取更新信息") '步骤 4:获取更新信息 - Dim ModrinthUpdate = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files/update", "POST", + Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update", HttpMethod.Post, $"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"", - ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json")), JObject) + ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json") For Each Entry In Mods If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.CompFile Is Nothing Then Continue For Dim UpdateFile As New CompFile(ModrinthUpdate(Entry.ModrinthHash), CompType.Mod) @@ -1015,8 +1014,8 @@ Finished: CurseForgeHashes.Add(Entry.CurseForgeHash) If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return Next - Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432", "POST", - $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer) + Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432", HttpMethod.Post, + $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches") Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息") '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系 If Not CurseForgeRaw.Any() Then Return @@ -1038,8 +1037,8 @@ Finished: Log($"[Mod] 需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息") '步骤 3:获取工程信息 If Not CurseForgeMapping.Any() Then Return - Dim CurseForgeProject = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods", "POST", - $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data") + Dim CurseForgeProject = DlModRequest("https://api.curseforge.com/v1/mods", HttpMethod.Post, + $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")("data") Dim UpdateFileIds As New Dictionary(Of Integer, List(Of McMod)) 'FileId -> 本地 Mod 文件列表 Dim FileIdToProjectSlug As New Dictionary(Of Integer, String) For Each ProjectJson In CurseForgeProject @@ -1079,8 +1078,8 @@ Finished: Log($"[Mod] 已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息") '步骤 4:获取更新文件信息 If Not UpdateFileIds.Any() Then Return - Dim CurseForgeFiles = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", - $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")), JObject)("data") + Dim CurseForgeFiles = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, + $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")("data") Dim UpdateFiles As New Dictionary(Of McMod, CompFile) For Each FileJson In CurseForgeFiles Dim File As New CompFile(FileJson, CompType.Mod) diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb index ce0c443..141f865 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb @@ -158,6 +158,7 @@ Retry: If File.Exists(OverridesIni) Then WriteIni(OverridesIni, "VersionArgumentIndie", 1) '开启版本隔离 WriteIni(OverridesIni, "VersionArgumentIndieV2", True) + WriteIni(OverridesIni, "IsStar", False) CopyFile(OverridesIni, VersionIni) '覆写已有的 ini Else WriteIni(VersionIni, "VersionArgumentIndie", 1) '开启版本隔离 @@ -244,7 +245,7 @@ Retry: '获取 Mod 下载信息 ModDownloadLoaders.Add(New LoaderTask(Of Integer, JArray)("获取 Mod 下载信息", Sub(Task As LoaderTask(Of Integer, JArray)) - Task.Output = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json"))("data") + Task.Output = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json")("data") '如果文件已被删除,则 API 会跳过那一项 If ModList.Count > Task.Output.Count Then Throw New Exception("整合包中的部分 Mod 版本已被 Mod 作者删除,所以没法继续安装了,请向整合包作者反馈该问题") End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb index 54ce14b..c4f08a4 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb @@ -349,14 +349,14 @@ State = MinecraftState.Crashed '崩溃分析 WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析") - Hint("检测到 Minecraft 出现错误,错误分析已开始……") + Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Critical) FeedbackInfo() RunInNewThread( Sub() Try Thread.Sleep(2000) WatcherLog("崩溃分析开始") - Dim Analyzer As New CrashAnalyzer(PID) + Dim Analyzer As New CrashAnalyzer(Version) Analyzer.Collect(Version.PathIndie, LatestLog.ToList) Analyzer.Prepare() Analyzer.Analyze(Version) diff --git a/Plain Craft Launcher 2/Modules/ModEvent.vb b/Plain Craft Launcher 2/Modules/ModEvent.vb index 693f4ca..87e9bf5 100644 --- a/Plain Craft Launcher 2/Modules/ModEvent.vb +++ b/Plain Craft Launcher 2/Modules/ModEvent.vb @@ -115,6 +115,10 @@ PageOtherTest.StartCustomDownload(Data(0), "未知") End Try + Case "修改设置" + Setup.Set(Data(0), Data(1)) + Hint($"已写入设置:{Data(0)} → {Data(1)}", HintType.Finish) + Case Else MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败") End Select diff --git a/Plain Craft Launcher 2/Modules/ModMusic.vb b/Plain Craft Launcher 2/Modules/ModMusic.vb index 060091a..dc129ec 100644 --- a/Plain Craft Launcher 2/Modules/ModMusic.vb +++ b/Plain Craft Launcher 2/Modules/ModMusic.vb @@ -247,7 +247,7 @@ Log("[Music] 已恢复播放") Try MusicNAudio?.Play() - Catch 'https://github.com/Hex-Dragon/PCL2/pull/5415#issuecomment-2751135223 + Catch 'https://github.com/Meloong-Git/PCL/pull/5415#issuecomment-2751135223 MusicNAudio?.Stop() MusicNAudio?.Play() End Try diff --git a/Plain Craft Launcher 2/Modules/ModSecret.vb b/Plain Craft Launcher 2/Modules/ModSecret.vb index 5353dfe..00635bb 100644 --- a/Plain Craft Launcher 2/Modules/ModSecret.vb +++ b/Plain Craft Launcher 2/Modules/ModSecret.vb @@ -9,9 +9,11 @@ Friend Module ModSecret '在开源版的注册表与常规版的注册表隔离,以防数据冲突 Public Const RegFolder As String = "PCLDebug" '用于微软登录的 ClientId - Public Const OAuthClientId As String = "" + Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "") 'CurseForge API Key - Public Const CurseForgeAPIKey As String = "" + Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "") + '用于匿名数据收集的腾讯云日志服务上报 URL,形如 https://{region}.cls.tencentcs.com/track?topic_id={topic_id} + Public Const ClsBaseUrl As String = "" Friend Sub SecretOnApplicationStart() '提升 UI 线程优先级 @@ -71,37 +73,23 @@ Friend Module ModSecret #Region "网络鉴权" - Friend Function SecretCdnSign(UrlWithMark As String) + Friend Function SecretCdnSign(UrlWithMark As String) As String If Not UrlWithMark.EndsWithF("{CDN}") Then Return UrlWithMark Return UrlWithMark.Replace("{CDN}", "").Replace(" ", "%20") End Function ''' ''' 设置 Headers 的 UA、Referer。 ''' - Friend Sub SecretHeadersSign(Url As String, ByRef Client As WebClient, Optional UseBrowserUserAgent As Boolean = False) + Friend Sub SecretHeadersSign(Url As String, ByRef Req As HttpRequestMessage, Optional UseBrowserUserAgent As Boolean = False) If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then - Client.Headers("User-Agent") = "LogStatistic" '#4951 + Req.Headers.Add("User-Agent", "LogStatistic") '#4951 ElseIf UseBrowserUserAgent Then - Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36" + Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode} Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36") Else - Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode + Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode}") End If - Client.Headers("Referer") = "http://" & VersionCode & ".open.pcl2.server/" - If Url.Contains("api.curseforge.com") Then Client.Headers("x-api-key") = CurseForgeAPIKey - End Sub - ''' - ''' 设置 Headers 的 UA、Referer。 - ''' - Friend Sub SecretHeadersSign(Url As String, ByRef Request As HttpWebRequest, Optional UseBrowserUserAgent As Boolean = False) - If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then - Request.UserAgent = "LogStatistic" '#4951 - ElseIf UseBrowserUserAgent Then - Request.UserAgent = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36" - Else - Request.UserAgent = "PCL2/" & VersionStandardCode - End If - Request.Referer = "http://" & VersionCode & ".open.pcl2.server/" - If Url.Contains("api.curseforge.com") Then Request.Headers("x-api-key") = CurseForgeAPIKey + Req.Headers.Add("Referer", $"http://{VersionCode}.open.pcl2.server/") + If Url.Contains("api.curseforge.com") Then Req.Headers.Add("x-api-key", CurseForgeAPIKey) End Sub #End Region @@ -225,11 +213,17 @@ Friend Module ModSecret If Not FrmMain.IsLoaded Then Return '顶部条背景 Dim Brush = New LinearGradientBrush With {.EndPoint = New Point(1, 0), .StartPoint = New Point(0, 0)} - Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue - 21, ColorSat, 53 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 0.33, .Color = New MyColor().FromHSL2(ColorHue - 7, ColorSat, 47 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 0.67, .Color = New MyColor().FromHSL2(ColorHue + 7, ColorSat, 47 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + 21, ColorSat, 53 + ColorLightAdjust)}) + If TypeOf ColorHueTopbarDelta Is Integer Then + Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue - ColorHueTopbarDelta, ColorSat, 48 + ColorLightAdjust)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 54 + ColorLightAdjust)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta, ColorSat, 48 + ColorLightAdjust)}) + Else + Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(0), ColorSat, 48 + ColorLightAdjust)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(1), ColorSat, 54 + ColorLightAdjust)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(2), ColorSat, 48 + ColorLightAdjust)}) + End If FrmMain.PanTitle.Background = Brush + FrmMain.PanTitle.Background.Freeze() '主页面背景 If Setup.Get("UiBackgroundColorful") Then Brush = New LinearGradientBrush With {.EndPoint = New Point(0.1, 1), .StartPoint = New Point(0.9, 0)} diff --git a/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb b/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb deleted file mode 100644 index 1f5ea45..0000000 --- a/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb +++ /dev/null @@ -1,59 +0,0 @@ -Imports System.Net - -Public Class CookieWebClient - Inherits WebClient - - Public Sub New(container As CookieContainer, Headers As Dictionary(Of String, String)) - Me.New(container) - For Each keyVal In Headers - Me.Headers(keyVal.Key) = keyVal.Value - Next - End Sub - - Public Sub New() - Me.New(New CookieContainer()) - End Sub - - Public Sub New(container As CookieContainer) - Me.container = container - End Sub - - Private Shadows ReadOnly container As New CookieContainer() - - ''' - ''' 以毫秒为单位的超时。 - ''' - Public Timeout As Integer = 600000 - - Protected Overrides Function GetWebRequest(address As Uri) As WebRequest - Dim r As WebRequest = MyBase.GetWebRequest(address) - Dim request = TryCast(r, HttpWebRequest) - - If request IsNot Nothing Then - request.CookieContainer = container - request.Timeout = Timeout - End If - - Return r - End Function - - Protected Overrides Function GetWebResponse(request As WebRequest, result As IAsyncResult) As WebResponse - Dim response As WebResponse = MyBase.GetWebResponse(request, result) - ReadCookies(response) - Return response - End Function - - Protected Overrides Function GetWebResponse(request As WebRequest) As WebResponse - Dim response As WebResponse = MyBase.GetWebResponse(request) - ReadCookies(response) - Return response - End Function - - Private Sub ReadCookies(r As WebResponse) - Dim response = TryCast(r, HttpWebResponse) - If response IsNot Nothing Then - Dim cookies As CookieCollection = response.Cookies - container.Add(cookies) - End If - End Sub -End Class diff --git a/Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb b/Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb new file mode 100644 index 0000000..e2d79e7 --- /dev/null +++ b/Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb @@ -0,0 +1,151 @@ +'来源:https://blog.csdn.net/simpleman2000/article/details/140952294 + +Imports System.ComponentModel +Imports System.Runtime.InteropServices +Imports System.Windows.Interop + +Public Class DragHelper + + Public Event DragDrop As EventHandler + + Public Property DropFilePaths As String() + Get + Return _DropFilePathsBackingField + End Get + Private Set(value As String()) + _DropFilePathsBackingField = value + End Set + End Property + Private _DropFilePathsBackingField As String() + + Public Property DropPoint As POINT + Get + Return _DropPointBackingField + End Get + Private Set(value As POINT) + _DropPointBackingField = value + End Set + End Property + Private _DropPointBackingField As POINT + + Public Property HwndIntPtrSource As HwndSource + + Public Sub AddHook() + RemoveDragHook() + HwndIntPtrSource.AddHook(AddressOf WndProc) + Dim handle As IntPtr = HwndIntPtrSource.Handle + If IsUserAnAdmin() Then RevokeDragDrop(handle) + DragAcceptFiles(handle, True) + ChangeMessageFilter(handle) + End Sub + + Public Sub RemoveDragHook() + HwndIntPtrSource.RemoveHook(AddressOf WndProc) + DragAcceptFiles(HwndIntPtrSource.Handle, False) + End Sub + + Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr + Dim filePaths As String() = Nothing + Dim point As New POINT() + + If TryGetDropInfo(msg, wParam, filePaths, point) Then + DropPoint = point + DropFilePaths = filePaths + RaiseEvent DragDrop(Me, EventArgs.Empty) + handled = True + End If + Return IntPtr.Zero + End Function + + + Private Shared Function ChangeWindowMessageFilterEx(hWnd As IntPtr, msg As UInteger, action As UInteger, ByRef pChangeFilterStruct As CHANGEFILTERSTRUCT) As Boolean + End Function + + + Private Shared Function ChangeWindowMessageFilter(msg As UInteger, flags As UInteger) As Boolean + End Function + + + Private Shared Sub DragAcceptFiles(hWnd As IntPtr, fAccept As Boolean) + End Sub + + + Private Shared Function DragQueryFile(hWnd As IntPtr, iFile As UInteger, lpszFile As StringBuilder, cch As Integer) As UInteger + End Function + + + Private Shared Function DragQueryPoint(hDrop As IntPtr, ByRef lppt As POINT) As Boolean + End Function + + + Private Shared Sub DragFinish(hDrop As IntPtr) + End Sub + + + Private Shared Function RevokeDragDrop(hWnd As IntPtr) As Integer + End Function + + + Private Shared Function IsUserAnAdmin() As Boolean + End Function + + + Public Structure POINT + Public X As Integer + Public Y As Integer + End Structure + + + Private Structure CHANGEFILTERSTRUCT + Public cbSize As UInteger + Public ExtStatus As UInteger + End Structure + + Private Const WM_COPYGLOBALDATA As UInteger = &H49 + Private Const WM_COPYDATA As UInteger = &H4A + Private Const WM_DROPFILES As UInteger = &H233 + Private Const MSGFLT_ALLOW As UInteger = 1 + Private Const MSGFLT_ADD As UInteger = 1 + Private Const MAX_PATH As Integer = 260 + + Private Shared Sub ChangeMessageFilter(handle As IntPtr) + Dim ver As Version = Environment.OSVersion.Version + Dim isVistaOrHigher As Boolean = ver >= New Version(6, 0) + Dim isNt61OrHiger As Boolean = ver >= New Version(6, 1) + + If isVistaOrHigher Then + Dim status As New CHANGEFILTERSTRUCT With {.cbSize = 8} + For Each msg As UInteger In New UInteger() {WM_DROPFILES, WM_COPYGLOBALDATA, WM_COPYDATA} + Dim [error] As Boolean = False + If isNt61OrHiger Then + [error] = Not ChangeWindowMessageFilterEx(handle, msg, MSGFLT_ALLOW, status) + Else + [error] = Not ChangeWindowMessageFilter(msg, MSGFLT_ADD) + End If + + If [error] Then Throw New Win32Exception(Marshal.GetLastWin32Error()) + Next + End If + End Sub + + Private Shared Function TryGetDropInfo(msg As Integer, wParam As IntPtr, ByRef dropFilePaths As String(), ByRef dropPoint As POINT) As Boolean + dropFilePaths = Nothing + dropPoint = New POINT() + + If msg <> WM_DROPFILES Then Return False + + Dim fileCount As UInteger = DragQueryFile(wParam, UInteger.MaxValue, Nothing, 0) + ReDim dropFilePaths(CInt(fileCount) - 1) + + For i As UInteger = 0 To CInt(fileCount) - 1 + Dim sb As New StringBuilder(MAX_PATH) + Dim result As UInteger = DragQueryFile(wParam, i, sb, sb.Capacity) + If result > 0 Then dropFilePaths(i) = sb.ToString() + Next + + DragQueryPoint(wParam, dropPoint) + DragFinish(wParam) + Return True + End Function + +End Class diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index 8d8a259..c257a02 100644 --- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb +++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb @@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 ' 方法是按如下所示使用“*” - - + + diff --git a/Plain Craft Launcher 2/My Project/Resources.Designer.vb b/Plain Craft Launcher 2/My Project/Resources.Designer.vb index 81e998d..dfc568a 100644 --- a/Plain Craft Launcher 2/My Project/Resources.Designer.vb +++ b/Plain Craft Launcher 2/My Project/Resources.Designer.vb @@ -63,9 +63,9 @@ Namespace My.Resources ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' - Friend ReadOnly Property Custom() As Byte() + Friend ReadOnly Property CacheCow_Client() As Byte() Get - Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture) + Dim obj As Object = ResourceManager.GetObject("CacheCow.Client", resourceCulture) Return CType(obj,Byte()) End Get End Property @@ -73,9 +73,29 @@ Namespace My.Resources ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' - Friend ReadOnly Property Dialogs() As Byte() + Friend ReadOnly Property CacheCow_Client_FileStore() As Byte() Get - Dim obj As Object = ResourceManager.GetObject("Dialogs", resourceCulture) + Dim obj As Object = ResourceManager.GetObject("CacheCow.Client.FileStore", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property CacheCow_Common() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("CacheCow.Common", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property Custom() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture) Return CType(obj,Byte()) End Get End Property @@ -105,7 +125,7 @@ Namespace My.Resources ''' Friend ReadOnly Property Imazen_WebP() As Byte() Get - Dim obj As Object = ResourceManager.GetObject("Imazen_WebP", resourceCulture) + Dim obj As Object = ResourceManager.GetObject("Imazen.WebP", resourceCulture) Return CType(obj,Byte()) End Get End Property @@ -120,16 +140,6 @@ Namespace My.Resources End Get End Property - ''' - ''' 查找 System.Byte[] 类型的本地化资源。 - ''' - Friend ReadOnly Property Json() As Byte() - Get - Dim obj As Object = ResourceManager.GetObject("Json", resourceCulture) - Return CType(obj,Byte()) - End Get - End Property - ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' @@ -160,6 +170,36 @@ Namespace My.Resources End Get End Property + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property Newtonsoft_Json() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Newtonsoft.Json", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property Ookii_Dialogs_Wpf() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Ookii.Dialogs.Wpf", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property System_Net_Http_Formatting() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("System.Net.Http.Formatting", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' diff --git a/Plain Craft Launcher 2/My Project/Resources.resx b/Plain Craft Launcher 2/My Project/Resources.resx index 8f1da65..8db2019 100644 --- a/Plain Craft Launcher 2/My Project/Resources.resx +++ b/Plain Craft Launcher 2/My Project/Resources.resx @@ -118,27 +118,30 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\CacheCow.Client.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\CacheCow.Client.FileStore.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\CacheCow.Common.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\resources\custom.xaml;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\resources\ookii.dialogs.wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ..\Resources\forge-installer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ..\Resources\Help.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + ..\Resources\Imazen.WebP.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ..\Resources\java-wrapper.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ..\Resources\libwebp64.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -148,6 +151,15 @@ ..\Resources\NAudio.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\ookii.dialogs.wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\System.Net.Http.Formatting.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\transformer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb index 12c51af..47b17e9 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb @@ -97,7 +97,7 @@ Dim Titles As New List(Of String) If FrmMain.PageCurrent.Page = FormMain.PageType.CompDetail Then For Each Card As MyCard In FrmDownloadCompDetail.PanResults.Children - If Card.Title <> "" AndAlso Not Card.IsSwaped Then Titles.Add(Card.Title) + If Card.Title <> "" AndAlso Not Card.IsSwapped Then Titles.Add(Card.Title) Next Log("[Comp] 记录当前已展开的卡片:" & String.Join("、", Titles)) FrmMain.PageCurrent.Additional(1) = Titles diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml index e66570e..944bca6 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml @@ -34,7 +34,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb index 3ad75d4..f3d278c 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb @@ -68,6 +68,10 @@ Public Class PageComp ''' 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。 ''' Public Shared TargetVersion As McVersion = Nothing + ''' + ''' 在切换到该页面时自动设置的搜索框内的内容。 + ''' + Public Shared TargetName As String = Nothing '在点击 MyCompItem 时会获取 Loader 的输入,以使资源详情页面可以应用相同的筛选项 Public Loader As New LoaderTask(Of CompProjectRequest, Integer)("社区资源获取:XXX", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000} @@ -94,6 +98,10 @@ Public Class PageComp ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge") End If TargetVersion = Nothing + If TargetName IsNot Nothing Then + TextSearchName.Text = TargetName + TargetName = Nothing + End If '如果已经完成请求,则重新开始 If IsLoaderInited Then StartNewSearch() ScrollToHome() diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb index 60a8ad3..1337e49 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb @@ -73,11 +73,14 @@ '筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表) Private Function GetResults() As List(Of CompFile) Dim Results As List(Of CompFile) = CompFileLoader.Output - If PageType = CompType.Any Then - Results = Results.Where(Function(r) r.Type <> CompType.Plugin).ToList - Else - Results = Results.Where(Function(r) r.Type = PageType).ToList - End If + Select Case PageType + Case CompType.Any + Results = Results.Where(Function(r) r.Type <> CompType.Plugin).ToList + Case CompType.Shader, CompType.ResourcePack + '不筛选光影和资源包,否则原版光影会因为是资源包格式而被过滤(#6473) + Case Else + Results = Results.Where(Function(r) r.Type = PageType).ToList + End Select Return Results End Function Private Sub Load_OnFinish() @@ -202,7 +205,7 @@ CType(FrmMain.PageCurrent.Additional(1), List(Of String)).Contains(NewCard.Title)) Then MyCard.StackInstall(NewStack, If(PageType = CompType.ModPack, 9, 8), Pair.Key) '9 是安装,8 是另存为 Else - NewCard.IsSwaped = True + NewCard.IsSwapped = True End If '增加提示 If Pair.Key = "其他版本" Then @@ -211,7 +214,7 @@ Next '如果只有一张卡片,展开第一张卡片 If PanResults.Children.Count = 1 Then - CType(PanResults.Children(0), MyCard).IsSwaped = False + CType(PanResults.Children(0), MyCard).IsSwapped = False End If Catch ex As Exception Log(ex, "可视化工程下载列表出错", LogLevel.Feedback) @@ -305,7 +308,7 @@ End Try End Sub '资源下载;整合包另存为 - Public Shared CachedFolder As String = Nothing '仅在本次缓存的下载文件夹 + Public Shared CachedFolder As New Dictionary(Of CompType, String) '仅在本次缓存的下载文件夹 Public Sub Save_Click(sender As Object, e As EventArgs) Dim File As CompFile = If(TypeOf sender Is MyListItem, sender, sender.Parent).Tag RunInNewThread( @@ -356,8 +359,8 @@ Return False End Function '获取常规资源默认下载位置 - If CachedFolder IsNot Nothing Then - DefaultFolder = CachedFolder + If CachedFolder.ContainsKey(Project.Type) Then + DefaultFolder = CachedFolder(Project.Type) Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}") ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then DefaultFolder = $"{McVersionCurrent.PathIndie}{SubFolder}" @@ -422,7 +425,7 @@ If Not Target.Contains("\") Then Return '构造步骤加载器 Dim LoaderName As String = Desc & "下载:" & GetFileNameWithoutExtentionFromPath(Target) & " " - If Target <> DefaultFolder AndAlso File.Type = CompType.Mod Then CachedFolder = GetPathFromFullPath(Target) + If Target <> DefaultFolder Then CachedFolder(Project.Type) = GetPathFromFullPath(Target) Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True}) '启动 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index 5829841..02c92e2 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -570,7 +570,8 @@ pause" '官方源 Dim PageData As String Try - PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True) + PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, + Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True) Task.Progress = 0.8 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0)) Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last) @@ -733,7 +734,8 @@ Retry: '官方源 Dim PageData As String Try - PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True) + PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, + Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True) Task.Progress = 0.8 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0)) Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last) @@ -1340,7 +1342,7 @@ Retry: If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then '下载原版 Json 文件 Task.Progress = 0.4 - Dim RawJson As JObject = GetJson(NetGetCodeByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True)) + Dim RawJson As JObject = GetJson(NetRequestByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True)) '[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg] Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).BeforeFirst("@") Dim Address = McLibGet(OriginalName).Replace(".jar", "-mappings." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1)) @@ -1641,30 +1643,31 @@ Retry: Public Sub McDownloadForgeRecommendedRefresh() If IsForgeRecommendedRefreshed Then Return IsForgeRecommendedRefreshed = True - RunInNewThread(Sub() - Try - Log("[Download] 刷新 Forge 推荐版本缓存开始") - Dim Result As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/forge/promos") - If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")") - Dim ResultJson As JContainer = GetJson(Result) - '获取所有推荐版本列表 - Dim RecommendedList As New List(Of String) - For Each Version As JObject In ResultJson - If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For - Dim Name As String = Version("name") - If Not Name.EndsWithF("-recommended") Then Continue For - '内容为:"1.15.2":"31.2.0" - RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """")) - Next - If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")") - '保存 - Dim CacheJson As String = "{" & Join(RecommendedList, ",") & "}" - WriteFile(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson) - Log("[Download] 刷新 Forge 推荐版本缓存成功") - Catch ex As Exception - Log(ex, "刷新 Forge 推荐版本缓存失败") - End Try - End Sub, "ForgeRecommendedRefresh") + RunInNewThread( + Sub() + Try + Log("[Download] 刷新 Forge 推荐版本缓存开始") + Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos") + If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")") + Dim ResultJson As JContainer = GetJson(Result) + '获取所有推荐版本列表 + Dim RecommendedList As New List(Of String) + For Each Version As JObject In ResultJson + If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For + Dim Name As String = Version("name") + If Not Name.EndsWithF("-recommended") Then Continue For + '内容为:"1.15.2":"31.2.0" + RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """")) + Next + If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")") + '保存 + Dim CacheJson As String = "{" & Join(RecommendedList, ",") & "}" + WriteFile(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson) + Log("[Download] 刷新 Forge 推荐版本缓存成功") + Catch ex As Exception + Log(ex, "刷新 Forge 推荐版本缓存失败") + End Try + End Sub, "ForgeRecommendedRefresh") End Sub Private IsForgeRecommendedRefreshed As Boolean = False diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb index 2af82b7..78c963c 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb @@ -88,7 +88,7 @@ Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack - NewCard.IsSwaped = True + NewCard.IsSwapped = True PanMain.Children.Add(NewCard) Next Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb index 04a74e0..b7f42f0 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb @@ -19,7 +19,7 @@ Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Version} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack - NewCard.IsSwaped = True + NewCard.IsSwapped = True PanMain.Children.Add(NewCard) Next ''非官方源警示 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml index 33d7bfe..21d408d 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml @@ -27,7 +27,7 @@ - + @@ -45,7 +45,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -63,7 +63,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -81,7 +81,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -99,7 +99,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -119,7 +119,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -137,7 +137,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb index dfd5042..51b25e1 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb @@ -54,18 +54,13 @@ DisabledPageAnimControls.Remove(BtnStart) BtnStart.Show = True - CardOptiFine.IsSwaped = True - CardLiteLoader.IsSwaped = True - CardForge.IsSwaped = True - CardNeoForge.IsSwaped = True - CardFabric.IsSwaped = True - CardFabricApi.IsSwaped = True - CardOptiFabric.IsSwaped = True - - If Not Setup.Get("HintInstallBack") Then - Setup.Set("HintInstallBack", True) - Hint("点击 Minecraft 项即可返回游戏主版本选择页面!") - End If + CardOptiFine.IsSwapped = True + CardLiteLoader.IsSwapped = True + CardForge.IsSwapped = True + CardNeoForge.IsSwapped = True + CardFabric.IsSwapped = True + CardFabricApi.IsSwapped = True + CardOptiFabric.IsSwapped = True '如果在选择页面按了刷新键,选择页的东西可能会由于动画被隐藏,但不会由于加载结束而再次显示,因此这里需要手动恢复 For Each Control In GetAllAnimControls(PanSelect) @@ -318,8 +313,8 @@ 'OptiFine Dim OptiFineError As String = LoadOptiFineGetError() CardOptiFine.MainSwap.Visibility = If(OptiFineError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If OptiFineError IsNot Nothing Then CardOptiFine.IsSwaped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 - SetOptiFineInfoShow(CardOptiFine.IsSwaped) + If OptiFineError IsNot Nothing Then CardOptiFine.IsSwapped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 + SetOptiFineInfoShow(CardOptiFine.IsSwapped) If SelectedOptiFine Is Nothing Then BtnOptiFineClear.Visibility = Visibility.Collapsed ImgOptiFine.Visibility = Visibility.Collapsed @@ -338,8 +333,8 @@ CardLiteLoader.Visibility = Visibility.Visible Dim LiteLoaderError As String = LoadLiteLoaderGetError() CardLiteLoader.MainSwap.Visibility = If(LiteLoaderError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If LiteLoaderError IsNot Nothing Then CardLiteLoader.IsSwaped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 - SetLiteLoaderInfoShow(CardLiteLoader.IsSwaped) + If LiteLoaderError IsNot Nothing Then CardLiteLoader.IsSwapped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 + SetLiteLoaderInfoShow(CardLiteLoader.IsSwapped) If SelectedLiteLoader Is Nothing Then BtnLiteLoaderClear.Visibility = Visibility.Collapsed ImgLiteLoader.Visibility = Visibility.Collapsed @@ -355,8 +350,8 @@ 'Forge Dim ForgeError As String = LoadForgeGetError() CardForge.MainSwap.Visibility = If(ForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If ForgeError IsNot Nothing Then CardForge.IsSwaped = True - SetForgeInfoShow(CardForge.IsSwaped) + If ForgeError IsNot Nothing Then CardForge.IsSwapped = True + SetForgeInfoShow(CardForge.IsSwapped) If SelectedForge Is Nothing Then BtnForgeClear.Visibility = Visibility.Collapsed ImgForge.Visibility = Visibility.Collapsed @@ -375,8 +370,8 @@ CardNeoForge.Visibility = Visibility.Visible Dim NeoForgeError As String = LoadNeoForgeGetError() CardNeoForge.MainSwap.Visibility = If(NeoForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwaped = True - SetNeoForgeInfoShow(CardNeoForge.IsSwaped) + If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwapped = True + SetNeoForgeInfoShow(CardNeoForge.IsSwapped) If SelectedNeoForge Is Nothing Then BtnNeoForgeClear.Visibility = Visibility.Collapsed ImgNeoForge.Visibility = Visibility.Collapsed @@ -396,8 +391,8 @@ CardFabric.Visibility = Visibility.Visible Dim FabricError As String = LoadFabricGetError() CardFabric.MainSwap.Visibility = If(FabricError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If FabricError IsNot Nothing Then CardFabric.IsSwaped = True - SetFabricInfoShow(CardFabric.IsSwaped) + If FabricError IsNot Nothing Then CardFabric.IsSwapped = True + SetFabricInfoShow(CardFabric.IsSwapped) If SelectedFabric Is Nothing Then BtnFabricClear.Visibility = Visibility.Collapsed ImgFabric.Visibility = Visibility.Collapsed @@ -417,8 +412,8 @@ CardFabricApi.Visibility = Visibility.Visible Dim FabricApiError As String = LoadFabricApiGetError() CardFabricApi.MainSwap.Visibility = If(FabricApiError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If FabricApiError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardFabricApi.IsSwaped = True - SetFabricApiInfoShow(CardFabricApi.IsSwaped) + If FabricApiError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardFabricApi.IsSwapped = True + SetFabricApiInfoShow(CardFabricApi.IsSwapped) If SelectedFabricApi Is Nothing Then BtnFabricApiClear.Visibility = Visibility.Collapsed ImgFabricApi.Visibility = Visibility.Collapsed @@ -438,8 +433,8 @@ CardOptiFabric.Visibility = Visibility.Visible Dim OptiFabricError As String = LoadOptiFabricGetError() CardOptiFabric.MainSwap.Visibility = If(OptiFabricError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If OptiFabricError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardOptiFabric.IsSwaped = True - SetOptiFabricInfoShow(CardOptiFabric.IsSwaped) + If OptiFabricError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardOptiFabric.IsSwapped = True + SetOptiFabricInfoShow(CardOptiFabric.IsSwapped) If SelectedOptiFabric Is Nothing Then BtnOptiFabricClear.Visibility = Visibility.Collapsed ImgOptiFabric.Visibility = Visibility.Collapsed @@ -639,7 +634,7 @@ Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack - NewCard.IsSwaped = True + NewCard.IsSwapped = True PanMinecraft.Children.Add(NewCard) Next '自动选择版本 @@ -676,6 +671,11 @@ VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then Return "与 Forge 不兼容" End If + '检查 Fabric 1.20.5+:全部不兼容 + If SelectedFabric IsNot Nothing AndAlso + VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then + Return "与 Fabric 不兼容" + End If '检查 Forge 版本 Dim HasAny As Boolean = False Dim HasRequiredVersion As Boolean = False @@ -750,14 +750,14 @@ OptiFabric_Loaded() Forge_Loaded() NeoForge_Loaded() - CardOptiFine.IsSwaped = True + CardOptiFine.IsSwapped = True SelectReload() End Sub Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp SelectedOptiFine = Nothing SelectedOptiFabric = Nothing AutoSelectedOptiFabric = False - CardOptiFine.IsSwaped = True + CardOptiFine.IsSwapped = True e.Handled = True Forge_Loaded() NeoForge_Loaded() @@ -811,12 +811,12 @@ '选择与清除 Private Sub LiteLoader_Selected(sender As MyListItem, e As EventArgs) SelectedLiteLoader = sender.Tag - CardLiteLoader.IsSwaped = True + CardLiteLoader.IsSwapped = True SelectReload() End Sub Private Sub LiteLoader_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnLiteLoaderClear.MouseLeftButtonUp SelectedLiteLoader = Nothing - CardLiteLoader.IsSwaped = True + CardLiteLoader.IsSwapped = True e.Handled = True SelectReload() End Sub @@ -897,14 +897,14 @@ '选择与清除 Private Sub Forge_Selected(sender As MyListItem, e As EventArgs) SelectedForge = sender.Tag - CardForge.IsSwaped = True + CardForge.IsSwapped = True If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, SelectedForge) Then SelectedOptiFine = Nothing OptiFine_Loaded() SelectReload() End Sub Private Sub Forge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnForgeClear.MouseLeftButtonUp SelectedForge = Nothing - CardForge.IsSwaped = True + CardForge.IsSwapped = True e.Handled = True OptiFine_Loaded() SelectReload() @@ -959,13 +959,13 @@ '选择与清除 Private Sub NeoForge_Selected(sender As MyListItem, e As EventArgs) SelectedNeoForge = sender.Tag - CardNeoForge.IsSwaped = True + CardNeoForge.IsSwapped = True OptiFine_Loaded() SelectReload() End Sub Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp SelectedNeoForge = Nothing - CardNeoForge.IsSwaped = True + CardNeoForge.IsSwapped = True e.Handled = True OptiFine_Loaded() SelectReload() @@ -981,6 +981,11 @@ Private Function LoadFabricGetError() As String If LoadFabric Is Nothing OrElse LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……" If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabric.State, Object).Error.Message + '检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容 + If SelectedOptiFine IsNot Nothing AndAlso + VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then + Return "与 OptiFine 不兼容" + End If For Each Version As JObject In DlFabricListLoader.Output.Value("game") If Version("version").ToString = SelectedMinecraftId.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") Then If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容" @@ -1020,7 +1025,7 @@ SelectedFabric = sender.Tag("version").ToString FabricApi_Loaded() OptiFabric_Loaded() - CardFabric.IsSwaped = True + CardFabric.IsSwapped = True SelectReload() End Sub Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp @@ -1029,7 +1034,7 @@ AutoSelectedFabricApi = False SelectedOptiFabric = Nothing AutoSelectedOptiFabric = False - CardFabric.IsSwaped = True + CardFabric.IsSwapped = True e.Handled = True SelectReload() End Sub @@ -1141,12 +1146,12 @@ '选择与清除 Private Sub FabricApi_Selected(sender As MyListItem, e As EventArgs) SelectedFabricApi = sender.Tag - CardFabricApi.IsSwaped = True + CardFabricApi.IsSwapped = True SelectReload() End Sub Private Sub FabricApi_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricApiClear.MouseLeftButtonUp SelectedFabricApi = Nothing - CardFabricApi.IsSwaped = True + CardFabricApi.IsSwapped = True e.Handled = True SelectReload() End Sub @@ -1233,12 +1238,12 @@ '选择与清除 Private Sub OptiFabric_Selected(sender As MyListItem, e As EventArgs) SelectedOptiFabric = sender.Tag - CardOptiFabric.IsSwaped = True + CardOptiFabric.IsSwapped = True SelectReload() End Sub Private Sub OptiFabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFabricClear.MouseLeftButtonUp SelectedOptiFabric = Nothing - CardOptiFabric.IsSwaped = True + CardOptiFabric.IsSwapped = True e.Handled = True SelectReload() End Sub diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb index ac18c47..7eff1c8 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb @@ -34,7 +34,7 @@ Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack - NewCard.IsSwaped = True + NewCard.IsSwapped = True PanMain.Children.Add(NewCard) Next Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb index 064dda5..37cdcda 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb @@ -23,7 +23,7 @@ Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack - NewCard.IsSwaped = True + NewCard.IsSwapped = True PanMain.Children.Add(NewCard) Next Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb index f843529..7eebac8 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb @@ -38,7 +38,7 @@ Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value} NewCard.Children.Add(NewStack) NewCard.SwapControl = NewStack - NewCard.IsSwaped = True + NewCard.IsSwapped = True PanMain.Children.Add(NewCard) Next Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb index ebe28ff..2ef95d9 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb @@ -87,7 +87,7 @@ LabTitle.Text = "登录 Minecraft" LabCaption.Text = $"登录网页将自动开启,请在网页中输入 {UserCode}(已自动复制)。" & vbCrLf & vbCrLf & - $"如果网络环境不佳,网页可能一直加载不出来,届时请使用使用加速器或 VPN 以改善网络环境。" & vbCrLf & + $"如果网络环境不佳,网页可能一直加载不出来,届时请使用加速器或 VPN 以改善网络环境。" & vbCrLf & $"你也可以用其他设备打开 {Website} 并输入上述代码。" Btn1.EventData = Website Btn2.EventData = UserCode @@ -105,13 +105,16 @@ Dim UnknownFailureCount As Integer = 0 Do While Not MyConverter.IsExited Try - Dim Result = NetRequestOnce( - "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", "POST", - "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" & - "client_id=" & OAuthClientId & "&" & - "device_code=" & DeviceCode & "&" & - "scope=XboxLive.signin%20offline_access", - "application/x-www-form-urlencoded", 5000 + UnknownFailureCount * 5000, MakeLog:=False) + Dim Result = NetRequestByClient( + "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", HttpMethod.Post, + Content:= + "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" & + "client_id=" & OAuthClientId & "&" & + "device_code=" & DeviceCode & "&" & + "scope=XboxLive.signin%20offline_access", + ContentType:="application/x-www-form-urlencoded", + Timeout:=5000 + UnknownFailureCount * 5000, + MakeLog:=False) '获取结果 Dim ResultJson As JObject = GetJson(Result) McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒") @@ -119,21 +122,29 @@ Finished({ResultJson("access_token").ToString, ResultJson("refresh_token").ToString}) Return Catch ex As Exception - If ex.Message.Contains("authorization_declined") Then - Finished(New Exception("$你拒绝了 PCL 申请的权限……")) - Return - ElseIf ex.Message.Contains("expired_token") Then - Finished(New Exception("$登录用时太长啦,重新试试吧!")) - Return - ElseIf ex.Message.Contains("service abuse") Then - Finished(New Exception("$非常抱歉,该账号已被微软封禁,无法登录。")) - Return - ElseIf ex.Message.Contains("AADSTS70000") Then '可能不能判 “invalid_grant”,见 #269 - Finished(New RestartException) - Return - ElseIf ex.Message.Contains("authorization_pending") Then - Thread.Sleep(2000) - ElseIf UnknownFailureCount <= 2 Then + If TypeOf ex Is ResponsedWebException Then + Dim Response = CType(ex, ResponsedWebException).Response + If Response.Contains("authorization_declined") Then + Finished(New Exception("$你拒绝了 PCL 申请的权限……")) + Return + ElseIf Response.Contains("expired_token") Then + Finished(New Exception("$登录用时太长啦,重新试试吧!")) + Return + ElseIf Response.Contains("Account security interrupt") Then + Finished(New Exception("$该账号由于安全问题无法登陆,请前往微软账户页获取更多信息。")) + Return + ElseIf Response.Contains("service abuse") Then + Finished(New Exception("$非常抱歉,该账号已被微软封禁,无法登录。")) + Return + ElseIf Response.Contains("AADSTS70000") Then '可能不能判 “invalid_grant”,见 #269 + Finished(New RestartException) + Return + ElseIf Response.Contains("authorization_pending") Then + Thread.Sleep(2000) + Continue Do + End If + End If + If UnknownFailureCount <= 2 Then UnknownFailureCount += 1 Log(ex, $"登录轮询第 {UnknownFailureCount} 次失败") Thread.Sleep(2000) diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb index 190326d..352c69b 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb @@ -259,12 +259,13 @@ Retry: End Sub) If SelId Is Nothing Then Return '发送请求 - Dim Result As String = NetRequestRetry("https://api.minecraftservices.com/minecraft/profile/capes/active", - If(SelId = 0, "DELETE", "PUT"), - If(SelId = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelId - 1)("id"))).ToString(0)), - "application/json", Headers:=New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}}) + Dim Result As String = NetRequestByClientRetry("https://api.minecraftservices.com/minecraft/profile/capes/active", + If(SelId = 0, HttpMethod.Delete, HttpMethod.Put), + Content:=If(SelId = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelId - 1)("id"))).ToString(0)), + ContentType:="application/json", + Headers:={{"Authorization", "Bearer " & AccessToken}}) If Result.Contains("""errorMessage""") Then - Hint("更改披风失败:" & GetJson(Result)("errorMessage"), HintType.Critical) + Hint("更改披风失败:" & GetJson(Result)("errorMessage").ToString, HintType.Critical) Return Else Hint("更改披风成功!", HintType.Finish) diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb index 3ab15d6..f22dcc2 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb @@ -243,19 +243,21 @@ If Not IsNothing(PageNew) AndAlso Not IsNothing(PageNew.Parent) Then PageNew.SetValue(ContentPresenter.ContentProperty, Nothing) If Anim Then '动画 - Dispatcher.Invoke(Sub() - '执行动画 - AniStart({ - AaOpacity(PanLogin, -PanLogin.Opacity, 100,, New AniEaseOutFluent), - AaCode(Sub() - AniControlEnabled += 1 - PanLogin.Children.Clear() - PanLogin.Children.Add(PageNew) - AniControlEnabled -= 1 - End Sub, 100), - AaOpacity(PanLogin, 1, 100, 120, New AniEaseInFluent) - }, "FrmLogin PageChange") - End Sub, Threading.DispatcherPriority.Render) + Dispatcher.Invoke( + Sub() + '执行动画 + AniStart({ + AaOpacity(PanLogin, -PanLogin.Opacity, 100,, New AniEaseOutFluent), + AaCode( + Sub() + AniControlEnabled += 1 + PanLogin.Children.Clear() + PanLogin.Children.Add(PageNew) + AniControlEnabled -= 1 + End Sub, 100), + AaOpacity(PanLogin, 1, 100, 120, New AniEaseInFluent) + }, "FrmLogin PageChange") + End Sub, Threading.DispatcherPriority.Render) Else '无动画 AniControlEnabled += 1 diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb index d7525d6..2a3ffd5 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb @@ -88,11 +88,11 @@ Download: " Case 2 Log("[Page] 主页预设:Minecraft 新闻") - Url = "http://pcl.mcnews.thestack.top" + Url = "https://pcl.mcnews.thestack.top" GoTo Download Case 3 Log("[Page] 主页预设:简单主页") - Url = "https://raw.gitcode.com/mfn233/PCL-Mainpage/raw/main/Custom.xaml" + Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/MFn233/Custom.xaml" GoTo Download Case 4 Log("[Page] 主页预设:每日整合包推荐") @@ -128,7 +128,7 @@ Download: GoTo Download Case 12 Log("[Page] 主页预设:PCL GitHub 仪表盘") - Url = "https://raw.gitcode.com/Deep-Dark-Forest/PCL2-GitHub-Dashboard-Homepage/raw/main/custom.xaml" + Url = "https://ddf.pcl-community.org/Custom.xaml" GoTo Download End Select End Select @@ -155,7 +155,7 @@ Download: Dim Version As String = "" Dim NeedDownload As Boolean = True Try - Version = NetGetCodeByRequestOnce(VersionAddress, Timeout:=10000) + Version = NetRequestByClientRetry(VersionAddress) If Version.Length > 1000 Then Throw New Exception($"获取的主页版本过长({Version.Length} 字符)") Dim CurrentVersion As String = Setup.Get("CacheSavedPageVersion") If Version <> "" AndAlso CurrentVersion <> "" AndAlso Version = CurrentVersion Then @@ -170,7 +170,7 @@ Download: End Try '实际下载 If NeedDownload Then - Dim FileContent As String = NetGetCodeByRequestRetry(Address) + Dim FileContent As String = NetRequestByClientRetry(Address) Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}") Setup.Set("CacheSavedPageUrl", Address) Setup.Set("CacheSavedPageVersion", Version) @@ -230,7 +230,9 @@ Download: Try '修改时应同时修改 PageOtherHelpDetail.Init Content = HelpArgumentReplace(Content) - If Content.Contains("xmlns") Then Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间 + Do While Content.Contains("xmlns") + Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间 + Loop Content = "" & Content & "" Log($"[Page] 实例化:加载主页 UI 开始,最终内容长度:{Content.Count}") PanCustom.Children.Add(GetObjectFromXML(Content)) diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml index ff528d2..2ca71d0 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:PCL" mc:Ignorable="d" d:DesignWidth="297.6"> - + diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb index f3879f4..33cafbb 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb @@ -64,6 +64,7 @@ Public Class PageLoginLegacy Private Sub ComboLegacy_TextChanged(sender As Object, e As TextChangedEventArgs) Handles ComboName.TextChanged If Setup.Get("LaunchSkinType") = 0 Then PageLaunchLeft.SkinLegacy.Start(IsForceRestart:=True) HintChinese.Visibility = If(RegexCheck(ComboName.Text, "^[0-9A-Za-z_]*$"), Visibility.Collapsed, Visibility.Visible) + RunInUi(Sub() PanMain.InvalidateMeasure(), True) '由于 WPF 的 Bug,它不一定会自动更新 HintChinese 的大小(#6627) End Sub Private Sub Skin_Click() Handles Skin.Click If (Setup.Get("UiHiddenPageSetup") OrElse Setup.Get("UiHiddenSetupLaunch")) AndAlso Not PageSetupUI.HiddenForceShow Then diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml index f8acc1a..f6c72da 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml @@ -23,10 +23,11 @@ - - + + + diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb index dd33df6..325a077 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb @@ -107,7 +107,7 @@ Retry: McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True) GoTo Retry ElseIf Result.Contains("""error""") Then - Hint("更改皮肤失败:" & GetJson(Result)("error"), HintType.Critical) + Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Critical) Return End If '获取新皮肤地址 diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml index 89eb32f..5298fd7 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml @@ -17,7 +17,7 @@ - + @@ -29,7 +29,7 @@ - + @@ -51,11 +51,12 @@ - + - - - + + + + @@ -70,12 +71,12 @@ - + - + @@ -104,265 +105,246 @@ - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -377,11 +359,13 @@ - + - - - + + + + + @@ -389,7 +373,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb index 10c4f07..13e7a3f 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb @@ -18,12 +18,6 @@ End Sub - Private Sub BtnAboutBmclapi_Click(sender As Object, e As EventArgs) Handles BtnAboutBmclapi.Click - OpenWebsite("https://afdian.com/a/bangbang93") - End Sub - Private Sub BtnAboutWiki_Click(sender As Object, e As EventArgs) Handles BtnAboutWiki.Click - OpenWebsite("https://www.mcmod.cn") - End Sub Public Shared Sub CopyUniqueAddress() Handles BtnDonateCopy.Click ClipboardSet(UniqueAddress) End Sub diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb index 46a8bf4..a1a4c96 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb @@ -55,7 +55,7 @@ If Type = "指南" Then MyCard.StackInstall(NewStack, 11, "指南") Else - NewCard.IsSwaped = True + NewCard.IsSwapped = True End If PanList.Children.Add(NewCard) Next diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb index 4686ce2..534af41 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb @@ -137,7 +137,7 @@ Case 1 Feedback(True, False) Case 2 - OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/") + OpenWebsite("https://github.com/Meloong-Git/PCL/issues/") End Select End Sub Private Sub TryVote(sender As Object, e As RouteEventArgs) Handles ItemVote.Changed @@ -148,7 +148,7 @@ Public Shared Sub TryVote() If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请使用 VPN 以改善网络环境。", "新功能投票", "打开", "取消") = 2 Then Return - OpenWebsite("https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created") + OpenWebsite("https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created") End Sub End Class diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb index 7d8d927..303120b 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb @@ -317,6 +317,9 @@ Public Sub Delete_Click(sender As Object, e As RoutedEventArgs) Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag Dim DeleteText As String = If((Folder.Type = McFolderType.Original OrElse Folder.Type = McFolderType.RenamedOriginal) AndAlso Folder.Path = Path & ".minecraft\" AndAlso McFolderList.Count = 1, "清空", "删除") + If Folder.Path.EndsWith(":\") OrElse Folder.Path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\" OrElse Folder.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\" Then '#7030 + If MyMsgBox("将要删除的游戏文件夹是系统关键目录(" & Folder.Path & "),确定要删除吗?", "删除警告", "取消", "确认", "取消", IsWarn:=True) <> 2 Then Return + End If If MyMsgBox("你确定要" & DeleteText & "这个文件夹吗?" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这会导致该文件夹中的所有存档与其他文件永久丢失,且不可恢复!", "删除警告", "取消", "确认", "取消") <> 2 Then Return If MyMsgBox("如果你在该文件夹中存放了除 MC 以外的其他文件,这些文件也会被一同删除!" & vbCrLf & "继续删除会导致该文件夹中的所有文件永久丢失,请在仔细确认后再继续!" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这是最后一次警告!", "删除警告", "确认" & DeleteText, "取消", IsWarn:=True) <> 1 Then Return '移出列表 diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb index b553cc7..e6e7d27 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb @@ -81,15 +81,15 @@ PanMain.Children.Add(NewCard) '确定卡片是否展开 If Card.Key = McVersionCardType.Rubbish OrElse Card.Key = McVersionCardType.Error OrElse Card.Key = McVersionCardType.Fool Then - NewCard.IsSwaped = True + NewCard.IsSwapped = True Else MyCard.StackInstall(NewStack, 0, CardTitle) End If Next '若只有一个卡片,则强制展开 - If PanMain.Children.Count = 1 AndAlso CType(PanMain.Children(0), MyCard).IsSwaped Then - CType(PanMain.Children(0), MyCard).IsSwaped = False + If PanMain.Children.Count = 1 AndAlso CType(PanMain.Children(0), MyCard).IsSwapped Then + CType(PanMain.Children(0), MyCard).IsSwapped = False End If '判断应该显示哪一个页面 @@ -227,7 +227,7 @@ DeleteDirectory(Version.Path) Hint("版本 " & Version.Name & " 已永久删除!", HintType.Finish) Else - FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) + FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) Hint("版本 " & Version.Name & " 已删除到回收站!", HintType.Finish) End If Case 2 diff --git a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb index 28fcd3d..38dca35 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb @@ -14,7 +14,6 @@ {"HintDownloadThread", New SetupEntry(False, Source:=SetupSource.Registry)}, {"HintNotice", New SetupEntry(0, Source:=SetupSource.Registry)}, {"HintDownload", New SetupEntry(0, Source:=SetupSource.Registry)}, - {"HintInstallBack", New SetupEntry(False, Source:=SetupSource.Registry)}, {"HintHide", New SetupEntry(False, Source:=SetupSource.Registry)}, {"HintHandInstall", New SetupEntry(False, Source:=SetupSource.Registry)}, {"HintBuy", New SetupEntry(False, Source:=SetupSource.Registry)}, @@ -42,6 +41,7 @@ {"SystemSystemCache", New SetupEntry("", Source:=SetupSource.Registry)}, {"SystemSystemUpdate", New SetupEntry(0)}, {"SystemSystemActivity", New SetupEntry(0)}, + {"SystemSystemTelemetry", New SetupEntry(True, Source:=SetupSource.Registry)}, {"CacheExportConfig", New SetupEntry("", Source:=SetupSource.Registry)}, {"CacheSavedPageUrl", New SetupEntry("", Source:=SetupSource.Registry)}, {"CacheSavedPageVersion", New SetupEntry("", Source:=SetupSource.Registry)}, @@ -124,7 +124,7 @@ {"ToolDownloadTranslateV2", New SetupEntry(1, Source:=SetupSource.Registry)}, {"ToolDownloadIgnoreQuilt", New SetupEntry(True, Source:=SetupSource.Registry)}, {"ToolDownloadCert", New SetupEntry(False, Source:=SetupSource.Registry)}, - {"ToolDownloadMod", New SetupEntry(1, Source:=SetupSource.Registry)}, + {"ToolDownloadMod", New SetupEntry(2, Source:=SetupSource.Registry)}, {"ToolModLocalNameStyle", New SetupEntry(0, Source:=SetupSource.Registry)}, {"ToolUpdateAlpha", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)}, {"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)}, @@ -181,6 +181,7 @@ {"VersionAdvanceRun", New SetupEntry("", Source:=SetupSource.Version)}, {"VersionAdvanceRunWait", New SetupEntry(True, Source:=SetupSource.Version)}, {"VersionAdvanceDisableJLW", New SetupEntry(False, Source:=SetupSource.Version)}, + {"VersionAdvanceDisableModUpdate", New SetupEntry(False, Source:=SetupSource.Version)}, {"VersionRamType", New SetupEntry(2, Source:=SetupSource.Version)}, {"VersionRamCustom", New SetupEntry(15, Source:=SetupSource.Version)}, {"VersionRamOptimize", New SetupEntry(0, Source:=SetupSource.Version)}, @@ -610,7 +611,7 @@ FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible) FrmSetupUI.HintCustom.Text = $"从指定网址联网获取主页内容。服主也可以用于动态更新服务器公告。{vbCrLf}如果你制作了稳定运行的联网主页,可以点击这条提示投稿,若合格即可加入预设!" FrmSetupUI.HintCustom.EventType = "打开网页" - FrmSetupUI.HintCustom.EventData = "https://github.com/Hex-Dragon/PCL2/discussions/2528" + FrmSetupUI.HintCustom.EventData = "https://github.com/Meloong-Git/PCL/discussions/2528" Case 3 '预设 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Visible FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index e193a9f..b1e7698 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -171,7 +171,7 @@ - + @@ -205,7 +205,7 @@ - + @@ -218,7 +218,7 @@ - + @@ -235,15 +235,15 @@ - + - - - + + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml index 412967f..43cd397 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml @@ -62,16 +62,18 @@ + + - - - + + @@ -79,13 +81,13 @@ - - + - @@ -109,7 +111,7 @@ - + @@ -124,6 +126,7 @@ + @@ -145,7 +148,10 @@ - + + @@ -155,7 +161,7 @@ - + @@ -182,7 +188,7 @@ --> - + @@ -198,7 +204,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb index 76776f3..7ead8ef 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb @@ -35,7 +35,7 @@ 'Mod 与整合包 ComboDownloadTranslateV2.SelectedIndex = Setup.Get("ToolDownloadTranslateV2") - 'ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod") + ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod") ComboModLocalNameStyle.SelectedIndex = Setup.Get("ToolModLocalNameStyle") CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt") @@ -50,6 +50,7 @@ ComboSystemUpdate.SelectedIndex = Setup.Get("SystemSystemUpdate") ComboSystemActivity.SelectedIndex = Setup.Get("SystemSystemActivity") TextSystemCache.Text = Setup.Get("SystemSystemCache") + CheckSystemTelemetry.Checked = Setup.Get("SystemSystemTelemetry") '调试选项 CheckDebugMode.Checked = Setup.Get("SystemDebugMode") @@ -81,6 +82,7 @@ Setup.Reset("SystemSystemCache") Setup.Reset("SystemSystemUpdate") Setup.Reset("SystemSystemActivity") + Setup.Reset("SystemSystemTelemetry") Log("[Setup] 已初始化启动器页设置") Hint("已初始化启动器页设置!", HintType.Finish, False) @@ -92,13 +94,13 @@ End Sub '将控件改变路由到设置改变 - Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change + Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change, CheckSystemTelemetry.Change If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked) End Sub Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderDebugAnim.Change, SliderDownloadThread.Change, SliderDownloadSpeed.Change If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value) End Sub - Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboModLocalNameStyle.SelectionChanged, ComboDownloadTranslateV2.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadSource.SelectionChanged + Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboModLocalNameStyle.SelectionChanged, ComboDownloadTranslateV2.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadSource.SelectionChanged, ComboDownloadMod.SelectionChanged If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex) End Sub Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextSystemCache.ValidatedTextChanged diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb index 647675f..92fe4e1 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb @@ -274,7 +274,7 @@ '顶部栏 Private Sub BtnLogoChange_Click(sender As Object, e As EventArgs) Handles BtnLogoChange.Click - Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片") + Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.jpeg;*.gif;*.webp)|*.png;*.jpg;*.jpeg;*.gif;*.webp", "选择图片") If FileName = "" Then Return Try '拷贝文件 @@ -319,7 +319,7 @@ Refresh: Return End If '没有图片则要求选择 - Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片") + Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpeg;*.jpg;*.gif;*.webp)|*.png;*.jpeg;*.jpg;*.gif;*.webp", "选择图片") If FileName = "" Then FrmMain.ImageTitleLogo.Source = Nothing e.Handled = True @@ -357,7 +357,9 @@ Refresh: PanMusicVolume.Visibility = Visibility.Visible PanMusicDetail.Visibility = Visibility.Visible BtnMusicClear.Visibility = Visibility.Visible - CardMusic.Title = "背景音乐(" & EnumerateFiles(Path & "PCL\Musics\").Count & " 首)" + CardMusic.Title = "背景音乐(" & + EnumerateFiles(Path & "PCL\Musics\").Count(Function(f) Not {".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png"}.Contains(f.Extension.ToLower)) & + " 首)" Else PanMusicVolume.Visibility = Visibility.Collapsed PanMusicDetail.Visibility = Visibility.Collapsed diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml index a35beb3..9483d95 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml @@ -102,7 +102,7 @@ @@ -133,6 +133,14 @@ DefaultChecked="False" /> + + + + + - + diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb index 6a5e6b0..9f03eed 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb @@ -564,8 +564,8 @@ Public Class PageVersionExport Sub() Try Dim ModrinthHashes = Loader.Input.Select(Function(m) m.ModrinthHash) - Dim ModrinthRaw = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files", "POST", - $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject) + Dim ModrinthRaw As JObject = DlModRequest("https://api.modrinth.com/v2/version_files", HttpMethod.Post, + $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json") For Each ModFile In Loader.Input '查找对应的文件 If Not ModrinthRaw.ContainsKey(ModFile.ModrinthHash) Then Continue For @@ -589,8 +589,8 @@ Public Class PageVersionExport Try If ModrinthUploadMode Then Return 'Modrinth 上传模式下,不能从 CurseForge 获取信息 Dim CurseForgeHashes = Loader.Input.Select(Function(m) m.CurseForgeHash) - Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", "POST", - $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer) + Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", HttpMethod.Post, + $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches") For Each ResultJson As JObject In CurseForgeRaw If Not ResultJson.ContainsKey("file") Then Continue For Dim File As JObject = ResultJson("file") diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb index 184e924..ed319a7 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb @@ -381,7 +381,9 @@ Install: ''' 下载 Mod。 ''' Private Sub BtnManageDownload_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageDownload.Click, BtnHintDownload.Click - PageComp.TargetVersion = PageVersionLeft.Version '将当前版本设置为筛选器 + '预设置当前版本和搜索框内容 + PageComp.TargetVersion = PageVersionLeft.Version + PageComp.TargetName = SearchBox.Text FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadMod) End Sub @@ -428,6 +430,11 @@ Install: If FrmMain.PageRight IsNot Me Then Return If My.Computer.Keyboard.CtrlKeyDown AndAlso e.Key = Key.A Then ChangeAllSelected(True) End Sub + Private Sub SearchBox_PreviewKeyDown(sender As Object, e As KeyEventArgs) Handles SearchBox.PreviewKeyDown + 'Ctrl + A 会被搜索框捕获,导致无法全选,所以在按下 Ctrl + A 时转移焦点以便捕获 + If SearchBox.Text.Any Then Return + If My.Computer.Keyboard.CtrlKeyDown AndAlso e.Key = Key.A Then PanBack.Focus() + End Sub #End Region @@ -636,14 +643,14 @@ Install: Try For Each Entry As McMod In ModList If File.Exists(Entry.Path) Then - My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) + My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) Else Log($"[Mod] 未找到更新前的 Mod 文件,跳过对它的删除:{Entry.Path}", LogLevel.Debug) End If Next For Each Entry As KeyValuePair(Of String, String) In FileCopyList If File.Exists(Entry.Value) Then - My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) + My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) Log($"[Mod] 更新后的 Mod 文件已存在,将会把它放入回收站:{Entry.Value}", LogLevel.Debug) End If If Directory.Exists(GetPathFromFullPath(Entry.Value)) Then @@ -732,7 +739,7 @@ Install: If IsShiftPressed Then File.Delete(ModEntity.Path) Else - My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) + My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) End If Catch ex As OperationCanceledException Log(ex, "删除 Mod 被主动取消") diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb index 91a8e0c..075e7eb 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb @@ -116,12 +116,14 @@ Dim TempName As String = NewName & "_temp" Dim TempPath As String = PathMcFolder & "versions\" & TempName & "\" Dim IsCaseChangedOnly As Boolean = NewName.ToLower = OldName.ToLower - '重新加载版本 Json 信息,避免 HMCL 项被合并 + '重新读取版本 JSON 信息,避免 JsonObject 中已被合并的项被重新存储 Dim JsonObject As JObject Try - JsonObject = GetJson(ReadFile(PageVersionLeft.Version.Path & PageVersionLeft.Version.Name & ".json")) + Dim JsonPath As String = PageVersionLeft.Version.GetJsonPath() + JsonObject = GetJson(ReadFile(JsonPath)) + File.Delete(JsonPath) Catch ex As Exception - Log(ex, "重命名读取 json 时失败") + Log(ex, "在重命名读取 json 时失败") JsonObject = PageVersionLeft.Version.JsonObject End Try '重命名主文件夹 @@ -130,27 +132,26 @@ '清理 ini 缓存 IniClearCache(PageVersionLeft.Version.PathIndie & "options.txt") IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini") - '遍历重命名所有文件与文件夹 - For Each Entry As DirectoryInfo In New DirectoryInfo(NewPath).EnumerateDirectories - If Not Entry.Name.Contains(OldName) Then Continue For + '重命名 jar 文件与 natives 文件夹 + '不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443) + If Directory.Exists($"{NewPath}{OldName}-natives") Then If IsCaseChangedOnly Then - My.Computer.FileSystem.RenameDirectory(Entry.FullName, Entry.Name & "_temp") - My.Computer.FileSystem.RenameDirectory(Entry.FullName & "_temp", Entry.Name.Replace(OldName, NewName)) + My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{OldName}natives_temp") + My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives_temp", $"{NewName}-natives") Else - DeleteDirectory(NewPath & Entry.Name.Replace(OldName, NewName)) - My.Computer.FileSystem.RenameDirectory(Entry.FullName, Entry.Name.Replace(OldName, NewName)) + DeleteDirectory($"{NewPath}{NewName}-natives") + My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{NewName}-natives") End If - Next - For Each Entry As FileInfo In New DirectoryInfo(NewPath).EnumerateFiles - If Not Entry.Name.Contains(OldName) Then Continue For + End If + If File.Exists($"{NewPath}{OldName}.jar") Then If IsCaseChangedOnly Then - My.Computer.FileSystem.RenameFile(Entry.FullName, Entry.Name & "_temp") - My.Computer.FileSystem.RenameFile(Entry.FullName & "_temp", Entry.Name.Replace(OldName, NewName)) + My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{OldName}_temp.jar") + My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}_temp.jar", $"{NewName}.jar") Else - If File.Exists(NewPath & Entry.Name.Replace(OldName, NewName)) Then File.Delete(NewPath & Entry.Name.Replace(OldName, NewName)) - My.Computer.FileSystem.RenameFile(Entry.FullName, Entry.Name.Replace(OldName, NewName)) + File.Delete($"{NewPath}{NewName}.jar") + My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{NewName}.jar") End If - Next + End If '替换版本设置文件中的路径 If File.Exists(NewPath & "PCL\Setup.ini") Then WriteFile(NewPath & "PCL\Setup.ini", ReadFile(NewPath & "PCL\Setup.ini").Replace(OldPath, NewPath)) @@ -159,15 +160,13 @@ If ReadIni(PathMcFolder & "PCL.ini", "Version") = OldName Then WriteIni(PathMcFolder & "PCL.ini", "Version", NewName) End If - '更改版本 Json - If File.Exists(NewPath & NewName & ".json") Then - Try - JsonObject("id") = NewName - WriteFile(NewPath & NewName & ".json", JsonObject.ToString) - Catch ex As Exception - Log(ex, "重命名版本 json 失败") - End Try - End If + '写入版本 Json + Try + JsonObject("id") = NewName + WriteFile(NewPath & NewName & ".json", JsonObject.ToString) + Catch ex As Exception + Log(ex, "重命名版本 json 失败") + End Try '刷新与提示 Hint("重命名成功!", HintType.Finish) PageVersionLeft.Version = New McVersion(NewName).Load() @@ -185,7 +184,7 @@ '选择 自定义 时修改图片 Try If ComboDisplayLogo.SelectedItem Is ItemDisplayLogoCustom Then - Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif", "选择图片") + Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpeg;*.jpg;*.gif;*.webp)|*.png;*.jpeg;*.jpg;*.gif;*.webp", "选择图片") If FileName = "" Then Reload() '还原选项 Return @@ -331,7 +330,7 @@ DeleteDirectory(PageVersionLeft.Version.Path) Hint("版本 " & PageVersionLeft.Version.Name & " 已永久删除!", HintType.Finish) Else - FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) + FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) Hint("版本 " & PageVersionLeft.Version.Name & " 已删除到回收站!", HintType.Finish) End If Case 2 diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml index 9151949..dacdda0 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml @@ -192,7 +192,7 @@ - + @@ -208,17 +208,19 @@ - - + - - + + + diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb index a11595b..a0bab03 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb @@ -61,6 +61,7 @@ CheckAdvanceAssetsV2.Checked = Setup.Get("VersionAdvanceAssetsV2", Version:=PageVersionLeft.Version) CheckAdvanceJava.Checked = Setup.Get("VersionAdvanceJava", Version:=PageVersionLeft.Version) CheckAdvanceDisableJLW.Checked = Setup.Get("VersionAdvanceDisableJLW", Version:=PageVersionLeft.Version) + CheckAdvanceDisableModUpdate.Checked = Setup.Get("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version) Catch ex As Exception Log(ex, "重载版本独立设置时出错", LogLevel.Feedback) @@ -91,6 +92,7 @@ Setup.Reset("VersionAdvanceRun", Version:=PageVersionLeft.Version) Setup.Reset("VersionAdvanceRunWait", Version:=PageVersionLeft.Version) Setup.Reset("VersionAdvanceDisableJLW", Version:=PageVersionLeft.Version) + Setup.Reset("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version) Setup.Reset("VersionArgumentJavaSelect", Version:=PageVersionLeft.Version) JavaSearchLoader.Start(IsForceRestart:=True) @@ -125,7 +127,7 @@ Private Shared Sub CheckBoxLikeComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndieV2.SelectionChanged If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex = 0, Version:=PageVersionLeft.Version) End Sub - Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckAdvanceAssetsV2.Change, CheckAdvanceJava.Change, CheckAdvanceDisableJLW.Change + Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckAdvanceAssetsV2.Change, CheckAdvanceJava.Change, CheckAdvanceDisableJLW.Change, CheckAdvanceDisableModUpdate.Change If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked, Version:=PageVersionLeft.Version) End Sub @@ -355,6 +357,15 @@ PreFin: #Region "服务器" + '自动替换标点 + Private Sub TextServerEnter_Change() Handles TextServerEnter.TextChanged + Dim NewText = TextServerEnter.Text.Replace(":", ":").Replace("。", ".") + If NewText = TextServerEnter.Text Then Return + Dim CurrentPosition = TextServerEnter.SelectionStart '重设焦点 + TextServerEnter.Text = NewText + TextServerEnter.SelectionStart = CurrentPosition + End Sub + '全局 Private ComboServerLoginLast As Integer Private Sub ComboServerLogin_Changed() Handles ComboServerLogin.SelectionChanged, TextServerNide.ValidatedTextChanged, TextServerAuthServer.ValidatedTextChanged, TextServerAuthRegister.ValidatedTextChanged diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj index 8d3a3e6..faea8fc 100644 --- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj +++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj @@ -140,6 +140,21 @@ false + + False + Resources\CacheCow.Client.dll + False + + + False + Resources\CacheCow.Client.FileStore.dll + False + + + False + Resources\CacheCow.Common.dll + False + False Resources\Imazen.WebP.dll @@ -149,8 +164,9 @@ False False - - ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + False + Resources\Newtonsoft.Json.dll False @@ -158,13 +174,20 @@ Resources\Ookii.Dialogs.Wpf.dll False + + + False + Resources\System.Net.Http.Formatting.dll + False + + @@ -182,6 +205,7 @@ MSBuild:Compile Designer + @@ -202,6 +226,7 @@ + MyLocalModItem.xaml @@ -235,9 +260,6 @@ - - Component - PageComp.xaml @@ -724,6 +746,7 @@ + @@ -961,5 +984,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll b/Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll new file mode 100644 index 0000000..e0457c9 Binary files /dev/null and b/Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll differ diff --git a/Plain Craft Launcher 2/Resources/CacheCow.Client.dll b/Plain Craft Launcher 2/Resources/CacheCow.Client.dll new file mode 100644 index 0000000..33c79be Binary files /dev/null and b/Plain Craft Launcher 2/Resources/CacheCow.Client.dll differ diff --git a/Plain Craft Launcher 2/Resources/CacheCow.Common.dll b/Plain Craft Launcher 2/Resources/CacheCow.Common.dll new file mode 100644 index 0000000..a47fb9e Binary files /dev/null and b/Plain Craft Launcher 2/Resources/CacheCow.Common.dll differ diff --git a/Plain Craft Launcher 2/Resources/Custom.xaml b/Plain Craft Launcher 2/Resources/Custom.xaml index 77bbd84..c171293 100644 --- a/Plain Craft Launcher 2/Resources/Custom.xaml +++ b/Plain Craft Launcher 2/Resources/Custom.xaml @@ -3,7 +3,7 @@ 下列内容均基于 WPF 框架。你可以在继续之前先尝试自学 XAML 与 WPF 的基础,这能让你更轻松地理解下列内容。 你也可以使用 Ctrl + F 快速查找。例如,若需要改变文本颜色,则搜索 “颜色” 即可。 --> - + @@ -14,17 +14,17 @@ - + + Text="卡片(local:MyCard)的 Title 属性决定了它的标题。 为卡片添加 CanSwap 属性让它可以被折叠,True 代表是,False 代表否。在此基础上,再使用 IsSwapped 属性调整它是否默认被折叠。" /> - + @@ -34,7 +34,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -106,14 +106,14 @@ - + + Text="查看 PCL 源代码" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL/blob/main/Plain%20Craft%20Launcher%202/FormMain.xaml.vb"/> + + + + - + @@ -162,7 +170,7 @@ - + @@ -179,7 +187,7 @@ - + @@ -217,7 +225,7 @@ - + @@ -238,7 +246,7 @@ - + @@ -249,7 +257,7 @@ - + @@ -272,7 +280,7 @@ - + @@ -297,12 +305,12 @@ - + + Text="打开 GitHub 讨论页" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL/discussions/2528" /> @@ -332,7 +340,7 @@ local:MyImage(图片): local:MyCard(卡片): - Title:设置显示的标题文本 - CanSwap:卡片是否可以折叠,True 为是,False 为否 - - IsSwaped:卡片是否默认折叠,要求 CanSwap 必须为 True + - IsSwapped:卡片是否默认折叠,要求 CanSwap 必须为 True - HorizontalAlignment:若使用,要求 CanSwap 必须为 False - UseAnimation:是否在展开等高度改变时触发动画;True 为是(默认),False 为否 - SwapLogoRight:卡片折叠时的箭头是朝下还是朝右;True 为朝右,False 为朝下(默认) diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip index 54d1dd0..224f7d5 100644 Binary files a/Plain Craft Launcher 2/Resources/Help.zip and b/Plain Craft Launcher 2/Resources/Help.zip differ diff --git a/Plain Craft Launcher 2/Resources/ModData.txt b/Plain Craft Launcher 2/Resources/ModData.txt index d5d9eac..88ddec9 100644 --- a/Plain Craft Launcher 2/Resources/ModData.txt +++ b/Plain Craft Launcher 2/Resources/ModData.txt @@ -19,7 +19,7 @@ modular-forcefield-system|模块化力场系统* minechem-recompile|化学 (MineChem) - 非官方重编译版¨73373|化学 (MineChem) - 已失效 iron-chests@|更多箱子*¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)¨iron-chests-fabric@|更多箱子 (Iron Chests) balkons-weaponmod|更多武器 (Balkon's WeaponMod) -advanced-machines|高级机械 (AdvancedMachines)¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版 +advanced-machines|高级机械 (AdvancedMachines) - Chocohead版¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版 advanced-solar-panels|高级太阳能* nuclear-control-2|核电控制2* @@ -121,7 +121,7 @@ blockphysics_by_id_miner|方块物理学 (BlockPhysics) remain-in-motion|运动红石机构 (Remain in Motion) -modular-powersuits|模块化动力套装* +modular-powersuits@modularpowersuits|模块化动力套装* @@ -189,7 +189,7 @@ mekanism@|通用机械* magic-crusade-rpg-mod|魔法远征:魔法十字军 (Magic Crusade) invasions|入侵 (Invasion) -mb-battlegear-2|挖矿与砍杀:双持与战备2 (Mine & Blade: Battlegear 2) +mb-battlegear-2|挖矿与砍杀:双持与战备 2 (Mine & Blade: Battlegear 2) galacticraft-legacy@|星系 (GalactiCraft) @@ -490,7 +490,7 @@ sim-u-kraft-old|模拟城市 (Sim-U-Kraft) superheroes-unlimited|超级英雄无限* oceancraft-mod|海洋世界 (Oceancraft) - +more-enchantments-legacy|更多附魔 (More Enchantments) witching-gadgets|巫师宝具* buildcraft-compat@|建筑兼容* @@ -1006,7 +1006,7 @@ roost|鸡窝* croparia@|矿石作物/魔种之咏* simple-corn|简单玉米* thaumic-jei@ -tinkers-steelworks|匠魂炼钢厂 (Tinkers' Steelworks) +tinkers-steelworks|匠魂炼钢厂 (Tinkers' Steelworks)¨tinkers-steelworks-fork|匠魂炼钢厂 (Tinkers' Steelworks) unicode-font-extension|Unicode 字体扩展* placeable-items|可放置物品* compendium|工匠防御 (Tinkers' Compendium (Tinkers' Defense)) @@ -1187,7 +1187,7 @@ minecraft-transport-simulator@immersive-vehicles|沉浸车辆 (Immersive Vehicle chest-transporter|搬箱器* i18nupdatemod@|自动汉化更新 (I18nUpdateMod) realistic-item-drops|真实物品掉落* -pixelmon@|像素精灵宝可梦 重铸* +pixelmon@|像素宝可梦 重铸 (Pixelmon Reforged) controlling@|键位冲突显示*¨controlling-for-fabric|键位冲突显示 (Controlling) @@ -1250,7 +1250,7 @@ career-bees|职业蜜蜂* level-up|升级!重置 (Level Up!Reloaded) level-up-legacy|升级!前版 (Level Up!Legacy) moar-boats|模块化船* -numina|模块化动力装甲前置* +numina@|模块化动力装甲前置* tinkers-complement@|匠魂补充 (Tinkers' Complement) everlastingabilities@everlasting-abilities|永恒能力 (Everlasting Abilities) abyssalcraft-heads|深渊国度头颅 (AbyssalCraft Heads) @@ -1274,7 +1274,7 @@ just-enough-pattern-banners primitive-mobs|远古生物 / 原始生物* realistic-cobwebs@ simpleharvest|简单收获 (Simple Harvest) -mrcrayfishs-device-mod|MrCrayfish的设备 (MrCrayfish's Device Mod) +mrcrayfishs-device-mod|MrCrayfish 的设备 (MrCrayfish's Device Mod) block-armor|方块盔甲* stockpile|储物桶* loot-slash-conquer @@ -1378,7 +1378,7 @@ arcade-mod|街机* asmodeuscore@ building-gadgets|建筑小帮手* dooglamoo-worlds|Dooglamoo的世界* -scp-lockdown|SCP基金会:封锁 (SCP: Lockdown) +scp-lockdown@|SCP基金会:封锁 (SCP: Lockdown) middle-earth-thaumaturgy|中土神秘学 (Middle-Earth Thaumaturgy) structpro-mod-fast-schematic-spawning-system|自然神殿 (Structpro) betterfps|更好的FPS (BetterFPS) @@ -1525,7 +1525,7 @@ better-questing-standard-expansion|更好的任务-基础扩展 (Better Questing exchangers|方块交换器* flexible-tools|灵活的工具* dimensional-control|维度控制* -valkyrien-warfare@valkyrien-skies|瓦尔基里天空/瓦尔基里战争 (Valkyrien Skies/Valkyrien Warfare) +valkyrien-skies@|瓦尔基里天空* item-scroller@|物品滚轮* better-advancements@|更好的进度* lucraft-core @@ -2165,7 +2165,7 @@ betterachievements|更好的成就 (Better Achievements) extra-bows|更多的弓* forgotten-relics|失落遗物学* ore-core@|矿石核心* -ordinary-coins|普通硬币 (Ordinary Coin) +ordinary-coins|普通硬币* peanut-mod|花生! (Peanuts!) in-game-wiki-mod @@ -2233,7 +2233,7 @@ citadel@ screenshot-browser|内置截图管理器* -curio-of-undying@charm-of-undying|不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying)¨curio-of-undying-fabric|不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying) +curio-of-undying@charm-of-undying|不死图腾插槽/不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying)¨curio-of-undying-fabric|不死图腾插槽/不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying) explorercraft-worldexpansion|探险者 (Explorercraft) enigmatic-legacy@|神秘遗物* @@ -2246,7 +2246,7 @@ the-rc-mod rubybyimxiaoanag|红宝石 (Ruby) jojo-hamon-era@|JoJo-波纹时代 (JoJo-Hamon Era) -OpenSecurity|开放式防卫* +OpenSecurity|开放式安全* the-legend-era|传说纪元* level-up-hp-forge|生命提升 (Level Up HP)¨level-up-hp|生命提升 (Level Up HP) more-planet-extras @@ -2442,7 +2442,7 @@ not-enough-crashes@notenoughcrashes|崩溃优化*¨not-enough-crashes-forge|崩 crash-to-main-menu|崩溃返回主菜单* tinkers-jei -better-questing-quest-book|更好的任务-任务书 (Better Questing - Quest Book) +better-questing-quest-book@|更好的任务-任务书 (Better Questing - Quest Book) engineers-doors|工程师的门 (Engineer's Doors) tps-generator|TPS发电机 (TPS Generator) better-drowning@|更好的溺水* @@ -2491,7 +2491,7 @@ beesourceful middle-earth-industry|中洲工业 (Middle-Earth Industry) thut-wearables after-the-drizzle|细雨田园 (After the Drizzle) -cmdcam|CMD 指令摄影机 (CMDCam) +cmdcam@|CMD 指令摄影机 (CMDCam) climate-control-geographicraft@geographicraft|气候控制 / 地理世界 (Climate Control / Geographicraft) cosmetic-armor-reworked|时装盔甲重置版* @@ -2743,7 +2743,7 @@ vials|瓶瓶罐罐* advanced-lightsabers|高级光剑* fall_attack|下落击杀 (fall_attack) swimsuit|泳装 (swimsuit) -more-overlays-updated +more-overlays-updated@ dothack-hack-weapons immersive-energy|沉浸能源* crimson-moon|绯红之月* @@ -3262,7 +3262,7 @@ devtech|GTCEu-CrT开发套件* -jurassic-world-reborn-mod|侏罗纪世界重生* +jurassic-world-reborn-mod|侏罗纪重生 (Jurassic Reborn) cardboard@ enchanting-with-thaumcraft|神秘附魔学* @@ -3920,7 +3920,7 @@ the-elemelon-mod|元素西瓜* dcsp|钻石压缩 (Diamonds Compressor SP) corpse-complex@ modularvoicechat|模块化语音聊天 (ModularVoiceChat) -elytra-slot@|鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot)¨elytra-slot-fabric|鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot) +elytra-slot@|鞘翅插槽/鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot)¨elytra-slot-fabric|鞘翅插槽/鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot) tconstructcompatible|匠魂兼容 (TConstructCompatible) @@ -3955,7 +3955,7 @@ satako-library¨satako-fabric unique-enchantments-utils|独特的附魔:效用扩展 (Unique Enchantments - Utils) forge-ae2-additional-opportunity@ae2ao¨ae2ao¨legacy-ae2-additional-opportunity iconexporter@icon-exporter -curious-shulker-boxes@shulker-box-slot|潜影盒槽位 (Shulker Box Slot)¨curious-shulker-boxes-fabric|潜影盒槽位 (Shulker Box Slot) +curious-shulker-boxes@shulker-box-slot|潜影盒插槽/潜影盒槽位 (Shulker Box Slot)¨curious-shulker-boxes-fabric|潜影盒插槽/潜影盒槽位 (Shulker Box Slot) xenoclus-2 darker-depths|深岩之下* terracraft-journey @@ -4136,7 +4136,7 @@ falling-leaves-forge@fallingleavesforge|落叶 (Falling Leaves (NeoForge/Forge)) custom-entity-models-cem@cem|自定义实体模型 (Custom Entity Models) -tweakmyclient +tweakmyclient@ lovely-snails@lovely_snails|可爱蜗牛* clipboard|备忘录* @@ -4288,7 +4288,7 @@ reroll|刷新附魔* tax-free-levels@|免税附魔* portable-dimensions|便携式传送门* stone-age-by-yanny -terra-world-generator|Terra - 已经失效¨@terra +terra-world-generator|Terra - 已失效¨@terra omnis|灾厄村民拓展* omnis-backpacks @@ -4472,7 +4472,7 @@ travelers-titles@|旅人标题 (Traveler's Titles)¨travelers-titles-fabric|旅 expand-chinese-sword|中式剑拓展 (Arsenal Integration) littleframes@littlepictureframes|小画 (LittlePictureFrames) -lavalogged-blocks|岩浆充填 (Lavalogged blocks) +lavalogged-blocks@|岩浆充填 (Lavalogged blocks) awoken-world gt4-reimagined|格雷科技4:重构想 (GregTech 4 Reimagined) camouflaged-creepers @@ -4925,7 +4925,7 @@ illagers@illagers+|更多的灾厄村民 (Illagers+) villagers-follow-emeralds-fabric@|村民跟随绿宝石 (Villagers follow Emeralds) -diabloloot|暗黑破坏神战利品 (DiabloLoot) +diabloloot|暗黑破坏神战利品 (DiabloLoot - Bosses Update) yggdrasil|世界树* ftb-dripper-forge forge-snad|子沙1.14+ (Snad)¨fabric-snad|子沙1.14+ (Snad) @@ -4967,7 +4967,7 @@ happiness-is-a-warm-gun@ kvc-blocks|蛋挞君的原版补全方块篇 (KVCBlocks) enigtech2-util -environmental-creepers|环保型苦力怕* +environmental-creepers@|环保型苦力怕* look-at-that advanced-peripherals@advancedperipherals|高级外设* @@ -4986,9 +4986,9 @@ expandability squirrels|松鼠* ftb-power-pots the-graveyard-forge@|墓园 (The Graveyard)¨the-graveyard-fabric@|墓园 (The Graveyard) -nether-agriculture|下界农业* +nether-agriculture@netheragriculture|下界农业* conjuring@ -awesome-dungeon-forge@¨awesome-dungeon-fabric@ +awesome-dungeon-forge@awesome-dungeon¨awesome-dungeon-fabric¨awesome-dungeon-neoforge out-of-sight@ decorative-blocks-modded-compat|装饰方块模组兼容 (Decorative Blocks modded compat) camp-chair @@ -5283,7 +5283,7 @@ ore-prospecting-stick-forge|探矿之戒 (Rings Of Prospecting)¨ore-prospecting worldedit-express-forge¨worldedit-express-fabric awesome-dungeon-edition-ocean-forge@awesome-dungeon-edition-ocean¨awesome-dungeon-edition-ocean-fabric pistorder -escape-rope-pokemon-item-forge¨awesome-dungeon-neoforge +escape-rope-pokemon-item-forge awesome-flooring-forge@¨awesome-flooring-fabric@ medievial-entity-forge@medieval-entity-forge|中世纪生物 (Medieval Entity)¨medievial-entity-fabric@medieval-entity-fabric|中世纪生物 (Medieval Entity) upgraded-shulkers|更多潜影盒* @@ -5292,7 +5292,7 @@ bobby@|服务器区块缓存* boosted-brightness@|亮度增强* effective@¨effective-forge -voidtotem|虚空图腾 (Void Totem)¨voidtotem-fabric|虚空图腾 (Void Totem) +voidtotem@|虚空图腾 (Void Totem)¨voidtotem-fabric|虚空图腾 (Void Totem) tool-leveling-plus@tool-leveling|工具升级 (Tool Leveling+)¨tool-leveling-plus-fabric|工具升级 (Tool Leveling+) mythicmetals@|神话金属 (Mythic Metals) creeper-firework@|烟花苦力怕* @@ -5635,7 +5635,7 @@ just-enough-piglin-bartering|JEI猪灵以物易物* @aftersans53228-fabric-road|暗访谈的Fabric道路 (aftersans53228's fabric roads) primal-magick@|原初魔法* -ore-creeper|矿石苦力怕* +ore-creeper@|矿石苦力怕* panthalassa|泛古洋* stardew-armory|星露谷武器*¨stardew-armory-fabric|星露谷武器 (Stardew Armory) no-more-nether-desert-edition @@ -5693,13 +5693,13 @@ storage-drawers-crafting-recipe enhanced-attack-indicator@|增强攻击提示器* parasites-addon-overlast|寄生兽:末世扩展 (OverLast) cancel-block-update@|取消方块更新* -franciscofunari|中世纪工艺 (Medieval Craft) +franciscofunari@medieval-craft-(structures)|中世纪工艺 (Medieval Craft) d3core green-thumb-v1-2-03 greenthumb tfc-adventure|群峦冒险 (TFC+ Adventure) just-zoom-forge@just-zoom -@holograms|全息图 (Holograms) +server-holograms@holograms|全息图 (Holograms) eidolons-hexblades|幻梦咒剑 (Eidolon's Hexblades) monke-madness @@ -5770,7 +5770,7 @@ zettai-psi psicaster tweakermore@ legacy-display@ -the-backrooms|后室* +the-backrooms creatures-of-the-snow@creatures-from-the-snow-fabric|雪地里的生物! (Creatures From The Snow!)¨creatures-from-the-snow-forge|雪地里的生物! (Creatures From The Snow!) collisions-lib@ wooled-boots@|羊毛靴子* @@ -5827,10 +5827,10 @@ realistic-block-physics@ domestication-innovation@|驯养革新* snuffles@|魄罗* flan-forge@flan¨flan -awesome-dungeon-the-end-forge@¨awesome-dungeon-the-end-fabric@ +awesome-dungeon-the-end-forge@awesome-dungeon-the-end¨awesome-dungeon-the-end-fabric tehnuts-torcherino library-ferret-forge@library-ferret¨library-ferret-fabric¨library-ferret-neoforge -awesome-dungeon-nether-forge@¨awesome-dungeon-nether-fabric@ +awesome-dungeon-nether-forge@awesome-dungeon-nether¨awesome-dungeon-nether-fabric ae2-extended-life|应用能源非官方延续版 (AE2 Unofficial Extended Life) refined-cooking@|精致厨房* @@ -5996,7 +5996,7 @@ oedldoedl-resources oedldoedl-gear disguiseheads@ simple-tcon -enchanted-tooltips +enchanted-tooltips@ stackablepotions-forge¨stackablepotions mobifier¨mobifier-fabric art-of-forging-a-tetra-addon|锻造之艺 (Art of Forging) @@ -6084,7 +6084,7 @@ onastick-forge@onastick¨onastick tradernpcs more-pads|更多Pad* texture-dump -spartan-weaponry-ice-and-fire|斯巴达的武器:冰火传说 (Spartan Weaponry: Ice and Fire) +spartan-weaponry-ice-and-fire@|斯巴达的武器:冰火传说 (Spartan Weaponry: Ice and Fire) hefty-crops@ @strangefuelsmod|奇奇怪怪的燃料 (Strange Fuels Mod) custom-player-models@|自定义玩家模型 (Customizable Player Models)¨custom-player-models-fabric|自定义玩家模型 (Customizable Player Models) @@ -6482,7 +6482,7 @@ ninjaxxs-needs pixelmon-gui-blocks more-mekanism-processing@ -item-banning +item-banning@ probejs@ impaled@|更多三叉戟 (Impaled🔱) laserio @@ -6507,7 +6507,7 @@ advancements-enlarger better-muffling|更好的消音器* lightest-lamps@¨lightest-lamps-fabric bigbuckets|大铁桶 (Big Buckets)¨bigbuckets-fabric|大铁桶 (Big Buckets) -dimstorage +dimstorage@ spawner-imbuer@ unnamed-delight|无名乐事* @@ -6858,7 +6858,7 @@ the-nexus-deep-dark-dimension jepp|JEI绘画预览 (Just Enough Painting Previews) create-curios -ctm-refabricated +ctm-refabricated¨@ctm-refabricated placeable-pumpkin-pie|可放置南瓜派* birch-forest-upgrade|白桦林更新* ok-zoomer-forge @@ -7011,7 +7011,7 @@ withcoffee|咖啡相伴/天堂之路 (WithCoffee) emeraldstuff grass-slabs-carpets-stairs not-mutually-exclusive|不冲突*¨@not-mutually-exclusive|不冲突 (Not Mutually Exclusive) - 已失效 -builders-delight-forge +builders-delight-forge@builders-delight miners-minerals@ days-to-mine@7-days-to-mine|七日杀 (7 Days to Mine) pieces-of-ore @@ -7082,7 +7082,7 @@ auto-workstations-fabric@auto-workstations|自动化工作站 (Auto Workstations l2-backpack@l2backpack|莱特兰-背包* @mbp -capybara-fabric¨unnamed-animal-mod-capybara-only +capybara-uam¨unnamed-animal-mod-capybara-only just-enough-petroleum diamethysts@diamethysts! twigs@ @@ -7713,7 +7713,7 @@ odysseymod libipn@ hulsealib blues-dynamic-lights -gtc-expansion +gtc-expansion@ nether-depths-upgrade@|炽海生机* cloud-factory disc-holders-dj @@ -7901,7 +7901,7 @@ your-items-are-safe@¨your-items-are-safe-fabric techemistry the-missing-villages continents -yungs-better-ocean-monuments@|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-fabric|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-neoforge|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments) +yungs-better-ocean-monuments@|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-fabric|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-neoforge|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments) echoing-depths advanced-finders @@ -7968,13 +7968,13 @@ status-effect-timer@statuseffecttimer logical-zoom@ the-space-age-mod|太空时代* -touhou-origins@|起源:东方project附加包* +touhou-origins@|起源:东方 Project 附加包* earth-mobs@earthmobs|地球生物* simpleores-tin just-enough-keys¨just-enough-keys-fabric why-am-i-on-fire@ -bouncing-balls-api@ +bouncing-balls-api@|弹跳球 API (Bouncing Balls API) bouncing-balls@|弹跳球* teletubbies-mod|天线宝宝* almost-unified@ @@ -7985,15 +7985,15 @@ time-arrow catch-me-if-you-can-cmiyc mc-modsis-one-way-glass|Alli的单向玻璃 (Alli's One Way Glass) sanguis -ender-trigon +ender-trigon@ litlaunch puzzle@ - +why-am-i-on-fire-refoxed @tdfp|暮色乐事:冰雪豪饮 (Twilight Delight: Frozen Pride) ftb-pack-companion teams-stages -kiwi-boi +kiwi-boi@ rubidium-toolkit@|铷 · 工具箱* advanced-combat|高级战斗装备* advanced-combat-revitalized@|高级战斗装备重置 (Advanced Combat (Revitalized)) @@ -8033,7 +8033,7 @@ better-stats@|更好的统计信息界面 (Better Statistics Screen) abyssalblocks|深渊国度幸运方块 (AbyssalBlocks) -archon +archon@ stackie @cullclouds @superfancyclouds @@ -8050,14 +8050,14 @@ berdinskiybears-armor-hud tiered-tooltips rgb-blocks-forge|调色方块 (RGB Blocks) -quicksand-fabric|流沙 (Quicksand) -better-durability|更好的耐久度* +quicksand-fabric@quicksand|流沙 (Quicksand) +better-durability@|更好的耐久度* passthrough-signs excorrelation@ packet-size-doubler @packet-size-double-forge arachnophobia-mode-for-minecraft@arachnophobia-mode -villagerconfig@|村民配置 (VillagerConfig) +villagerconfig@|村民配置 (Villager Config) jasm@speedometer paginated-advancements@paginatedadvancements|分页进度 (Paginated Advancements & Custom Frames) @backport-gamerules @@ -8070,7 +8070,7 @@ ingame-info-xml-addon-serene-seasons staff-of-building|建筑之杖 (Staff of Building) -dark-mode-everywhere|夜间模式无处不在* +dark-mode-everywhere@|夜间模式无处不在* custom-shop|自定义商店 (CustomShop) another-liquid-milk@ dark-mode-everywhere-fabric@|夜间模式无处不在Fabric版 (Dark Mode Everywhere - Fabric) @@ -8820,7 +8820,7 @@ glacier-ice gtnhmixins@ dawn-to-dusk open-tower-defence@|开放式塔防* -mtr-station-decoration@station-decoration|MTR车站装饰 (MTR Station Decoration) +mtr-station-decoration@station-decoration|MTR车站装饰 (Station Decoration) perviaminvenire silverbirch@silver-birch @@ -8841,7 +8841,7 @@ extended-slabs-plus brewin-and-chewin-fabric@|饮酒作乐 Fabric 版 (Brewin' And Chewin' [Fabric]) particular longer-login-times -broglis-plants +broglis-plants@ rlmixins@ simplehats@simple-hats|简单帽子 (Simple Hats) timeoutout-fabric@timeoutout @@ -8855,7 +8855,7 @@ create-industrial-chemistry@|机械动力:工业化学 (Create: Industrial Che cruelars-triforcemod @morespawneggs|更多刷怪蛋 (MoreSpawnEggs) -beautify-refabricated@|美化!Fabric版 (Beautify: Refabricated) +beautify-refabricated@|美化!重织 (Beautify: Refabricated) manatweaks chicken-nugget @@ -8908,7 +8908,7 @@ clear-skies-forge-port spell-power@ morecrashinfo|更多崩溃报告信息 (MoreCrashInfo) -epicfight-saopack@epicacg|史诗战斗:ACG附属包 (EpicFight ACG AddonPack) +epicfight-saopack|史诗战斗:ACG附属包 (EpicFight ACG AddonPack) screenshot-viewer@|截图查看器* gml@ @futurepack-additions|飞向未来附加 (Futurepack Additions) @@ -9981,13 +9981,13 @@ tskimi-seirans-fruit-wine@tskimiseirans-fruit-wine|月见清兰的果酒 (Tskimi graduated-cylinders@|刻度量筒* fps-monitor -stitch@ +athena@athena-mod calm-mornings@ fog-looks-good-now@ guarding@ cats-expanded@ modular-bosses -experience-obelisk@ +experience-obelisk@cognition here-be-no-dragons gnumus-settlement fading-night-vision @@ -10003,7 +10003,7 @@ mannequins simple-mango@ betterdrawhoveringtext|更好的工具信息显示 (betterDrawHoveringText) -badges-by-harmex +badges-by-harmex@cobblebadges2 terrarian-arsenal zombie-veterinarian|僵尸科兽医* just-enough-sculk-sensor @@ -10453,7 +10453,7 @@ easydropper|轻松投掷 (Easy Dropper) varied-adventure kubejs-delight block-physics-overhaul -searchlight-forge|探照灯 (Searchlight(& Wall Lights))¨searchlight|探照灯 (Searchlight(& Wall Lights)) +searchlight-forge|探照灯 (Searchlight (& Wall Lights))¨searchlight|探照灯 (Searchlight (& Wall Lights)) ancient-manuscripts@ gazebos@ scoreboard-get-time|记分板获取时间 (scoreboard get time) @@ -10705,7 +10705,7 @@ enhancedsnowman new-world-height-and-depth @inertiaanticheat -scroll-of-harvests +scroll-of-harvests@ @scripts-chunk-loaders clean-tooltips@ @@ -10893,7 +10893,7 @@ do-api@ grabby-mobs@ pandas-falling-trees@ iron-ender-chests@ -delightful-creators-fabric@|动力乐事 (Delightful Creators) +delightful-creators-forge@delightful-creators|动力乐事 (Delightful Creators)¨delightful-creators-fabric|动力乐事 (Delightful Creators) deathbutthree@death-but-three|逝不过三 (Death But Three) comparties|组队兼容* @@ -10906,7 +10906,7 @@ light-the-night tweakerge@ terrafirmaships@ better-ocelots -actually-useful-smithing-table +actually-useful-smithing-table@ denyblocks sculk-horn@-sculk-horn ydms-red-panda@ @@ -11052,7 +11052,7 @@ toomuchrain@ thirst-was-remade|口渴-重制*¨@thirst-was-remade|口渴-重制 (Thirst Was Remade) - 已失效 ies-tweaks-fabric@ brown-mooshrooms -@whetstones +whetstones@ @better-log4j-config @harvest wooden-hoppers @@ -11168,7 +11168,7 @@ shulker-meat accurate-block-placement-reborn@ favorite-items -@smart-villagers-follow-emeralds|见钱眼开 (Smart Villagers Follow Emeralds) +smart-villagers-follow-emeralds@|见钱眼开* @villager-pickup step-it-up@stepitup @@ -11307,7 +11307,7 @@ jagms-kiwis@ improved-trident@ mystic-bows forgiving-world -@skillcloaks +skill-cloaks@skillcloaks swordinthestone@ lazy-ae2-patch|懒人AE2补丁 (Lazy AE2 Patch) @@ -11322,7 +11322,7 @@ rei-custom-command|REI自定义命令 (REI Custom Command) copperative@|铜心协力* growable-storage-cells growable-cells-2 -cinchcraft +cinchcraft@ day-counter@ag-day-counter makecold|冷境 (MakeCold) @@ -11362,9 +11362,9 @@ cute-kiwi-birds-new-animal moremobheads@ ouchies@ @traditional-asphalt -@thaumic_boots +thaumic-boots@thaumic_boots carpeted-stairs@ -just-enough-archaeology +just-enough-archaeology@ bedrock_world world-preview@|世界预览* @@ -11426,7 +11426,7 @@ gigeresque lookaround projectileimmunityfix @betterzoom -@player-visibility +player-visibility@ bag-of-holding-forge@bag-of-holding @@ -11645,7 +11645,7 @@ simply-skills@ cctv-craft -redeco@ +redeco@|重铸家具 (Re:Deco) pocket-storage animated-player-mod|动画化玩家* its-thirst@ @@ -12254,7 +12254,7 @@ more-crafting-tables-mct@more-crafting-tables-lieonlion @cobblemon-quests akkamaddis-classical-alchemy|Classical Alchemy - 新 @big-pony|彩虹大马 (Big Pony) - +qraftyfied @theft akkamaddis-golden-glitter@ @obsidianbucket @@ -12331,7 +12331,7 @@ advancement-based-spawning@ heroic-armory-legacy yungs-menu-tweaks@|YUNG的菜单调整 (YUNG's Menu Tweaks)¨yungs-menu-tweaks-fabric|YUNG的菜单调整 (YUNG's Menu Tweaks)¨yungs-menu-tweaks-neoforge|YUNG的菜单调整 (YUNG's Menu Tweaks) -entity-distance-1-12-2|实体距离 (Entity Distance) +entity-distance-1-12-2@entity-distance-1.12.2|实体距离 (Entity Distance) coolesthardcore@coolest-hardcore taxcastlepillager pesky-pirates@ @@ -12412,7 +12412,7 @@ halloween-luckyblocks spooky-halloween isle-of-berk-variant-loader@ spooky-arms - +qraftys-end-villages bed-is-not-too-far-away@ @enderman-tweaks @@ -12542,7 +12542,7 @@ the-monkey-bomb@ @cobblemon-hunt ancient-animals beavery@ - +qraftys-halloween-villages vemerioraptor technique-to-kill-thrasher|毙鲨技 (technique to kill thrasher) the-open-sauce-toast-killer@ @@ -12552,7 +12552,7 @@ better-wake-up-position@|更好的起床位置 (Better Wake-up Position) railroad-blocks-forge@railroad-blocks¨railroad-blocks fossilmon@ -opack2reload|资源包惰性重载/抑验怠载 (0Pack2Reload) +opack2reload|资源包惰性重载/抑验怠载 (0Pack2Reload/OPack2Reload) wits@ @motleys-music-discs @@ -12678,7 +12678,7 @@ cheat-command pumpkinpal johnnys-biological-notes@johnnys-biological-notes-1.0-beta|Johnny的生物笔记 (Johnny's biological notes) -@autodrop +autodrop@ mutation-rerun @compactspawners @@ -12936,7 +12936,7 @@ combatify@ go-fish-updated|一起钓鱼吧!(更新版) (Go Fish (updated)) what-are-they-up-to@ -@fumo +fumofumo|Fumo - 仅 16.0.0+¨@fumo @elytraplus mcaic@ @zombification @@ -12994,7 +12994,7 @@ default-world-generator-ssp homyatos-explosives infchest@|无限箱子 (Inf Chest) @squashed -unremovableeffects +unremovableeffects@unremovable-effects traders-in-disguise-the-illager-assassin-project@ cherished-items verdantvibes@|绿意盎然 (VerdantVibes) @@ -13077,7 +13077,7 @@ rpg-parties@ astikor-carts-tfc@tfc-astikor-carts nanexs-mobs no-feather-trample@ -embers-extended-life|余烬非官方延续版 (Embers Unofficial Extended Life) +embers-extended-life@embers-unofficial-extended-life|余烬非官方延续版 (Embers Unofficial Extended Life) terrafirmajousting@ jujutsu-animation @@ -13199,12 +13199,12 @@ pescatore@ apocalypse-now@ cenozoic-nightmare-the-horror-of-prehistory -quickbackupmulti@|多槽位备份 (QuickBackupMulti-Fabric) +quickbackupmulti@|多槽位备份 (QuickBackupMulti) slashblade-japanese-addon-pack-reforked|拔刀剑日系附属重置版 (SlashBlade Japanese Addon Pack Reforked) @create-fabric-fixes crate-delight-forge@crate-delight|装箱乐事 (Crate Delight)¨crate-delight-fabric|装箱乐事 (Crate Delight) -cultural-creators-fabric-create-and-cultural +cultural-creators-forge-create-and-cultural@cultural-crators¨cultural-creators-fabric-create-and-cultural shuffle@ probejs-legacy create-destroy@destroy|坍毁化合 (Destroy) @@ -13256,7 +13256,7 @@ bagus-cosmetic@ floatingitems ars-ocultas@ tetracelium@ -fugue +fugue@ create-food@ @@ -13367,7 +13367,7 @@ magical-jewelry epicsoulsbosses epic-puss-in-boots soul-like-npc -epic-empires@ +epic-empires¨@epic-empires|Epic Empires - 已失效 @@ -13530,7 +13530,7 @@ timeless-heroes@superhero sky-origins@sky glitchcore@ light-level-overlay-reloaded -eirin-yagokoros-pharmacy@eirin-yagokoro-pharmacy|八意永琳的药房 (Eirin Yagokoro's Pharmacy) +eirin-yagokoros-pharmacy@eirin-yagokoro-pharmacy|蓬莱药局 (Eirin Yagokoro's Pharmacy) tfc-ore-excavation tac-interact-key|TAC交互优化 (TAC Interact Key) @@ -13687,7 +13687,7 @@ missions@ votemission@ immersive-vehicles-automobilwerke-schwikau-veb cheesemaking -cardiac +cardiac@ materialtweaker@ whirlwind@ warden-tools@ @@ -13888,7 +13888,7 @@ ad-astra-extra-additions@ infinite-fluid-bucket@|无限流体桶* @voxy -vmod|VMod - 已失效¨@vmod +vmod@ minestuck star-wars-planets-ad-astra@star-wars-planets @@ -13900,7 +13900,7 @@ cat_jam@ remastered-structure spacore fancyvideo-api -goatman-jar-v2-dweller +goatman-jar-v2-dweller@the-goatman-dwller mineral-overhaul-shellstone-tuff clear-gpu-cache@|清除GPU缓存* @@ -13958,7 +13958,7 @@ ash-of-sin-custom-entity-item|罪业余烬:自定义实体装备 (Ash Of Sin: ash-of-sin-eternal-entity|罪业余烬:永恒实体 (Ash Of Sin: Eternal Entity) fundamental@ -wendigo +wendigo-jar-dweller the-super-god-road@|超神之路 (TheSuperGodRoad) @backrooms @@ -14042,7 +14042,7 @@ melee-ex-machina-meexma@melee-ex-machina arc@ cores@ -@citymod|我的城市 (City Mod) +@citymod|我的城市:交通与生活 (CityMod:Traffic and life) too-hard-to-dig@|隐岩砥凿 (Too Hard to Dig) simplecobblestonegenerator@simple-cobble-gen natural-baby-animals @@ -14138,7 +14138,7 @@ mace-backport @steve-eat-stew imaginary kuvalich|赤毒玄骸 (KuvaLich) -ae2-import-export-card@ +ae2-import-export-card@|AE2输入输出卡 (AE2 Import Export Card) @magick-experience @repairable-disc-11 @@ -14255,7 +14255,7 @@ simple-dimensions|简单维度* portals-gui@|传送门GUI* interworlds-splashscreens@|进入维度加载界面* @creative-menu-tweaks -vampires-delight@ +vampires-delight@|血族乐事 (Vampire's Delight) thats-my-bed@ caligo@ @sharpenedswords @@ -14264,7 +14264,7 @@ donut-and-mochi ducky@ducky! lost-idols simple-trashcan@ -darwin-water-ambience +darwin-water-ambience@ decorative-wooden-lattices apotheotic-additions lithosphere @@ -14687,8 +14687,8 @@ bananarangs@ remorphed@ particle-core@ fermiumbooter@ -ytech -@bbrb +ytech@ +better-biome-reblend@bbrb mcterra@ siren-head-the-arrival classic-musical-discs@ @@ -14740,7 +14740,7 @@ seed-delight@|种子乐事* transmog@|幻化* nuclear-guitars cubic-racers - +lotr-ucp|魔戒非官方兼容性补丁 (LOTR UCP) delightful-sandwiches@ shader-fixer@|光影修复 (Shader fixer) resiever@ @@ -14842,7 +14842,7 @@ dwarven-realm|矮人诸国* renewed-eras-of-arda-addon|阿尔达纪元:复兴 (Eras of Arda: Renewed Addon (First and Second Age)) legendary-item@|魔戒传说物品* lotr-middleearth-tweaks|中洲微调 (MiddleEarth Tweaks) -thaumcraft-integration +thaumcraft-integration@ reach-display@ gravitation-suite-patcher anti-rat-dungeon @@ -14916,7 +14916,7 @@ frostbytes-improved-inventory@ mekanism-extras@mekanism_extra|通用机械:扩展* leaf-beds@ unidentified-enchantments@unidentified_enchantments -protection-pixel@|机素防护* +protection-pixel|机素防护*¨@protection-pixel|机素防护 (Protection Pixel) - 已失效 createdpcompat@ @@ -15313,7 +15313,7 @@ more-affordable-than-ever-never-too-expensive@more-affordable-than-ever what-am-i @camoverlay -autoplant +autoplant|自动种植 (AutoPlant) @skip-backup-screen @polysprint @crashpatch @@ -15458,7 +15458,7 @@ lush-cave-trader@ oceans-enhancements@ xp-jellies aromatic -onekeyminer-nf@onekeyminer_nf|一键连锁(Neo)Forge (OneKeyMiner(Forge/NeoForge)) +onekeyminer-nf@onekeyminer_nf|一键连锁(Neo)Forge (OneKeyMiner(Neo)Forge) retargetato@|约谊敌众 (ReTargetato) iceandfire-ce@|冰火传说社区版 (IceAndFire Community Edition) @@ -15485,7 +15485,7 @@ hungry-zombies @gravity-gourds usa-delight|USA Delight - 已失效¨@usa-delight experience-farm -ad-extendra-dandi2k8-version@ad_extendra-continuation +ad-extendra-dandi2k8-version|Dandi2k8's Ad Extendra - 已失效¨@ad_extendra-continuation nebulus-juice-trader@ cthulhu-fishing@ large-super-potato|大型超级马铃薯* @@ -15851,7 +15851,7 @@ nbt-tool -dglab-x-minecraft@ +dglab-x-minecraft¨@dglab-x-minecraft|Minecraft x DgLab - 已失效 twintails|双马尾 (TwinTails)¨twin-tails|双马尾 (TwinTails) gt-wireless|格雷无线科技 (GT Wireless) forcejava21@|强制 Java21 (ForceJava21) @@ -15946,7 +15946,7 @@ to-tweaks-irons-spells@ horde-moon@ laser-mod-2 -mekanism-lasers +mekanism-lasers@ wizards-reborn@ tinkervillager|工匠村民 (Tinker Villager) customnpc-contentback@cnpc-items @@ -15992,7 +15992,7 @@ refreshdisplaylists vulkanmod-android-libs @black-bar-concealer building-shift@ -mob-ai-tweaks +mob-ai-tweaks@ the-masquerade-illager-boss@the-masquerade-(illager-boss) better-armory-x sweetys-horse-armor-tweaks@ @@ -16163,7 +16163,7 @@ ynet-an-xnet-fork rosatech nutrition-gtceu@ nutrition-unofficial-extended-life@|营养学非官方延续版* -@manametalmod-tweaks|魔金微调 (ManaMetalMod Tweaks) +manametalmod-tweaks-m3-t@manametalmod-tweaks|魔金微调 (ManaMetalMod Tweaks) ae2-crafting-tree|合成树 (AE2: Crafting Tree) client-automatic-doors@client_automatic_doors|客户端自动门* @@ -16207,7 +16207,7 @@ fibers-guns-pack-for-tacz beams@ discarnate@ @fishmc -mob-armor-trims@ +mob-armor-trims bubbles-a-baubles-fork arst-motion-blur@ @mpalladium|钯 (Palladium) @@ -16469,7 +16469,7 @@ water-balloon@ cataclysm-spellbooks@ call-of-drowner@|溺亡者之嚎 (Call of Drowner) - +endofday|终焉之日 (Endofdays) fastfood-delight-unofficial|重启速食/快餐乐事非官方版 (FastFood Delight Unofficial) faster-fly-block-breaking|飞行速速破* @@ -16830,7 +16830,7 @@ languagereloadunofficial@ mekanism-covers@ enchanted-wilderness-elegant-countryside@enchanted-wilderness-elegant-countryside.|雅致乡下 (Elegant Countryside) better-durability-reforged|更好的耐久度:重铸* -sos|我来助你! (SOS) +fast-tpa|我来助你! (Fast TPA) cherry-on-1-12-2@cherry_on_1.12.2|1.12.2的樱花 (Cherry_on_1.12.2) scape-and-spartan-parasites|寄体斯巴达 (Scape and Spartan: Parasites) @ripples-of-the-past-extra-stands-addon @@ -16850,7 +16850,7 @@ rple@ reign-of-nether-rts-in-minecraft@reign-of-nether-rts|下界之治 (Reign of Nether RTS) the-secret-doors - +immersive-vehicles-iv-mts-veb-iav-improved-2000|“改进-2000” tame-tools@|驯兽工具* @bia vounierns-turrets @@ -16937,7 +16937,7 @@ better-brightness-slider-respawn@ animatica-foxified@ mcchatgpt@ @aimobs -ambiances@ambiance|氛围! (Ambiance!) +ambiances@aurae|氛围! (Aurae!) screw-your-items@ mace-port bloodlines @@ -16994,7 +16994,7 @@ rank-system|评分系统* @creative-one-punch-forge bundle-inventory|Bundle Inventory - 问题反馈¨@bundle-inventory scroll-tweaks@scrolltweaks|鼠标滚轮调整* -neko-server@ +st@server-tools|服务器工具 (Server Tools) ride-every-thing@|骑乘一切* cit-resewn-neopatcher@ legendary-creatures|传说生物* @@ -17019,7 +17019,7 @@ passive-shield@ trap-expansion roost-2-flying-higher @amber -@liteminer +liteminer@ @lvlz-better-nether @lvlz-better-end cc-shops@ @@ -17081,7 +17081,7 @@ iron-nugget-recycling re-exposer@|Exposer 重置版 (Re-Exposer) gtmoreoreprocessing@ computercraftedu -bibliocraft-legacy +bibliocraft-legacy@ simple-clouds@ @@ -17107,7 +17107,7 @@ creeping-woods dragon-mounts-2-expanded@dragon-mounts-2|龙骑士2:延续 (DragonMounts2: Expanded) life-fruits@|生命果* ati-structures -fantasy-armor@fantasy_armor +fantasy-armor@fantasy_armor|幻想铠甲* beautiful-enchanted-books@beautiful-enchanted-books-mod-edition @@ -17125,7 +17125,7 @@ paladins-oath roundabout-the-jojo-mod@roundabout_the_jojo_mod -enviromine-continuation@ +enviromine-continuation@|更多生存要素:延续 (EnviroMine continuation) @toffys-hooks mutants-buff @@ -17152,7 +17152,7 @@ mutils@ better-campfires@|更好的营火* @mtr-renfe-addon gt-community-additions@ -more-create-burners|机械动力:燃烧室附加* +more-create-burners|机械动力:更多燃烧室* create-logistics@ create-pneumatic-equipment@ collisiondamage|碰撞伤害 (CollisionDamage) @@ -17293,7 +17293,7 @@ storage-drawers-unofficial@|储物抽屉非官方版* centrifuge-tiers-reproduced@ dense-mekanism@ simple-missles@ -galosphere-spellbooks +galosphere-spellbooks@ discerning-the-eldritch @hexcassettes wardens-plus @@ -17302,7 +17302,7 @@ ender-wyrmlings new-classic-battle-towers chitin-equipment@|甲壳装备* -mowzies-cataclysm|Mowzie的灾变 (Mowzie's Cataclysm) +mowzies-cataclysm@|Mowzie的灾变 (Mowzie's Cataclysm) stardew-fishing-fabric@ piglin-slayer@ cataclysm-tools@ @@ -17460,7 +17460,7 @@ book-scroll@ -@qca +qualia-carpet-addition@qca mouse-tweaks-unofficial|鼠标手势非官方版* baubles-lts elysium-api@ @@ -17470,7 +17470,7 @@ antibat@ illage-and-spell-age origincore|起源核心* - +pet-connect|收容宠物* heaven-destiny-moment|天命时刻* industrial-foregoing-extra-upgrades|工业先锋:更多升级 (Industrial Foregoing: Extra Upgrades) growableores-extension@|甘蔗转换机 (GrowableOres Extension) @@ -17618,7 +17618,7 @@ nanny@ resource-library@ resource-config-api@ @threatengl -hybridfix|混合修复 (HybridFix) +hybridfix@|混合修复 (HybridFix) @disable-portal-checks eat-eat-eat|吃吃吃* @recipe-stages-js|配方阶段 (Recipe Stages JS) @@ -17678,7 +17678,7 @@ cute-companions-ducks loot-n-explore@ epic-knights-ice-and-fire medieval-origins-revival@ -construction-sticks@ +construction-sticks@|建筑棒* sophisticated-storage-in-motion@|精妙物流 (Sophisticated Storage in Motion) imgur-display@ extra-sponges@extrasponges|更多海绵* @@ -17939,10 +17939,10 @@ mythic-charms@|神话护符* minecraft-add@|原版补充* stacked-blocks@|捆扎方块* stacked-blocks-farmers-delight@|捆扎方块:农夫乐事 (Stacked Blocks: Farmer's Delight) -@fowlplay +fowlplay@ ender-totem -@faewufs-diversity +diversity@faewufs-diversity tonsofenchants @@ -17951,7 +17951,7 @@ cave-root whos-there-ghost-players@ the-cuckoo-clock final-samurai -classic-battle-towers|经典战斗高塔* +classic-battle-towers@|经典战斗高塔* sihywtcamd-extensions@difficulty-extensions @heart-crystals @@ -17995,7 +17995,7 @@ lost-cities-survive-the-dead-edition power-of-the-void|虚空之力 (Power of the Void) aerlune-rpg -hitboxbackport +hitboxbackport@ fsmm@ create-pantographs-and-wires@ footwork-api@footwork @@ -18018,7 +18018,7 @@ sky-breaker-blue-skies-limit-remover-1-18-2|天穹突破_1.18.2 (Sky Breaker_1.1 xiyus-lucky-block|饩雨的幸运方块 (Xiyu's Lucky Block) @epicfight_oldsolar|史诗战斗:旧版solar (EpicFight_oldsolar) reskillable-reimagined -renaissance-core +renaissance-core@ @startup-chime|启动音效 (Startup Chime) carpet-takeneko-addition@tnca|竹猫的地毯扩展* chain-mod @@ -18072,7 +18072,7 @@ irons-spell-book-mob-attribute-addon epic-fightexsilium-gladius|EpicFight—放逐之刃 (Epic Fight — Exsilium Gladius) @kamenriderweaponcraft|假面骑士武器大全集 (KamenRiderWeaponCraft) -advancearmy@|先遣部队 (AdvanceArmy) +advancearmy@|先遣部队 (Advance Army) @blood-blade @mineimpact|方神 (MineImpact) jadens-nether-expansion-delight@ @@ -18258,7 +18258,7 @@ create-picky-waterwheels@create-picky-wheels fancy-lamps resizing-potion miku -advanced-smelter|荒古炼金炉 (Advanced Alchemical Furnace) +advanced-smelter@|荒古炼金炉 (Advanced Alchemical Furnace) @@ -18361,8 +18361,8 @@ scopic memory-leak-fix-unofficial-remastered @distance-traveled-calculation -@control-craft -reforbidden-magic|禁忌魔法:重生 (ReForbidden Magic) +@control-craft|控制学 (Control Craft) +reforbidden-magic@|禁忌魔法:重生 (ReForbidden Magic) moses-mod craftable-animals|可合成动物* avaritia-1-1x-kedition @@ -18386,10 +18386,10 @@ rhyme-pvz@|PVZ:润 (Rhyme PVZ) ichorium-kit reforgotten-relics|失落遗物学:重生 (ReForgotten Relics) in-your-world@ -warpapocalypse +warpapocalypse@warp-apocalypse arcane-arteries-kedition|奥法献祭学非官方版* -thaumic-tinkerer-unofficial|神秘工匠非官方版* +thaumic-tinkerer-unofficial@|神秘工匠非官方版* asyncparticles@|异步粒子 (AsyncParticles) @dragonfly-model-library @@ -18457,11 +18457,11 @@ gud_coolers suppergerrie2s-drone-mod data-essence@ -nautec@ +nautec@|海蓝科技 (NauTec) thorny-bush-protection@ gudlib -reflex-antilag +reflex-antilag@ tfc-nei nolan @@ -18480,7 +18480,7 @@ apotheosis-totem tbone - +fastfurnace-decreased|熔炉性能优化-- (FastFurnace--) @cdnperspective3d @volume-fix @@ -18519,13 +18519,13 @@ tinkers-advanced@|前沿匠艺 (Tinkers' Advanced) more-structure-processors@ quick-stack-to-nearby-chests@quickstack|快速堆叠 (QuickStack / Quick Stack To Nearby Chests) - +@kallfix yggdrasilproxy@yggdrasil-proxy thaumic-meme|神秘吐槽* starlightxae|星光xAE2修复 (StarlightxAE) - +@levelclear @drops-into-shulker ftb-team-dimensions epicfight-resurrection|史诗战斗-重生 (EpicFight : Resurrection) @@ -18627,7 +18627,7 @@ loquat-forge@loquat¨loquat @customhealthhud @fix-horizontal-camera-lag create-cyber-goggles@|机械动力:赛博护目镜 (Create: Cyber Goggles) -mekanism-science +mekanism-elements bots-lib@ botaniaoverpowered@ @@ -18839,12 +18839,12 @@ health-bar see-the-rarities-str|查看稀有度 (See The Rarities) craftpilot@ @game-of-reborn|复活赛 (Game of Reborn) -legendary-slash|传说之刃* +rough-blade|无锋道* @dota.rg @appledog|苹狗 (Appledog) superb-warfare@|卓越前线* -aetherworks-extended-life +aetherworks-extended-life@aetherworks-unofficial-extended-life pomkots-mechs-extension-pack@ @monkey-utils some-more-blocks@ @@ -18896,7 +18896,7 @@ create-deep-dark@ create-ranged@ create-enlightend@ create-winery@ -create-filters-anywhere +create-filters-anywhere@ create-cheese@ create-copper-zinc@ create-ultimate-factory@ @@ -18930,7 +18930,7 @@ apothic-science-moa-stats ye-gamol-chattels-reborn@ -@my-more-enchantments|我的更多附魔 (My More Enchantments) +mme@my-more-enchantments|我的更多附魔 (My More Enchantments) @some-little-enchant|一些小附魔 (Some Little Enchant) minepulse@|一脉相挖 (Mine Pulse) @@ -18966,12 +18966,12 @@ cable-facades@ easy-storage@ezstorage|简易存储* @tmwin|问君何所需 (TellMeWhatINeed) elrols-gui-elevator -@minecraft-found-footage +backrooms-found-footage@minecraft-found-footage exposure-polaroid@ fallingalchemy|坠落炼金 (FallingAlchemy) -fluxloading +fluxloading@ inventory-swapping seamless-trading@ sophisticated-auto-walk @@ -19009,7 +19009,7 @@ gluttony-oversaturation-overeat overeat oversaturation -etheria +etheria@ legends-of-slugterra@ gussdx-decoration @@ -19156,7 +19156,7 @@ summer-cottage@ marius0x-furniture youssefs-currency@youssefs_currency bibliobiomes-legacy@ -sniffers-delight +sniffers-delight@sniffers_delight easters-delight@ @@ -19179,7 +19179,7 @@ disturb-not-my-dream@|勿扰吾梦 / 起床气* lootbagsrenewal@lootbags|战利品袋重制版 (LootBagsRenewal) - +autoswaplowdurabilityelytra|自动鞘翅替换 (AutoSwapLowDurabilityElytra) no-elytra-equip@no-elytra-mod no-elytra title-changer@ @@ -19249,7 +19249,7 @@ epic-fight-mesh-model|史诗战斗网格模型模组* monster-of-the-night-skies@ spooky-doors@ @mclientapi -@addonapi +moffs-addonapi-dynload@addonapi @shiv @tab-list dokimobs@|萌化生物 (DokiMobs) @@ -19286,7 +19286,7 @@ blades-derby@|骐骥刀剑风云* create-simple-generator@|机械动力:简单发电机 (Create: Simple Generator) sophisticated-backpacks-create-integration@ -@ticex +ticex-tinkers-construct-ex@ticex eternal-starlight-vanilla-ores@eternal-starlight-vo supercritical supercritical-gtqt @@ -19317,7 +19317,7 @@ expanded-ae modular-machinery-reborn-ars-nouveau@ bonsai-crops@|盆栽作物* playing-cards-chips|扑克牌与筹码 (Playing Cards & Chips) -enderdrives +enderdrives@ lucky77 i-dont-see-you@ @@ -19343,7 +19343,7 @@ whimcraft@|奇思妙想 (WhimCraft) recipeconflict-fixer|合成冲突消除:修复版 (RecipeConflict Fixer) dooglamoo-painter-mod|Dooglamoo 的绘画* notafreaks-plushies@ -enchanting-vanilla +enchanting-vanilla@|原韵新生* quest-items-rpg@quest-items @@ -19397,7 +19397,7 @@ apotheosis-x-irons-spellbooks-compat customtab@ nightmares|噩梦* graveyards -sky-arena +sky-arena@demis-sky-arena progressive-mechanics-library@ @shy @@ -19452,7 +19452,7 @@ slightly-newer-art-of-forging custom-treasure-maps@ evolved-mekanism|通用机械:进化* - +brain-delight|脑子乐事* villager-library|村民图书馆* trade-enchant-overhaul more-additional-paintings@ @@ -19513,7 +19513,7 @@ applied-web-terminal@|AE网络终端* itemzoommore @noitemtooltips shuttfup|Stfu - 已停更¨@shuttfup -baublesreforked|饰品重铸 (BaublesReforked) +baublesreforked@|饰品重铸 (BaublesReforked) @wills-lively-villages tmrv@ @@ -19521,7 +19521,7 @@ twilight-treehouses@ improved-rails@|更好的铁轨(可转弯の动力铁轨!) (Improved Rail (TURNABLE POWERED RAILS)) redstone-signal-bind-wand@|非Create版无线红石* - +@ingametranslator|游戏内聊天翻译 (In game Translator) carnivorous-plants-addition mekanism-curios tetra-compat @@ -19563,7 +19563,7 @@ scaffolding-backported hold-my-items-reforged@ distraction-free-recipes@ -seeking-immortals-nightmare@|求仙者:噩梦之章 (Seeking Immortals : Nightmare) +seeking-immortals-nightmare@|月之石:噩梦之章/求仙者:噩梦之章 (Moonstone :Nightmare/Seeking Immortals : Nightmare) enchanted-crystal-arrow|魔法水晶箭 (Enchanted Crystal Arrows) touhou-danmaku-kagura-core|东方弹幕神乐核心 (Touhou Danmaku Kagura core) hah-ueuh@hah!-ueuh @@ -19583,7 +19583,7 @@ dontstarve4@|不要饿死4 (Don't Starve 4) spark-core@|星火核心* nether-core-useful-nether-rock|下界核心 (Nether Core) slashblade-js|拔刀剑 JS (SlashBlade JS) -@caerula-arbor|方块与海嗣与深蓝之树 (Caerula Arbor) +caerula-arbor@|方块与深蓝之树* alices-doll|元歌 (Alice's Doll) bedrock-redux@|基岩再现* item-entity-control|掉落物清理* @@ -19600,12 +19600,12 @@ pokecube-alternative create-propulsion@|机械动力:推进工程 (Create: Propulsion) -event-wrapper|事件包装器*¨@event-wrapper|事件包装器 (Event Wrapper) - 审核中 +event-wrapper@|事件包装器* @modwhitelist|模组白名单 (ModWhiteList) beacon-conduit-tweaks|信标与潮涌核心调整 (Beacon & Conduit Tweaks) -@tfc-tools-trim|群峦传说:工具纹饰 (TFC Tools Trim) +tfc-tools-trim@|群峦传说:工具纹饰 (TFC Tools Trim) @kill-youkai-with-knives mo-bees@|更多蜜蜂 (Mo' Bees) @@ -19618,7 +19618,7 @@ create-unlimited@ skeleton-ai-fix@ climate-rivers@ dungeonsdelight@dungeons_delight|地牢乐事 (Dungeons Delight) -cleanroom-relauncher-unofficial|Cleanroom 模组式启动器非官方版* +cleanroom-relauncher-unofficial@|Cleanroom 模组式启动器非官方版* silents-power-scale@ trident-return-fabric resource-world@|资源世界* @@ -19702,7 +19702,7 @@ alienevo banit|干掉它! (BanIt) alexs-mobs-naturalist-compat@ - +tinkers-ingenuity|匠心之作 (Tinkers' Ingenuity) curiosities@|趣味物品* @cs2_c4|Qis的C4 (C4) @@ -19790,7 +19790,7 @@ verdant-wonders@ underlay@ lazy-item-generator|咸鱼物品生成器* -crab-street-light|螃蟹的路灯* +crab-street-light@|螃蟹的路灯* undertale-delight@|决心乐事* @@ -19828,7 +19828,7 @@ projecteteams@ battle-frenzy|越战越勇* @plane-advancements cosmopolitan@|四海一家* - +redtassel|红缨 (redtassel) sridr|拔刀剑锻造增伤改革 (SlashBlade Refine Increased Damage Reform) mianbaos-modernwarfare|面包军火库 (Mianbao's ModernWarfare) @@ -19838,7 +19838,7 @@ create-no-touching create-aether-industry|机械动力:天境工业* create-entitycontroller@create-entity-control|机械动力:实体控制 (Create: Entity Control) better-advancements-blessed-edtion|更好的进度 - 祝福版 (Better Advancements - Blessed Edtion) -@gloom-raider +@gloom-raiders herobrine-prime legend-of-the-dwerden@ dnt-stronghold-overhaul-lite-edition @@ -19859,7 +19859,7 @@ fluxed-core@ backpack-plus|背倍加* @hydration-mod -defiled-lands-reborn|污秽之地重置版* +defiled-lands-reborn@|污秽之地重置版* @copper-bulb-early|铜灯补充 (Copper Bulb Early) future-fireproof@|未来防火* @low-durability-switcher @@ -19888,11 +19888,11 @@ aesthetic-shelving arboreal-nature@ warborn-resurgence@warbornresurgence +@ceres - -super-creeper|智能爆破专家* +super-creeper|智能爆破专家 (Creeper Super) expanded-experience -reworked-phantoms +reworked-phantoms@ ex-nihilo-fabricio@ hybrid-delights@ create-clothes@|机械动力:服装 (Create: Clothes) @@ -19947,4 +19947,924 @@ tinkers-jewelry-ex|工匠珠宝拓展 (Tinkers' Jewelry EX) lovely-sparkle-pieces|璀璨饰品* yuns-weather@ -qliphoth-awakening@ \ No newline at end of file +qliphoth-awakening@ + + +universal-mod-localizer@universal_mod_localizer|通用模组汉化器* +smart-creeper@|智慧苦力怕* +peaceful-depletion@peaceful_depletion|和平消耗* +@everything-is-tnt +structure-pool-api@ +create-bluemap@ +tragicmc-2-continuation|悲惨世界2:延续 (TragicMC 2 Continuation) +nebula-parasites@ +bonsai-trees-unofficial-extended-life|盆栽非官方延续版* +@its-as-shrimple-as-that +worldedit-items@ +slashblade-skin@|拔刀皮肤 (SlashBlade Skin) +iron-door-key@ +just-enough-fishing-jef@just-enough-fishing-(jef) +containersearch@container-search +mousepets@ +backported-minecarts@ +simple-incubator@ +amethyst-stairs-slabs-and-walls +@vessels + +legendary-weapons-byititor +slender-reborn +craft-vania-lite +pollution-of-create +create-chocolate-fountain@|机械动力:巧克力喷泉 (Create: Chocolate Fountain) +create-currency-shops@|机械动力:货币商店 (Create: Currency Shops) +create-nether-industry@|机械动力:下界工业 (Create: Nether Industry) +backported-diamond-ore-gen +tfthreadsafetyaddon +serverautosaver +infinistack@|无限堆叠 (InfiniStack) +powerful-dummy|强力假人* +beyond-bosses@beyondbosses +mini-utilities-y +concoction@concoction! +zombietactics2@ +radiation-zone-reborn|辐射荒原:再行 (Radiation Zone: Reborn) + +@crumbling-hearts + +dye-the-world@ +modernmarkings@ +hololive-product@ +fetzis-displays@ +shadered@ +sweety-garden@ + +@twilight-forest-aurora-palace-regeared + + +medieval-buildings-nether-edition +@ribbits-croaks +echoes-of-time@ +horror-alexbrine +zzz-craft|绝区零·星见雅 (ZZZ Miyabi) + +cozy-cabins-and-cottages +servereye +deepseek@deepseek-neoforge +ai-command|AI指令生成 (AI Command Generator) + +flighthud-do-a-barrel-roll-fix + +@fast-container-placement|快速容器放置 (Fast Container Placement) +@teleport-mod +@what-meme +crackers-wither-storm-mod-v-sides-soundtrack@ + +roberts-game-tweaks|Robert的游戏调整 (Robert's Game Tweaks) + +@datapack-load-error-fix|数据包错误修复:重生 (Datapack Load Error Fix Reborn) +@whats-this +@additional-jewelryz +tacz-tes-compat@ + + +directionhud +raw-netherite-reforged +blast-travel-reborn@blasttravelreborn|旅行火炮·重生* +shanxi-skeleton@shanxiskeleton|山西骷髅* +@gunpowder-overhaul|火药改革 (Gunpowder Overhaul) +smoking-stuff-a-wizards-hobby +@alloy-smelter +@autocrafter-early +time-in-a-bottle-charger@ +just-a-sculk-vial@ +lockylocks@ +server-i18n-api¨@server-i18n-api|Server I18n API - 审核中 +fragmentum-forge@fragmentum¨fragmentum-neoforge¨fragmentum-fabric +@monument-data-fix|Monument 数据修复 (Monument Data Fix) +@better_tp +waystone-currencys@|消币则至 / 传送石碑:货币 (Waystone: Currencys) +@armor-stand|盔甲架 (ArmorStand) +threadtweak-reforged|ThreadTweak (Neo)Forge 移植版 (ThreadTweak Reforged) +@environmental-creepers-backport +com-bat +touhou-little-maid-rpg-class-task + +heat-and-climate-patch|热量与气候修复* + +@clientupdater + +modern-lucky-block@ +foxrecipemaker +@global-mob-health-multiplier|全局生物血量倍率 (Global Mob Health Multiplier) +chatbox-for-mc@chatbox|对话框 (ChatBox) +perfectly-balanced-bows +@playerex-alcohol|PlayerEx 酒精版 (PlayerEx: Alcohol) +hak-minepiece +grease@ +cleaver-compedium +phayriosis-parasite-infection +srp-calamity-catastrophe-cataclysm@|逃寄:无妄之灾 (SRP: Calamity Catastrophe Cataclysm) +moss-overgrowth +create-pillager-arise +create-qol@ +create-remote-terminal@|机械动力:远程终端 (Create: Remote Terminal) + + +songs-of-war@|战争之歌 (Songs of War) +cataclysm-summons@ +cutepuppymod@ +giant-swamp-tree|沼泽巨树* +big-oak-tree@|大橡树* +giant-tree-stump|大树桩* + +polars-mad-tweaks@ +cobblemon-ranked@|方块宝可梦-排位赛* +eiramoticons +horse-breeding-fix@ +tablistextension@|TAB 列表扩展 (TabListExtension) +rally-of-the-guard-guardvillagers +bncci +@recipe-maker +portaltransform@|传送门嬗变 (PortalTransform) +@a555sd|更多的画 (More Paintings) +unusual-furniture@ +xiaoanmooncake@|小安的月饼狂热 (Xiaoan's Mooncake Fever) +foragers-insight@ +palestinian-delight@ +mekanism-more-machine@mekanismmoremachine|通用机械:更多机器 (Mekanism: More Machine) +custom-battleroyale@|自定义大逃杀 (Custom BattleRoyale) +improved-pillager-outpost +rebalanced-enchanting@ +dark-doppelganger@ +enders-spellsandstuff +hazen-n-stuff +spongeforge@ + +@don%22t-die|补药趋势啊! (Don't Die!) +sdm-market@ +@omnilook + +survivalist-essentials@ +villager-trading-plus@trading+ +mending-fix|经验修补修复* + +colorwheel@ +tlib@ +smoothscreen@ + +@fuzz +advanced-loot-info@ +resourcepackcached@ +aotake-sweep|竹叶清* + +effectual@ +cold-light@|冷晖之枪* + +huanles-weapons@|Huanle的武器 (Huanle's weapons) +@clay-soldiers-remake +threateningly-mobs@ +basic-spinosaurus +european-wildlife +marvelous-menagerie@ +roman-weaponary +compat-structure@ +flower-bed@ +archery-expansion-squared +create-more-linked-remote@ + + + +gambagoons-better-emeralds +ingameime-vintage@|游戏内输入法-1.12.2 (IngameIME-Vintage) +extreme-sound-muffler-legacy@ +amazing-trophies + + +global-server-configs@global-server-config|全局服务器配置* +expandedbonemeal +@aireplyassistant|AI回复助手 (AIReplyAssistant) +utiljs +moon-info-updated@ +@server-market|服务器市场 (Server Market) +nbt-tooltip@nbttooltip +delicate-dyes@ +@modernmixins +sdm-economy +sdm-ui-lib@ +mcllm¨@mcllm|MCLLM - 审核中 +@inventory-pause-yipeekiyaay +chestsearch|容器检索 (ChestSearch) +clicktor|Clicktor连点器 (Clicktor - Auto Clicker) +extended-bone-meal@¨extended-bone-meal-fabric +show-me-the-enchantment|怎魔,你不附器* +better-sign-edit@ +disc-recipes@|唱片合成 (Disc Recipe) + + +scoutreforked|侦察兵 重构 (Scout Reforked)¨@scoutreforked|侦察兵 重构 (Scout Reforked) - 审核中 +immersion-in-traffic-context|沉浸交通道路 (Immersion in Traffic Context)¨@immersion-in-traffic-context|沉浸交通道路 (Immersion in Traffic Context) - 审核中 +arknightsfurniture@|明日方舟家具 (ArknightsFurniture) +gunsmith-lib + +experiencelib@ +mystical-mobs + +progressive-difficulty-chaos-integration@ +ottapomos-scps +scp-foundation-requisites@ +thermal-construct@ +war-wither|战争凋灵* +status-effect-bars-reforged +dark-tower-maze + +the-occult +wolftailui@ +better-client@better-client-enc|更好的客户端* +clientharvest@ +auto-afk@ +particle-effects-reforged +hold-my-items-refoxed +dont-claim-our-structures +cosy-critters-creepy-crawlies@cosy-critters-creepy-crawlies-neo +thin-air-ad-astra-compat +yyzsbackpack@yyzs-backpack|yyz的背包 (Yyz's Backpack) + +vital-delight + +@mining-in-place +pathfinder-navigation|寻路者导航* +adorable-eggs@ +food-addition@ +everything-totem|万物皆可图腾* +thaumic-renaissance@thaumcraft-renaissance +@executive +@make-i18n-great-again +steve-goes-fishing@ +voltaic-api@voltaic +boat-delete-begone +zs-packet +raise-them-hotbars +quick-leafdecay +footprint-particle-returns +inventory-crafter-returns +colorwheel-patcher@ + + + + +difficulty-lock@¨difficulty-lock-fabric +ryymr@ +@lazybeefix + +lazy-ae2-tweaks +coordinates-hud@coordinateshud +just-enough-botania +fumo-fumo +touhoupixelcanteen-a-farmers-delight-plugin@touhoupixelcanteen|东方像素食堂 (TouhouPixelCanteen) +perfect-parity-the-garden-awakens-edition@ +perfect-parity-spring-to-life-reforged +hypertubes@hypertube|机械动力:超级管道 (Create: Hypertubes) +perfect-parity-spring-to-life-edition@perfect-parity-1-21-5 +create-geography|机械动力:地理学 (Create: Geography) +create-apothic-enchanting@ +demis-enigmatic-dice@ + +@mpem|月夜性能优化 (Moonlit Performance Enhancement Module) +@eventbenchmark|事件性能测试 (EventBenchmark) +no-more-garbage-in-cell +pick-up-filter-puf@pick-up-filter + +identity-fix-morph@identity-fix + +stick-it@ +aimod +srp-reset-mod|逃逸寄生体:重置 +legendary-relics|传说遗物* + +true-power@|真正的力量 (True POWER) +ender-villages|末地村庄* +twilight-forest-villages@|暮色森林村庄* +poland-nature +amethyst-extra +villagers-windmill +extended-wrenches@ +why-stack-only-64|为什么才64个呢? (Why Stack Only 64?) +@digcooldown|挖掘冷却控制 (DigCoolDown) + + +potentials@ +deltaboxlib@ +gobbobs-animated-blocks|Gobbob 的动画方块 (GobBob's Animated Blocks) +glowingfootsteps|发光足迹粒子 (GlowingFootsteps) +simply-swords-overhaul@ +farms-and-foods@farms-foods +@achievement-fix|成就修复 (Achievement Fix) + +lang-magic-tweaker +spawn-dimension-choice|诞生维度自选* +reforge-stone@|重铸石* +numericalbotany +cameraoverhaul-vintage@ + + +advanced-hoppers + +ars-meteorites +the-hard-herobrine-mod + +tektopia-addons +jason-voorhees +biome-improvement-mindthemoods-pile-of-rocks +mochila@|简彩跃光* +soulytra +@advanced-tfc-blueprint|高级群峦蓝图 (Advanced TFC Blueprint) +@accesswidener-loader +improperui-forged@ + +enchantment-to-attack-faster|并非环取/攻击速度附魔非官方版 (Attack Speed Enchantment Unofficial) +@stopgrowth|停止生长 (StopGrowth) +squid-no-glitch +@permanent-night-vision|无痕夜视 (Permanent Night Vision) +@rems-carpet-addition + +colorful-hex@ + +bbl-no-rain-fog + +@be-quiet-negotiator +link15s-no-i-frames +jei-integration-reborn@|JEI扩展:重生 (JEI Integration: Reborn) +appliedsync +jei-uu-assembler@ +ominous-overlays +create-nuclear-shell@ +world-of-wonders +horrible-meteor-shower|陨石雨* +desertification@ +cinchs-abandoned-houses@ +curse-breaker@ +hundred-years-warfare@|百年战争* +biologica@ +oritech-things +potato-vehicle-industries +warfare-wings@ +avaritias-delight@|无尽乐事 (Avaritia's Delight) +mamas-herbs-and-harvest@ +vs-orbit@|瓦尔基里:轨道学 (VS Orbit) +touhou-little-maid-spell@ +despawn-notifier|余影待终/掉落物消失提醒* +customnpcs-unofficial-from-betazavr|自定义 NPC BetaZavr 非官方版 (CustomNPCs Unofficial from BetaZavr) + +efficient-core-automatic-mining|Efficient Core自动挖掘器* +@bento_box|便当盒 (Bento Box) +custom-100-day-boss@|自定义100天Boss (Custom 100-Day BOSS) + +cinchs-extra-torches@ +corpse-x-cosmetic-armor-reworked-compat +modular-angel-ring +magic-feather-replumed@magicfeather +essentialcraft-magic-reflux +ad-astra-star +neomopeds +create-more-parallel-pipes@ +create-garnished-reworked@ +create-shimmer|机械动力:微光 (Create: Shimmer) +blue-archive-school-banners +cialloblade +windows-keyboard-fixes-returns +aquatic-torches-returns +cratetech-crate-storage +bigger-and-better@ +blabber@ +reducedores@ +durabilityrarity@ +ironfist-lite +shear-chicken@shear-chickens +no-moon-jar@no_moon.jar +@deep_space +blue-grove +quarkponders@ +sussy-sniffers +firefly-bush-backport@ +agritech-trees-automated-trees@agritech-trees +agritech-evolved@ +wealizh|WLH工业故事* +@hydrometallurgy|湿法冶金学 (Hydrometallurgy) +mekanism-ecoenergistics-unofficial + +woodlandfoxverses@|林间狐咏 (WoodlandFoxVerses) + + +swaying-garden|摇曳花园* +time-feeds-villager@|逝者如饲:村民 (Time Feeds: Villager) +ftb-backups-3|FTB 备份 3 (FTB Backups 3) +kurolib@ +trimmed@ +free-camera-api-tripod@|自由相机API-三脚架 (Free Camera API Tripod) +abelia¨@abelia|Abelia - 审核中 + +simple-weather +simple-copy@ +natures-aura-render-fix@ +creakingchopperenchantment@ +daily-rewards@ +initialdimensionreborn + + + + +the-plugin-mod@ +cave-vision +@el-addon-retrieve-to-slot +aether-gloves-for-all +reducedloot +@irisfixes|Iris 修复 (IrisFixes) +musicjs + +suspendtheserver|自动挂起服务器 (Suspend The Server) +screenshot-layers +anti-spider-climbing +rechiseled-chipped@ + +weatherdisplay@ +radium-fabric|Radium - 已失效¨@radium-mod +cinnabar + +pquality@ + +mte-patches +happy-ghast-breeding +happy-ghast-flight +the-angry-ghast@ +@morfonica-in-minecraft +for-vanilla-flowers +@skillpractise|技能练习 (SkillPractise) + + + +bedrock-hotbar-offset¨@bedrock-hotbar|Bedrock Hotbar Offset - 审核中 +mss-more-slot-swap¨more-slot-swap +show-all-advancements|显示所有进度*¨@show-all-advancements|显示所有进度 (Show All Advancements) - 审核中 + +mana-display@ + +curse-forge|诅咒锻造* + + +disconnect-packet-fix@ +happyghast-on-1-12-2@happyghast_on_1.12.2|1.12.2的快乐恶魂 (HappyGhast_on_1.12.2) +automationera@|自动化时代 (Automation Era) +@tears-backport +lava-chicken-disc +lava-dennis +album-go-wall +forgotten-java +johanness-renaissance-rebirth@johanness-renaissancerebirth|约翰内斯的文艺复兴:火器重生 (Johannes's Renaissance:Musket Rebirth) +swarm-infection@ +necro-rooted +@mycelial-crisis|菌丝危机:崛起 (Mycelial Crisis: Mushroom Rising) + +lethalsteelproject|致命拔刀计划 (Lethal Steel Project) +lovecraft-girlfriends-3@ +@faithful-mace +bard-craft + +items-timer +neo-language-reload@ +@oxidizium +ixeris@ +substrate-sodium-addon@substrate +hide-item-frame@|隐藏物品展示框* +@smoothmaps +neoessentials@ +structure-layout-optimizer@ +mutant-creatures-legacy +dynamites-overhaul@ +dynamite-addon¨dynamite-addon-fabric +maid-mana-source|女仆魔源* +maidens-merry-making@mamas-merrymaking +bbl-invisible-lights@ +superior-trees@ +underground-rivers@ +improved-village-placement@ +@photosynthesis +@mineno +slashbladetetra + +irons-spells-n-spellbooks-dynamic-skill-trees@ +spell-elemental@ +ward-ring-extension-for-irons-spells-n-spellbooks +dungeons-and-combat-x-irons-spells +smallcolonies +farmers-cutting-regions-unexplored +serene-landscapes@ +blood-arsenal-reawakened@ + + +spell-colonies +nature-reimagined +ominous-bottle-add +only-hammers@ +auto-resource|自动资源* +iu-additions +rubiks-cube|魔方* +clockworklib + +fabridge + +true-fullbright +quickslots +hide-hud-in-third-person-view@hide-hud-third-person +automlg@auto_mlg +elytraautopilot@elytra_autopilot +simple-conveyor-belts@ +smidgeon-o-bliss-remastered +xl-food-reborn|超多食物:重生 (XL Food Mod Reborn) +create-spin-spawner +vs-rotanator +create-refilling-boxes +cbc-chemical-warfare|机械动力火炮:化学与生物战争 (CBC: Chemical Warfare) +cbc-warium-projectiles|机械动力火炮:Warium炮弹 (CBC: Warium Projectiles) +create-colored +create-law-and-order|机械动力:法律与秩序 (Create: Law and Order) +blockexporter +fix-attack-lag@|修复攻击卡顿* +@clienthings +@misinput +lavablocker@ +gildedarmors@gilded-armor|鎏金盔甲 (Gilded Armor) +inventory-management-deluxe +runningmapmusic|跑图音乐 (RunningMapMusic) +lava-chicken-music-disc +porting-dead-libs +panoramica¨panoramica-fabric +create-mt-integration|资源统合-机械动力配方附加* +@valkyrien-warpeace|瓦尔基里:战争与和平 (Valkyrien War and Peace) +sillys-auto-totem +repairables-anvil@|可修复的铁砧* +@throwable-bricks +@now-time|准点报时 (Now Time) +fluidjs¨@fluidjs|FluidJS - 审核中 +hybridcompat|混合兼容 (HybridCompat) +@recipecooldown +counter-mod +lotr-renewed-class-mod|魔戒种族 (LOTR Races) + +craftable-chain-armors +item-cooling-reduction@|物品冷却缩减* +yosbs + + +@no-bugs +dark-window-bar@ +sodium-core-shader-support@ +@anvilcraft-headtap|铁砧工艺:敲你脑袋 (AnvilCraft: HeadTap) +hearts-in-a-bottle@ +copper-tools-and-shears@ +windchime-unofficial-continued@ + +potionsplus +tinkers-wood@ +illuminated@ +galosphere-argent-edge +brutality-a-terramity-addon +free-epic-games@|FE 游戏框架 (FreeEpicGames) + +fabric-database-h2@|H2数据库* +tokien-theme-bq|更好的任务-托尔金主题 (Tolkien Theme [BQ]) +tpa-utilities +more-diamond-spawn|更多钻石生成* +shutup-opengl +maid-command|女仆指令* +speedy-hopper-vanilla +suggestions-fix-fabric +no-ixapi@no.ixmodapi + +epicfight-zylob|史诗战斗:刀光修改 (EpicFight Sword Light Modifications) +perfect-parity-pale-garden-awakens-neo +expressive-advanced +philia-amulet|友爱护符 (PhiLia Amulet) +stronger-fire-boss|原焰帝臻* +myec@|更多渴望:附魔与制造 (More of Yearn: Enchants & Crafts) +abandoned-urban|废弃城市 (Abandoned urban) +fungal-hazard|蕈骸迷途 +more-useful-copper-2-0@more-useful-copper|更有用的铜 (More Useful Copper) +boom-bombs@ +polars-exploration-additions@ + +ozymandias-sundries +tensura-enigmatic + +create-collection-bundles@ +create-enhanced-mastery +create-industrial-tools +create-smart-bounds +create-fantasizing-again@|机械动力:又在幻想了 (Create: Fantasizing Again) +crafting-station-jei-edition-updated +uncraft-everything@ +@videoplayer +@net-music-play-list|网络音乐机:播放列表 (NetMusicList) + +gregs-modern-construct + +smart-key-prompts@|灵活按键提示 (SmartKeyPrompts) +@zero-whitelist|Zero白名单 (Zero Whitelist) + +@pearlchunkloading|珍珠加载区块移植 (Pearl Chunk Loading) +clean-drops +auto-replace +@footprint + +simple-villager-follow@ +tinkers-experience +hedgehog-companion +i-want-to-spin@|我要开转 (I want to spin) +@kiss-fabric + + + +nuggets-repair-items@|矿粒修复物品* + + +terra-atmos|群峦环境音效* +tlm-true-power@true-power-of-maid|车万女仆:真正的力量 (TLM: True POWER) + + + +@splash-window + +@showcase +enhancedtooltips@ +locate-fixer +journeymap-fixes +ancient-structure@ancient-structures + +wizard-samurai-irons-spells-addon¨@wizard-samurai-irons-spells-addon|Wizard Samurai - Iron's Spells Addon - 更新较慢 +survival-instinct +enderzoo-patched +illager-keep +codex-of-champions +ars-controle@ +medieval-craft-historical-firearms +too-many-withers +reliquified-l-ender-s-cataclysm@ + +@log-creator-finder-findshit|找史大王 / 日志溯源 (findshit) +enchantment-lookup@|附魔查询* + + + + +@mods-checker +no-enchanting-xp-requirment +client-dynamic-light|客户端动态光源* + +multiyggdrasil@ +thaumcraft-4-patched +patchful +gtnh-customizer@ +view-sounds@ +ae-inventory-profiles +tooltips-reforged@|提示框:重铸* +@wyr +soaring-phantoms@ +tick128¨@tick128|Tick128 - 审核中 +ritchies-projectile-library@rpl +modopedia@ +@translate-allinone|一体化翻译 (Translate All in One) +button-fix@ +@birthday-fabric|生日模组 (Birthday Fabric) +bucketstacking +creeper-no-break-blocks@ +create-cog-hud@|机械动力:齿轮 HUD (Create: Cog HUD) + +create-pipe-networks + +multicolored-redstone-lamps +reliquified-twilight-forest@ +witch-tower +the-nightmare-000 +meowmere +@fallenflowers-server-economy|Fallenflower的服务器经济 (Fallenflower's Server Economy) +deadout-medical-supplies|死亡突围医疗用品 (Deadout Medical) +@thermal-shock +better-questing-npc-integration|更好的任务-NPC扩展 (Better Questing - NPC Integration) + +eu2emc|EU转EMC能量转换器 (EU2EMC) + +simplesorter@|简单整理 (SimpleSorter) +spawncursion|生成式入侵 (SpawnCursion) +grass-be-gone|不要草* +tacz-unidict|TACZ:铳械协议 (TACZ: Unidict) +gem-lichens|宝石地衣* +lethality-a-terramity-addon@ +lootr-fixes +no-name-provideds-potion-fruit@potion-fruit +fungus-stew +auto-sapling|自动树苗种植* +alans-bees +snack-sticks@ +fish-fix +stuttering-screen-lag-protection +tilefinder@ +immersive-furniture@|沉浸式家具* +modernnetworking +like1-8 +spoduos-future-mineration +enderio-continuous +enchantment-broker@ + +new-dawn +enhanced-glass +hospitals +comfy-cozy +epicnpc|史诗战斗NPC (epicnpc) +epic-fight-script-extension|史诗战斗:脚本扩展版* +@undead-transformation + +skullskinless|头颅无皮肤/易颜得帧 (SkullSkinless) +staynight|永夜定驻 (Stay Night) +blockblocker|方块放置阻止/抑用挡置 (BlockBlocker) +shoving-shovels@ +electric-coating|不止电池 / 电力镀层 (NotOnlyBattery / Electric Coating) +araxers-bestiary +better-questing-immersive-engineering-multiblock +bloodyquests +croaker@ +crawl-on-demand@ +gaze-a-malum-addon +osce|矿石菌种社区版 (OreSpawn Community Edition) +explosive-arrow +more-relics@ +no-sun@no-sun-mod|暗无天日* +opposing-force@ + +@pingfix +@volumescroll +talk-balloons@ +health-indicator-txf@ + +snowy-weather + + +fluidium|锍* + +peritia + + +corpse-kibou-no-hana|希望の花 (Corpse: Kibou no Hana) +redyeable +@natural-motion-blur +@modpack-checker +convenient-achievements +vintage-vibes@ + +@smelter-the-hedgehog +@hopper-the-hedgehog +custom-trades-mod|自定义村民交易* +tameable-zombie-horse-and-skeleton-horse|可驯服的僵尸马和骷髅马* +mi-tweaks +qa-villager-tweaks|Qa村调整* +multiblocked-extended-compatibility +enum-extender-js +@btc + +no-pigs +vanimals@ +wind-charged-weaponry@ +untagged-mobs +enchanted-potato +cit-for-nbt + +bonemealtweaker +hellstone-golem|狱岩魔像* +netherstar-crafting|可合成的下界之星* +tutes-cn-sweetinks-items|塔兹与甜墨的物品 (Tutes-cn & Sweetink's Items) +i-am-steve@i..am-steve +backported-stufff + +tumbleweeds¨@tumbleweeds|Tumbleweeds - 审核中 +mending-mirror|破镜重圆* + +natures-life +yggdrasils + + +@deepseeks-enchant|DeepSeek的附魔 (DeepSeek's Enchant) + + + +isitwaxed +simple-hp-bar +login-hp-fix +shape-shifter-curse|幻形者诅咒* + + +tinkers-planner-reborn|匠魂蓝图:重燃 (Tinker's Planner Reborn)¨@tinkers-planner-reborn|匠魂蓝图:重燃 (Tinker's Planner Reborn) - 审核中 +hunger-overflow +nocreeper +mo-creatures-extended +skippy-pearls +spelunkery-plus +forge-better-redstone +jeb-just-enough-book@justenoughbook +pal@ +data-anchor@ +elytra-hopping +oh-more-wandering-trades|更多游商交易*¨@oh-more-wandering-trades|更多游商交易 (Oh More Wandering Trades) - 审核中 +beacon-anti-phantom@|不时轻轻肘击幻翼的邻座信标同学 (Beacon Anti-Phantom) + + +cooler-animations + +hardcore-architect + +carpet-neoforged@neoforge-carpet +no-campfire-smoke +tiny-tasks +fancy-outline +echo-compass-hud@ +decorative-stands +confluence-otherworld-gun-fix@ + +sbm-cardboardboxes + + +bedrock-age|基岩时代* + + +@koniavacraft|娜拉工艺 (Koniavacraft) +emojicraft|Emoji工艺* +kylin-arm|麒麟臂* +server-hats@ +forgotten-ruins + +creeperhost-presents-steves-carts@ +perspatium@per-spatium +face-stealer +offlawn-legacy +metalborn +@wizards-mod +mo-creatures-nostalgia-edition@ +mocreatures-awakened +sterling +cuisine-tfc|料理群峦 (Cuisine TFC) +@natural-temperature +silent-gear-metalworks +treetap@ +better-cave-update +@affix +starry-end +dynamic-trees-for-cobblemon +immersive-portal-gun-for-forge@ +adorable-hamster-pets@ +infinite-lava@|无限熔岩* +ethereal-shots|虚无射击* + +epic-fight-super-golem|超级铁傀儡 (Epic Fight - Super Golem) +fallen-log-hideout +pastel@pastel-mod|彩绘世界* +mob-catcher-unofficial +retro-pet-items +confluence-dimension-patch|汇流来世维度补丁* +convenient-storage|便捷存储* +legendreliclib|传世遗产库 (LegendRelicLib) +jauml +nemexlib +cryonicconfig@ +@knight-lib + + +@okom + +craft-slabs-back-into-blocks@ +highland-statue +restore-ore@|复原矿石* +factory-blocks@|工厂方块* + +rotten-planks-and-logs + +create-unify-updated-to-create-6 +create-arm-made-cuisine|机械动力:臂力臂力炒菜 (Create: Arm-made Cuisine) +create-cultivation-craft@|机械动力:栽培工艺 +kaleidoscope-cookery@|森罗物语:烹饪 (kaleidoscope cookery) +retroemi-forge@ +@pearlecho +@mcdaycounter +minecolonies-war-n-taxes +falling-down@ +more-red-x-cc-tweaked-compat + + + +wither-reincarnated + +toros-auto-attack +creeperfire|自定义烧货 (CreeperFire) +fastspawner|刷怪笼渲染优化/产器速算 (Fast Spawner) +sophisticated-storage-plus|存取思深/精致存储扩容* +whaleborne + +newspaper-big-book-bibliocraft-legacy-backport +bic-clipboard +templates-2@ \ No newline at end of file diff --git a/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll b/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll index 8069902..341d08f 100644 Binary files a/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll and b/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll differ diff --git a/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll b/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll new file mode 100644 index 0000000..e9e80be Binary files /dev/null and b/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll differ diff --git a/Plain Craft Launcher 2/Resources/java-wrapper.jar b/Plain Craft Launcher 2/Resources/java-wrapper.jar index bd14bd6..9ab92b2 100644 Binary files a/Plain Craft Launcher 2/Resources/java-wrapper.jar and b/Plain Craft Launcher 2/Resources/java-wrapper.jar differ diff --git a/README.md b/README.md index 99e8076..8b989a8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Plain Craft Launcher -[![Stars](https://img.shields.io/github/stars/Hex-Dragon/PCL2?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTggLjI1YS43NS43NSAwIDAgMSAuNjczLjQxOGwxLjg4MiAzLjgxNSA0LjIxLjYxMmEuNzUuNzUgMCAwIDEgLjQxNiAxLjI3OWwtMy4wNDYgMi45Ny43MTkgNC4xOTJhLjc1MS43NTEgMCAwIDEtMS4wODguNzkxTDggMTIuMzQ3bC0zLjc2NiAxLjk4YS43NS43NSAwIDAgMS0xLjA4OC0uNzlsLjcyLTQuMTk0TC44MTggNi4zNzRhLjc1Ljc1IDAgMCAxIC40MTYtMS4yOGw0LjIxLS42MTFMNy4zMjcuNjY4QS43NS43NSAwIDAgMSA4IC4yNVoiIGZpbGw9IiNlYWM1NGYiLz48L3N2Zz4=&logoSize=auto&label=Stars&labelColor=444444&color=eac54f)](https://github.com/Hex-Dragon/PCL2/) -[![Issues](https://img.shields.io/github/issues/Hex-Dragon/PCL2?style=flat&label=Issues&labelColor=444444&color=1F883D)](https://github.com/Hex-Dragon/PCL2/issues) +[![Stars](https://img.shields.io/github/stars/Meloong-Git/PCL?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTggLjI1YS43NS43NSAwIDAgMSAuNjczLjQxOGwxLjg4MiAzLjgxNSA0LjIxLjYxMmEuNzUuNzUgMCAwIDEgLjQxNiAxLjI3OWwtMy4wNDYgMi45Ny43MTkgNC4xOTJhLjc1MS43NTEgMCAwIDEtMS4wODguNzkxTDggMTIuMzQ3bC0zLjc2NiAxLjk4YS43NS43NSAwIDAgMS0xLjA4OC0uNzlsLjcyLTQuMTk0TC44MTggNi4zNzRhLjc1Ljc1IDAgMCAxIC40MTYtMS4yOGw0LjIxLS42MTFMNy4zMjcuNjY4QS43NS43NSAwIDAgMSA4IC4yNVoiIGZpbGw9IiNlYWM1NGYiLz48L3N2Zz4=&logoSize=auto&label=Stars&labelColor=444444&color=eac54f)](https://github.com/Meloong-Git/PCL/) +[![Issues](https://img.shields.io/github/issues/Meloong-Git/PCL?style=flat&label=Issues&labelColor=444444&color=1F883D)](https://github.com/Meloong-Git/PCL/issues) [![哔哩哔哩](https://img.shields.io/badge/动态-BiliBili-00A4DB?style=flat&labelColor=444444&logoSize=auto)](https://space.bilibili.com/11343203/dynamic) [![爱发电](https://img.shields.io/badge/赞助-爱发电-946ce6?style=flat&labelColor=444444&logoSize=auto)](https://afdian.com/@LTCat) @@ -13,5 +13,5 @@ ### 相关资源 - [PCL 下载](https://afdian.com/p/0164034c016c11ebafcb52540025c377):下载可免费使用的正式版 PCL! -- [PCL 功能投票](https://github.com/Hex-Dragon/PCL2/discussions/2):来参加投票吧,开发者会优先处理票数较高的项目! +- [PCL 功能投票](https://github.com/Meloong-Git/PCL/discussions/2):来参加投票吧,开发者会优先处理票数较高的项目! - [帮助文档库](https://github.com/LTCatt/PCL2Help):PCL 内置帮助文档的存储库 diff --git a/最新正式版.zip b/最新正式版.zip index 3e01985..831ebc0 100644 Binary files a/最新正式版.zip and b/最新正式版.zip differ