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
-[](https://github.com/Hex-Dragon/PCL2/)
-[](https://github.com/Hex-Dragon/PCL2/issues)
+[](https://github.com/Meloong-Git/PCL/)
+[](https://github.com/Meloong-Git/PCL/issues)
[](https://space.bilibili.com/11343203/dynamic)
[](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