diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb index b527334..4080d52 100644 --- a/Plain Craft Launcher 2/Application.xaml.vb +++ b/Plain Craft Launcher 2/Application.xaml.vb @@ -67,9 +67,9 @@ Public Class Application If Not CheckPermission(PathTemp) Then Throw New Exception("PCL 没有对 " & PathTemp & " 的访问权限") Catch ex As Exception If PathTemp = IO.Path.GetTempPath() & "PCL\" Then - MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用") + MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDetail(), "缓存文件夹不可用") Else - MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用") + MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDetail(), "缓存文件夹不可用") Setup.Set("SystemSystemCache", "") PathTemp = IO.Path.GetTempPath() & "PCL\" End If @@ -104,6 +104,13 @@ WaitRetry: ToolTipService.PlacementProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(Primitives.PlacementMode.Bottom)) ToolTipService.HorizontalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(8.0)) ToolTipService.VerticalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(4.0)) + '设置网络配置默认值 + ServicePointManager.Expect100Continue = False + ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12 + ServicePointManager.DefaultConnectionLimit = 10000 + ServicePointManager.UseNagleAlgorithm = False + ServicePointManager.EnableDnsRoundRobin = True + ServicePointManager.ReusePort = True '设置初始窗口 If Setup.Get("UiLauncherLogo") Then FrmStart = New SplashScreen("Images\icon.ico") @@ -136,11 +143,6 @@ WaitRetry: Setup.Load("ToolDownloadThread") Setup.Load("ToolDownloadCert") Setup.Load("ToolDownloadSpeed") - '网络配置初始化 - 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() @@ -155,7 +157,7 @@ WaitRetry: FilePath = PathWithName Catch End Try - MsgBox(GetExceptionDetail(ex, True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误") + MsgBox(ex.GetDetail(True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误") FormMain.EndProgramForce(ProcessReturnValues.Exception) End Try End Sub @@ -168,10 +170,20 @@ WaitRetry: '异常 Private Sub Application_DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException On Error Resume Next + '触发页面的 Dispatcher + If FrmMain?.PageLeft IsNot Nothing AndAlso TypeOf FrmMain.PageLeft Is IDispatcherUnhandledException Then + CType(FrmMain.PageLeft, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e) + If e.Handled Then Return + End If + If FrmMain?.PageRight IsNot Nothing AndAlso TypeOf FrmMain.PageRight Is IDispatcherUnhandledException Then + CType(FrmMain.PageRight, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e) + If e.Handled Then Return + End If + '正常处理 e.Handled = True If IsProgramEnded Then Return FeedbackInfo() - Dim Detail As String = GetExceptionDetail(e.Exception, True) + Dim Detail As String = e.Exception.GetDetail(True) If Detail.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse Detail.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse Detail.Contains("未能加载文件或程序集") OrElse Detail.Contains(".NET Framework") Then ' “自动错误判断” 的结果分析 OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-offline-installer") diff --git a/Plain Craft Launcher 2/Controls/Interfaces.vb b/Plain Craft Launcher 2/Controls/Interfaces.vb new file mode 100644 index 0000000..19a2f34 --- /dev/null +++ b/Plain Craft Launcher 2/Controls/Interfaces.vb @@ -0,0 +1,15 @@ +Imports System.Windows.Threading + +''' +''' 指示页面可以通过按下 F5 或以其他方式刷新。 +''' +Public Interface IRefreshable + Sub Refresh() +End Interface + +''' +''' 指示页面可以处理未捕获的异常。 +''' +Public Interface IDispatcherUnhandledException + Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) +End Interface diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb index 444bcb9..0274be0 100644 --- a/Plain Craft Launcher 2/Controls/MyCard.vb +++ b/Plain Craft Launcher 2/Controls/MyCard.vb @@ -123,14 +123,6 @@ End Select '控件转换 Select Case Type - Case 5 - Dim LoadingPickaxe As New MyLoading With {.Text = "正在获取版本列表", .Margin = New Thickness(5)} - Dim Loader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Main", AddressOf DlForgeVersionMain) - LoadingPickaxe.State = Loader - Loader.Start(Stack.Tag) - AddHandler LoadingPickaxe.StateChanged, AddressOf FrmDownloadForge.Forge_StateChanged - AddHandler LoadingPickaxe.Click, AddressOf FrmDownloadForge.Forge_Click - Stack.Children.Add(LoadingPickaxe) Case 6 ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True) Case 8 @@ -145,9 +137,6 @@ Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True)) Case 3 Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True)) - Case 4 - Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf FrmDownloadLiteLoader.DownloadStart, False)) - Case 5 Case 6 Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True)) Case 7 @@ -332,13 +321,8 @@ 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 Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False, + Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean))) Public Property SwapLogoRight As Boolean = False Private IsMouseDown As Boolean = False diff --git a/Plain Craft Launcher 2/Controls/MyImage.vb b/Plain Craft Launcher 2/Controls/MyImage.vb index 06ffc39..1fa2d79 100644 --- a/Plain Craft Launcher 2/Controls/MyImage.vb +++ b/Plain Craft Launcher 2/Controls/MyImage.vb @@ -25,7 +25,7 @@ ''' ''' 与 Image 的 Source 类似。 - ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。 + ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示;若 EnableCache 设为 True,图片还会保存为本地缓存。 ''' 支持 WebP 格式的图片。 ''' Public Shadows Property Source As String '覆写 Image 的 Source 属性 @@ -46,7 +46,7 @@ Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString()))) ''' - ''' 当 Source 首次下载失败时,会从该备用地址加载图片。 + ''' 若 Source 是一个网络图片,该地址将作为第二下载源。 ''' Public Property FallbackSource As String Get @@ -111,74 +111,36 @@ Return End If '从缓存加载网络图片 - Dim Url As String = Source - Dim Retried As Boolean = False - Dim TempPath As String = GetTempPath(Url) - Dim TempFile As New FileInfo(TempPath) Dim EnableCache As Boolean = Me.EnableCache + Dim TempPath As String = GetTempPath(Source) & If(EnableCache, "", GetUuid()) '不启用缓存时加上随机字符串,避免冲突 + Dim TempFile As New FileInfo(TempPath) If EnableCache AndAlso TempFile.Exists Then ActualSource = TempPath If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Return '无需刷新缓存 End If RunInNewThread( Sub() - Dim TempDownloadingPath As String = Nothing Try RetryStart: '下载 - ActualSource = LoadingSource '显示加载中图片 - TempDownloadingPath = TempPath & RandomInteger(0, 10000000) - Directory.CreateDirectory(GetPathFromFullPath(TempPath)) '重新实现下载,以避免携带 Header(#5072) - Using Client As New Net.WebClient - Client.DownloadFile(Url, TempDownloadingPath) - End Using - If Url <> Source AndAlso Url <> FallbackSource Then - '已经更换了地址 - File.Delete(TempDownloadingPath) - ElseIf EnableCache Then + ActualSource = LoadingSource '显示加载中的占位图片 + NetDownloadByLoader( + If(String.IsNullOrEmpty(FallbackSource), {Source}, {Source, FallbackSource}), + TempPath, SimulateBrowserHeaders:=True) + If EnableCache Then '保存缓存并显示 - If File.Exists(TempPath) Then File.Delete(TempPath) - FileSystem.Rename(TempDownloadingPath, TempPath) RunInUi(Sub() ActualSource = TempPath) Else '直接显示 - RunInUiWait(Sub() ActualSource = TempDownloadingPath) - File.Delete(TempDownloadingPath) + RunInUiWait(Sub() ActualSource = TempPath) + File.Delete(TempPath) End If Catch ex As Exception Try If TempPath IsNot Nothing Then File.Delete(TempPath) - If TempDownloadingPath IsNot Nothing Then File.Delete(TempDownloadingPath) Catch End Try - If Not Retried Then - '更换备用地址 - Log(ex, $"下载图片可重试地失败({Url})", LogLevel.Developer) - Retried = True - Url = If(FallbackSource, Source) - '空 - If Url Is Nothing Then - ActualSource = Nothing - Return - End If - '本地图片 - If Not Url.StartsWithF("http") Then - ActualSource = Url - Return - End If - '从缓存加载网络图片 - TempPath = GetTempPath(Url) - TempFile = New FileInfo(TempPath) - If EnableCache AndAlso TempFile.Exists() Then - ActualSource = TempPath - If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Return '无需刷新缓存 - End If - '下载 - If Source = Url Then Thread.Sleep(1000) '延迟 1s 重试 - GoTo RetryStart - Else - Log(ex, $"下载图片失败({Url})", LogLevel.Hint) - End If + Log(ex, $"下载图片失败", LogLevel.Hint) End Try End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal) End Sub diff --git a/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb b/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb index b6a4638..5a735ba 100644 --- a/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb @@ -79,11 +79,7 @@ Public Class MyLoading Do While Ex.InnerException IsNot Nothing Ex = Ex.InnerException Loop - LabText.Text = StrTrim(Ex.Message) - If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", - "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then - LabText.Text = "网络环境不佳,请稍后重试,或使用 VPN 以改善网络环境" - End If + LabText.Text = If(Ex.IsNetworkRelated(), "网络环境不佳,请稍后再试,或使用 VPN 改善网络环境", StrTrim(Ex.Message)) End If Else LabText.Text = TextError diff --git a/Plain Craft Launcher 2/Controls/MyPageLeft.vb b/Plain Craft Launcher 2/Controls/MyPageLeft.vb index 1160ee9..afc047f 100644 --- a/Plain Craft Launcher 2/Controls/MyPageLeft.vb +++ b/Plain Craft Launcher 2/Controls/MyPageLeft.vb @@ -3,28 +3,15 @@ Private Uuid As Integer = GetUuid() '执行逐个进入动画的控件 - Public AnimatedControl As FrameworkElement - Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.RegisterAttached("AnimatedControl", GetType(String), GetType(MyPageLeft), New PropertyMetadata(Nothing, AddressOf OnAnimatedControlChanged)) - - Public Shared Sub SetAnimatedControl(obj As DependencyObject, value As String) - obj.SetValue(AnimatedControlProperty, value) - End Sub - - Private Shared Sub OnAnimatedControlChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) - Dim control = TryCast(d, MyPageLeft) - If control IsNot Nothing Then - Dim handler As RoutedEventHandler - handler = Sub(sender, args) - Dim targetName = CStr(e.NewValue) - Dim target = TryCast(control.FindName(targetName), FrameworkElement) - If target IsNot Nothing Then - control.AnimatedControl = target - End If - RemoveHandler control.Loaded, handler - End Sub - AddHandler control.Loaded, handler - End If - End Sub + Public Property AnimatedControl As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664) + Get + Return GetValue(AnimatedControlProperty) + End Get + Set(value As String) + SetValue(AnimatedControlProperty, value) + End Set + End Property + Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(String), GetType(MyPageLeft), New PropertyMetadata(Nothing)) Public Sub TriggerShowAnimation() If AnimatedControl Is Nothing Then @@ -97,7 +84,7 @@ '遍历获取所有需要生成动画的控件 Private Function GetAllAnimControls(Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement) Dim AllControls As New List(Of FrameworkElement) - GetAllAnimControls(AnimatedControl, AllControls, IgnoreInvisibility) + GetAllAnimControls(FindName(AnimatedControl), AllControls, IgnoreInvisibility) Return AllControls End Function Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean) @@ -118,7 +105,3 @@ End Sub End Class - -Public Interface IRefreshable - Sub Refresh() -End Interface \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyPageRight.vb b/Plain Craft Launcher 2/Controls/MyPageRight.vb index 52df5a3..151ad44 100644 --- a/Plain Craft Launcher 2/Controls/MyPageRight.vb +++ b/Plain Craft Launcher 2/Controls/MyPageRight.vb @@ -3,29 +3,16 @@ Public PageUuid As Integer = GetUuid() '“返回顶部” 按钮检测的滚动区域 - Public PanScroll As MyScrollViewer + Public Property PanScroll As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664) + Get + Return GetValue(PanScrollProperty) + End Get + Set(value As String) + SetValue(PanScrollProperty, value) + End Set + End Property Private Shared ReadOnly PanScrollProperty = - DependencyProperty.RegisterAttached("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing, AddressOf OnPanScrollChanged)) - - Public Shared Sub SetPanScroll(obj As DependencyObject, value As String) - obj.SetValue(PanScrollProperty, value) - End Sub - - Private Shared Sub OnPanScrollChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) - Dim control = TryCast(d, MyPageRight) - If control IsNot Nothing Then - Dim handler As RoutedEventHandler - handler = Sub(sender, args) - Dim targetName = CStr(e.NewValue) - Dim target = TryCast(control.FindName(targetName), FrameworkElement) - If target IsNot Nothing Then - control.PanScroll = target - End If - RemoveHandler control.Loaded, handler - End Sub - AddHandler control.Loaded, handler - End If - End Sub + DependencyProperty.Register("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing)) '当前状态 Public Enum PageStates diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index f1befdc..270da2e 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -96,6 +96,14 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- + If LastVersion < 368 Then 'Snapshot 2.10.7 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "新增:允许在版本设置中设置禁止更新 Mod,以防整合包玩家误操作")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:若整合包需要 PCL 不兼容的加载器,允许选择跳过它的安装")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "删除:由于已不再需要,删除手动安装包下载功能")) + FeatureCount += 19 + BugCount += 28 + End If 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, "修复:正版登录出错时无法给出正确的错误信息")) @@ -1025,18 +1033,12 @@ Public Class FormMain ''' Public Enum PageSubType [Default] = 0 - DownloadInstall = 1 - DownloadClient = 4 - DownloadOptiFine = 5 - DownloadForge = 6 - DownloadNeoForge = 7 - DownloadFabric = 8 - DownloadLiteLoader = 9 - DownloadMod = 11 - DownloadPack = 12 - DownloadDataPack = 13 - DownloadResourcePack = 14 - DownloadShader = 15 + DownloadInstall = 0 + DownloadMod = 2 + DownloadPack = 3 + DownloadDataPack = 4 + DownloadResourcePack = 5 + DownloadShader = 6 SetupLaunch = 0 SetupUI = 1 SetupSystem = 2 @@ -1514,7 +1516,8 @@ Public Class FormMain End Function Private Function BtnExtraBack_GetRealChild() As MyScrollViewer If PanMainRight.Child Is Nothing OrElse TypeOf PanMainRight.Child IsNot MyPageRight Then Return Nothing - Return CType(PanMainRight.Child, MyPageRight).PanScroll + Dim Page As MyPageRight = PanMainRight.Child + Return Page.FindName(Page.PanScroll) End Function #End Region diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index b4ef294..ad918bf 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -13,13 +13,13 @@ Public Module ModBase #Region "声明" '下列版本信息由更新器自动修改 - Public Const VersionBaseName As String = "2.10.6" '不含分支前缀的显示用版本名 - Public Const VersionStandardCode As String = "2.10.6." & VersionBranchCode '标准格式的四段式版本号 + Public Const VersionBaseName As String = "2.10.7" '不含分支前缀的显示用版本名 + Public Const VersionStandardCode As String = "2.10.7." & VersionBranchCode '标准格式的四段式版本号 Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换 #If BETA Then Public Const VersionCode As Integer = 367 'Release #Else - Public Const VersionCode As Integer = 366 'Snapshot + Public Const VersionCode As Integer = 368 'Snapshot #End If '自动生成的版本信息 Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName @@ -1283,7 +1283,7 @@ Re: Return Nothing Catch ex As Exception Log(ex, "检查文件出错") - Return GetExceptionSummary(ex) + Return ex.GetBrief() End Try End Function End Class @@ -1329,6 +1329,14 @@ Re: ''' Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer If Not Directory.Exists(Path) Then Return 0 + If Not Path.EndsWithF("\") Then Path &= "\" + If Path.EndsWith(":\") OrElse + Path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\" OrElse + Path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) & "\" OrElse + Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\" Then '#7030 + Throw New UnauthorizedAccessException("这是重要的系统文件夹,无法删除:" & Path) + End If + '实际删除 Dim DeletedCount As Integer = 0 Dim Files As String() Try @@ -1364,6 +1372,7 @@ RetryFile: RetryDir: Try Directory.Delete(Path, True) + Catch ex As DirectoryNotFoundException Catch ex As Exception If Not RetriedDir AndAlso Not RunInUi() Then RetriedDir = True @@ -1420,10 +1429,10 @@ RetryDir: Public vbRQ As Char = Convert.ToChar(8221) ''' - ''' 提取 Exception 的具体描述与堆栈。 + ''' 提取 Exception 与 Inner Exception 的详细描述与堆栈信息。返回内容总是多于一行。 ''' ''' 是否必须显示所有堆栈。通常用于判定堆栈信息。 - Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String + Public Function GetDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String If Ex Is Nothing Then Return "无可用错误信息!" '获取最底层的异常 @@ -1450,17 +1459,17 @@ RetryDir: Loop '构造输出信息 - Dim CommonReason As String = AnalyzeCommonReason(InnerEx, DescList) - If CommonReason Is Nothing Then + Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList) + If UsualReason Is Nothing Then Return DescList.Join(vbCrLf) Else - Return CommonReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf) + Return UsualReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf) End If End Function ''' - ''' 提取 Exception 描述,汇总到一行。 + ''' 提取 Exception 与 Inner Exception 的描述,汇总到一行。 ''' - Public Function GetExceptionSummary(Ex As Exception) As String + Public Function GetBrief(Ex As Exception) As String If Ex Is Nothing Then Return "无可用错误信息!" '获取最底层的异常 @@ -1479,28 +1488,27 @@ RetryDir: Dim Desc As String = Join(DescList, vbCrLf & "→ ") '构造输出信息 - Dim CommonReason As String = AnalyzeCommonReason(InnerEx, DescList) - If CommonReason IsNot Nothing Then - Return CommonReason & "详细错误:" & DescList.First + Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList) + If UsualReason IsNot Nothing Then + Return UsualReason & "详细错误:" & DescList.First Else DescList.Reverse() '让最深层错误在最左边 Return Join(DescList, " ← ") End If End Function - Private Function AnalyzeCommonReason(InnerEx As Exception, DescList As List(Of String)) As String + Private Function AnalyzeUsualReason(InnerEx As Exception, OuterEx 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,选择以管理员身份运行。" + Return "PCL 的权限不足。请尝试右键 PCL 选择以管理员身份运行,或将目标从 C 盘或桌面挪到其他硬盘。" 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 以改善网络环境。" + ElseIf OuterEx.IsNetworkRelated() Then + Return "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。" Else Return Nothing End If @@ -2981,10 +2989,10 @@ Retry: If TypeOf Ex Is ThreadInterruptedException Then Return '获取错误信息 - Dim ExFull As String = Desc & ":" & GetExceptionDetail(Ex) + Dim ExFull As String = $"{Desc}:{Ex.GetDetail()}" '输出日志 - Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{GetExceptionDetail(Ex, True)}{vbCrLf}" '减轻同步锁占用 + Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{Ex.GetDetail(True)}{vbCrLf}" '减轻同步锁占用 If ModeDebug Then SyncLock LogListLock LogList.Append(AppendText) @@ -3002,19 +3010,19 @@ Retry: Case LogLevel.Normal #If DEBUG Then Case LogLevel.Developer - Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex) + Dim ExLine As String = Desc & ":" & Ex.GetBrief() Hint("[开发者模式] " & ExLine, HintType.Info, False) Case LogLevel.Debug - Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex) + Dim ExLine As String = Desc & ":" & Ex.GetBrief() Hint("[调试模式] " & ExLine, HintType.Info, False) #Else Case LogLevel.Developer Case LogLevel.Debug - Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex) + Dim ExLine As String = Desc & ":" & Ex.GetBrief() If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False) #End If Case LogLevel.Hint - Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex) + Dim ExLine As String = Desc & ":" & Ex.GetBrief() Hint(ExLine, HintType.Critical, False) Case LogLevel.Msgbox MyMsgBox(ExFull, Title, IsWarn:=True) @@ -3093,8 +3101,8 @@ Retry: Url &= "&" & WebUtility.UrlEncode(Datas(i)) & "=" & WebUtility.UrlEncode(Datas(i + 1).Replace(vbCrLf, vbLf)) Next NetRequestByClient(Url, MakeLog:=False) - Catch - Log("[System] 匿名数据上报失败", LogLevel.Debug) + Catch ex As Exception + Log(ex, "匿名数据上报失败") 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 4719587..88a5d09 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb @@ -290,11 +290,12 @@ '检验输入以确定情况 If IsForceRestart Then Return True '强制要求重启 If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同 - If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束 - (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '没有超时 - Return False '则不重试 + If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束… + (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse + LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '…且没有超时… + Return False '…则不重试 Else - Return True '需要开始 + Return True '否则需要重启 End If End Function Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False) diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb index 6539356..dc065af 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb @@ -1,4 +1,7 @@ Imports System.Net.Http.Headers +Imports System.Net.Sockets +Imports System.Runtime.CompilerServices +Imports System.Security.Policy Imports System.Threading.Tasks Imports CacheCow.Client @@ -11,15 +14,28 @@ Public Module ModNet ''' 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 + Optional Timeout As Integer = 25000, Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False, + Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders 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 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)) + Dim Result = If(Encoding, Encoding.UTF8).GetString(SendRequest(Url, Method, Content, HeaderDictionary, Timeout:=Timeout, Encoding:=Encoding, SimulateBrowserHeaders:=SimulateBrowserHeaders)) + '检查结果是否为完整 JSON(在 #6683 中可能返回空,在被 GFW 截断时可能不完整) + '在此处检查并报错,可以让 NetRequestByClientRetry 等方法进行重试 + If RequireJson Then + Result = Result.Trim((vbCrLf & " ").ToCharArray) 'Authlib-Injector 的结果会在末尾附带一个空行 + If Not (Result.StartsWithF("{") AndAlso Result.EndsWithF("}")) AndAlso + Not (Result.StartsWithF("[") AndAlso Result.EndsWithF("]")) Then + Throw New FormatException("返回结果并非 JSON 格式:" & + If(Result.Length > 2000, Result.Substring(0, 1000) & "..." & Result.Substring(Result.Length - 1000), Result)) + End If + End If + Return Result + Catch ex As FormatException + Throw Catch ex As ThreadInterruptedException Throw Catch ex As Exception @@ -34,8 +50,8 @@ Public Module ModNet ''' 如果第一次尝试失败,换用的备用 URL。 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 + Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False, + Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False) As String Dim RetryCount As Integer = 0 Dim RetryException As Exception = Nothing Dim StartTime As Long = GetTimeTick() @@ -43,15 +59,15 @@ Public Module ModNet Retry: Select Case RetryCount Case 0 '正常尝试 - Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 10000, Headers, Encoding, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 10000, Headers, RequireJson, Encoding, SimulateBrowserHeaders) Case 1 '慢速重试 Thread.Sleep(500) - Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 30000, Headers, Encoding, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 30000, Headers, RequireJson, Encoding, SimulateBrowserHeaders) Case Else '快速重试 If GetTimeTick() - StartTime > 5500 Then '若前两次加载耗费 5 秒以上,才进行重试 Thread.Sleep(500) - Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 4000, Headers, Encoding, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 4000, Headers, RequireJson, Encoding, SimulateBrowserHeaders) Else Throw RetryException End If @@ -78,8 +94,8 @@ Retry: ''' 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 + Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False, Optional Timeout As Integer = 25000, + Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False) As String Dim Threads As New List(Of Thread) Dim RequestResult = Nothing Dim RequestEx As Exception = Nothing @@ -88,7 +104,7 @@ Retry: Dim th As New Thread( Sub() Try - RequestResult = NetRequestByClient(Url, Method, Content, ContentType, Accept, Timeout, Headers, Encoding, UseBrowserUserAgent) + RequestResult = NetRequestByClient(Url, Method, Content, ContentType, Accept, Timeout, Headers, RequireJson, Encoding, SimulateBrowserHeaders) Catch ex As Exception FailCount += 1 RequestEx = ex @@ -127,16 +143,16 @@ RequestFinished: ''' 以多线程下载网页文件的方式获取内容。 ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。 ''' - 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) + Public Function NetRequestByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional SimulateBrowserHeaders As Boolean = False) As String + Return NetRequestByLoader({Url}, Timeout, IsJson, SimulateBrowserHeaders) End Function ''' ''' 以多线程下载网页文件的方式获取内容。 ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。 ''' - 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 + Public Function NetRequestByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional SimulateBrowserHeaders 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)}) + Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson}, SimulateBrowserHeaders)}) Try NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(第一下载源:" & Urls.First & ")") NetRequestByLoader = ReadFile(Temp) @@ -154,7 +170,7 @@ RequestFinished: ''' 通过网络请求直接下载小文件,若文件已存在将被覆盖。 ''' 不建议用于下载大文件。 ''' - Public Sub NetDownloadByClient(Url As String, LocalPath As String) + Public Sub NetDownloadByClient(Url As String, LocalPath As String, Optional SimulateBrowserHeaders As Boolean = False) Log($"[Net] 通过网络请求直接下载小文件:{Url} → {LocalPath}") '初始化文件路径 Try @@ -165,7 +181,7 @@ RequestFinished: End Try '下载 Try - File.WriteAllBytes(LocalPath, SendRequest(Url, HttpMethod.Get)) + File.WriteAllBytes(LocalPath, SendRequest(Url, HttpMethod.Get, SimulateBrowserHeaders:=SimulateBrowserHeaders)) Catch ex As Exception File.Delete(LocalPath) Throw New WebException($"通过网络请求直接下载小文件失败({LocalPath})", ex) @@ -175,16 +191,16 @@ RequestFinished: ''' ''' 通过多线程下载引擎下载文件。 ''' - 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) + Public Sub NetDownloadByLoader(Url As String, LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False) + NetDownloadByLoader({Url}, LocalPath, LoaderToSyncProgress, Check, SimulateBrowserHeaders) End Sub ''' ''' 通过多线程下载引擎下载文件。 ''' ''' 文件的 Url 列表。 ''' 下载的本地地址。 - 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)}) + Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False) + Dim NewTask As New LoaderDownload($"文件下载 {GetUuid()}#", New List(Of NetFile) From {New NetFile(Urls, LocalPath, Check, SimulateBrowserHeaders)}) Try NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress) Catch ex As Exception @@ -201,36 +217,37 @@ RequestFinished: '基础请求函数 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() + Optional SimulateBrowserHeaders 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 线程执行了网络请求") - Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing, Response As HttpResponseMessage = Nothing + Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing, + Response As HttpResponseMessage = Nothing, HostIp As String = Nothing Try - 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 + If TypeOf Content Is HttpContent Then + Request.Content = DirectCast(Content, HttpContent) + ElseIf TypeOf Content Is Byte() Then + Request.Content = New ByteArrayContent(DirectCast(Content, Byte())) + ElseIf Content IsNot Nothing Then + Request.Content = New ByteArrayContent(If(Encoding, Encoding.UTF8).GetBytes(Content.ToString())) + End If End If '写入 Headers - If Headers IsNot Nothing Then - 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 - SecretHeadersSign(Url, Request, UseBrowserUserAgent) - '发送请求 + For Each Header In If(Headers, New Dictionary(Of String, String)) + 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 + SecretHeadersSign(Url, Request, SimulateBrowserHeaders) + 'DNS 解析 CancelToken = New CancellationTokenSource(Timeout) - Response = Client.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult() + HostIp = DnsLookup(Request, CancelToken) + '发送请求 + Response = RequestClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult() Dim ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult() Dim ResponseBytes As Byte() Using Stream As New MemoryStream @@ -239,28 +256,111 @@ RequestFinished: End Using '输出 If Response.IsSuccessStatusCode Then + RecordIPReliability(HostIp, 0.5) Return ResponseBytes Else + RecordIPReliability(HostIp, -0.7) Dim ResponseMessage = If(Encoding, Encoding.UTF8).GetString(ResponseBytes) Throw New ResponsedWebException( - $"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url}" & + $"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url},{HostIp}" & If(String.IsNullOrEmpty(ResponseMessage), "", vbCrLf & ResponseMessage), ResponseMessage) End If Catch ex As ThreadInterruptedException Throw Catch ex As ResponsedWebException Throw - Catch ex As TaskCanceledException 'CancellationTokenSource 超时 - Throw New WebException($"连接服务器超时,请检查你的网络环境是否良好({Method}, {Url})", WebExceptionStatus.Timeout) + Catch ex As DnsLookupException + Throw Catch ex As Exception - Throw New Exception($"网络请求出现意外异常({Method}, {Url})", ex) + RecordIPReliability(HostIp, -1) + If TypeOf ex Is OperationCanceledException Then 'CancellationToken 超时 + Throw New WebException($"连接服务器超时,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url},{HostIp})", WebExceptionStatus.Timeout) + Else + Throw New Exception($"网络请求出现意外异常({Method}, {Url},{HostIp})", ex) + End If Finally Request?.Dispose() CancelToken?.Dispose() Response?.Dispose() End Try End Function + Private RequestClient 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 + }) + ''' + ''' 进行 DNS 解析。它仅在选择的 IP 与系统默认的不一致时才对 URL 中的 Host 进行替换。 + ''' 返回请求时应使用的 IP;若为 IPv6,则加上了方括号。 + ''' + ''' + Private Function DnsLookup(Request As HttpRequestMessage, CancelToken As CancellationTokenSource) As String + Dim GetIPReliability = Function(Key) IPReliability.GetOrDefault(Key.ToString, 0) + '初步 DNS 解析 + Dim Host = Request.RequestUri.Host + Dim DnsTask As Task(Of IPAddress()) = Nothing + Try + DnsTask = Dns.GetHostAddressesAsync(Host) + DnsTask.Wait(CancelToken.Token) + Catch ex As Exception + Throw New DnsLookupException($"DNS 解析失败,请检查你的网络连接({Host})", ex) + End Try + Dim Candidates As IPAddress() = DnsTask.Result.ToArray + If Not Candidates.Any Then Throw New DnsLookupException($"DNS 解析无结果,请检查你的网络连接({Host})") + '若同时存在 IPv4 和 IPv6 地址,仅选择其中一类(因为 GFW 可能只屏蔽了 IPv4 或 IPv6) + Dim IPv4Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetwork).ToArray + Dim IPv6Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetworkV6).ToArray + If IPv4Targets.Any AndAlso IPv6Targets.Any Then + Dim IPv4Reliability = IPv4Targets.Average(Function(i) GetIPReliability(i)) + Dim IPv6Reliability = IPv6Targets.Average(Function(i) GetIPReliability(i)) + If Host = "api.modrinth.com" Then IPv6Reliability -= 0.1 '让 Modrinth 优先使用 IPv4 地址(#6887) + Candidates = If(IPv4Reliability >= IPv6Reliability, IPv4Targets, IPv6Targets) + End If + '选择可靠度最高的 IP + Dim MaxReliability As Double = Candidates.Max(Function(i) GetIPReliability(i)) + Dim Target As IPAddress = Candidates.First(Function(i) GetIPReliability(i) = MaxReliability) + Dim TargetIp As String = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString) + If Target IsNot Candidates.First Then + '优选结果与系统默认 IP 不一致,替换域名并设置 Host 头 + If ModeDebug Then Log($"[Net] DNS 结果:{Host} → {Target},所有可能的 IP 与可靠度:{DnsTask.Result.Select(Function(i) $"{i} → {GetIPReliability(i):0.000}").Join(";")}") + Request.Headers.Host = Host + Dim Builder As New UriBuilder(Request.RequestUri) + Builder.Host = TargetIp + Request.RequestUri = Builder.Uri + End If + If Not IPReliability.ContainsKey(TargetIp) Then IPReliability(TargetIp) = -0.01 '预先降低一点,这样快速的重复请求会使用不同的 IP 以提高成功率 + Return TargetIp + End Function + ''' + ''' 记录每个 IP 地址的请求可靠度。 + ''' 通常取值 -1 ~ +0.5,越高越好。未尝试过的 IP 应视为 0。 + ''' + Private IPReliability As New Dictionary(Of String, Double) + ''' + ''' 根据请求结果,记录 IP 地址的可靠度。 + ''' + Private Sub RecordIPReliability(IP As String, Result As Double) + If IP Is Nothing Then Return + If Not IPReliability.ContainsKey(IP) Then IPReliability(IP) = 0 + IPReliability(IP) = (IPReliability(IP) + Result) / 2 + End Sub + + ''' + ''' DNS 解析失败引发的异常。 + ''' + Private Class DnsLookupException + Inherits Exception + Public Sub New(Message As String, Optional InnerException As Exception = Nothing) + MyBase.New(Message, InnerException) + End Sub + Public Sub New(Message As String) + MyBase.New(Message) + End Sub + End Class + ''' + ''' 当网络请求失败时引发的异常。 + ''' 附带 Response 属性,可用于获取远程服务器给予的回复。 + ''' Public Class ResponsedWebException Inherits WebException ''' @@ -277,6 +377,10 @@ RequestFinished: #Region "多线程下载引擎" + Private ThreadClient As New HttpClient(New HttpClientHandler With { + .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None + }) + ''' ''' 最大线程数。 ''' @@ -581,12 +685,12 @@ RequestFinished: End SyncLock End Function ''' - ''' 是否已经没有可用源了。 + ''' 是否存在可用源。 ''' Public Function HasAvailableSource(Optional AllowOnceSource As Boolean = True) As Boolean SyncLock LockSource - If AllowOnceSource AndAlso SourcesOnce.Any Then Return True - If Sources.Any(Function(s) Not s.IsFailed) Then Return True + If Sources.Any(Function(s) Not s.IsFailed) Then Return True '存在多线程可用源 + If AllowOnceSource AndAlso SourcesOnce.Any Then Return True '存在单线程可用源 End SyncLock Return False End Function @@ -648,7 +752,7 @@ RequestFinished: ''' ''' 下载时是否添加浏览器 UA。 ''' - Public UseBrowserUserAgent As Boolean + Public SimulateBrowserHeaders As Boolean ''' ''' 上次记速时的时间。 @@ -746,7 +850,7 @@ RequestFinished: ''' 新建一个需要下载的文件。 ''' ''' 包含文件名的本地地址。 - Public Sub New(Urls As IEnumerable(Of String), LocalPath As String, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) + Public Sub New(Urls As IEnumerable(Of String), LocalPath As String, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False) Dim Sources As New List(Of NetSource) Dim Count As Integer = 0 Urls = Urls.Distinct.ToArray @@ -757,7 +861,7 @@ RequestFinished: Me.Sources = Sources Me.LocalPath = LocalPath Me.Check = Check - Me.UseBrowserUserAgent = UseBrowserUserAgent + Me.SimulateBrowserHeaders = SimulateBrowserHeaders Me.LocalName = GetFileNameFromPath(LocalPath) End Sub @@ -768,17 +872,17 @@ RequestFinished: Public Function TryBeginThread() As NetThread Try - '条件检测 - 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 - If State < NetState.Connect Then State = NetState.Connect - End SyncLock - '初始化参数 Dim StartPosition As Long, StartSource As NetSource = Nothing Dim Th As Thread, ThreadInfo As NetThread - SyncLock LockChain + '条件检测 + SyncLock LockSource + 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 + If State < NetState.Connect Then State = NetState.Connect + End SyncLock + '初始化参数 '获取线程起点与下载源 '不分割 If IsNoSplit Then GoTo StartSingleThreadDownload @@ -789,6 +893,8 @@ StartSingleThreadDownload: Reset() State = NetState.Get End If + End SyncLock + SyncLock LockChain '首个开始点 If Threads Is Nothing Then StartPosition = 0 @@ -805,7 +911,9 @@ StartSingleThreadDownload: End If Next '是否禁用多线程,以及规定碎片大小 - Dim TargetUrl As String = GetSource().Url + Dim Source As NetSource = GetSource() + If Source Is Nothing Then Return Nothing + Dim TargetUrl As String = Source.Url If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("bmclapi") OrElse TargetUrl.Contains("github.com") OrElse TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing '寻找最大碎片 @@ -850,7 +958,7 @@ StartThread: Return ThreadInfo Catch ex As Exception - Log(ex, "尝试开始下载线程失败(" & If(LocalName, "Nothing") & ")", LogLevel.Hint) + Fail(New Exception($"尝试开始下载线程失败({LocalName})", ex)) Return Nothing End Try End Function @@ -859,7 +967,9 @@ StartThread: ''' 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 + Dim ResultStream As Stream = Nothing, HttpRequest As HttpRequestMessage = Nothing, + Response As HttpResponseMessage = Nothing, ResponseStream As Stream = Nothing, + CancelToken As CancellationTokenSource = Nothing, HostIp As String = "" '部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟 '延迟过长会导致某些特别慢的链接迟迟不被掐死 Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Th.Source.FailCount), 30000) @@ -867,145 +977,140 @@ StartThread: Try Dim HttpDataCount As Integer = 0 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) + HttpRequest = New HttpRequestMessage(HttpMethod.Get, Th.Source.Url) + SecretHeadersSign(Th.Source.Url, HttpRequest, SimulateBrowserHeaders) + CancelToken = New CancellationTokenSource(Timeout) + HostIp = DnsLookup(HttpRequest, CancelToken) 'DNS 预解析 If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New RangeHeaderValue(Th.DownloadStart, Nothing) Dim ContentLength As Long = 0 - 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 - Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持") - GoTo NotSupportRange - End If - Else - FileSize = -1 : IsUnknownSize = True - Log($"[Download] {LocalName}:文件大小未知") - End If - 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 + Response = ThreadClient.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 - Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp" - ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read) + Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持") + GoTo NotSupportRange 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 + Else + FileSize = -1 : IsUnknownSize = True + Log($"[Download] {LocalName}:文件大小未知") + End If + 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 + '开始下载 + 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 Th.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While + '检查速度是否过慢 + If DeltaTime > 5000 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 5s,且速度小于 1K/s + Th.Source.SingleThread Is Nothing Then '且并非单线程下载 + Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。") + End If + Th.LastReceiveTime = GetTimeTick() + ElseIf Th.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then + '无数据,且已超时 + Throw New TimeoutException("操作超时,无数据。") + End If + HttpDataCount = ResponseStream.Read(ResponseBytes, 0, 16384) + End While SourceBreak: If State = NetState.Error OrElse (Th.Source.IsFailed AndAlso Th.Source.SingleThread <> Th) OrElse (Th.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then '被外部中断 @@ -1018,16 +1123,24 @@ SourceBreak: '本线程完成 Th.State = NetState.Finish If ModeDebug Then Log($"[Download] {LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})") + RecordIPReliability(HostIp, 0.5) End If Catch ex As Exception - Log($"[Download] {LocalName}:出错,{If(TypeOf ex Is TaskCanceledException, $"已超时({Timeout}ms)", GetExceptionDetail(ex))}") + Log($"[Download] {LocalName}:出错,{If(TypeOf ex Is OperationCanceledException, $"已超时({Timeout}ms)", ex.GetDetail())},{HostIp}") + RecordIPReliability(HostIp, -0.7) SourceFail(Th, ex, False) Finally + '释放资源 + HttpRequest?.Dispose() + Response?.Dispose() + ResponseStream?.Dispose() + CancelToken?.Dispose() + If Not IsNoSplit Then ResultStream?.Dispose() + '改变计数 SyncLock NetTaskThreadCountLock NetTaskThreadCount -= 1 End SyncLock '合并 - 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 @@ -1046,7 +1159,7 @@ SourceBreak: 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,所以不应因此禁用下载源 + ((ex.Message.Contains("(403)") OrElse ex.Message.Contains("(429)")) AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403/429,所以不应因此禁用下载源 (Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then '当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理 Dim IsThisFail As Boolean = False @@ -1140,10 +1253,10 @@ SourceBreak: Dim RetryCount As Integer = 0 Try Retry: + '创建文件夹 + If File.Exists(LocalPath) Then File.Delete(LocalPath) + Directory.CreateDirectory(GetPathFromFullPath(LocalPath)) SyncLock LockChain - '创建文件夹 - If File.Exists(LocalPath) Then File.Delete(LocalPath) - Directory.CreateDirectory(GetPathFromFullPath(LocalPath)) '合并文件 If IsNoSplit Then '仅有一个线程,从缓存中输出 @@ -1386,13 +1499,13 @@ NextElement: If File Is Nothing Then Throw New ArgumentException("存在空文件请求!") For Each Source As NetSource In File.Sources If Not (Source.Url.StartsWithF("https://", True) OrElse Source.Url.StartsWithF("http://", True)) Then - Source.Ex = New ArgumentException("输入的下载链接不正确!") + Source.Ex = New ArgumentException("输入的下载链接不正确:" & Source.Url) Source.IsFailed = True End If Next 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("请输入含文件名的完整文件路径!") + If Not File.LocalPath.ToLower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath) + If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径:" & File.LocalPath) '文件夹检测 Dim DirPath As String = New FileInfo(File.LocalPath).Directory.FullName If Not Directory.Exists(DirPath) Then Directory.CreateDirectory(DirPath) @@ -1522,12 +1635,12 @@ Retry: If State > LoadState.Loading Then Return If ExList Is Nothing OrElse Not ExList.Any() Then ExList = New List(Of Exception) From {New Exception("未知错误!")} '寻找有效的错误信息 - Dim UsefulExs = ExList.Where(Function(e) TypeOf e IsNot TaskCanceledException AndAlso TypeOf e IsNot ThreadInterruptedException).ToList + Dim UsefulExs = ExList.Where(Function(e) TypeOf e IsNot OperationCanceledException AndAlso TypeOf e IsNot ThreadInterruptedException).ToList [Error] = If(UsefulExs.FirstOrDefault, ExList.FirstOrDefault) '获取实际失败的文件 For Each File In Files 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) + Dim Detail As String = Join(File.Sources.Select(Function(s) $"{If(s.Ex Is Nothing, "无错误信息。", s.Ex.GetBrief())}({s.Url})"), vbCrLf) [Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf & "各下载源的错误如下:" & vbCrLf & Detail, [Error]) @@ -1547,7 +1660,7 @@ Retry: '在退出同步锁后再进行日志输出 Dim ErrOutput As New List(Of String) For Each Ex As Exception In ExList - ErrOutput.Add(GetExceptionDetail(Ex)) + ErrOutput.Add(Ex.GetDetail()) Next Log("[Download] " & Join(ErrOutput.Distinct.ToArray, vbCrLf)) End Sub @@ -1697,7 +1810,7 @@ Retry: For Each File As NetFile In WaitingFiles If NetTaskThreadCount >= NetTaskThreadLimit Then Continue While '最大线程数检查 Dim NewThread = File.TryBeginThread() - If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) + If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(40) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) Next '为进行中的文件追加线程 If Speed >= NetTaskSpeedLimitLow Then Continue While '下载速度足够,无需新增 @@ -1717,7 +1830,7 @@ Retry: '新增线程 If PreparingCount > DownloadingCount Then Continue For '准备中的线程已多于下载中的线程,不再新增 Dim NewThread = File.TryBeginThread() - If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) + If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(40) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) Next End While Catch ex As Exception @@ -1842,4 +1955,15 @@ Retry: End If End Function + ''' + ''' 判断某个 Exception 是否为网络问题所导致。 + ''' + Public Function IsNetworkRelated(Ex As Exception) As Boolean + If TypeOf Ex Is DnsLookupException Then Return True + Dim Detail = Ex.GetDetail() + If Detail.Contains("(403)") Then Return False + Return {"(408)", "超时", "timeout", "网络请求失败", "连接尝试失败", "远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝"}. + Any(Function(k) Detail.ContainsF(k, True)) + End Function + End Module diff --git a/Plain Craft Launcher 2/Modules/Base/ModValidate.vb b/Plain Craft Launcher 2/Modules/Base/ModValidate.vb index 55eaff9..b8181e8 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModValidate.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModValidate.vb @@ -191,7 +191,7 @@ Public Class ValidateExceptSame Me.ErrorMessage = ErrorMessage Me.IgnoreCase = IgnoreCase End Sub - Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False) + Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False) Me.Excepts = New ObjectModel.Collection(Of String) For Each Data As String In Excepts Me.Excepts.Add(Data) @@ -199,6 +199,11 @@ Public Class ValidateExceptSame Me.ErrorMessage = ErrorMessage Me.IgnoreCase = IgnoreCase End Sub + Public Sub New(Except As String, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False) + Me.Excepts = New ObjectModel.Collection(Of String) From {Except} + Me.ErrorMessage = ErrorMessage + Me.IgnoreCase = IgnoreCase + End Sub Public Overrides Function Validate(Str As String) As String If Str Is Nothing Then Return ErrorMessage.Replace("%", "null") For Each Ch As String In Excepts diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb index c62beb9..d7f0dc8 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb @@ -347,7 +347,7 @@ Case 4475 : Tags.Add("冒险") Case 4476 : Tags.Add("探索") Case 4477 : Tags.Add("小游戏") - Case 4471 : Tags.Add("科幻") + Case 4474 : Tags.Add("科幻") Case 4736 : Tags.Add("空岛") Case 5128 : Tags.Add("原版改良") Case 4487 : Tags.Add("FTB") @@ -1161,9 +1161,9 @@ Retry: ElseIf [Error] IsNot Nothing Then '有结果但是有错误 If CurseForgeFailed Then - Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,搜索结果可能不全。{vbCrLf}请稍后重试,或使用 VPN 以改善网络环境。" + Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,搜索结果可能不全。{vbCrLf}请稍后再试,或使用 VPN 改善网络环境。" Else - Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。{vbCrLf}请稍后重试,或使用 VPN 以改善网络环境。" + Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。{vbCrLf}请稍后再试,或使用 VPN 改善网络环境。" End If End If @@ -1354,7 +1354,7 @@ Retry: ''' ''' 目标本地文件夹,或完整的文件路径。会自动判断类型。 Public Function ToNetFile(LocalAddress As String) As NetFile - Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), UseBrowserUserAgent:=True) + Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), SimulateBrowserHeaders:=True) End Function '实例化 @@ -1395,10 +1395,9 @@ Retry: If Hash Is Nothing Then Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value") '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, WebUtility.UrlEncode(FileName)) '对文件名进行编码 - DownloadUrls = HandleCurseForgeDownloadUrls(Url) '对脑残 CurseForge 的下载地址进行多种修正 - DownloadUrls.AddRange(DownloadUrls.Select(Function(u) DlSourceModGet(u)).ToList) '添加镜像源,这个写法是为了让镜像源排在后面 + If Url = "" Then Url = $"https://edge.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}" + DownloadUrls = HandleCurseForgeDownloadUrls(Url.Replace(FileName, WebUtility.UrlEncode(FileName))) '对脑残 CurseForge 的下载地址进行多种修正 + DownloadUrls.Add(DlSourceModGet(Url)) '添加镜像源;注意 MCIM 源不支持 URL 编码后的文件名,必须传入 URL 编码前的文件名 DownloadUrls = DownloadUrls.Distinct.ToList '最终去重 'Dependencies If Data.ContainsKey("dependencies") Then diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb index fc72957..0ce9566 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb @@ -485,7 +485,7 @@ 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)}) + 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)}) '#7071 End If '游戏日志分析 @@ -542,8 +542,8 @@ Done: If LogMc.Contains("Found a duplicate mod") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "Found a duplicate mod[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase)) If LogMc.Contains("Found duplicate mods") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=Mod ID: ')\w+?(?=' from mod files:)").Distinct.ToList) If LogMc.Contains("ModResolutionException: Duplicate") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "ModResolutionException: Duplicate[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase)) - If LogMc.Contains("Incompatible mods found!") Then '#5006 - AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), "")) + If LogMc.Contains("Incompatible mods found!") Then '#5006, #5115 + AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), "").BeforeFirst("更多信息:").Replace("Some of your mods are incompatible with the game or each other!", "").Trim((vbCrLf & " ").ToCharArray)) End If If LogMc.Contains("Missing or unsupported mandatory dependencies:") Then AppendReason(CrashReason.Mod缺少前置或MC版本错误, @@ -581,6 +581,7 @@ 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 @@ -658,7 +659,7 @@ Done: End If 'Mod 解析错误(常见于 Fabric 前置校验失败) If LogMc.Contains("Mod resolution failed") Then AppendReason(CrashReason.Mod加载器报错) - 'Mixin 失败可以导致大量 Mod 实例创建失败 + 'Mixin 失败可以导致大量 Mod 实例创建失败,因此需要放到低优先级中 If LogMc.Contains("Failed to create mod instance.") Then AppendReason(CrashReason.Mod初始化失败, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModID: )[^,]+"), If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModId )[^\n]+(?= for )"), "")).TrimEnd(vbCrLf))) '注意:Fabric 的 Warnings were found! 不一定是崩溃原因,它可能是单纯的警报 End If @@ -717,7 +718,7 @@ NextStack: "preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules", "service", "fastutil", "optifine", "internal", "platform", "override", "fabricmc", "neoforge", "injection", "listeners", "scheduler", "minecraft", "universal", "multipart", "neoforged", "microsoft", - "transformer", "transformers", "minecraftforge", "blockentity", "spongepowered", "electronwill" + "transformer", "transformers", "minecraftforge", "blockentity", "spongepowered", "electronwill", "concurrent" }.Contains(Word.ToLower) Then Continue For PossibleWords.Add(Word.Trim) Next @@ -949,7 +950,7 @@ NextStack: Dim Additional As List(Of String) = Reason.Value Select Case Reason.Key Case CrashReason.Java虚拟机参数有误 - Results.Add("由于 Java 虚拟机参数有误,导致游戏无法继续运行。\n请检查高级启动选项中设置的 Java 虚拟机参数是否有误。") + Results.Add("由于 Java 参数有误,导致游戏无法继续运行。\n请检查高级选项中的 Java 虚拟机参数和游戏参数设置是否有误。") Case CrashReason.Mod文件被解压 Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。") Case CrashReason.内存不足 @@ -1030,14 +1031,17 @@ NextStack: End If Case CrashReason.特定实体导致崩溃 If Additional.Count = 1 Then - 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") + If Additional.First.Contains("minecraft:player") Then + If Additional.First.Contains(" ") Then + Results.Add($"游戏因为位于 {Additional.First.AfterFirst(" ")} 的玩家实体导致了崩溃。\h") + Else + Results.Add($"游戏因为玩家实体导致了崩溃。\h") + End If Else - Results.Add("游戏似乎因为实体 " & entityInfo & " 出现了问题。\n\n请尝试以下解决方案:\n - 创建一个新世界,并尝试生成该类型的实体,观察游戏运行情况\n - 如果新世界中该实体正常,问题可能在于当前世界的数据损坏\n - 如果新世界中仍然崩溃,可能是某个 Mod 导致了实体处理异常\n - 尝试逐个禁用可能影响实体的 Mod(如生物类、AI 类 Mod)\n - 也可以尝试使用指令删除该坐标附近的实体\h") + Results.Add($"游戏因为实体 {Additional.First} 导致了崩溃。\h") End If Else - Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n请尝试以下解决方案:\n - 创建一个新世界,观察游戏的运行情况\n - 如果新世界正常运行,则是当前世界的实体数据导致出错\n - 可以尝试使用指令清理异常实体\n - 如果问题持续存在,可能是某个处理实体的 Mod 存在问题\n - 建议逐个禁用近期安装的 Mod,特别是涉及生物、AI 或实体处理的 Mod\h") + Results.Add($"游戏因为下列实体导致了崩溃:{Additional.Join("、")}\h") End If Case CrashReason.OptiFine与Forge不兼容 Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。") diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb index e6d517e..c2f766b 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 = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json")) + Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", RequireJson:=True)) 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 = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json")) + Dim Json As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", RequireJson:=True)) Try Dim Versions As JArray = Json("versions") If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")") @@ -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 = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList")) + Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList", RequireJson:=True)) 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 = NetRequestByClientRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding:=Encoding.Default, Accept:="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", SimulateBrowserHeaders:=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)") @@ -631,9 +631,9 @@ Try Result = NetRequestByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057 - ".html", UseBrowserUserAgent:=True) + ".html", SimulateBrowserHeaders:=True) Catch ex As Exception - If GetExceptionSummary(ex).Contains("(404)") Then + If ex.GetBrief().Contains("(404)") Then Throw New Exception("不可用") Else Throw @@ -701,7 +701,7 @@ ''' Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry))) Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" & - Loader.Input.Replace("-", "_"))) '兼容 Forge 1.7.10-pre4,#4057 + Loader.Input.Replace("-", "_"), RequireJson:=True)) '兼容 Forge 1.7.10-pre4,#4057 Dim Versions As New List(Of DlForgeVersionEntry) Try Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input) @@ -835,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 = 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") + Dim ResultLatest As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", RequireJson:=True) + Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", RequireJson:=True) If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")") '解析 Try @@ -853,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 = 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") + Dim ResultLatest As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", RequireJson:=True) + Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", RequireJson:=True) If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")") '解析 Try @@ -959,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 = GetJson(NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json")) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json", RequireJson:=True)) Try Dim Json As JObject = Result("versions") Dim Versions As New List(Of DlLiteLoaderListEntry) @@ -987,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 = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", RequireJson:=True)) Try Dim Json As JObject = Result("versions") Dim Versions As New List(Of DlLiteLoaderListEntry) @@ -1058,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 = GetJson(NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions")) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions", RequireJson:=True)) 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("获取到的列表缺乏必要项") @@ -1073,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 = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions")) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", RequireJson:=True)) 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("获取到的列表缺乏必要项") @@ -1130,7 +1130,7 @@ Dim Exs As String = "" For Each Source In Urls Try - Return GetJson(NetRequestByClient(Source.Key, Method, Content, ContentType, Timeout:=Source.Value * 1000, Encoding:=Encoding.UTF8)) + Return GetJson(NetRequestByClient(Source.Key, Method, Content, ContentType, Timeout:=Source.Value * 1000, Encoding:=Encoding.UTF8, RequireJson:=True)) Catch ex As Exception Exs += ex.Message + vbCrLf End Try diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb index b5ea00a..d85c884 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb @@ -10,7 +10,7 @@ '路径 ''' - ''' Java.exe 文件的完整路径。 + ''' java.exe 文件的完整路径。 ''' Public ReadOnly Property PathJava As String Get @@ -18,15 +18,7 @@ End Get End Property ''' - ''' Javaw.exe 文件的完整路径。 - ''' - Public ReadOnly Property PathJavaw As String - Get - Return PathFolder & "javaw.exe" - End Get - End Property - ''' - ''' Javaw.exe 文件所在文件夹的路径,以 \ 结尾。 + ''' java.exe 文件所在文件夹的路径,以 \ 结尾。 ''' Public PathFolder As String ''' @@ -102,20 +94,13 @@ Dim Output As String = Nothing Try '确定文件存在 - If Not File.Exists(PathJavaw) Then - Throw New FileNotFoundException("未找到 javaw.exe 文件", PathJavaw) - End If - If Not File.Exists(PathFolder & "java.exe") Then - Throw New FileNotFoundException("未找到 java.exe 文件", PathFolder & "java.exe") - End If - If File.Exists(PathFolder & "pdf-bookmark") Then - Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326 - End If + If Not File.Exists(PathJava) Then Throw New FileNotFoundException("未找到 java.exe 文件", PathJava) + If File.Exists(PathFolder & "pdf-bookmark") Then Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326 IsJre = Not File.Exists(PathFolder & "javac.exe") '运行 -version - Output = ShellAndGetOutput(PathFolder & "java.exe", "-version", 15000).ToLower + Output = ShellAndGetOutput(PathJava, "-version", 15000).ToLower If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败") - If ModeDebug Then Log("[Java] Java 检查输出:" & PathFolder & "java.exe" & vbCrLf & Output) + If ModeDebug Then Log("[Java] Java 检查输出:" & PathJava & 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 或系统存在问题") '获取详细信息 @@ -136,17 +121,17 @@ End If Is64Bit = Output.Contains("64-bit") If Version.Minor <= 4 OrElse Version.Minor >= 100 Then Throw New ApplicationException("分析详细信息失败,获取的版本为 " & Version.ToString) - '基于 #3649,在 64 位系统上禁用 32 位 Java + '#3649:在 64 位系统上禁用 32 位 Java If Not Is64Bit AndAlso Not Is32BitSystem Then Throw New Exception("该 Java 为 32 位版本,请安装 64 位的 Java") - '基于 #2249 发现 JRE 17 似乎也导致了 Forge 安装失败,干脆禁用更多版本的 JRE + '#2249:JRE 17 似乎会导致 Forge 安装失败,干脆禁用更多版本的 JRE If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!") Catch ex As ApplicationException Throw ex Catch ex As ThreadInterruptedException Throw ex Catch ex As Exception - Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出")) - Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex) + Log("[Java] 检查失败的 Java 输出:" & If(PathJava, "Nothing") & vbCrLf & If(Output, "无程序输出")) + Throw New Exception("检查 Java 失败(" & If(PathJava, "Nothing") & ")", ex) End Try IsChecked = True End Sub @@ -689,7 +674,7 @@ Wait: "应用", "运行", "前置", "mojang", "官启", "新建文件夹", "eclipse", "microsoft", "hotspot", "runtime", "x86", "x64", "forge", "原版", "optifine", "官方", "启动", "hmcl", "mod", "高清", "download", "launch", "程序", "path", "version", "baka", "pcl", "zulu", "local", "packages", - "4297127d64ec6", "1.", "启动", "jbr-"} + "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 '用于搜索的字符串 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb index 393355b..644ce97 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb @@ -178,7 +178,7 @@ NextInner: GoTo NextInner Else '没有特殊处理过的错误信息 - McLaunchLog("错误:" & GetExceptionDetail(ex)) + McLaunchLog("错误:" & ex.GetDetail()) Log(ex, If(IsSavingBatch, "导出启动脚本失败", "Minecraft 启动失败"), LogLevel.Msgbox, If(IsSavingBatch, "导出启动脚本失败", "启动失败")) @@ -187,7 +187,7 @@ NextInner: Telemetry("Minecraft 启动失败", "Version", McVersionCurrent.Version.ToString, "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType)), - "Exception", FilterUserName(FilterAccessToken(GetExceptionDetail(ex), "*"), "*")) + "Exception", FilterUserName(FilterAccessToken(ex.GetDetail(), "*"), "*")) End If Throw End If @@ -609,12 +609,8 @@ SkipLogin: McLoginRequestValidate(Data) GoTo LoginFinish Catch ex As Exception - Dim AllMessage = GetExceptionDetail(ex) - McLaunchLog("验证登录失败:" & AllMessage) - If (AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout")) AndAlso Not AllMessage.Contains("403") Then - McLaunchLog("已触发超时登录失败") - Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后重试,或使用加速器或 VPN 以改善网络环境。") - End If + Log(ex, "验证登录失败") + If IsNetworkRelated(ex) Then Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后再试,或使用加速器、VPN 改善网络环境。") End Try Data.Progress = 0.25 '尝试刷新登录 @@ -624,7 +620,7 @@ Refresh: McLoginRequestRefresh(Data, NeedRefresh) GoTo LoginFinish Catch ex As Exception - McLaunchLog("刷新登录失败:" & GetExceptionDetail(ex)) + McLaunchLog("刷新登录失败:" & ex.GetDetail()) If WasRefreshed Then Throw New Exception("二轮刷新登录失败", ex) End Try Data.Progress = If(NeedRefresh, 0.85, 0.45) @@ -634,7 +630,7 @@ Refresh: If Data.IsAborted Then Throw New ThreadInterruptedException NeedRefresh = McLoginRequestLogin(Data) Catch ex As Exception - McLaunchLog("登录失败:" & GetExceptionDetail(ex)) + McLaunchLog("登录失败:" & ex.GetDetail()) Throw End Try If NeedRefresh Then @@ -699,7 +695,7 @@ LoginFinish: '发送登录请求 Dim RequestData As New JObject( New JProperty("accessToken", AccessToken), New JProperty("clientToken", ClientToken), New JProperty("requestUser", True)) - NetRequestByClientRetry( + NetRequestByClient( Data.Input.BaseUrl & "/validate", HttpMethod.Post, Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None), Headers:={{"Accept-Language", "zh-CN"}}, @@ -726,7 +722,7 @@ LoginFinish: New JProperty("requestUser", True) ).ToString(Newtonsoft.Json.Formatting.None), Headers:={{"Accept-Language", "zh-CN"}}, - ContentType:="application/json; charset=utf-8")) + ContentType:="application/json; charset=utf-8", RequireJson:=True)) '将登录结果输出 If LoginJson("selectedProfile") Is Nothing Then Throw New Exception("选择的角色 " & Setup.Get("Cache" & Data.Input.Token & "Name") & " 无效!") Data.Output.AccessToken = LoginJson("accessToken").ToString @@ -756,7 +752,7 @@ LoginFinish: Data.Input.BaseUrl & "/authenticate", HttpMethod.Post, Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None), Headers:={{"Accept-Language", "zh-CN"}}, - ContentType:="application/json; charset=utf-8")) + ContentType:="application/json; charset=utf-8", RequireJson:=True)) '检查登录结果 If LoginJson("availableProfiles").Count = 0 Then If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Critical) @@ -814,13 +810,14 @@ LoginFinish: McLaunchLog("登录成功(Login, " & Data.Input.Token & ")") Return NeedRefresh Catch ex As Exception - Dim AllMessage As String = GetExceptionSummary(ex) + Dim AllMessage As String = ex.GetBrief() Log(ex, "登录失败原始错误信息", LogLevel.Normal) '读取服务器返回的错误 If TypeOf ex Is ResponsedWebException Then Dim ErrorMessage As String = Nothing Try - ErrorMessage = GetJson(DirectCast(ex, ResponsedWebException).Response)("errorMessage") + Dim Response = DirectCast(ex, ResponsedWebException).Response + If ErrorMessage.ContainsF("errorMessage", True) Then ErrorMessage = GetJson(Response)("errorMessage") Catch End Try If Not String.IsNullOrWhiteSpace(ErrorMessage) Then @@ -838,7 +835,7 @@ LoginFinish: End If End If '通用关键字检测 - If AllMessage.Contains("403") Then + If AllMessage.Contains("(403)") Then Select Case Data.Input.Type Case McLoginType.Auth Throw New Exception("$登录失败,以下为可能的原因:" & vbCrLf & @@ -852,8 +849,8 @@ LoginFinish: If(Data.Input.UserName.Contains("@"), "", " - 登录账号应为邮箱或统一通行证账号,而非游戏角色 ID。" & vbCrLf) & " - 只注册了账号,但没有加入对应服务器。") End Select - 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.IsNetworkRelated() Then + Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后再试,或使用加速器、VPN 改善网络环境。") ElseIf ex.Message.StartsWithF("$") Then Throw Else @@ -872,7 +869,7 @@ Retry: McLaunchLog("开始微软登录步骤 1/6(原始登录)") 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")) + ContentType:="application/x-www-form-urlencoded", RequireJson:=True)) McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString) '弹窗 @@ -907,6 +904,7 @@ Retry: Headers:={{"Accept-Language", "en-US,en;q=0.5"}, {"X-Requested-With", "XMLHttpRequest"}}, ThreadCount:=2) Catch ex As ResponsedWebException + '修改错误列表时,同时检查 MyMsgLogin.xaml.vb 中的对应代码 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 @@ -941,7 +939,7 @@ Retry: ""TokenType"": ""JWT"" }" Dim Result As String = NetRequestByClientMultiple("https://user.auth.xboxlive.com/user/authenticate", HttpMethod.Post, - Content:=Request, ContentType:="application/json") + Content:=Request, ContentType:="application/json", RequireJson:=True) Dim ResultJson As JObject = GetJson(Result) Dim XBLToken As String = ResultJson("Token").ToString Return XBLToken @@ -963,7 +961,7 @@ Retry: Dim Result As String Try Result = NetRequestByClientMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post, - Content:=Request, ContentType:="application/json") + Content:=Request, ContentType:="application/json", RequireJson:=True) Catch ex As ResponsedWebException '参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js If ex.Response.Contains("2148916227") Then @@ -975,7 +973,7 @@ Retry: End If Throw New Exception("$$") ElseIf ex.Response.Contains("2148916235") Then - MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请使用加速器或 VPN。", "登录失败", "我知道了") + MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请使用加速器或 VPN,然后再试。", "登录失败", "我知道了") Throw New Exception("$$") ElseIf ex.Response.Contains("2148916238") Then If MyMsgBox("该账号年龄不足,你需要先修改出生日期,然后才能登录。" & vbCrLf & @@ -994,15 +992,11 @@ Retry: End If End Try - 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 + Dim ResultJson As JObject = GetJson(Result) + Return { + ResultJson("Token"), 'XSTSToken + ResultJson("DisplayClaims")("xui")(0)("uhs") 'UHS + } End Function '微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken Private Function MsLoginStep4(Tokens As String()) As String @@ -1012,9 +1006,9 @@ Retry: Dim Result As String Try Result = NetRequestByClientRetry("https://api.minecraftservices.com/authentication/login_with_xbox", HttpMethod.Post, - Content:=Request, ContentType:="application/json") + Content:=Request, ContentType:="application/json", RequireJson:=True) Catch ex As Net.WebException - Dim Message As String = GetExceptionSummary(ex) + Dim Message As String = ex.GetBrief() If Message.Contains("(429)") Then Log(ex, "微软登录第 4 步汇报 429") Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!") @@ -1026,16 +1020,14 @@ Retry: End If End Try - Dim ResultJson As JObject = GetJson(Result) - Dim AccessToken As String = ResultJson("access_token").ToString - Return AccessToken + Return GetJson(Result)("access_token").ToString End Function '微软登录步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP Private Sub MsLoginStep5(AccessToken As String) McLaunchLog("开始微软登录步骤 5/6") Dim Result As String = NetRequestByClientMultiple("https://api.minecraftservices.com/entitlements/mcstore", - ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}) + ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}, RequireJson:=True) Try Dim ResultJson As JObject = GetJson(Result) If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then @@ -1057,9 +1049,9 @@ Retry: Dim Result As String Try 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) + ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}, RequireJson:=True) + Catch ex As WebException + Dim Message As String = ex.GetBrief() If Message.Contains("(429)") Then Log(ex, "微软登录第 6 步汇报 429") Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!") @@ -1130,7 +1122,7 @@ Retry: If Len(Uuid) = 32 Then Return Uuid '从官网获取 Try - Dim GotJson As JObject = GetJson(NetRequestByClientRetry("https://api.mojang.com/users/profiles/minecraft/" & Name)) + Dim GotJson As JObject = GetJson(NetRequestByClientRetry("https://api.mojang.com/users/profiles/minecraft/" & Name, RequireJson:=True)) If GotJson Is Nothing Then Throw New FileNotFoundException("正版玩家档案不存在(" & Name & ")") Uuid = If(GotJson("id"), "") Catch ex As Exception @@ -1495,6 +1487,9 @@ Retry: DataList.Add(Version.JsonObject("mainClass")) End If + '处理 DataList 的换行符 + DataList = DataList.Select(Function(d) d.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ").Trim).ToList + Return Join(DataList, " ") End Function Private Function McLaunchArgumentsJvmNew(Version As McVersion) As String @@ -1538,9 +1533,7 @@ NextVersion: '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)) + Dim Server As String = Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent) Try Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8) DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server & @@ -1562,7 +1555,7 @@ NextVersion: '如果不合并,会导致 Forge 1.17 启动无效,它有两个 --add-exports,进一步导致其中一个在后面被去重 Dim DeDuplicateDataList As New List(Of String) For i = 0 To DataList.Count - 1 - Dim CurrentEntry As String = DataList(i) + Dim CurrentEntry As String = DataList(i).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ").Trim If DataList(i).StartsWithF("-") Then Do While i < DataList.Count - 1 If DataList(i + 1).StartsWithF("-") Then @@ -1803,7 +1796,7 @@ NextVersion: File.Delete(FilePath) Catch ex As UnauthorizedAccessException McLaunchLog("删除原 dll 访问被拒绝,这通常代表有一个 MC 正在运行,跳过解压:" & FilePath) - McLaunchLog("实际的错误信息:" & GetExceptionSummary(ex)) + McLaunchLog("实际的错误信息:" & ex.GetDetail()) Exit For End Try End If @@ -1823,7 +1816,7 @@ NextVersion: File.Delete(FileName) Catch ex As UnauthorizedAccessException McLaunchLog("删除多余文件访问被拒绝,跳过删除步骤") - McLaunchLog("实际的错误信息:" & GetExceptionSummary(ex)) + McLaunchLog("实际的错误信息:" & ex.GetDetail()) Return End Try Next @@ -1849,7 +1842,7 @@ NextVersion: '要求 Java 使用高性能显卡 If Setup.Get("LaunchAdvanceGraphicCard") Then Try - SetGPUPreference(McLaunchJavaSelected.PathJavaw) + SetGPUPreference(McLaunchJavaSelected.PathJava) SetGPUPreference(PathWithName) Catch ex As Exception If IsAdmin() Then @@ -1857,7 +1850,7 @@ NextVersion: Else Log(ex, "直接调整显卡设置失败,将以管理员权限重启 PCL 再次尝试") Try - If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJavaw}""") = ProcessReturnValues.TaskDone Then + If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJava}""") = ProcessReturnValues.TaskDone Then McLaunchLog("以管理员权限重启 PCL 并调整显卡设置成功") Else Throw New Exception("调整过程中出现异常") @@ -2218,7 +2211,7 @@ IgnoreCustomSkin: '启动信息 Dim GameProcess = New Process() - Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJavaw) + Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJava) '使用 javaw.exe 会导致 #6263 '设置环境变量 Dim Paths As New List(Of String)(StartInfo.EnvironmentVariables("Path").Split(";")) @@ -2231,13 +2224,13 @@ IgnoreCustomSkin: StartInfo.UseShellExecute = False StartInfo.RedirectStandardOutput = True StartInfo.RedirectStandardError = True - StartInfo.CreateNoWindow = False + StartInfo.CreateNoWindow = True StartInfo.Arguments = McLaunchArgument GameProcess.StartInfo = StartInfo '开始进程 GameProcess.Start() - McLaunchLog("已启动游戏进程:" & McLaunchJavaSelected.PathJavaw) + McLaunchLog("已启动游戏进程:" & McLaunchJavaSelected.PathJava) If Loader.IsAborted Then McLaunchLog("由于取消启动,已强制结束游戏进程") '#1631 GameProcess.Kill() diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb index 6c11868..6a3a527 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb @@ -86,7 +86,7 @@ Public Module ModMinecraft Next If Not Renamed Then CacheMcFolderList.Add(New McFolder With {.Name = Name, .Path = Path, .Type = McFolderType.Custom}) Catch ex As Exception - MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & GetExceptionSummary(ex), "Minecraft 文件夹失效", IsWarn:=True) + MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & ex.GetBrief(), "Minecraft 文件夹失效", IsWarn:=True) Log(ex, $"无法访问 Minecraft 文件夹 {Path}") End Try Next @@ -695,7 +695,7 @@ Recheck: Catch ex As Exception Log(ex, "依赖版本检查出错(" & Name & ")") State = McVersionState.Error - Info = "未知错误:" & GetExceptionSummary(ex) + Info = "未知错误:" & ex.GetBrief() Return False End Try @@ -808,7 +808,7 @@ ExitDataLoad: WriteIni(Path & "PCL\Setup.ini", "VersionOriginalSub", Version.McCodeSub) End If Catch ex As Exception - Info = "未知错误:" & GetExceptionSummary(ex) + Info = "未知错误:" & ex.GetBrief() Logo = PathImage & "Blocks/RedstoneBlock.png" State = McVersionState.Error Log(ex, "加载版本失败(" & Name & ")", LogLevel.Feedback) @@ -1186,6 +1186,7 @@ OnLoaded: If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(200, 3000)) Catch ex As ThreadInterruptedException Catch ex As Exception + If Loader.IsAborted Then Return '#5617 WriteIni(Path & "PCL.ini", "VersionCache", "") '要求下次重新加载 Log(ex, "加载 .minecraft 版本列表失败", LogLevel.Feedback) End Try @@ -1592,11 +1593,11 @@ OnLoaded: ''' ''' 获取 Uuid 对应的皮肤文件地址,失败将抛出异常。 ''' - Public Function McSkinGetAddress(Uuid As String, Type As String) As String - If Uuid = "" Then Throw New Exception("Uuid 为空。") - If Uuid.StartsWithF("00000") Then Throw New Exception("离线 Uuid 无正版皮肤文件。") + Public Function McSkinGetAddress(UUID As String, Type As String) As String + If UUID = "" Then Throw New Exception("UUID 为空。") + If UUID.StartsWithF("00000") AndAlso Type <> "Auth" Then Throw New Exception("离线 UUID 无正版皮肤文件:" & UUID) '尝试读取缓存 - Dim CacheSkinAddress As String = ReadIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid) + Dim CacheSkinAddress As String = ReadIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", UUID) If Not CacheSkinAddress = "" Then Return CacheSkinAddress '获取皮肤地址 Dim Url As String @@ -1610,7 +1611,7 @@ OnLoaded: Case Else Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null")) End Select - Dim SkinString = NetRequestByClientRetry(Url & Uuid) + Dim SkinString = NetRequestByClientRetry(Url & UUID, RequireJson:=True) If SkinString = "" Then Throw New Exception("皮肤返回值为空,可能是未设置自定义皮肤的用户") '处理皮肤地址 Dim SkinValue As String @@ -1635,8 +1636,8 @@ OnLoaded: SkinValue = If(SkinUrl.Contains("minecraft.net/"), SkinUrl.Replace("http://", "https://"), SkinUrl) End If '保存缓存 - WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid, SkinValue) - Log("[Skin] UUID " & Uuid & " 对应的皮肤文件为 " & SkinValue) + WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", UUID, SkinValue) + Log("[Skin] UUID " & UUID & " 对应的皮肤文件为 " & SkinValue) Return SkinValue End Function @@ -1966,7 +1967,7 @@ OnLoaded: Try Log("[Minecraft] 开始获取统一通行证下载信息") '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/ - DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version))) + DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version), RequireJson:=True)) Catch ex As Exception Log(ex, "获取统一通行证下载信息失败") End Try @@ -1983,16 +1984,20 @@ OnLoaded: 'Authlib-Injector 文件 If Setup.Get("VersionServerLogin", Version:=Version) = 4 Then - Dim TargetFile = PathPure & "\authlib-injector.jar" + Dim TargetFile = PathPure & "authlib-injector.jar" Dim DownloadInfo As JObject = Nothing '获取下载信息 Try Log("[Minecraft] 开始获取 Authlib-Injector 下载信息") DownloadInfo = GetJson(NetRequestByClientRetry( "https://authlib-injector.yushi.moe/artifact/latest.json", - BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json")) + BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json", RequireJson:=True)) Catch ex As Exception - Log(ex, "获取 Authlib-Injector 下载信息失败") + If File.Exists(TargetFile) Then + Log(ex, "获取 Authlib-Injector 下载信息失败") + Else + Throw New Exception("获取 Authlib-Injector 下载信息失败", ex) + End If End Try '校验文件 If DownloadInfo IsNot Nothing Then @@ -2415,7 +2420,12 @@ 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("\Users\" & Token, "\Users\" & New String(FilterChar, Token.Count)) + Next + End If + If Raw.Contains(":/Users/") Then + For Each Token In RegexSearch(Raw, "(?<=:/Users/)[^/]+") + Raw = Raw.Replace("/Users/" & Token, "/Users/" & 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 cc634fa..db5e47a 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb @@ -1113,8 +1113,8 @@ Finished: End Sub, "Mod List Detail Loader CurseForge") '等待线程结束 Do Until EndedThreadCount = 2 - If Loader.IsAborted Then Return Thread.Sleep(10) + If Loader.IsAborted Then Return Loop '保存缓存 Mods = Mods.Where(Function(m) m.Comp IsNot Nothing).ToList() @@ -1126,6 +1126,7 @@ Finished: Next WriteFile(PathTemp & "Cache\LocalMod.json", Cache.ToString(If(ModeDebug, Newtonsoft.Json.Formatting.Indented, Newtonsoft.Json.Formatting.None))) '刷新边栏 + If Loader.IsAborted Then Return If FrmVersionMod?.Filter = PageVersionMod.FilterType.CanUpdate Then RunInUi(Sub() FrmVersionMod?.RefreshUI()) '同步 “可更新” 列表 (#4677) Else @@ -1212,7 +1213,7 @@ Finished: If ReqVersionHead = ReqVersionTail Then VersionRequire = "应为 " & ReqVersionHead ElseIf ReqVersionHead.Contains(".") AndAlso ReqVersionTail.Contains(".") Then - VersionRequire = "应为 " & ReqVersionHead & " 至 " & ReqVersionTail + VersionRequire = $"应为 {ReqVersionHead} 至 {ReqVersionTail}" ElseIf ReqVersionHead.Contains(".") Then If ReqVersionHeadCanEqual Then VersionRequire = "最低应为 " & ReqVersionHead @@ -1230,7 +1231,7 @@ Finished: End If '检查前置 Mod 是否存在,并获取其版本 If Not CurrentDependencies.ContainsKey(ReqId) Then - Result.Add("缺少前置 Mod:" & ReqId & If(VersionRequire = "", "", ",其版本" & VersionRequire) & "。" & vbCrLf & " - " & ModEntity.FileName) + Result.Add($"缺少前置 Mod:{ReqId}{If(VersionRequire = "", "", ",其版本" & VersionRequire)}。{vbCrLf} - {ModEntity.FileName}") Continue For End If Dim CurrentVersion As String = If(CurrentDependencies(ReqId)(0), "0.0") @@ -1239,7 +1240,7 @@ Finished: If ReqVersionHead.Contains(".") Then If VersionSortInteger(ReqVersionHead, CurrentVersion) > If(ReqVersionHeadCanEqual, 0, -1) Then Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过低,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf & - " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), "")) + " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", $"{vbCrLf} - 前置:{CurrentDependencies(ReqId)(1)}", "")) Continue For End If End If @@ -1247,26 +1248,26 @@ Finished: If ReqVersionTail.Contains(".") Then If VersionSortInteger(CurrentVersion, ReqVersionTail) > If(ReqVersionTailCanEqual, 0, -1) Then Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过高,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf & - " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), "")) + " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", $"{vbCrLf} - 前置:{CurrentDependencies(ReqId)(1)}", "")) Continue For End If End If Else If Not CurrentDependencies.ContainsKey(Dependency.Key) Then - Result.Add("缺少前置 Mod:" & Dependency.Key & "。" & vbCrLf & " - " & ModEntity.FileName) + Result.Add($"缺少前置 Mod:{Dependency.Key}。{vbCrLf} - {ModEntity.FileName}") Continue For End If End If Next Catch ex As Exception - Result.Add("检查 Mod 时出错:" & GetExceptionSummary(ex) & vbCrLf & " - " & ModEntity.FileName) + Result.Add($"检查 Mod 时出错:{ex.GetBrief()}{vbCrLf} - {ModEntity.FileName}") Log(ex, "检查 Mod 时出错") End Try Next If Not Result.Any() Then Log("[Minecraft] Mod 检查未发现异常") Else - Log("[Minecraft] Mod 检查异常结果:" & vbCrLf & Join(Result, vbCrLf)) + Log($"[Minecraft] Mod 检查异常结果:{vbCrLf}{Join(Result, vbCrLf)}") End If Return Result End Function diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb index 141f865..29271c5 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb @@ -72,7 +72,7 @@ Public Module ModModpack If FullNames(1) = "modpack.zip" OrElse FullNames(1) = "modpack.mrpack" Then PackType = 9 : Exit Try '带启动器的压缩包 Next Catch ex As Exception - If GetExceptionDetail(ex, True).Contains("Error.WinIOError") Then + If ex.GetDetail(True).Contains("Error.WinIOError") Then Throw New Exception("打开整合包文件失败", ex) ElseIf File.EndsWithF(".rar", True) Then Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex) @@ -166,6 +166,15 @@ Retry: End If IniClearCache(VersionIni) '重置缓存,避免被安装过程中写入的 ini 覆盖 End Sub + ''' + ''' 弹窗提示整合包中存在不兼容的加载器。 + ''' 如果取消,则抛出 CancelledException。 + ''' + ''' + Private Sub NotifyIncompatibleLoader(LoaderName As String) + If MyMsgBox($"整合包中存在不兼容的加载器:{LoaderName}{vbCrLf}如果你知道如何手动安装它,可以先选择跳过,然后在整合包下载结束后手动安装。", + "不兼容的加载器", "取消", $"不安装 {LoaderName} 并继续") = 1 Then Throw New CancelledException + End Sub #Region "不同类型整合包的安装方法" @@ -211,7 +220,8 @@ Retry: Log("[ModPack] 整合包 Fabric 版本:" & Id) FabricVersion = Id.Replace("fabric-", "") Else - Log("[ModPack] 未知 Mod 加载器:" & Id) + 'ElseIf Id.StartsWithF("quilt-") Then + NotifyIncompatibleLoader(Id) End If Next '解压 @@ -376,12 +386,9 @@ Retry: Case "fabric-loader" 'eg. 0.14.14 FabricVersion = Entry.Value.ToString Log("[ModPack] 整合包 Fabric 版本:" & FabricVersion) - Case "quilt-loader" 'eg. 1.0.0 - Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical) - Throw New CancelledException Case Else - Hint($"无法安装整合包,其中出现了未知的 Mod 加载器 {Entry.Value}!", HintType.Critical) - Throw New CancelledException + 'Case "quilt-loader" 'eg. 1.0.0 + NotifyIncompatibleLoader(Entry.Name) End Select Next '获取版本名 @@ -633,7 +640,8 @@ Retry: If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!") Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"} For Each Component In PackJson("components") - Select Case If(Component("uid"), "").ToString + If Not Component.Contains("uid") Then Continue For + Select Case Component("uid").ToString Case "org.lwjgl" Log("[ModPack] 已跳过 LWJGL 项") Case "net.minecraft" @@ -644,9 +652,9 @@ Retry: Request.NeoForgeVersion = Component("version") Case "net.fabricmc.fabric-loader" Request.FabricVersion = Component("version") - Case "org.quiltmc.quilt-loader" 'eg. 1.0.0 - Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical) - Throw New CancelledException + Case Else + 'Case "org.quiltmc.quilt-loader" 'eg. 1.0.0 + NotifyIncompatibleLoader(Component("uid")) End Select Next '构造加载器 diff --git a/Plain Craft Launcher 2/Modules/ModMain.vb b/Plain Craft Launcher 2/Modules/ModMain.vb index 2ceb04c..68aaebb 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.vb +++ b/Plain Craft Launcher 2/Modules/ModMain.vb @@ -397,12 +397,6 @@ EndHint: '下载页面声明 Public FrmDownloadLeft As PageDownloadLeft Public FrmDownloadInstall As PageDownloadInstall - Public FrmDownloadClient As PageDownloadClient - Public FrmDownloadOptiFine As PageDownloadOptiFine - Public FrmDownloadLiteLoader As PageDownloadLiteLoader - Public FrmDownloadForge As PageDownloadForge - Public FrmDownloadNeoForge As PageDownloadNeoForge - Public FrmDownloadFabric As PageDownloadFabric Public FrmDownloadMod As PageDownloadMod Public FrmDownloadPack As PageDownloadPack Public FrmDownloadDataPack As PageDownloadDataPack diff --git a/Plain Craft Launcher 2/Modules/ModSecret.vb b/Plain Craft Launcher 2/Modules/ModSecret.vb index 00635bb..f0d622f 100644 --- a/Plain Craft Launcher 2/Modules/ModSecret.vb +++ b/Plain Craft Launcher 2/Modules/ModSecret.vb @@ -80,15 +80,16 @@ Friend Module ModSecret ''' ''' 设置 Headers 的 UA、Referer。 ''' - Friend Sub SecretHeadersSign(Url As String, ByRef Req As HttpRequestMessage, Optional UseBrowserUserAgent As Boolean = False) + Friend Sub SecretHeadersSign(Url As String, ByRef Req As HttpRequestMessage, Optional SimulateBrowserHeaders As Boolean = False) + If Req.Headers.UserAgent.Any Then Return If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then Req.Headers.Add("User-Agent", "LogStatistic") '#4951 - ElseIf UseBrowserUserAgent Then + ElseIf SimulateBrowserHeaders Then Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode} Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36") Else Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode}") End If - Req.Headers.Add("Referer", $"http://{VersionCode}.open.pcl2.server/") + If Not SimulateBrowserHeaders Then 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 diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index c257a02..d781a20 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/Pages/PageDownload/Comp/PageComp.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml index 944bca6..4de9f55 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml @@ -71,7 +71,7 @@ - + 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 1337e49..63125e2 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb @@ -290,7 +290,7 @@ Sub(MyLoader) Select Case MyLoader.State Case LoadState.Failed - Hint(MyLoader.Name & "失败:" & GetExceptionSummary(MyLoader.Error), HintType.Critical) + Hint(MyLoader.Name & "失败:" & MyLoader.Error.GetBrief(), HintType.Critical) Case LoadState.Aborted Hint(MyLoader.Name & "已取消!", HintType.Info) Case LoadState.Loading diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml index f6e5831..d000fd8 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml @@ -32,4 +32,4 @@ - + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml index 9eb1208..0954583 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml @@ -17,7 +17,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index 02c92e2..a19a93c 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -449,7 +449,7 @@ pause" '开始启动 SyncLock InstallSyncLock Dim Info = New ProcessStartInfo With { - .FileName = Java.PathJavaw, + .FileName = Java.PathJava, .Arguments = Arguments, .UseShellExecute = False, .CreateNoWindow = True, @@ -571,7 +571,7 @@ pause" Dim PageData As String Try PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, - Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True) + Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", SimulateBrowserHeaders:=True) Task.Progress = 0.8 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0)) Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last) @@ -735,7 +735,7 @@ Retry: Dim PageData As String Try PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, - Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True) + Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", SimulateBrowserHeaders:=True) Task.Progress = 0.8 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0)) Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last) @@ -1120,7 +1120,7 @@ Retry: '开始启动 SyncLock InstallSyncLock Dim Info = New ProcessStartInfo With { - .FileName = Java.PathJavaw, + .FileName = Java.PathJava, .Arguments = Arguments, .UseShellExecute = False, .CreateNoWindow = True, @@ -1647,7 +1647,7 @@ Retry: Sub() Try Log("[Download] 刷新 Forge 推荐版本缓存开始") - Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos") + Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos", RequireJson:=True) If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")") Dim ResultJson As JContainer = GetJson(Result) '获取所有推荐版本列表 @@ -1991,7 +1991,7 @@ Retry: Case LoadState.Finished Hint(Loader.Name & "成功!", HintType.Finish) Case LoadState.Failed - Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical) + Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical) Case LoadState.Aborted Hint(Loader.Name & "已取消!", HintType.Info) End Select @@ -2005,7 +2005,7 @@ Retry: WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存) Hint(Loader.Name & "成功!", HintType.Finish) Case LoadState.Failed - Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical) + Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical) Case LoadState.Aborted Hint(Loader.Name & "已取消!", HintType.Info) Case LoadState.Loading diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml deleted file mode 100644 index 98ba882..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb deleted file mode 100644 index 78c963c..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb +++ /dev/null @@ -1,111 +0,0 @@ -Public Class PageDownloadClient - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanBack, Nothing, DlClientListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '归类 - Dim Dict As New Dictionary(Of String, List(Of JObject)) From { - {"正式版", New List(Of JObject)}, {"预览版", New List(Of JObject)}, {"远古版", New List(Of JObject)}, {"愚人节版", New List(Of JObject)} - } - Dim Versions As JArray = DlClientListLoader.Output.Value("versions") - For Each Version As JObject In Versions - '确定分类 - Dim Type As String = Version("type") - Select Case Type - Case "release" - Type = "正式版" - Case "snapshot" - Type = "预览版" - 'Mojang 误分类 - If Version("id").ToString.StartsWith("1.") AndAlso - Not Version("id").ToString.ToLower.Contains("combat") AndAlso - Not Version("id").ToString.ToLower.Contains("rc") AndAlso - Not Version("id").ToString.ToLower.Contains("experimental") AndAlso - Not Version("id").ToString.ToLower.Contains("pre") Then - Type = "正式版" - Version("type") = "release" - End If - '愚人节版本 - Select Case Version("id").ToString.ToLower - Case "20w14infinite", "20w14∞" - Type = "愚人节版" - Version("id") = "20w14∞" - Version("type") = "special" - Version.Add("lore", GetMcFoolName(Version("id"))) - Case "3d shareware v1.34", "1.rv-pre1", "15w14a", "2.0", "22w13oneblockatatime", "23w13a_or_b", "24w14potato", "25w14craftmine" - Type = "愚人节版" - Version("type") = "special" - Version.Add("lore", GetMcFoolName(Version("id"))) - Case Else '4/1 自动视作愚人节版 - Dim ReleaseDate = Version("releaseTime").Value(Of Date).ToUniversalTime().AddHours(2) - If ReleaseDate.Month = 4 AndAlso ReleaseDate.Day = 1 Then - Type = "愚人节版" - Version("type") = "special" - End If - End Select - Case "special" - '已被处理的愚人节版 - Type = "愚人节版" - Case Else - Type = "远古版" - End Select - '加入辞典 - Dict(Type).Add(Version) - Next - '排序 - For i = 0 To Dict.Keys.Count - 1 - Dict(Dict.Keys(i)) = Dict.Values(i).OrderByDescending(Function(v) v("releaseTime").Value(Of Date)).ToList - Next - '清空当前 - PanMain.Children.Clear() - '添加最新版本 - Dim CardInfo As New MyCard With {.Title = "最新版本", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 2} - Dim TopestVersions As New List(Of JObject) - Dim Release As JObject = Dict("正式版")(0).DeepClone() - Release("lore") = "最新正式版,发布于 " & Release("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm") - TopestVersions.Add(Release) - If Dict("正式版")(0)("releaseTime").Value(Of Date) < Dict("预览版")(0)("releaseTime").Value(Of Date) Then - Dim Snapshot As JObject = Dict("预览版")(0).DeepClone() - Snapshot("lore") = "最新预览版,发布于 " & Snapshot("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm") - TopestVersions.Add(Snapshot) - End If - Dim PanInfo As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = TopestVersions} - MyCard.StackInstall(PanInfo, 2) - CardInfo.Children.Add(PanInfo) - PanMain.Children.Add(CardInfo) - '添加其他版本 - For Each Pair As KeyValuePair(Of String, List(Of JObject)) In Dict - If Not Pair.Value.Any() Then Continue For - '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 2} - 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.IsSwapped = True - PanMain.Children.Add(NewCard) - Next - Catch ex As Exception - Log(ex, "可视化 MC 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - Public Sub DownloadStart(sender As MyListItem, e As Object) - McDownloadClient(NetPreDownloadBehaviour.HintWhileExists, sender.Title, sender.Tag("url").ToString) - End Sub - - ''介绍栏 - 'Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - ' OpenWebsite("https://www.minecraft.net/zh-hans") - 'End Sub - 'Private Sub BtnInstall_Click(sender As Object, e As EventArgs) Handles BtnInstall.Click - ' FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall) - 'End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml deleted file mode 100644 index d2c6af3..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb deleted file mode 100644 index fa65b79..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb +++ /dev/null @@ -1,32 +0,0 @@ -Public Class PageDownloadFabric - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, CardVersions, CardTip, DlFabricListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - Dim Versions As JArray = DlFabricListLoader.Output.Value("installer") - PanVersions.Children.Clear() - For Each Version In Versions - PanVersions.Children.Add(FabricDownloadListItem(Version, AddressOf Fabric_Selected)) - Next - CardVersions.Title = "版本列表 (" & Versions.Count & ")" - Catch ex As Exception - Log(ex, "可视化 Fabric 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - Private Sub Fabric_Selected(sender As MyListItem, e As EventArgs) - McDownloadFabricLoaderSave(sender.Tag) - End Sub - - Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - OpenWebsite("https://www.fabricmc.net") - End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml deleted file mode 100644 index 4ba7b34..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb deleted file mode 100644 index b7f42f0..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb +++ /dev/null @@ -1,61 +0,0 @@ -Public Class PageDownloadForge - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlForgeListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '清空当前 - PanMain.Children.Clear() - '转化为 UI - For Each Version As String In DlForgeListLoader.Output.Value.Sort(AddressOf VersionSortBoolean) - '增加卡片 - Dim NewCard As New MyCard With {.Title = Version.Replace("_p", " P"), .Margin = New Thickness(0, 0, 0, 15), .SwapType = 5} - 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.IsSwapped = True - PanMain.Children.Add(NewCard) - Next - ''非官方源警示 - 'If Setup.Get("ToolDownloadOutOfDate") AndAlso Not DlForgeListLoader.Output.IsOfficial Then - ' Dim CardWarn As New MyCard With {.Title = "过期提示", .Margin = New Thickness(0, 0, 0, 15)} - ' CardWarn.Children.Add(New TextBlock With { - ' .Margin = New Thickness(25, MyCard.SwapedHeight, 15, 15), .VerticalAlignment = VerticalAlignment.Top, .HorizontalAlignment = HorizontalAlignment.Left, .TextTrimming = TextTrimming.None, .TextWrapping = TextWrapping.Wrap, - ' .Text = "获取官方源失败,正在使用 " & DlForgeListLoader.Output.SourceName & " 镜像源,版本列表可能并非最新。" & vbCrLf & "官方源错误原因:" & If(DlForgeListLoader.Output.OfficialError, New Exception("连接服务器超时")).Message}) - ' PanMain.Children.Insert(0, CardWarn) - 'End If - Catch ex As Exception - Log(ex, "可视化 Forge 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - 'Forge 版本列表加载 - Public Sub Forge_Click(sender As MyLoading, e As MouseButtonEventArgs) - If sender.State.LoadingState = MyLoading.MyLoadingState.Error Then - CType(sender.State, LoaderTask(Of String, List(Of DlForgeVersionEntry))).Start(IsForceRestart:=True) - End If - End Sub - Public Sub Forge_StateChanged(sender As MyLoading, newState As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) - If newState <> MyLoading.MyLoadingState.Stop Then Return - - Dim Card As MyCard = CType(sender.Parent, FrameworkElement).Parent - Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = sender.State - '载入列表 - Card.SwapControl.Children.Clear() - Card.SwapControl.Tag = Loader.Output - Card.SwapType = 6 - Card.StackInstall() - End Sub - - '介绍栏 - Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - OpenWebsite("https://files.minecraftforge.net") - End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml index 21d408d..74a753d 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml @@ -196,4 +196,4 @@ ---> +--> \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml index 4b84395..2aa4592 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml @@ -5,110 +5,55 @@ - - + - + - - - - + + + + + + + + + + + + + + + + - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb index b63590b..3ddece3 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb @@ -11,7 +11,7 @@ ''' ''' 勾选事件改变页面。 ''' - Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemInstall.Check, ItemClient.Check, ItemOptiFine.Check, ItemForge.Check, ItemNeoForge.Check, ItemLiteLoader.Check, ItemMod.Check, ItemFabric.Check, ItemPack.Check, ItemResourcePack.Check, ItemShader.Check, ItemDataPack.Check + Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemInstall.Check, ItemMod.Check, ItemPack.Check, ItemResourcePack.Check, ItemShader.Check, ItemDataPack.Check '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0 '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False) If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag)) @@ -23,24 +23,6 @@ Case FormMain.PageSubType.DownloadInstall If FrmDownloadInstall Is Nothing Then FrmDownloadInstall = New PageDownloadInstall Return FrmDownloadInstall - Case FormMain.PageSubType.DownloadClient - If FrmDownloadClient Is Nothing Then FrmDownloadClient = New PageDownloadClient - Return FrmDownloadClient - Case FormMain.PageSubType.DownloadOptiFine - If FrmDownloadOptiFine Is Nothing Then FrmDownloadOptiFine = New PageDownloadOptiFine - Return FrmDownloadOptiFine - Case FormMain.PageSubType.DownloadForge - If FrmDownloadForge Is Nothing Then FrmDownloadForge = New PageDownloadForge - Return FrmDownloadForge - Case FormMain.PageSubType.DownloadNeoForge - If FrmDownloadNeoForge Is Nothing Then FrmDownloadNeoForge = New PageDownloadNeoForge - Return FrmDownloadNeoForge - Case FormMain.PageSubType.DownloadLiteLoader - If FrmDownloadLiteLoader Is Nothing Then FrmDownloadLiteLoader = New PageDownloadLiteLoader - Return FrmDownloadLiteLoader - Case FormMain.PageSubType.DownloadFabric - If FrmDownloadFabric Is Nothing Then FrmDownloadFabric = New PageDownloadFabric - Return FrmDownloadFabric Case FormMain.PageSubType.DownloadMod If FrmDownloadMod Is Nothing Then FrmDownloadMod = New PageDownloadMod Return FrmDownloadMod @@ -163,24 +145,6 @@ FrmDownloadDataPack.PageLoaderRestart() End If ItemDataPack.Checked = True - Case FormMain.PageSubType.DownloadClient - DlClientListLoader.Start(IsForceRestart:=True) - ItemClient.Checked = True - Case FormMain.PageSubType.DownloadOptiFine - DlOptiFineListLoader.Start(IsForceRestart:=True) - ItemOptiFine.Checked = True - Case FormMain.PageSubType.DownloadForge - DlForgeListLoader.Start(IsForceRestart:=True) - ItemForge.Checked = True - Case FormMain.PageSubType.DownloadNeoForge - DlNeoForgeListLoader.Start(IsForceRestart:=True) - ItemNeoForge.Checked = True - Case FormMain.PageSubType.DownloadLiteLoader - DlLiteLoaderListLoader.Start(IsForceRestart:=True) - ItemLiteLoader.Checked = True - Case FormMain.PageSubType.DownloadFabric - DlFabricListLoader.Start(IsForceRestart:=True) - ItemFabric.Checked = True End Select Hint("正在刷新……", Log:=False) End Sub @@ -191,55 +155,4 @@ FrmDownloadInstall.ExitSelectPage() End Sub - '展开手动安装 - Private Sub ItemHand_Click(sender As Object, e As RouteEventArgs) Handles ItemHand.Changed - If ItemHand.Checked = False Then Return - e.Handled = True - AniControlEnabled += 1 - If Not Setup.Get("HintHandInstall") Then - Setup.Set("HintHandInstall", True) - If MyMsgBox("手动安装包功能提供了 OptiFine、Forge 等组件的 .jar 安装文件下载,但无法自动安装。" & vbCrLf & - "在自动安装页面先选择 MC 版本,然后就可以选择 OptiFine、Forge 等组件,让 PCL 自动进行安装了。", "自动安装提示", "返回自动安装", "继续下载手动安装包") = 1 Then - FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.Download}, FormMain.PageSubType.DownloadInstall) - AniControlEnabled -= 1 - Return - End If - End If - ItemHand.Visibility = Visibility.Collapsed - LabGame.Visibility = Visibility.Collapsed - LabHand.Visibility = Visibility.Visible - ItemClient.Visibility = Visibility.Visible - ItemOptiFine.Visibility = Visibility.Visible - ItemFabric.Visibility = Visibility.Visible - ItemForge.Visibility = Visibility.Visible - ItemNeoForge.Visibility = Visibility.Visible - ItemLiteLoader.Visibility = Visibility.Visible - RunInThread( - Sub() - Thread.Sleep(20) - RunInUiWait(Sub() ItemClient.SetChecked(True, True, True)) - AniControlEnabled -= 1 - End Sub) - End Sub - '折叠手动安装 - Private Sub LabHand_Click(sender As Object, e As MouseButtonEventArgs) Handles LabHand.MouseLeftButtonUp - e.Handled = True - AniControlEnabled += 1 - ItemHand.Visibility = Visibility.Visible - LabGame.Visibility = Visibility.Visible - LabHand.Visibility = Visibility.Collapsed - ItemClient.Visibility = Visibility.Collapsed - ItemOptiFine.Visibility = Visibility.Collapsed - ItemNeoForge.Visibility = Visibility.Collapsed - ItemFabric.Visibility = Visibility.Collapsed - ItemForge.Visibility = Visibility.Collapsed - ItemLiteLoader.Visibility = Visibility.Collapsed - RunInThread( - Sub() - Thread.Sleep(20) - RunInUiWait(Sub() ItemInstall.SetChecked(True, True, True)) - AniControlEnabled -= 1 - End Sub) - End Sub - End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml deleted file mode 100644 index 3cd1eb7..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb deleted file mode 100644 index 7eff1c8..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb +++ /dev/null @@ -1,52 +0,0 @@ -Public Class PageDownloadLiteLoader - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlLiteLoaderListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '归类 - Dim Dict As New Dictionary(Of String, List(Of DlLiteLoaderListEntry)) - For VersionCode As Integer = 30 To 0 Step -1 - Dict.Add("1." & VersionCode, New List(Of DlLiteLoaderListEntry)) - Next - Dict.Add("未知版本", New List(Of DlLiteLoaderListEntry)) - For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value - Dim MainVersion As String = "1." & Version.Inherit.Split(".")(1) - If Dict.ContainsKey(MainVersion) Then - Dict(MainVersion).Add(Version) - Else - Dict("未知版本").Add(Version) - End If - Next - '清空当前 - PanMain.Children.Clear() - '转化为 UI - For Each Pair As KeyValuePair(Of String, List(Of DlLiteLoaderListEntry)) In Dict - If Not Pair.Value.Any() Then Continue For - '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 10} - 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.IsSwapped = True - PanMain.Children.Add(NewCard) - Next - Catch ex As Exception - Log(ex, "可视化 LiteLoader 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - Public Sub DownloadStart(sender As MyListItem, e As Object) - McDownloadLiteLoader(sender.Tag) - End Sub - Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - OpenWebsite("https://www.liteloader.com") - End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml deleted file mode 100644 index ecd8aad..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb deleted file mode 100644 index 37cdcda..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb +++ /dev/null @@ -1,34 +0,0 @@ -Public Class PageDownloadNeoForge - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlNeoForgeListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '归类 - Dim Dict = DlNeoForgeListLoader.Output.Value.GroupBy(Function(d) d.Inherit).OrderByDescending(Function(g) g.Key). - ToDictionary(Function(g) g.Key, Function(g) g.ToList()) - '清空当前 - PanMain.Children.Clear() - '转化为 UI - For Each Pair As KeyValuePair(Of String, List(Of DlNeoForgeListEntry)) In Dict - If Not Pair.Value.Any() Then Continue For - '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 13} - 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.IsSwapped = True - PanMain.Children.Add(NewCard) - Next - Catch ex As Exception - Log(ex, "可视化 NeoForge 版本列表出错", LogLevel.Feedback) - End Try - End Sub - -End Class \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml deleted file mode 100644 index ab2fcb6..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb deleted file mode 100644 index 7eebac8..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb +++ /dev/null @@ -1,53 +0,0 @@ -Public Class PageDownloadOptiFine - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlOptiFineListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '归类 - Dim Dict As New Dictionary(Of String, List(Of DlOptiFineListEntry)) - Dict.Add("快照版本", New List(Of DlOptiFineListEntry)) - For VersionCode As Integer = 50 To 0 Step -1 - Dict.Add("1." & VersionCode, New List(Of DlOptiFineListEntry)) - Next - For Each Version As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value - If Version.Inherit.StartsWith("1.") Then - Dim MainVersion As String = "1." & Version.NameDisplay.Split(".")(1).Split(" ")(0) - If Dict.ContainsKey(MainVersion) Then - Dict(MainVersion).Add(Version) - Else - Dict("快照版本").Add(Version) - End If - Else - Dict("快照版本").Add(Version) - End If - Next - '清空当前 - PanMain.Children.Clear() - '转化为 UI - For Each Pair As KeyValuePair(Of String, List(Of DlOptiFineListEntry)) In Dict - If Not Pair.Value.Any() Then Continue For - '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 3} - 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.IsSwapped = True - PanMain.Children.Add(NewCard) - Next - Catch ex As Exception - Log(ex, "可视化 OptiFine 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - OpenWebsite("https://www.optifine.net/") - End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb index 2ef95d9..dd48c06 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 @@ -114,7 +114,7 @@ "scope=XboxLive.signin%20offline_access", ContentType:="application/x-www-form-urlencoded", Timeout:=5000 + UnknownFailureCount * 5000, - MakeLog:=False) + MakeLog:=False, RequireJson:=True) '获取结果 Dim ResultJson As JObject = GetJson(Result) McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒") @@ -122,6 +122,7 @@ Finished({ResultJson("access_token").ToString, ResultJson("refresh_token").ToString}) Return Catch ex As Exception + '修改错误列表时,同时修改 ModLaunch.MsLoginStep1Refresh 中的对应代码 If TypeOf ex Is ResponsedWebException Then Dim Response = CType(ex, ResponsedWebException).Response If Response.Contains("authorization_declined") Then diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb index f22dcc2..64f43b6 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb @@ -392,14 +392,14 @@ UnknownType: Result = McSkinDownload(Result) If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName) Data.Output = Result + Catch ex As ThreadInterruptedException + Data.Output = "" + Return Catch ex As Exception - If ex.GetType.Name = "ThreadInterruptedException" Then - Data.Output = "" - Return - ElseIf GetExceptionSummary(ex).Contains("429") Then + If ex.GetBrief.Contains("(429)") Then Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(UserName)) & ".png" Log("[Minecraft] 获取正版皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint) - ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then + ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(UserName)) & ".png" Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载") Else @@ -462,11 +462,11 @@ UseDefault: If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & ID) Data.Output = Result End If + Catch ex As ThreadInterruptedException + Data.Output = "" + Return Catch ex As Exception - If ex.GetType.Name = "ThreadInterruptedException" Then - Data.Output = "" - Return - ElseIf GetExceptionSummary(ex).Contains("429") Then + If ex.GetBrief.Contains("(429)") Then Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(ID)) & ".png" Log("获取离线登录使用的正版皮肤失败(" & ID & "):获取皮肤太过频繁,请 5 分钟后再试!") Else @@ -514,14 +514,14 @@ UseDefault: Result = McSkinDownload(Result) If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName) Data.Output = Result + Catch ex As ThreadInterruptedException + Data.Output = "" + Return Catch ex As Exception - If ex.GetType.Name = "ThreadInterruptedException" Then - Data.Output = "" - Return - ElseIf GetExceptionSummary(ex).Contains("429") Then + If ex.GetBrief.Contains("(429)") Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 获取统一通行证皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint) - ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then + ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载") Else @@ -550,26 +550,26 @@ Finish: RunInUi(Sub() If FrmLoginAuthSkin IsNot Nothing AndAlso FrmLoginAuthSkin.Skin IsNot Nothing Then FrmLoginAuthSkin.Skin.Clear()) '获取 Url Dim UserName As String = Data.Input(0) - Dim Uuid As String = Data.Input(1) + Dim UUID As String = Data.Input(1) If UserName = "" Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 获取 Authlib-Injector 皮肤失败,ID 为空") GoTo Finish End If Try - Dim Result As String = McSkinGetAddress(Uuid, "Auth") + Dim Result As String = McSkinGetAddress(UUID, "Auth") If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName) Result = McSkinDownload(Result) If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName) Data.Output = Result + Catch ex As ThreadInterruptedException + Data.Output = "" + Return Catch ex As Exception - If ex.GetType.Name = "ThreadInterruptedException" Then - Data.Output = "" - Return - ElseIf GetExceptionSummary(ex).Contains("429") Then + If ex.GetBrief.Contains("(429)") Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 获取 Authlib-Injector 皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint) - ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then + ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载") Else diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb index 2a3ffd5..65b5086 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb @@ -1,5 +1,7 @@ -Public Class PageLaunchRight - Implements IRefreshable +Imports System.Windows.Threading + +Public Class PageLaunchRight + Implements IRefreshable, IDispatcherUnhandledException Private Sub Init() Handles Me.Loaded PanBack.ScrollToHome() @@ -27,7 +29,7 @@ ''' 刷新主页。 ''' Private Sub Refresh() Handles Me.Loaded - RunInNewThread( + RunInThread( Sub() Try SyncLock RefreshLock @@ -36,37 +38,20 @@ Catch ex As Exception Log(ex, "加载 PCL 主页自定义信息失败", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint)) End Try - End Sub, $"刷新主页 #{GetUuid()}") + End Sub) End Sub Private Sub RefreshReal() - Dim Content As String = "" - Dim Url As String + Dim Content As String = Nothing, Url As String = Nothing Select Case Setup.Get("UiCustomType") Case 1 '加载本地文件 Log("[Page] 主页自定义数据来源:本地文件") Content = ReadFile(Path & "PCL\Custom.xaml") 'ReadFile 会进行存在检测 Case 2 + '联网下载 Url = Setup.Get("UiCustomNet") -Download: - '加载联网文件 - If String.IsNullOrWhiteSpace(Url) Then Exit Select - If Url = Setup.Get("CacheSavedPageUrl") AndAlso File.Exists(PathTemp & "Cache\Custom.xaml") Then - '缓存可用 - Log("[Page] 主页自定义数据来源:联网缓存文件") - Content = ReadFile(PathTemp & "Cache\Custom.xaml") - '后台更新缓存 - OnlineLoader.Start(Url) - Else - '缓存不可用 - Log("[Page] 主页自定义数据来源:联网全新下载") - Hint("正在加载主页……") - RunInUiWait(Sub() LoadContent("")) '在加载结束前清空页面 - Setup.Set("CacheSavedPageVersion", "") - OnlineLoader.Start(Url) '下载完成后将会再次触发更新 - Return - End If Case 3 + '预设 Select Case Setup.Get("UiCustomPreset") Case 0 Log("[Page] 主页预设:你知道吗") @@ -88,50 +73,58 @@ Download: " Case 2 Log("[Page] 主页预设:Minecraft 新闻") - Url = "https://pcl.mcnews.thestack.top" - GoTo Download + Url = "https://mcnews.meloong.com" Case 3 Log("[Page] 主页预设:简单主页") Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/MFn233/Custom.xaml" - GoTo Download Case 4 Log("[Page] 主页预设:每日整合包推荐") Url = "https://pclsub.sodamc.com/" - GoTo Download Case 5 Log("[Page] 主页预设:Minecraft 皮肤推荐") Url = "https://forgepixel.com/pcl_sub_file" - GoTo Download Case 6 Log("[Page] 主页预设:OpenBMCLAPI 仪表盘 Lite") Url = "https://pcl-bmcl.milu.ink/" - GoTo Download Case 7 Log("[Page] 主页预设:主页市场") Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/JingHai-Lingyun/Custom.xaml" - GoTo Download Case 8 Log("[Page] 主页预设:更新日志") Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Joker2184/UpdateHomepage.xaml" - GoTo Download Case 9 Log("[Page] 主页预设:PCL 新功能说明书") Url = "https://raw.gitcode.com/WForst-Breeze/WhatsNewPCL/raw/main/Custom.xaml" - GoTo Download Case 10 Log("[Page] 主页预设:OpenMCIM Dashboard") Url = "https://files.mcimirror.top/PCL" - GoTo Download Case 11 Log("[Page] 主页预设:杂志主页") Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Ext1nguisher/Custom.xaml" - GoTo Download Case 12 Log("[Page] 主页预设:PCL GitHub 仪表盘") Url = "https://ddf.pcl-community.org/Custom.xaml" - GoTo Download End Select End Select + '联网下载 + If Not String.IsNullOrWhiteSpace(Url) Then + If Url = Setup.Get("CacheSavedPageUrl") AndAlso File.Exists(PathTemp & "Cache\Custom.xaml") Then + '缓存可用 + Log("[Page] 主页自定义数据来源:联网缓存文件") + Content = ReadFile(PathTemp & "Cache\Custom.xaml") + '后台更新缓存 + OnlineLoader.Start(Url) + Else + '缓存不可用 + Log("[Page] 主页自定义数据来源:联网全新下载") + Hint("正在加载主页……") + RunInUiWait(Sub() LoadContent(Nothing)) '在加载结束前清空页面 + Setup.Set("CacheSavedPageVersion", "") + OnlineLoader.Start(Url) '下载完成后将会再次触发更新 + Return + End If + End If + '同步到 UI RunInUi(Sub() LoadContent(Content)) End Sub Private RefreshLock As New Object @@ -169,15 +162,13 @@ Download: Log($"[Page] 无法检查联网主页版本,将直接下载,检查源:{VersionAddress}") End Try '实际下载 - If NeedDownload Then - Dim FileContent As String = NetRequestByClientRetry(Address) - Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}") - Setup.Set("CacheSavedPageUrl", Address) - Setup.Set("CacheSavedPageVersion", Version) - WriteFile(PathTemp & "Cache\Custom.xaml", FileContent) - End If - '要求刷新 - RunInUi(AddressOf Refresh) '不直接调用 Refresh,以防止死循环(#6245) + If Not NeedDownload Then Return + Dim FileContent As String = NetRequestByClientRetry(Address) + Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}") + Setup.Set("CacheSavedPageUrl", Address) + Setup.Set("CacheSavedPageVersion", Version) + WriteFile(PathTemp & "Cache\Custom.xaml", FileContent) + Refresh() '要求刷新 Catch ex As Exception Log(ex, $"下载主页失败({Address})", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint)) End Try @@ -215,19 +206,19 @@ Download: ''' 必须在 UI 线程调用。 ''' Private Sub LoadContent(Content As String) - SyncLock LoadContentLock - '如果加载目标内容一致则不加载 - Dim Hash = Content.GetHashCode() - If Hash = LoadedContentHash Then Return - LoadedContentHash = Hash - '实际加载内容 - PanCustom.Children.Clear() - If String.IsNullOrWhiteSpace(Content) Then - Log($"[Page] 实例化:清空主页 UI,来源为空") - Return - End If - Dim LoadStartTime As Date = Date.Now - Try + Try + SyncLock LoadContentLock + '如果加载目标内容一致则不加载 + Dim Hash = If(Content, "").GetHashCode() + If Hash = LoadedContentHash Then Return + LoadedContentHash = Hash + '实际加载内容 + PanCustom.Children.Clear() + If String.IsNullOrWhiteSpace(Content) Then + Log($"[Page] 实例化:清空主页 UI,来源为空") + Return + End If + Dim LoadStartTime As Date = Date.Now '修改时应同时修改 PageOtherHelpDetail.Init Content = HelpArgumentReplace(Content) Do While Content.Contains("xmlns") @@ -236,26 +227,38 @@ Download: Content = "" & Content & "" Log($"[Page] 实例化:加载主页 UI 开始,最终内容长度:{Content.Count}") PanCustom.Children.Add(GetObjectFromXML(Content)) - Catch ex As Exception - If ModeDebug Then - Log(ex, "加载失败的主页内容:" & vbCrLf & Content) - If MyMsgBox(If(TypeOf ex Is UnauthorizedAccessException, ex.Message, $"主页内容编写有误,请根据下列错误信息进行检查:{vbCrLf}{GetExceptionSummary(ex)}"), - "加载主页界面失败", "重试", "取消") = 1 Then - GoTo Refresh '防止 SyncLock 死锁 - End If - Else - Log(ex, "加载主页界面失败", LogLevel.Hint) - End If - Return - End Try - Dim LoadCostTime = (Date.Now - LoadStartTime).Milliseconds - Log($"[Page] 实例化:加载主页 UI 完成,耗时 {LoadCostTime}ms") - If LoadCostTime > 3000 Then Hint($"主页加载过于缓慢(花费了 {Math.Round(LoadCostTime / 1000, 1)} 秒),请向主页作者反馈此问题,或暂时停止使用该主页") - End SyncLock - Return -Refresh: - ForceRefresh() + '加载计时 + Dim LoadCostTime = (Date.Now - LoadStartTime).Milliseconds + Log($"[Page] 实例化:加载主页 UI 完成,耗时 {LoadCostTime}ms") + If LoadCostTime > 3000 Then Hint($"主页加载过于缓慢(花费了 {Math.Round(LoadCostTime / 1000, 1)} 秒),请向主页作者反馈此问题,或暂时停止使用该主页") + End SyncLock + Catch ex As Exception + Log(ex, "加载失败的主页内容:" & vbCrLf & Content) + OnLoadContentFailed(ex) + End Try End Sub + ''' + ''' 加载主页失败时调用。 + ''' + Private Sub OnLoadContentFailed(ex As Exception) + If ModeDebug OrElse Setup.Get("UiCustomType") = 1 Then + Log(ex, "加载主页失败") + If MyMsgBox(If(TypeOf ex Is UnauthorizedAccessException, ex.Message, $"主页内容编写有误,请根据下列错误信息进行检查:{vbCrLf}{ex.GetBrief}"), + "加载主页失败", "重试", "取消") = 1 Then ForceRefresh() + Else + Log(ex, "加载主页失败", LogLevel.Hint) + End If + End Sub + ''' + ''' 捕获主页在 Measure 和 Arrange 阶段抛出的异常。 + ''' + Private Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Implements IDispatcherUnhandledException.DispatcherUnhandledException + If TypeOf e.Exception IsNot Markup.XamlParseException Then Return + e.Handled = True + LoadContent(Nothing) + OnLoadContentFailed(e.Exception) + End Sub + Private LoadedContentHash As Integer = -1 Private LoadContentLock As New Object diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb index 325a077..4b808f9 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb @@ -86,22 +86,21 @@ IsChanging = True '开始实际获取 RunInNewThread( - Async Sub() + Sub() Try Retry: - If McLoginMsLoader.State = LoadState.Loading Then McLoginMsLoader.WaitForExit() '等待登录结束 + Do While McLoginMsLoader.State = LoadState.Loading '等待登录结束 + Thread.Sleep(10) + Loop + If McLoginMsLoader.State = LoadState.Failed Then Throw New Exception("登录失败", McLoginMsLoader.Error) Dim AccessToken As String = Setup.Get("CacheMsV2Access") Dim Uuid As String = Setup.Get("CacheMsV2Uuid") - - Dim Client As New Net.Http.HttpClient With {.Timeout = New TimeSpan(0, 0, 30)} - Client.DefaultRequestHeaders.Authorization = New Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccessToken) - Client.DefaultRequestHeaders.Accept.Add(New Net.Http.Headers.MediaTypeWithQualityHeaderValue("*/*")) - Client.DefaultRequestHeaders.UserAgent.Add(New Net.Http.Headers.ProductInfoHeaderValue("MojangSharp", "0.1")) - Dim Contents As New Net.Http.MultipartFormDataContent From { - {New Net.Http.StringContent(If(SkinInfo.IsSlim, "slim", "classic")), "variant"}, - {New Net.Http.ByteArrayContent(ReadFileBytes(SkinInfo.LocalFile)), "file", GetFileNameFromPath(SkinInfo.LocalFile)} - } - Dim Result As String = Await (Await Client.PostAsync(New Uri("https://api.minecraftservices.com/minecraft/profile/skins"), Contents)).Content.ReadAsStringAsync + Dim Result As String = NetRequestByClientRetry("https://api.minecraftservices.com/minecraft/profile/skins", HttpMethod.Post, + Content:=New Net.Http.MultipartFormDataContent From { + {New Net.Http.StringContent(If(SkinInfo.IsSlim, "slim", "classic")), "variant"}, + {New Net.Http.ByteArrayContent(ReadFileBytes(SkinInfo.LocalFile)), "file", GetFileNameFromPath(SkinInfo.LocalFile)} + }, + Headers:={{"Authorization", "Bearer " & AccessToken}, {"Accept", "*/*"}, {"User-Agent", "MojangSharp/0.1"}}) If Result.Contains("request requires user authentication") Then Hint("正在登录,将在登录完成后继续更改皮肤……") McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True) @@ -122,8 +121,8 @@ Retry: Next Throw New Exception("未知错误(" & Result & ")") Catch ex As Exception - If ex.GetType.Equals(GetType(Tasks.TaskCanceledException)) Then - Hint("更改皮肤失败:与 Mojang 皮肤服务器的连接超时,请检查你的网络是否通畅!", HintType.Critical) + If TypeOf ex Is OperationCanceledException Then + Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Critical) Else Log(ex, "更改皮肤失败", LogLevel.Hint) End If diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml index 5298fd7..38f1c20 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml @@ -1,452 +1,493 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb index 534af41..5ecaa33 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb @@ -132,7 +132,7 @@ End Sub Public Shared Sub TryFeedback() If Not CanFeedback(True) Then Return - Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请使用 VPN 以改善网络环境。", + Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请使用 VPN 改善网络环境。", "反馈", "提交新反馈", "查看反馈列表", "取消") Case 1 Feedback(True, False) @@ -146,7 +146,7 @@ e.Handled = True End Sub Public Shared Sub TryVote() - If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请使用 VPN 以改善网络环境。", + If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请使用 VPN 改善网络环境。", "新功能投票", "打开", "取消") = 2 Then Return 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 diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb index 303120b..7d8d927 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb @@ -317,9 +317,6 @@ 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/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index b1e7698..f207a1d 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -218,7 +218,7 @@ - + @@ -255,7 +255,7 @@ - diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb index cf7f598..d194840 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb @@ -71,7 +71,7 @@ Try Setup.Reset("LaunchArgumentTitle") Setup.Reset("LaunchArgumentInfo") - Setup.Reset("LaunchArgumentIndieV2") + Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndieV2")) Setup.Reset("LaunchArgumentVisible") Setup.Reset("LaunchArgumentWindowType") Setup.Reset("LaunchArgumentWindowWidth") @@ -585,4 +585,12 @@ PreFin: FrmMain.PageChange(FormMain.PageType.VersionSetup, FormMain.PageSubType.VersionSetup) End Sub + '去除参数中的回车 + Private Sub ReplaceEnter(sender As MyTextBox, e As TextChangedEventArgs) Handles TextAdvanceJvm.TextChanged, TextAdvanceGame.TextChanged + Dim NewText = sender.Text.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ") + If NewText = sender.Text Then Return + Dim CaretIndex = sender.CaretIndex + sender.Text = NewText + sender.CaretIndex = Math.Max(0, CaretIndex - 1) + End Sub End Class diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml index 43cd397..6cdea43 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml @@ -150,7 +150,7 @@ + ToolTip="当下载失败、PCL 出错、游戏崩溃等事件时,PCL 会匿名上报情况,以通知龙猫 “哦吼,出错了”,让龙猫能更好地修 Bug…… 你可以关闭上报,不过龙猫就不知道你遇到了什么 Bug 了…… PCL 不会上传或收集任何个人信息,包括上报的东西里面也没有! 详情:https://github.com/Meloong-Git/PCL/discussions/7036" /> diff --git a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb index 0bcad59..3796d42 100644 --- a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb @@ -92,7 +92,7 @@ Public Class PageSpeedLeft Card.Children.Clear() Card.Children.Add(GetObjectFromXML("")) Dim Tb As TextBlock = GetObjectFromXML("") - Tb.Text = GetExceptionDetail(Loader.Error) + Tb.Text = Loader.Error.GetDetail() AddHandler Tb.MouseLeftButtonDown, Sub(sender As TextBlock, e As EventArgs) ClipboardSet(sender.Text, False) diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml index 9483d95..3574f12 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml @@ -106,6 +106,14 @@ DefaultChecked="True" /> + + + + + - - - - - - + @@ -219,7 +219,8 @@ - + @@ -282,18 +283,12 @@ PageDownloadMod.xaml - - PageDownloadFabric.xaml - PageDownloadInstall.xaml PageDownloadCompDetail.xaml - - PageDownloadNeoForge.xaml - MyMsgLogin.xaml @@ -352,15 +347,6 @@ - - PageDownloadForge.xaml - - - PageDownloadLiteLoader.xaml - - - PageDownloadOptiFine.xaml - PageOtherTest.xaml @@ -394,9 +380,6 @@ PageDownloadLeft.xaml - - PageDownloadClient.xaml - PageOtherLeft.xaml @@ -556,10 +539,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - MSBuild:Compile Designer @@ -568,10 +547,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - MSBuild:Compile Designer @@ -625,18 +600,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -681,10 +644,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - MSBuild:Compile Designer diff --git a/Plain Craft Launcher 2/Resources/ModData.txt b/Plain Craft Launcher 2/Resources/ModData.txt index 88ddec9..f40150f 100644 --- a/Plain Craft Launcher 2/Resources/ModData.txt +++ b/Plain Craft Launcher 2/Resources/ModData.txt @@ -92,7 +92,7 @@ applied-energistics-1@ae1|应用能源* Universal-Electricity|通用电力* @better-than-wolves|比狼好 (Better Than Wolves) aether@|天境 (The Aether) - +@miscperipherals|电脑外设 (MiscPeripherals) assassincraft-assassins-creed-mod|刺客信条 (AssassinCraft) @@ -120,7 +120,7 @@ blockphysics_by_id_miner|方块物理学 (BlockPhysics) remain-in-motion|运动红石机构 (Remain in Motion) - +openperipheralcore|开放式电脑外设 (OpenPeripheral) - 核心¨openperipheraladdons|开放式电脑外设 (OpenPeripheral) - 附加¨openperipheralintegration|开放式电脑外设 (OpenPeripheral) - 联动 modular-powersuits@modularpowersuits|模块化动力套装* @@ -368,7 +368,7 @@ steves-factory-manager|史蒂夫工厂管理 (Steve's Factory Manager)¨steves-f thaumcraftgates|奥法管道 (ThaumcraftGates) - +thaumcraft-recipes|神秘合成* player-heads-forge|玩家死亡掉头 (Player Heads) notenoughkeys|更好的按键控制 (Not Enough Keys) @@ -522,7 +522,7 @@ inventory-pets|背包宠物* levels|装备等级* keeping-inventory orespawn|矿石菌种 (OreSpawn) -reliquary-reincarnations|圣遗物 (Reliquary Reincarnations (Reliquary) / Xeno's Reliquary)¨reliquary|圣遗物 (Reliquary Reincarnations* / Xeno's Reliquary) +reliquary-reincarnations@|圣遗物 (Reliquary Reincarnations (Reliquary) / Xeno's Reliquary)¨reliquary|圣遗物 (Reliquary Reincarnations* / Xeno's Reliquary) mo-bends|更多弯曲 (Mo' Bends) quark@|夸克* @@ -782,7 +782,7 @@ sleeping-bag|睡袋* the-one-probe@|检测器* simply-jetpacks-2|简易喷气背包2* -snad|子沙* +snad@|子沙* storage-drawers-extras solar-flux-reborn@|日光通量:重制版* simple-void-world|简单虚空世界* @@ -963,7 +963,7 @@ blue-power|蓝石力量 (Blue power) redstone-armory|红石军械库* qmunitylib quest-for-blue-name -the-last-smith|最后的太刀匠人* +the-last-smith@|最后的太刀匠人* mineamp|我的音乐 (MineAmp) quiverbow|Quiver Bow - 官方原版¨quiverbow-restrung|Quiver Bow - 重制版 cookiecore @@ -1054,7 +1054,7 @@ radixcore familiar-fauna|熟悉的动物* tinkers-tool-leveling|匠魂工具升级 (Tinkers' Tool Leveling) -arcana-rpg-final-version|神秘RPG (Arcana RPG) + ingame-info-xml|游戏信息显示XML版 (InGame Info XML) lovelyrobot|萌化机器人 (LovelyRobot) @@ -1341,7 +1341,7 @@ thaumic-calculations|神秘计算器* pams-weee-flowers|潘马斯花园 (Pam's Weee! Flowers) -paragliders|滑翔伞* +paragliders@|滑翔伞* ender-io-endergy|末影接口:管道拓展 (Ender IO:Endergy) bwm-suite ambience-music-mod|环境音乐 (Ambience) @@ -1489,7 +1489,7 @@ bitcoin|比特币* hungering-darkness roots-classic|根源魔法经典版* embers-rekindled@embers|余烬复刻版* -vending-machines-revamped|自动贩卖机 (VendingMachinesRevamped) +vending-machines-revamped|自动贩卖机* thaumic-alchemy|神秘炼金学* move-plus|移动方式增强* @@ -1752,7 +1752,7 @@ toolscombine|工具组合 (Tools Combine) pollutant-pump|污染泵* infinite-pollution-filter|无限污染过滤器* jei-hider -fastflyblockbreaking|飞行速破 (Fast Fly Block Breaking) +fastflyblockbreaking|飞行速破 (FastFlyBlockBreaking) planttech-2|植物科技2 (PlantTech 2) toast-control|消息框控制* @@ -1837,7 +1837,7 @@ spice-of-life-carrot-edition|生活调味料:胡萝卜版 (Spice of Life: Carr refined-exchange rs-requestify|精致存储:请求者 (Refined Storage: Requestify) pizzacraft@|披萨工艺 (PizzaCraft) -super-factory-manager@|超级工厂管理* +super-factory-manager@|超级工厂管理器* sakura|樱* sips|更多液体容器* gregs-construct|格雷与匠魂 (Greg's Construct) @@ -1873,7 +1873,7 @@ auto-feeder-helmet|自动进食头盔* cc-tweaked-fabric simple-farming|简单农业* bigger-packets-please -lockyz-extra-dimensions-mod|更多维度世界 (Lockyz Extra Dimensions) +lockyz-extra-dimensions-mod|更多维度世界* - 已失效¨@ledm|更多维度世界 (Lockyz Extra Dimensions Mod) psi-combat-magic dtphc|动态的树:潘马斯附属 (Dynamic Trees - Pam's Harvestcraft Compat) largefluidtank@large-fluid-tank|大型储罐 (Large Fluid Tank)¨largefluidtank-fabric|大型储罐 (Large Fluid Tank) @@ -2155,7 +2155,7 @@ usefultnt|更有用的TNT (UsefulTNT) crow-flight|乌鸦坐飞机* findme@|东寻西觅 (FindMe) minecraft-transit-railway@|我的世界铁路* -random-loot-mod|随机战利品* +random-loot-mod@randomloot|随机战利品* tinkers-aether|天境工匠* mine-and-slash-auto-compatibility|挖矿与砍杀自动兼容 (Mine and Slash Auto Compatibility) @@ -2189,7 +2189,7 @@ glibys-physics|Gliby的物理 (Gliby's Physics) electro-magic-tools-ic2-classic|电子神秘工具 (Electro-Thaumic Tools - IC2 Classic) forceasciifont|强制ASCII字体 (ForceASCIIFont) skewers|烤串* -get-in-the-bucket-mod|进入水桶*¨get-in-the-bucket-mod-fabric|进入水桶 (Get In The Bucket Mod) +get-in-the-bucket-mod@|进入水桶*¨get-in-the-bucket-mod-fabric|进入水桶 (Get In The Bucket Mod) fertilization|施肥*¨fertilization-fabric|施肥 (Fertilization) additional-banners@|更多旗帜* dropt @@ -2300,7 +2300,7 @@ infraredstone incorporeal|幻想多媒体* amun-ra -mysterious-mountain-lib|妖怪之山通用库 (MysteriousMountainLib) +mysterious-mountain-lib@mmlib|妖怪之山通用库 (MysteriousMountainLib) loottweaker rediscovered@|特性重现* @@ -2343,7 +2343,7 @@ multiverse-pouch|多元宇宙袋* electroblobs-wizardry-twilight-forest-spell-pack|巫术学:暮色森林法术包 (Electroblob's Wizardry: Twilight Forest Spell Pack) blockbuster fishs-undead-rising|Fish的不死崛起/Fish的亡灵崛起 (Fish's Undead Rising) -cloth-config@ +cloth-config@¨cloth-config-legacy just-map too-realistic-inventory|真实背包 (Realistic Inventory) mob-farm|生物农场* @@ -2451,7 +2451,7 @@ kubejs@¨kubejs-fabric tickcentral lowocalizatiwn redlib -modern-ui|现代化 UI (Modern UI) - 主页¨@modern-ui|现代化 UI (Modern UI) +modern-ui@|现代化 UI (Modern UI) auto-config-updated-api medieval-madness|中世纪的疯狂* @@ -2478,7 +2478,7 @@ lang_tool|加长工具 (lang_tool) emojiful item-compatibility -aether-lost-content@|天境: 遗失之物 (Aether: Lost Content) +aether-lost-content@|天境: 遗失之物 (Aether: Lost Content Addon) gunpowderlib nuclearcraft-overhauled@|核电工艺:重制版 (NuclearCraft: Overhauled) nei-lotr|魔戒NEI插件 (NEI LotR) @@ -2923,7 +2923,7 @@ netherite-horse-armor@netherite_horse_armor|下界合金马铠* tacocraft-fabric lootr@¨lootr-fabric biolib@bio-library -scavenge +scavenge@ heaven-earth-ring|乾坤戒指* @@ -3050,7 +3050,7 @@ light-overlay@|亮度覆盖* smallerfpsincrements regex-filters|正则表达式过滤器* -fluidsystem|流体系统 (FluidSystem) +fluidsystem@|流体系统 (FluidSystem) falling-tree@fallingtree|伐树 (FallingTree) untranslated-items|物品名称双语同屏* particle-culling@|粒子渲染机制优化* @@ -3128,7 +3128,7 @@ cardinal-energy|能量基本* travel-anchors@|旅行锚* bnbgaminglib triumph -overloaded-armor-bar|护甲上限突破*¨overloaded-armor-bar-fabric|护甲上限突破 (Overloaded Armor Bar) +overloaded-armor-bar@|护甲上限突破*¨overloaded-armor-bar-fabric|护甲上限突破 (Overloaded Armor Bar) toomanydanyores glass-pipe|玻璃管道* origins-classes@|起源:职业 (Origins: Classes) @@ -3374,7 +3374,7 @@ dawn assembly-line-machines|装配线* netherending-ores Flex-FOV|扭曲视野* -hooked|抓钩* +hooked@|抓钩* createtweaker@ iTorch @@ -3407,7 +3407,7 @@ avatar-mod-2-out-of-the-iceberg|降世神通:突破冰山 (Avatar Mod 2: Out o lazydfu@|DFU载入优化 (LazyDFU) ridable-ender-pearls|可骑乘末影珍珠* -gildedarmor|镀金盔甲 (GildedArmor) +gildedarmor@|镀金盔甲 (GildedArmor) deadly-book|致命书本* speedy-hoppers|极速漏斗* indium@|铟* @@ -3666,7 +3666,7 @@ gt-remastered|格雷科技重制版 (GT: Remastered) tips@|提示* Interactio advancement-book|进度之书* -drawers-tooltip|抽屉提示* +drawers-tooltip@|抽屉提示* paradise-lost@|天境:重生* @gregfood|格雷的食物 (GregFood) @@ -3757,7 +3757,7 @@ loading-timer@|加载时长显示* ocrocketry chime-fabric@chime witherite@|凋灵合金* -mermod-fabric@mermod¨mermaid-tail-mod|人鱼尾*¨mermod-forge +mermod-fabric@mermod¨mermaid-tail-mod|人鱼尾*¨mermod-neoforge astromine-core|天体矿工:核心 (Astromine: Core) dungeondq dynamic-trees-integrated-dynamics|动态的树:动态联合/集成动力附属 (Dynamic Trees - Integrated Dynamics) @@ -3776,7 +3776,7 @@ flower-seeds astemirs-forest-craft astromine-foundations|天体矿工:基础 (Astromine: Foundations) -create-stuff-additions@|机械动力:物品附加 (Create Stuff & Additions) +create-stuff-additions@|机械动力:物品附加 (Create Stuff 'N Additions) slab-machines|台阶型设备* more-create-stuffs|更多机械动力物品* rsinfinitybooster|RS 无限范围增幅卡 (RSInfinityBooster) @@ -3885,10 +3885,10 @@ easy-villagers@|村民物品化/简单村民* largebar tis-3d@tis3d entity-nan-health-fix|假死修复 (Entity NaN Health Fix) -ferritecore@ferrite-core|铁氧体磁芯 (FerriteCore)¨ferritecore-fabric|铁氧体磁芯 (FerriteCore) +ferritecore-fabric@ferrite-core|铁氧体磁芯 (FerriteCore)¨ferritecore|铁氧体磁芯 (FerriteCore) thaumcraft-research-patcher masterful-machinery@ -minecraft-earth-mod|Derec 的地球生物 (Derec's Earth Mobs) +minecraft-earth-mod@earth-mobs-mod|Derec 的地球生物 (Derec's Earth Mobs) scotts-essential drawerfps|储物抽屉渲染配置 (DrawerFPS) @@ -4089,7 +4089,7 @@ minemenufabric@|我的菜单 Fabric 版 (MineMenuFabric) @tabtps afkpeace@ - +secret-rooms-fabric@ smooth-chunks|平滑区块加载*¨@smooth-chunks|平滑区块加载 (Smooth Chunks) - 已失效 saturationoverflow|饱和度溢出 (SaturationOverflow) @@ -4160,7 +4160,7 @@ selene@moonlight compatibilitylayerforcustomskinloader|万用皮肤补丁兼容层 (CompatibilityLayerForCustomSkinLoader) no-potion-shift no-potion-offset -demonslayer|鬼灭之刃 (Kimetsu no Yaiba) +demonslayer@orca-kimetsu-no-yaiba|鬼灭之刃 (Kimetsu no Yaiba) nautilium|鹦鹉螺* auto-attack|自动攻击* tamagotti @@ -4192,7 +4192,7 @@ wooden-armors-stone-armor@woodenarmor|木制盔甲 (Wooden Armors Mod) insanelib@ daydreamer phantomfaces|幻灵接口* -improved-villagers|改进的村民 (Improved Villager Mod) +improved-villagers|改进的村民* equipment-compare@|装备比较*¨equipment-compare-fabric|装备比较 (Equipment Compare) PickupWidely @@ -4212,7 +4212,7 @@ tfc-cellars-add-on boats-and-beeps konkrete@¨konkrete-fabric xp-from-harvest@|收获经验 (XP From Harvest) -modern-beta¨modern-beta-forge +modern-beta@modern-beta-forge¨modern-beta-forge tfcflux tfc-medicinal water-flasks @@ -4655,7 +4655,7 @@ draggable-resource-packs@draggable-lists|可拖动列表/可拖动资源包 (Dra fastchest|快速箱子渲染 (FastChest) - 已不再更新¨@fastchest|快速箱子渲染 (FastChest) - 在此处更新 yuushya-townscape@|方块小镇* architects-palette-fabric@|建筑师的调色板 (Architect's Palette)¨architects-palette@architects-palette#all-versions|建筑师的调色板 (Architect's Palette) -whisperwoods|低语之森* +whisperwoods@|低语之森* dimensional-dungeons@|维度地牢* bosses-of-mass-destruction@|祸乱鬼魅 (Bosses of Mass Destruction) neoelfeos-medieval-pub-decoration @@ -4941,7 +4941,7 @@ open-to-lan simple-translation@|简单翻译* fx-control@|效果控制 (Fx control) what-are-you-voting-for@ -steam-powered-create|蒸汽动力 (Create: Steam Powered) +steam-powered-create@create-steam-powered|蒸汽动力 (Create: Steam Powered) creepers-burn@CreepersBurn pretty-game-enhhancing-gobblet-pgeg-remake|魔法森林重置版 (Pretty, Game-Enhhancing Gobblet Remake) @@ -5484,7 +5484,7 @@ ae-shield|奕 秉甲* giant-player-boss|巨人玩家 Boss* set-bonus -tntutils +tntutils@ giant-swords|巨剑* terrablender@¨terrablender-fabric¨terrablender-neoforge commandhelper|命令助手 (CommandHelper) @@ -5678,14 +5678,14 @@ delogger just-another-vanilla-addon-java|迫真原版增强 (Just Another Vanilla Addon) spectrite-mod|彩虹 (Spectrite) genshin-nature@|原神:自然* -power-utilities-remastered|能源转换重制* +power-utilities-remastered@power-utilities|能源转换重制* create-gear-addon gun-mod-tl energymeter@ headindex@|头颅菜单 (Head Index) -quantum-generators|量子发电机* -simply-quarries|简易采石场* +quantum-generators@|量子发电机* +simply-quarries@|简易采石场* lethal-peaceful @@ -5774,7 +5774,7 @@ 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@|羊毛靴子* -zawa-evolved|野生动物园:进化 (Zoo and Wild Animals: Evolved) +zawa-evolved@|野生动物园:进化 (Zoo and Wild Animals: Evolved) bongo@|宾果游戏* impersonate@ calemi-core@ccore @@ -5817,7 +5817,7 @@ delete-worlds-to-trash-forge@delete-worlds-to-trash¨delete-worlds-to-trash-fabr simple-magnets@|简易磁铁* -guns-without-roses@ +guns-without-roses@|枪炮“没”瑰* buddycards-expansions|巴迪卡牌扩展* zettai-ars cant-sleep-clowns-will-eat-me@|夜不能寐 (Can't Sleep Clowns Will Eat Me) @@ -5866,7 +5866,7 @@ double-jump-attribute¨@double-jump-attribute|Double Jump Attribute - 已失效 overweight-farming@ healthcare@ betterloadingscreen|加载画面改良 (Better Loading Screen) -fabric-title-changer +fabric-title-changer@ window-title-changer @betterlight @@ -6269,7 +6269,7 @@ extrasounds@ brooms the-stormlight-archive-mod|飓光志* extrae -projecte-integration|等价兼容 (ProjectE Integration) +projecte-integration@|等价兼容 (ProjectE Integration) mekajs antique-dragons@ badpackets@ @@ -6658,7 +6658,7 @@ astemirs-fireflies ender-crop@ modupdater -fivehead +fivehead@5head attained-drops|可种植掉落物* attained-drops-2|可种植掉落物 2* @@ -6673,7 +6673,7 @@ dynamx mob-cards mavm@ @waxednotwaxed -hidden-names|隐藏名称* +hidden-names@|隐藏名称* doph@|远程命中提示 (Ding On Projectile Hit) keybinds@ hat-list@hatlist @@ -6689,12 +6689,12 @@ ftb-backups-2|FTB备份2 (FTB Backups 2) darkkore@ -illagers-wear-armor +illagers-wear-armor@ hudium@ ocean-world@|海洋的世界* cobblegen@ zombie-extreme -modularmovements|模块化动作 (ModularMovements) +modularmovements|模块化动作 (Modular Movements) uncle-js wither-bones|凋灵之骨* sleep-warp@ @@ -6955,7 +6955,7 @@ better-bad-omen can-you-trash-it croptopias-chocolaterie-forge@croptopia-chocolaterie|作物盛景:巧克力工坊 (Croptopia's Chocolaterie)¨croptopias-chocolaterie-fabric|作物盛景:巧克力工坊 (Croptopia's Chocolaterie) crawl@|爬行* -fabric-chisel@chisel-reborn +chisel-reborn@ roost-ultimate@roostultimate cammies-wearable-backpacks@ @@ -6981,7 +6981,7 @@ had-enough-characters medieval-origins -elytra-chestplate-swapper +elytra-chestplate-swapper@ the-ends-flora apollos-additional-structures @@ -7172,7 +7172,7 @@ extrabotany2|额外植物学重置版 (ExtraBotany2) terramine@ aeinfinitybooster@ endertech-infinity|Endertech Infinity - 已失效¨@endertech-infinity -more-frogs-fabric@morefrogs|更多青蛙 (More Frogs) +more-frogs-fabric@morefrogs|更多青蛙2 (More Frogs 2) create-big-cannons@|机械动力:火炮* @@ -7225,7 +7225,7 @@ dynamic-surrounding-resurrected spells-shields -player-companions +player-companions@ technical-engineering-3|科能工程3* extra-utilities-reborn|更多实用设备:重生* @@ -7331,7 +7331,7 @@ textrues-rubidium-options@|TexTrue的铷视频界面 (TexTrue's Rubidium Options rubber-duck|橡皮鸭* unlimited-dragon-eggs-fabric@unlimited-dragon-eggs -moreplayermodels-plus@moreplayermodels+ +moreplayermodels-plus@moreplayermodels+|更多玩家模型+ (MorePlayerModels+) @actioninventory custom-void @@ -7345,7 +7345,7 @@ unusual-end@unusual_end|不同寻常的末地* solar-flux|日光通量* custom-background-color@cbc|自定义背景颜色* -simple-shulker-preview|Simple Shulker Preview - 已弃用¨@simple-shulker-preview +simple-shulker-preview@ bygone-nether|逝去的下界*¨bygone-nether-fabric|逝去的下界 (Bygone Nether) @clickopener kokoalinux-backport @@ -7630,7 +7630,7 @@ the-hordes@ inventory-management@ soul-fired@soul-fire-d -faithfulbackrooms +faithfulbackrooms@ ex-machinis@¨ex-machinis-divitiae-deorum@ goety@|诡厄巫法* torch-hit @@ -8431,7 +8431,7 @@ windchimes@|风铃* diamond-ingots@|钻石锭* inventory-sorting@ -mine-piece +mine-piece@orca-mine-piece mine-piece-animation hbms-nuclear-tech-mod-extended-edition custom-skyboxes @@ -8971,7 +8971,7 @@ json-entity-models-jems@|JSON实体模型 (JSON Entity Models) rain-of-hell -suggestion-provider-fix +suggestion-provider-fix@suggestionproviderfix worldblender@¨world-blender-fabric@worldblender-fabric no-oceans|无海洋的世界* monoblocks @@ -9050,7 +9050,7 @@ chomper flamelib better-planting@ -superfancyclouds-refabricated@|超级华丽云:重筑版 (SuperFancyClouds: Rearchitected) +superfancyclouds-refabricated@|超级华丽云:重织版 (SuperFancyClouds: Refabricated) pet-tracker @splashfox|狐狸加载界面 (SplashFox) @@ -9310,7 +9310,7 @@ easy-experience full-turtle-armor@ -tokusatsu-hero-completion-plan@tokusatsu-hero-instrumentality-project|特摄英雄补完计划 (Tokusatsu Hero Instrumentality Project) +thip@tokusatsu-hero-instrumentality-project|特摄英雄补完计划 (Tokusatsu Hero Instrumentality Project) spyglass-astronomy@ @@ -9440,7 +9440,7 @@ falsetweaks@ metaworlds-unofficial@metaworldsunofficial|元世界:非官方版* tslateffectslib@ tslatentitystatus-tes@tslatentitystatus -@invisible-frames-mod +rounds-invisible-frames@invisible-frames-mod elytra-capes@elytracapes config-swapper viafabricplus@ @@ -9569,7 +9569,7 @@ skybox-loader-forge easy-feed@mcf-easyfeed|简单饲养 (Easy feeding) create-buffers-beams small-boats-mod -reimplemented +redev@ wands-and-wizardry geore-nouveau@ crimsonsteves-more-mobs @@ -9695,7 +9695,7 @@ cursed-difficulty@ algane -dawn-of-time@dawn-of-time-builder-edition +dawn-of-time@ kubejs-projecte@ biome-backport infernal-geode @@ -9859,7 +9859,7 @@ item-obliterator@ you-shall-not-spawn@ hell-on-earth zombie-friends@ - +@stone-craft|石头工艺 (Stone Craft) trade-cycling@ illager-revolution@ extra-trades @@ -9981,7 +9981,7 @@ tskimi-seirans-fruit-wine@tskimiseirans-fruit-wine|月见清兰的果酒 (Tskimi graduated-cylinders@|刻度量筒* fps-monitor -athena@athena-mod +athena¨@athena-mod|Athena / Stitch - 已失效 calm-mornings@ fog-looks-good-now@ guarding@ @@ -10499,7 +10499,7 @@ arboria mekanism-evolution chiseledadditions - +@tinkers_ingenuity|工匠匠心 (Tinkers Ingenuity) gregtech-4|格雷科技4移植版 (GregTech 4 Rewritten) lacrimis productive-villagers¨@productive-villagers|Productive Villagers - 已失效 @@ -10573,7 +10573,7 @@ pokefood armor-souls-reforged aether-redux@the-aether-redux|天境:新生代 (The Aether: Redux) -@banner + staaaaaaaaaaaack@ thanks-i-have-rei@tihr @@ -10848,7 +10848,7 @@ xannoszs-better-minecarts|Xannosz的更好的矿车 (Xannosz's Better Minecarts) meliusvanish@vanish tooltip-tool-tips@ sanity-descent-into-madness@ -@no-darkness-effect +no-darkness-effect-mod@no-darkness-effect|无黑暗效果 (No darkness effect) @seamless-loading-screen-renewed armorskin@ @translucent-glass @@ -11274,7 +11274,7 @@ recipe-book-delight@|配方书乐事* chest-hoppers|箱子漏斗* @gravy-delight ubes-delight@|香芋乐事 (Ube's Delight) -torcherino-unofficial|加速火把非官方版 (Torcherino(Unofficial)) +torcherino-unofficial@torcherino(unofficial)|加速火把非官方版 (Torcherino(Unofficial)) more-bows-and-arrows@ xp-shop acclaimed-origins@ @@ -11333,8 +11333,8 @@ unbreakable-enchantment-forge@unbreakable-enchantment immersive-portal-gun@portal-gun|沉浸式传送枪* tac-craft-tactical-armor-pack @flexible-arms|灵巧双手 (Flexible Arms) -boat-tweaks|船只调整* -new-effect-descriptions@effect-descriptions +boat-tweaks@|船只调整* +new-effect-descriptions@effect-insights diagonal-windows@ illager-invasion@|灾厄侵扰* @@ -11455,7 +11455,7 @@ ilmusu-enchantments@ilmusuenchantments desired-servers@ attributizer@ -reactive@ +reactive@|反应炼金术 (Reactive Alchemy) simple-deepslate minunrpg more-sheep-wools @@ -11473,7 +11473,7 @@ green-thing-mod lmft@ peripheralium@ resource-switcher@|资源切换器 (ResourceSwitcher) -serializationisbad +serializationisbad@ clutter@ no-enchantment-cap-1-12-2-backport survivingtheaftermath|劫后余生 (Surviving The Aftermath) @@ -11753,7 +11753,7 @@ dangerous-oceans @mc-249136-fix|MC-249136 修复 (MC-249136 Fix) @featherfallflying @lidar -anvil-never-too-expensive +anvil-never-too-expensive@ante @crossbow|弩 (Crossbow) @place-item @@ -11847,7 +11847,7 @@ simple-enchantments simple-weapons-for-better-combat@ wither-drops-netherite-templates @spineless -immersive-melodies@ +immersive-melodies@|沉浸式奏乐* theobsidianboat dragon-survival-compatibility@|龙之生存:兼容* bettercookedaxolotls @@ -12221,7 +12221,7 @@ akkamaddis-hadite-coal|Hadite Coal - 新 akkamaddis-arsenic-and-lace|Arsenic and Lace - 新 -xiyus-exchanted-book@xiyus-enchanted-book|饩雨的附魔书 (xiyu's Exchanted book) +xiyus-exchanted-book@xiyus-enchanted-book|饩雨的附魔书 (Xiyu's Enchanted Book) la-abuelita rotted-forge@rotted rustic-furniture-compat @@ -12246,7 +12246,7 @@ snifferent jei-recipe-history@ @yeet macaws-paths-and-pavings@|Macaw的小径和路面 (Macaw's Paths and Pavings) -@chiseled-bookshelf-visualizer +chiseled-bookshelf-visualizer@ @elytra-skins mcv-biome-makeover-lieonlion@ more-chest-variants-lieonlion@ @@ -12316,7 +12316,7 @@ dragon-nest-forge-fabric geode-creatures haraldsbetterstructures squake -ae2-uel-extended|应用能源2非官方拓展 (AE2 Unofficial Extended) +ae2-uel-extended@|应用能源2非官方拓展 (AE2 Unofficial Extended) terraria-calamity-mod|泰拉瑞亚灾厄 (Terraria Calamity) darkrtp-rtp-random-teleport@darkrtp cute-bears @@ -12481,7 +12481,7 @@ weapons-of-miracles-epicfight@weapons-of-miracles|奇迹武器 (Weapons of Mirac @hsrpaths -mcdar|我的世界:地下城法器 (MC Dungeons Artifacts) +mcdar@|我的世界:地下城法器 (MC Dungeons Artifacts) dnt-stronghold-overhaul dnt-swamp-hut-overhaul jaopcagt@ @@ -12699,7 +12699,7 @@ boliviamod leawind-third-person@|Leawind的第三人称 (Leawind's Third Person) fabled-weaponry lazurite@ -lets-do-nethervinery@ +lets-do-nethervinery@|下界葡园 (Let's Do: NetherVinery) shinnyladders|发光梯子/洇荧登纵 (Shinny Ladders) macaws-holidays@ unions-upgrade-scrolls @@ -13326,7 +13326,7 @@ custom-health-bar@ lotrimprovements|魔戒改进 (LOTR Improvements) mousekeyinventoryfix@ visible-offhand@|可见副手* -phosphorcrashfix +phosphorcrashfix@phosphor-crash-fix sound-physics-remixin|籁* hextended-staves@ aquaculture-delight@|水产乐事* @@ -14067,7 +14067,7 @@ tinyfoes pyradium anvilcraft@|铁砧工艺:重力科技 (AnvilCraft) tiny-mob-farm-remastered@|迷你刷怪场:重制* -create-aquatic-ambitions@ +create-aquatic-ambitions@|机械动力:瀚海雄心 (Create: Aquatic Ambitions) ffl-trinkets-compat kaleidios-guns mc-vr-playground @@ -14083,7 +14083,7 @@ kriforfab@ multiscoreboard@ @minerally-me! @cover-them-in-armor -jerotes-village|Jerotes村庄* +jerotes-village|Jerotes村庄-二轮世界 (Jerotes Village - Second Round World) @a-cold-day-in-the-nether @translucencyfix create-ponder@|机械动力:思索* @@ -14674,12 +14674,12 @@ the-fletching-table-mod@ customemoji|自定义表情 (CustomEmoji) sword-soaring|史诗战斗:御剑凌虚 (Epic Fight - Sword Soaring) bigger-ae2@ -scalar +scalar¨scalar-legacy|Scalar - Scala 2.11,一般使用此 trials-chambers-backport@ blus-sign-pack-mts-iv alexs-mediumcore@ sherdsapi@ -@kontraption|精妙机械 (Kontraption) +@kontraption|精致机械 (Kontraption) musketeer-illager daker-acg|Daker ACG工艺 (DakerACG) @@ -14706,7 +14706,7 @@ paniclecraft cpmpvc@|自定义玩家模型Plasmo语音兼容 (Customizable Player Models Plasmo Voice compat) ydms-gunblades-mod@ydms-gunblades warp-pads@warppads -marbleds-arsenal@marbledsarsenal +marbleds-arsenal¨@marbledsarsenal|Marbled's Arsenal - 已失效 @inventory-tabs mob-spawning-fix|生物生成修复* lh-miracle-road|奇迹之路之魂系加点 (LH Miracle Road) @@ -14898,7 +14898,7 @@ froglins create-armor-trim slashblade-resharped@|拔刀剑:重锋 (SlashBlade: Resharped) spawn-mod@ -@smooth-swapping-1.20.4-port +@smooth-swapping-1.20.4-port|平滑转移 1.20.4 移植 (Smooth Swapping 1.20.4 Port) @create-tab-fix scape-and-run-origins@srp-origins origins-extra-keybinds@origins-extra-keybinds-fabric @@ -15196,7 +15196,7 @@ dralards-rock-candy-mod -@explosion-breaks-no-block|爆炸不破坏方块 (Explosion Breaks No Block) +explosion-breaks-no-block@|爆炸不破坏方块* via-romana@ lithic-coins dynamic-trees-for-natures-spirit@|动态的树:自然之灵附属 (Dynamic Trees for Nature's Spirit) @@ -15276,7 +15276,7 @@ no-shield-delay@ musicpower@|音乐之力 (MusicPower) macrocraft@ camerapture@ -simple-voice-radio@ +simple-voice-radio@simple-radio adorned fence-on-slab @@ -15354,7 +15354,7 @@ dragon-mounts-even-more-dragons|龙骑士附属:附加龙 (Dragon Mounts: Even xplus-contingameime@|游戏内输入法 XPlus 版 (XPlus ContingameIME) @watersource2|水源 2 (Water Source 2) radomraidspawn|随机袭击生成 (Random Raid Spawn) -enigmatic-legacy-legacy|神秘²遗物* +enigmatic-legacy-legacy|神秘遗物经典版* nco-java-legacy-lib forge-souls-like-universe@souls-like-universe @@ -15643,7 +15643,7 @@ tilt-break@ item-frament@ water-vision@ randomized-default-fireworks@randomizeddefaultfireworks -glowing-torchflower@glowing-torchflower#all-versions +glowing-torchflower@ @clock-hud-renew cameranoclip barriers-dont-block-rain@ @@ -15799,7 +15799,7 @@ mage-flame@ @powerless-refined-storage forgotten-graves@ day-zombies -morecoal|更多煤炭 +morecoal|更多煤炭 (More Coal) jjku immersive-combat-feathers @@ -15917,7 +15917,7 @@ thaumcraftfix|神秘时代6修复 (Thaumcraft Fix) extended-industrialization@ small-tetra-additions -lets-do-furniture@ +lets-do-furniture@|家居装饰 ([Let's Do] Furniture) tesseract-api-neoforge@tesseract-api tickrate-api spectrelib @@ -15942,7 +15942,7 @@ improved-grindstone themetip@ woodwalkers@ peaceful-moon@ -to-tweaks-irons-spells@ +to-tweaks-irons-spells¨@to-tweaks-irons-spells|T.O Magic 'n Extras - Iron's Spells Addon - 已失效 horde-moon@ laser-mod-2 @@ -15966,7 +15966,7 @@ agenta@ more-beehive-variants@ -persistent-creative-inventory@ +persistent-creative-inventory lets-do-wildernature@|野性自然 ([Let's Do] WilderNature) @quitconfirm @ressourcepackcommand @@ -16083,7 +16083,7 @@ glowy-players-renewed@|玩家高亮:重置版* epic-fight-kenjis-combat-forms mekanism-unleashed@ -easy-cake +easy-cake|简易蛋糕* weave-craft morefarming@|更多农耕 (MoreFarming) iron-bushes @@ -16186,7 +16186,7 @@ jmx@imx another-dyeable-shulkers@|可染色潜影贝* @thatskyinteractions - +@tinker-transplant|匠魂移植 (Tinker Transplant) chisatos-more-redstone@|Chisato的更多红石 (Chisato's More Redstone) assetmover@ duclib@ @@ -16274,7 +16274,7 @@ dragon-gear-renewed @archers-plushies @hephaestus-expansion create-structures-arise@ - +kilt@ gregtech-extended-chemistry-extended ulv-covers-modern farmers-structures @@ -16411,7 +16411,7 @@ scaling-mobs multiline-mastery@ @confetti-stuff pirates-treasures -ars-nouveau-delight@ +ars-nouveau-delight|Ars Nouveau Delight - 已失效¨@ars-nouveau-delight crossbow-enchants@ extendeddrawersaddon@ minecolonies-letsdo @@ -16475,7 +16475,7 @@ fastfood-delight-unofficial|重启速食/快餐乐事非官方版 (FastFood Deli faster-fly-block-breaking|飞行速速破* upgraded-additional-trims create-let-the-adventure-begin@ -bountiful-critters +bountiful-critters@ flashlight-mcowners smartpot@|智能锅 (SmartPot) @@ -16537,7 +16537,7 @@ mca-api sodium-leaf-culling@sodiumleafculling|钠:树叶剔除* homing-xp-orb|引验导追/归航经验球* -multiblocked2 +multiblocked2@ armorstatushud-updated|耐久信息显示更新版 (ArmorStatusHUD Updated) tfc-better-blast-furnace tfc-textile@ @@ -16802,7 +16802,7 @@ gold-nugget-recycling sodium-options-api@ @totom-register -crazyae|疯狂的AE (CrazyAE) +crazyae@|疯狂的AE (CrazyAE) ae2-crafting-tree-legacy|AE2合成树-传统版 (AE2CT-Legacy) ae2-alchemistry-addon @@ -16926,7 +16926,7 @@ project-lambda @pretty-hitboxes-create-addition|可配置的碰撞箱显示-机械动力功能附加 (Pretty Hitbox-Create Addition) watersource-reborn@|水源:重生 (WaterSource: Reborn) locks-unofficial@|锁:非官方版 (Locks-Unofficial) -@epicfight_improve|史诗战斗:改进 (EpicFight:Improve) +epicfight-improve@epicfight_improve|史诗战斗:改进 (EpicFight:Improve) just-in-nether@ jinghui@|镜回 (JingHui) @ripples-of-the-past-mandom-addon|RotP:男人领域附属 (Ripples of the Past: Mandom Addon) @@ -16947,7 +16947,7 @@ enchanting-tweaker@enchanting-tweaks just-a-lot-more-enchantments@ alessandrvs-enchantments@alessandrv-enchantments armor-abilities@ -sleepy-hollows@ +sleepy-hollows@|沉眠空谷* nametag-tweaks@nametagtweaks|名称标签调整 (Nametag (Display) Tweaks) @sodium-no-alerts @@ -17009,7 +17009,7 @@ eventjs backup-manager@ the-root-of-fear@ the-modifiger@ -@keypreset|键位预设 (KeyPreset) +keypreset@|键位预设 (KeyPreset) @tweakeroo-features-menu sanity-prequel|理智:前传 (Sanity: Prequel) ayame-paperdoll@|Ayame 纸娃娃 (Ayame PaperDoll) @@ -17103,8 +17103,8 @@ neepmeat@ @huangsheng1s-more-monster|更多怪物 (HuangSheng1's More Monster) creeping-woods -@zombie-apocalypse-core -dragon-mounts-2-expanded@dragon-mounts-2|龙骑士2:延续 (DragonMounts2: Expanded) +@zac +dragon-mounts-2@|龙骑士2 (DragonMounts2) life-fruits@|生命果* ati-structures fantasy-armor@fantasy_armor|幻想铠甲* @@ -17161,7 +17161,7 @@ tensura-reincarnated@|关于我转生变成史莱姆这档事:转生 (Tensura: transformer|奥特化身:无限 (Becomes Ultraman:Infinity) henshin|变身* ridiculous-world - +enhanced-biomes quartz_craft@|石英工艺 (Quartz_Craft) cursed-stare@ legendblade|传世之刃 (LegendBlade) @@ -17181,7 +17181,7 @@ formidulus@ ames-stranger-things herobrine-origins milkable-blazes@ - +@rotp-stone-legacy|RotP:暗之遗物 (Stone Legacy (Ripples of the Past addon)) furniture-beetle@ mekanism-x-create-northstar@ @createz @@ -17313,7 +17313,7 @@ oo-ee-a-e-a@ sodium-embeddium-options-mod-compat@sodium-options-mod-compat -secureseed-reborn +secureseed-reborn@ creative-eats mazerooms@ friendly-chests @@ -17464,7 +17464,7 @@ qualia-carpet-addition@qca mouse-tweaks-unofficial|鼠标手势非官方版* baubles-lts elysium-api@ -noisethreader +noisethreader@ @no-bats antibat@ illage-and-spell-age @@ -17975,7 +17975,7 @@ distinctpaintings@ @search-on-mcmod|MC百科搜索 (Search on MCMOD) infinite-lava-renewed @better-fabric-console - +mine-tooltips fastchest-reforged @better-caves-archaic|古早洞穴优化 (Better Caves Archaic) @@ -18174,7 +18174,7 @@ stacksonstacks more-of-all@ -popular-biology|热门生物 +popular-biology|热门生物* tolkien-tweaks tolkien-tweaks-mobs-edition soulplied-energistics @@ -18202,7 +18202,7 @@ vehicular-movement-light vehicular-movement onysdvehicles@onysd-vehicles -zunpet-forge|Zun号非官方Forge版 (ZUNPet Forge) +zunpet-forge|Zun号非官方NeoForge/Forge版 (ZUNPet NeoForge/Forge) more-hitboxes@|更多碰撞箱* shipwreck-fix|沉船修复/沉船卡顿优化* @@ -18384,7 +18384,7 @@ thaumic-staff|神秘法杖* thaumic-bases-mystical-rose-rework|神秘基础学玫瑰重制* rhyme-pvz@|PVZ:润 (Rhyme PVZ) ichorium-kit -reforgotten-relics|失落遗物学:重生 (ReForgotten Relics) +reforgotten-relics@|失落遗物学:重生 (ReForgotten Relics) in-your-world@ warpapocalypse@warp-apocalypse @@ -18606,7 +18606,7 @@ bountifulbaubles-reforked@bountifulbaublesreforked|丰富的饰品:重铸 (Bou @mekanismmoremodules|通用机械:更多单元 (Mekanism: More Modules) @better-armor-swap -the-man-from-the-shadow +the-man-from-the-shadow@the-man-from-the-shadow-(horror) @@ -18667,7 +18667,7 @@ configurable-keepinventory better-chat@betterchat mekalus-oculus-fork-with-fixed-mekanism-mekasuit view-emc -@fortitude +fortitude@ experience-ore|经验矿石* passive-torch productive-metalworks @@ -18677,7 +18677,7 @@ spirit-of-fight@|战魂 (Spirit of Fight) grim-bleak@grim-and-bleak carrymyboss ranged-damage-limit|远域挡招/远程伤害保护* -magichem +magichem|魔法炼金 (MagiChem) iglee-library@ component-model-hider @@ -18756,7 +18756,7 @@ dungeon-revival new-order create-more-recipes-addon cbc-nuclear@ -@smart-zombies +smarter-zombies¨@smart-zombies|Smart Zombies - 已失效 libguifoxified@ @@ -18968,20 +18968,20 @@ easy-storage@ezstorage|简易存储* elrols-gui-elevator backrooms-found-footage@minecraft-found-footage exposure-polaroid@ - +surpass@|超限* fallingalchemy|坠落炼金 (FallingAlchemy) fluxloading@ -inventory-swapping +inventory-swapping@ seamless-trading@ sophisticated-auto-walk mmmmmmmmmmmmpatch|试验假人补丁 (MmmMmmMmmMmmPatch) global-datapacks@datapacks world-guard@ - +more-murphy-pumpkins|更多南瓜 (morepumpkins) tetradelight -dragon-mounts-2 + heroes-of-might-and-magic-3@ another-changed randomly-tp|随意传送 (Freely/Randomly TP) @@ -19074,7 +19074,7 @@ screenshot-with-coords@|带标截图 (Screenshot with Coords) ccddi-damage-indicator@ccddi lidar @segmented -goety-gotta-summon-em-all +goety-gotta-summon-em-all@goety-gotta-summon-em-all! aerlune-rpg-2 origin-furs@ grounded-origins-forge@groundedorigins¨grounded-origins-fabric @@ -19099,7 +19099,7 @@ immersive-armor-hud@|沉浸式盔甲 HUD (Immersive Armor HUD) yeast-n-feast@ - +vampirism-tinker|血族工匠 (VampirismTinker) essentia-pipes@|源质管道* msp-backpacks @@ -19169,11 +19169,11 @@ create-advanced-logistics@createadvlogistics create-electric-drive @lachryvisions the-heaven-sword-and-dragon-saber|倚天屠龙 (The Heaven Sword and Dragon Saber) - +dreamtinker@|工匠幻梦 (DreamTinker) wolfs-enchantment-lib|狼王的附魔库 (Wolf's Enchantment Lib) -@caoplus|槽 (Cao) +@caoplus|槽 (CaoPlus) disturb-not-my-dream@|勿扰吾梦 / 起床气* @contigency-shop-lite|轻量兑换所 (Exchange Site Lite) lootbagsrenewal@lootbags|战利品袋重制版 (LootBagsRenewal) @@ -19213,11 +19213,11 @@ blacksmith-weapon|铁匠武器 (Blacksmith_Weapon) mrqx-s-slashblade-core@mrqx%60s-slashblade-core|墨染琴弦的拔刀剑核心 (mrqx's Slashblade Core) goldblade|神刀 (GodBlade) -shanghai-metro-line-one-mod@|上海地铁一号线 (Shanghai Metro Line one Mod) +shanghai-metro-line-one-mod@|上海地铁一号线* cat-burger|猫猫汉堡* -mineral-backpacks +mineral-backpacks@ @furry_bohe|兽薄荷 (Furry Bohe) narcissusfarewell-reflorescence|水仙辞:重绽 (NarcissusFarewell: Reflorescence) @@ -19374,7 +19374,7 @@ emcgadgets custom-armor-bar@ roughly-enough-input-methods@ pirates-doom@ -techrebornjei@ +techrebornjei@|科技复兴JEI (TechRebornJEI) little-joys@ blasting-raw-metal-blocks-into-blocks-fabric-forge@blasting-raw-metal-blocks-into-blocks @@ -19536,7 +19536,7 @@ picks-of-power@ super-hot chill-thrill -pyrellium +pyrellium@ luminous-nether@ brush-mining-machine|刷矿机 (Ore Generator) @@ -19547,7 +19547,7 @@ projecte-kubejs actually-additions-v2-unofficial|实用拓展 V2 非官方版* create-ender-link@ progressive-tweaks -portable-blueprints +portable-blueprints@ carrasconlib@ blockparticlespawning@|方块粒子生成 (Block Particle Spawning) @@ -19556,7 +19556,7 @@ blockparticlespawning@|方块粒子生成 (Block Particle Spawning) aether-dragon-dragon-survival-addon tundra-dragon-dragon-survival-add-on naturalist-delight -@civilizations +civilizations@ block-factory-biomes@block-factorys-biomes underwater-swim-fix-mc-220390|水下游泳修复 (Underwater Swim Fix) scaffolding-backported @@ -19567,7 +19567,7 @@ seeking-immortals-nightmare@|月之石:噩梦之章/求仙者:噩梦之章 ( enchanted-crystal-arrow|魔法水晶箭 (Enchanted Crystal Arrows) touhou-danmaku-kagura-core|东方弹幕神乐核心 (Touhou Danmaku Kagura core) hah-ueuh@hah!-ueuh -eternal-nether@ +eternal-nether@|永恒下界* @@ -19603,7 +19603,7 @@ create-propulsion@|机械动力:推进工程 (Create: Propulsion) event-wrapper@|事件包装器* @modwhitelist|模组白名单 (ModWhiteList) - +@instant-build|瞬时建造 (Instant Build) beacon-conduit-tweaks|信标与潮涌核心调整 (Beacon & Conduit Tweaks) tfc-tools-trim@|群峦传说:工具纹饰 (TFC Tools Trim) @kill-youkai-with-knives @@ -19712,7 +19712,7 @@ shield-system @burythelight command-aliases@ pigium-performance-mod -spectrumjei@ +spectrumjei@|光谱世界JEI (SpectrumJEI) @itemban @skiesclear vminus@ @@ -19854,7 +19854,7 @@ starve-library@|饿死图书馆* infinite-refills|无限续杯* systrace@|一导即出* fluxed-core@ -@myluckyblock|我的幸运方块 (MyLuckyBlock) +myluckyblock@|我的幸运方块 (MyLuckyBlock) backpack-plus|背倍加* @hydration-mod @@ -19917,7 +19917,7 @@ mice@ ancient-golems@|上古傀儡* model-citizens botanical-machinery-extra@tPTR9GcJ -flowerary +flowerary@ extrabotany-reburn@extrabotany|额外植物学:重燃 (Extrabotany: Reburn) @@ -19971,7 +19971,7 @@ simple-incubator@ amethyst-stairs-slabs-and-walls @vessels -legendary-weapons-byititor +legendary-weapons-byititor@legendaryweapons slender-reborn craft-vania-lite pollution-of-create @@ -19999,7 +19999,7 @@ shadered@ sweety-garden@ @twilight-forest-aurora-palace-regeared - +changshengjue|长生诀 (ChangShengJue) medieval-buildings-nether-edition @ribbits-croaks @@ -20007,14 +20007,14 @@ echoes-of-time@ horror-alexbrine zzz-craft|绝区零·星见雅 (ZZZ Miyabi) -cozy-cabins-and-cottages +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) +fast-container-placement@|快速容器放置* @teleport-mod @what-meme crackers-wither-storm-mod-v-sides-soundtrack@ @@ -20038,7 +20038,7 @@ smoking-stuff-a-wizards-hobby time-in-a-bottle-charger@ just-a-sculk-vial@ lockylocks@ -server-i18n-api¨@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 @@ -20073,7 +20073,7 @@ create-remote-terminal@|机械动力:远程终端 (Create: Remote Terminal) songs-of-war@|战争之歌 (Songs of War) cataclysm-summons@ cutepuppymod@ -giant-swamp-tree|沼泽巨树* +giant-swamp-tree@|沼泽巨树* big-oak-tree@|大橡树* giant-tree-stump|大树桩* @@ -20089,7 +20089,7 @@ portaltransform@|传送门嬗变 (PortalTransform) @a555sd|更多的画 (More Paintings) unusual-furniture@ xiaoanmooncake@|小安的月饼狂热 (Xiaoan's Mooncake Fever) -foragers-insight@ +foragers-insight@|巧觅巧食 (Forager's Insight) palestinian-delight@ mekanism-more-machine@mekanismmoremachine|通用机械:更多机器 (Mekanism: More Machine) custom-battleroyale@|自定义大逃杀 (Custom BattleRoyale) @@ -20113,7 +20113,7 @@ tlib@ smoothscreen@ @fuzz -advanced-loot-info@ +advanced-loot-info@|高级战利品信息显示* resourcepackcached@ aotake-sweep|竹叶清* @@ -20161,11 +20161,11 @@ 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) - 审核中 +scoutreforked@|侦察兵 重构 (Scout Reforked) +immersion-in-traffic-context@|沉浸交通道路 (Immersion in Traffic Context) arknightsfurniture@|明日方舟家具 (ArknightsFurniture) gunsmith-lib - +@emixx experiencelib@ mystical-mobs @@ -20211,21 +20211,21 @@ colorwheel-patcher@ - +@llpswsounds|llpsw的大喇叭 (llpswsounds) difficulty-lock@¨difficulty-lock-fabric ryymr@ @lazybeefix lazy-ae2-tweaks coordinates-hud@coordinateshud -just-enough-botania +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-geography@|机械动力:地理学 (Create: Geography) create-apothic-enchanting@ demis-enigmatic-dice@ @@ -20238,7 +20238,7 @@ identity-fix-morph@identity-fix stick-it@ aimod -srp-reset-mod|逃逸寄生体:重置 +srp-reset-mod|逃逸寄生体:重置 (SRP Reset) legendary-relics|传说遗物* true-power@|真正的力量 (True POWER) @@ -20261,7 +20261,7 @@ farms-and-foods@farms-foods @achievement-fix|成就修复 (Achievement Fix) lang-magic-tweaker -spawn-dimension-choice|诞生维度自选* +spawn-dimension-choice@|诞生维度自选* reforge-stone@|重铸石* numericalbotany cameraoverhaul-vintage@ @@ -20311,7 +20311,7 @@ warfare-wings@ avaritias-delight@|无尽乐事 (Avaritia's Delight) mamas-herbs-and-harvest@ vs-orbit@|瓦尔基里:轨道学 (VS Orbit) -touhou-little-maid-spell@ +touhou-little-maid-spell@|车万女仆:万法皆通 (Touhou Little Maid: Spell) despawn-notifier|余影待终/掉落物消失提醒* customnpcs-unofficial-from-betazavr|自定义 NPC BetaZavr 非官方版 (CustomNPCs Unofficial from BetaZavr) @@ -20330,7 +20330,7 @@ create-more-parallel-pipes@ create-garnished-reworked@ create-shimmer|机械动力:微光 (Create: Shimmer) blue-archive-school-banners -cialloblade +cialloblade|重锋:cia出如llo (CialloBlade) windows-keyboard-fixes-returns aquatic-torches-returns cratetech-crate-storage @@ -20348,7 +20348,7 @@ sussy-sniffers firefly-bush-backport@ agritech-trees-automated-trees@agritech-trees agritech-evolved@ -wealizh|WLH工业故事* +wealizh|工业故事* @hydrometallurgy|湿法冶金学 (Hydrometallurgy) mekanism-ecoenergistics-unofficial @@ -20368,21 +20368,21 @@ simple-copy@ natures-aura-render-fix@ creakingchopperenchantment@ daily-rewards@ -initialdimensionreborn - +initialdimensionreborn@ +@oo-ee-a-e-a-fabric the-plugin-mod@ cave-vision @el-addon-retrieve-to-slot aether-gloves-for-all -reducedloot +reducedloot@ @irisfixes|Iris 修复 (IrisFixes) musicjs suspendtheserver|自动挂起服务器 (Suspend The Server) -screenshot-layers +screenshot-layers@ anti-spider-climbing rechiseled-chipped@ @@ -20393,8 +20393,8 @@ cinnabar pquality@ mte-patches -happy-ghast-breeding -happy-ghast-flight +happy-ghast-breeding@ +happy-ghast-flight@ the-angry-ghast@ @morfonica-in-minecraft for-vanilla-flowers @@ -20402,7 +20402,7 @@ for-vanilla-flowers -bedrock-hotbar-offset¨@bedrock-hotbar|Bedrock Hotbar Offset - 审核中 +bedrock-hotbar-offset@bedrock-hotbar mss-more-slot-swap¨more-slot-swap show-all-advancements|显示所有进度*¨@show-all-advancements|显示所有进度 (Show All Advancements) - 审核中 @@ -20419,16 +20419,16 @@ lava-chicken-disc lava-dennis album-go-wall forgotten-java -johanness-renaissance-rebirth@johanness-renaissancerebirth|约翰内斯的文艺复兴:火器重生 (Johannes's Renaissance:Musket Rebirth) +johanness-renaissance-rebirth@johanness-renaissancerebirth|约翰内斯的文艺复兴:火器重生 (Johannes's Renaissance: Musket Rebirth) swarm-infection@ necro-rooted -@mycelial-crisis|菌丝危机:崛起 (Mycelial Crisis: Mushroom Rising) +mycelial-crisis@|菌丝危机:崛起 (Mycelial Crisis: Mushroom Rising) lethalsteelproject|致命拔刀计划 (Lethal Steel Project) lovecraft-girlfriends-3@ @faithful-mace bard-craft - +@bilibilimedia|哔哩哔哩WaterMedia兼容模组 items-timer neo-language-reload@ @oxidizium @@ -20466,11 +20466,11 @@ nature-reimagined ominous-bottle-add only-hammers@ auto-resource|自动资源* -iu-additions +iu-additions@ rubiks-cube|魔方* clockworklib -fabridge +fabridge@ true-fullbright quickslots @@ -20487,7 +20487,7 @@ cbc-chemical-warfare|机械动力火炮:化学与生物战争 (CBC: Chemical W cbc-warium-projectiles|机械动力火炮:Warium炮弹 (CBC: Warium Projectiles) create-colored create-law-and-order|机械动力:法律与秩序 (Create: Law and Order) -blockexporter +blockexporter@ fix-attack-lag@|修复攻击卡顿* @clienthings @misinput @@ -20500,7 +20500,7 @@ porting-dead-libs panoramica¨panoramica-fabric create-mt-integration|资源统合-机械动力配方附加* @valkyrien-warpeace|瓦尔基里:战争与和平 (Valkyrien War and Peace) -sillys-auto-totem +sillys-auto-totem@ repairables-anvil@|可修复的铁砧* @throwable-bricks @now-time|准点报时 (Now Time) @@ -20512,7 +20512,7 @@ lotr-renewed-class-mod|魔戒种族 (LOTR Races) craftable-chain-armors item-cooling-reduction@|物品冷却缩减* -yosbs +yosbs@ @no-bugs @@ -20547,18 +20547,18 @@ philia-amulet|友爱护符 (PhiLia Amulet) stronger-fire-boss|原焰帝臻* myec@|更多渴望:附魔与制造 (More of Yearn: Enchants & Crafts) abandoned-urban|废弃城市 (Abandoned urban) -fungal-hazard|蕈骸迷途 +fungal-hazard|蕈骸迷途* more-useful-copper-2-0@more-useful-copper|更有用的铜 (More Useful Copper) boom-bombs@ polars-exploration-additions@ ozymandias-sundries tensura-enigmatic - +createtraincontrol@|机械动力:列车控制 (CreateTrainControl) create-collection-bundles@ create-enhanced-mastery -create-industrial-tools -create-smart-bounds +create-industrial-tools@ +create-smart-bounds@ create-fantasizing-again@|机械动力:又在幻想了 (Create: Fantasizing Again) crafting-station-jei-edition-updated uncraft-everything@ @@ -20569,7 +20569,7 @@ gregs-modern-construct smart-key-prompts@|灵活按键提示 (SmartKeyPrompts) @zero-whitelist|Zero白名单 (Zero Whitelist) - +milometer|计程器* @pearlchunkloading|珍珠加载区块移植 (Pearl Chunk Loading) clean-drops auto-replace @@ -20577,16 +20577,16 @@ auto-replace simple-villager-follow@ tinkers-experience -hedgehog-companion -i-want-to-spin@|我要开转 (I want to spin) +hedgehog-companion@ +i-want-to-spin@|我要开转* @kiss-fabric - +neo-the-titans@|新泰坦生物 (Neo Titans Mod) nuggets-repair-items@|矿粒修复物品* -terra-atmos|群峦环境音效* +terra-atmos@terra_atmos|群峦环境音效* tlm-true-power@true-power-of-maid|车万女仆:真正的力量 (TLM: True POWER) @@ -20601,7 +20601,7 @@ ancient-structure@ancient-structures wizard-samurai-irons-spells-addon¨@wizard-samurai-irons-spells-addon|Wizard Samurai - Iron's Spells Addon - 更新较慢 survival-instinct -enderzoo-patched +enderzoo-patched@enderzoopatched illager-keep codex-of-champions ars-controle@ @@ -20609,13 +20609,13 @@ medieval-craft-historical-firearms too-many-withers reliquified-l-ender-s-cataclysm@ -@log-creator-finder-findshit|找史大王 / 日志溯源 (findshit) +findshit@log-creator-finder-findshit|找史大王 / 日志溯源 (find log's creator / findshit) enchantment-lookup@|附魔查询* -@mods-checker +@mods-checker|模组校验 (ModsChecker) no-enchanting-xp-requirment client-dynamic-light|客户端动态光源* @@ -20628,7 +20628,7 @@ ae-inventory-profiles tooltips-reforged@|提示框:重铸* @wyr soaring-phantoms@ -tick128¨@tick128|Tick128 - 审核中 + ritchies-projectile-library@rpl modopedia@ @translate-allinone|一体化翻译 (Translate All in One) @@ -20637,13 +20637,13 @@ button-fix@ bucketstacking creeper-no-break-blocks@ create-cog-hud@|机械动力:齿轮 HUD (Create: Cog HUD) - -create-pipe-networks - +@createtools-n-weapons +create-pipe-networks@ +jiagu-reappearance|岁在甲骨* multicolored-redstone-lamps reliquified-twilight-forest@ witch-tower -the-nightmare-000 +the-nightmare-000@ meowmere @fallenflowers-server-economy|Fallenflower的服务器经济 (Fallenflower's Server Economy) deadout-medical-supplies|死亡突围医疗用品 (Deadout Medical) @@ -20706,12 +20706,12 @@ health-indicator-txf@ snowy-weather - +dimensionlock@ fluidium|锍* peritia - +inventory-sort|背包整理* corpse-kibou-no-hana|希望の花 (Corpse: Kibou no Hana) redyeable @natural-motion-blur @@ -20724,7 +20724,7 @@ vintage-vibes@ custom-trades-mod|自定义村民交易* tameable-zombie-horse-and-skeleton-horse|可驯服的僵尸马和骷髅马* mi-tweaks -qa-villager-tweaks|Qa村调整* +qa-villager-tweaks@qa-villager-extends|Qa 村拓展 & 废物革新 (Qa Villager Extends & Waste Innovations) multiblocked-extended-compatibility enum-extender-js @btc @@ -20771,9 +20771,9 @@ jeb-just-enough-book@justenoughbook pal@ data-anchor@ elytra-hopping -oh-more-wandering-trades|更多游商交易*¨@oh-more-wandering-trades|更多游商交易 (Oh More Wandering Trades) - 审核中 +oh-more-wandering-trades@|更多游商交易* beacon-anti-phantom@|不时轻轻肘击幻翼的邻座信标同学 (Beacon Anti-Phantom) - +@flexible-painting|灵活的画 (Flexible Painting) cooler-animations @@ -20792,16 +20792,16 @@ sbm-cardboardboxes bedrock-age|基岩时代* - +return-scepter|回程魔杖* @koniavacraft|娜拉工艺 (Koniavacraft) emojicraft|Emoji工艺* kylin-arm|麒麟臂* server-hats@ -forgotten-ruins +forgotten-ruins@ creeperhost-presents-steves-carts@ perspatium@per-spatium -face-stealer +face-stealer@ offlawn-legacy metalborn @wizards-mod @@ -20819,7 +20819,7 @@ dynamic-trees-for-cobblemon immersive-portal-gun-for-forge@ adorable-hamster-pets@ infinite-lava@|无限熔岩* -ethereal-shots|虚无射击* +ethereal-shots|无限箭制 (Unlimited Arrow Works) epic-fight-super-golem|超级铁傀儡 (Epic Fight - Super Golem) fallen-log-hideout @@ -20829,10 +20829,10 @@ retro-pet-items confluence-dimension-patch|汇流来世维度补丁* convenient-storage|便捷存储* legendreliclib|传世遗产库 (LegendRelicLib) -jauml -nemexlib +jauml@ +nemexlib@ cryonicconfig@ -@knight-lib +knight-lib@ @okom @@ -20841,30 +20841,1120 @@ craft-slabs-back-into-blocks@ highland-statue restore-ore@|复原矿石* factory-blocks@|工厂方块* - +camouflaged-doors rotten-planks-and-logs -create-unify-updated-to-create-6 +create-unify-updated-to-create-6@ create-arm-made-cuisine|机械动力:臂力臂力炒菜 (Create: Arm-made Cuisine) -create-cultivation-craft@|机械动力:栽培工艺 -kaleidoscope-cookery@|森罗物语:烹饪 (kaleidoscope cookery) +create-cultivation-craft@|机械动力:栽培工艺 (Create: Cultivation Craft) +kaleidoscope-cookery@|森罗物语:厨房* retroemi-forge@ @pearlecho @mcdaycounter minecolonies-war-n-taxes falling-down@ -more-red-x-cc-tweaked-compat - +more-red-x-cc-tweaked-compat@ +@speed-uuuuuuup|加加加加加加加速! (Speed Uuuuuuup) wither-reincarnated -toros-auto-attack -creeperfire|自定义烧货 (CreeperFire) -fastspawner|刷怪笼渲染优化/产器速算 (Fast Spawner) -sophisticated-storage-plus|存取思深/精致存储扩容* +toros-auto-attack@ +creeperfire|自定义烧货 / 苦力怕阳光下燃烧 (CreeperFire) +fastspawner|刷怪笼渲染优化/产器速算 (FastSpawner) +sophisticated-storage-plus|存取思深/精妙存储扩容* whaleborne - +uniful newspaper-big-book-bibliocraft-legacy-backport bic-clipboard -templates-2@ \ No newline at end of file +templates-2@ + + + + +workstation-recipe-exporter@|可视化配方编写* +simple-auto-attack +hostile-to-neutral +tracer +fishing-tick-fix +fzmm@ +pasteljei@|彩绘世界JEI (PastelJEI) +e4mc-retro@ + +mantlejs@ +one-enough-hand|只手遮天* +persistent-moon-phases +sakurapack-compatability-patch +cattlestrophic@cattlestrophic! +ae-recipe-tool +minecraft-cursor@ +stalker-creepers-fabric@ +epiv-fight-l-enders-cataclysm-compatibility|史诗战斗灾变兼容 (Epic Fight & L_Enders Cataclysm compatibility) +lunar-nether-backport +lookinmyeyes +more-relics-more-rpg-content@more-relics-rpg +backstab-reforged +armamentarium + +remnant-bosses@ +choccos-mobs@ + + +uncrafting-table-xiaoyu-2009|拆解台 (Uncrafting Table) + +aewirelesschannel|AE无线频道 (AE Wireless Channel) +infinitepower|无尽能源 / 能量叠生 (Infinite Power) +hoshikima@|随意作品* +create-bugfix-clipboard-patch|机械动力:剪贴板修复 (Create Bugfix: Clipboard Patch)¨@create-bugfix-clipboard-patch|机械动力:剪贴板修复 (Create Bugfix: Clipboard Patch) - 审核中 +tool-disassemble|拆器碎散/匠魂工具拆解* +summer-to-life|夏意盎然 (Summer to Life) +@freedraw +geckojs@ +sanandreasps-manager-pack + +touhoulittlemaid-orihime@|车万女仆:织姬 (Touhou Little Maid: Orihime) +more-mod-tetra|更多Tetra模组锻造材料* +cataclysm-dimensions|灾变:维度* + +misttaczskills@|TaCZ:圣化技能扩展 (MistCloud Tacz Skills Extension) +quality-railway@|品质铁路* + +custom-mob-attributes@ +custom-durability@ +@dragons-egg(s) +zombie-variants@ +extra-carts +fairy-factions-revived +netherited@netherited-fireproof¨fabric-netherited +creeperhost-presents-soul-shards@ +myths-n-legends@myths-and-legends +snackcraft|零食工艺 (SnackCraft) + +copper-backport¨@copper-backport|Leaf's Copper Backport - 审核中 + +@shield-indicator|盾牌指示器 (Shield Indicator) +a-bastion-remnant-treasure-reset|堡垒遗迹藏宝室重置 (Bastion Remnant Treasure Reset) +a-bastion-remnant-hoglin-stable-reforged|堡垒遗迹疣猪兽棚重铸 (Bastion Remnant Hoglin Stable Reforged) +colonial-arcana-minecolonies-addon|殖民地魔法 (Colonial Arcana) + +fires-ender-expansion +thetitansneo@|泰坦生物Neo (The Titans Neo) +vectorientation-reneoforge +copyid@|复制ID (CopyID) +@a-lightcolor-beacon +@surveyor +@create-portable-packaging +create-more-pipe-bombs-in-packages@ +create-bugfix-schematic-patch|机械动力:蓝图补丁 (Create Bugfix: Schematic Patch)¨@create-bugfix-schematic-patch|机械动力:蓝图补丁 (Create Bugfix: Schematic Patch) - 待审核 + +create-ornithopter-glider +create-train-parts@ +@breakable-blocks +flux-network-x-cc-tweaked +twotility +opticubes +closets-by-smplesco@ +variety-chests-mod +light-detector +quality-hails + +tiger-shark@tigershark|海虎 (TigerShark) + + + +better-end-cities-better-end@better-end-cities-for-betterend¨better-end-cities-vanilla@better-end-cities-base +fallen-gems-affixes +skill-tree +blatium@ +trimica@ +fantasy-weapons-medieval-series@fantasy_weapons + +ksit@ +bestia@ +@itemfinderinall-items +filterchat +kit-keep-inventory-timer-keepinventorytimer +useful-luck@ +@sneak-fertilizing +p1neros-epic-fight-leawind-compat +custom-entity-leveling@|自定义实体等级* +no-monster-rooms +@carpetrmsaddition +@fireworkindicator +squeedometer-reforged-special-port + +difficulty-patch +squake-reforged@squake-fabric-updated¨squake-fabric-1-20 +squake-reforged-special-port +brigo@ +ae2-web-integration@ +skillet-man-core +p1neros-dialogue-lib + + +create-arm-made-barbeque|机械动力:臂力臂力烧烤 (Create : Arm-made Barbeque) +indestructible-blocks + +epic-fight-super-chef +epicfight-nightfall|史诗战斗:黄昏 (EpicFight-Nightfall) +genesis-official +featurecreep + +fabric-orm-jimmer@ +aces-spell-utils +targeteventbackport +silk-kt@silk + + +@playercommands-(old-mc) +legends-forge-series-vikings-n-northerners +tinkers-battle-spades +@its-not-real +ly-meteors +ashvehicle +@smart-absorption +@mooshees +city-of-chambers +tower-of-chambers@tower-of-chambers-neoforge + + +eypipes +shipping-bin +hbm-nuclear-tech-mod-community-edition|HBM的核科技社区版 (HBM's Nuclear Tech Mod Community Edition) +gte2-coremod@ +japan-props@ + +one-enough-item@ +o123456789@ +friendly-chests-no-fluid +herios-floral-expansion@ +pinkcabbage-transparent-betterscreen|粉红大包菜的屏幕透明度优化 (PinkCabbage Transparent BetterScreen) +wastify-returns +sillys-crossbow-fix@ +thaumpotion-tweaker@ +novoatlas@ +resource-pack-tweaks +night-vision-decay-fix|夜视衰减修复* +roid-tweaker +translatorpp@translatorppv +modular-machinery-community-edition-addons +randomaddon|随意之作 (Random Addon) +myhead|持器射首 / MyGO 你个头 (BanG Dream! It's MyHead!!!) +boss-dps-tracker|查其所伤/Boss伤害追踪器 (Boss DPS Tracker) +wandering-trader-may-leave@ +legendary-tabs-neoport +totem-bar +visualores-meowmel +savebabystriders +bygone-nether-fixes@|逝去的下界修复* +bossesrise + +advancementinfo-update +expanded-mace-enchanting@ +silky-spawners +starhud@ +celeritas-dynamic-lights@ +resistance-balancer@ +silkier-touch@ +wither-spawn-fix@|凋灵生成修复* +accelerated-rendering@acceleratedrendering|加速渲染* +cii@ +simpler-wooden-pipes@ +garden-cloche-backport +bh-boss-hatred@boss-hatred|首领仇恨 (Boss Hatred) + +camol +bbb-unforged +performance-monitoring-enhancer +create-upsizing@ +better-world-loading-forge@better-world-loading¨better-world-loading-fabric + +apocalypse-minimap-hud@apocalypseminimaphud|启示录:小地图Hud* +advancementnomore|进度移除 (AdvancementNoMore) +collisium +dypsis +@chat-plus +mcreatormemfix@ +@atlas +legacy-tweaks@legacytweaks +easy-elevators@|简单电梯* +dakimakura-unofficial|抱枕-非官方 (Dakimakura-Unofficial) +davincing +aeronautical-radio|航空无线电* +hunter-vs-mice@|猎人鼠鼠游戏* +citymod-one-block-one-building@city_mod +gedrite-mod@ +@shorkie +enchantable-boat@ +throw-bricks +animal-pens@animalpens + + + +@glys-can|GLY的罐头 (GLYCan) +fargo-soul|Fargo魂石* +@brokenbows|破损弓 (Broken Bows) +mekanism-trimming|通用机械纹饰* +teleportcakes@|跃迁蛋糕 (Teleport Cakes) +repair-amulet +alpine +clientchatclean|客户端消息清理 (ClientChatClean) +clickmobs +vlib@ +wabbanode-companion +@mcmti|麦克风语音识别文本输入 (Microphone Text Input) +powernexus-menu +hit-delay-fix +betterexcavate|更好的挖掘机制 (BetterExcavate) +optimization-of-campfire-smoke|营火烟雾优化 (Optimization of Campfire Smoke) +armored-combat-enhancer + +@animal-hats-yeah +@raid-restore +asgardandelion-for-botania +trotting-wagons@ +grindstonehoning +rainbowwrenchforjei-reforge|彩虹扳手:Forge版 (RainbowWrenchForJei:ReForge) +better-locate-structure-command|更好的结构定位指令* +att-miss|闪避攻击* +energizexp@ +no-more-magic-choices|更好的铁魔法UI* +patternbetter@|更好的ME样板供应器 (Pattern Better) +legacy-skins@ +@oh-my-pc +vsprinter@ +clickvillagers +@button-packet-id-fix +accessories-compat-layer@ +world-downloader@ +tameable-guinea-pigs +familiarslib +dmr@|龙骑士重制版 (Dragon Mounts Remastered) + +avaritia-expand|无尽贪婪 : 拓展 (Avaritia : Expand) + +ecliptic-seasons-multimod-patch +random-things-returns +infinite-storage-cell +operation-eight-treasure-porridge|八宝粥行动* +elegant-spells@|雅致咒术* +@seelescraft|希儿的工坊 (Seele's Craft) +readyhomes|随时可住 (ReadyHomes) +arknights-weapon|方舟武器* + +illager-warship@ +asian-fountains@ +illager-chef-trader +skyblock-villager +@redstoneer-villager +perrys-urban@ +neo-blahaj + +teleportation-crystal|传送水晶* +majo-spell-enchantment|魔女的魔咒 (Majo's Spell Enchantment) +oaks-capes@ + +cobblemon-ride-on@|Cobblemon:骑乘! (Cobblemon: Ride On!) +clayium-additions +farmers-tfc|农夫群峦 (Farmers TFC) +oaks-delight@ + +pathfinding-edition-for-minecolonies|殖民地寻路大修* +clicksigns@ +@salted-fish-craft|咸鱼 (Salted Fish) +herios-helian-mod@ +@decorative-food|装饰性食物 (Decorative Food) + +anvilcraft-alloyextension@|铁砧工艺:合金扩展 (Anvilcraft: Alloy Extension) +arkifs-hoverboard-mod +glow@glows +leaf-villager-trader +guard-villager-trader +hunter-villager-trader +garbage-collector-villager +more-traders@ +ae2-better-villagers@ +harvester-trader +railman-villager@ +dimensionaltraders +taxbiometrader@tax-leons-biome-trader +quickelytraswapperplus-client@elytraswapper+ +@elytrakey +elytraswapperplus +taste-fatigue@|味觉疲劳* +realistic-nametag@ +hitboxplus@ +@hittable +hitmarker +autolandingwater@|自动落地水 (AutoLandingWater) +skygrid-reloaded@ +@stone-wood-industry|石木工艺 (Stone & Wood Industry) +roomopolis@ +bbl-placers@ + +draconic-legacy-models|龙之进化旧版模型* +furnaces +tinkersconstruct-classicsfluidvalues +rain-be-gone-ritual@ +the-vegan-option|素食者的选择* +gensokyo-delight-youkais-feasts@|幻想乡乐事 ~~ 妖怪们的飨宴 (Gensokyo Delight ~ Youkais' Feasts) + +tuttas-doors@ +@effectlivingdead|行尸走肉 (Effect Living Dead) +vs-chunkloader@|VS区块加载器 (VS ChunkLoader) +create-blocks-bogies@blocks-bogies +create-netherite-additions@ +create-sophisticated-backpacks-compat@ +create-item-pets +c-bc-nukes-6-0-port +plushieverse@ +thavma@ +arsarms +yorupo-mod@ +yande-mod@ +gtcefucontent +mcheli-overdrive-ext@mcheli-o-ext +iron-biplane@ +realistic-computers + +boss-mob-enhancer +flight-api|飞行 API (Flight API) +unlimited-spawners@|无限刷怪笼* + +@minecart-visualizer|敞骑速视 / 矿车信息可视化 (MinecartVisualizer) +@shadowborders +authenticate@|认证* + +unbinilium +veinbreaker +actions@ +i-have-no-mouth-but-i-must-chat@ihavenomouthbutimustchat +renderscale@ +proportional-destruction-particles +tacz-ammo-query +fast-happy-ghasts +@starhudforged +@slotted-armor-hud + +@say-sorry! +@playerautoma +im-fast@ +@unpath +no-more-popups@ +sussypatches@ +@simple-item-editor +baby-zombie-removal@ +immersive-sounds +boatview360@ +@my-resource-pack +oaks-bits@oaks-bits-and-bops +@starsource|星源拔刀剑 (Star Source Blade) +snow-waifu-spell|雪下的誓言* +mob-farm-trap@|香草刷怪塔陷阱强化:涌渊敌冢* +twograve +perrys-decorations@ +perry-s-decorations-2 +bookshelf-extended-enchanting-reach +enchanter-fix@ +dynamic-regen-enhancer +guinea-pig-variety@ + +@areas-hint|域名 (areas-hint) +yammo + +fps-display-doubler@ +inventory-stages|背包阶段* +gregmek|格雷矿物通用机械支持 (GregMek) +@armor-hud +@ukus-armor-hud +flowtech-mob-systems@ + +ftb-materials +the-great-below +illagerblabber@ + +serene-season-extended-fix +create-enchantment-industry-plus@ +boiled-one-unleashed +creaturecraft-plus +war-elephants + +one-click-one-block-1c1b@oneclickoneblock|点到为止 (One Click One Block) +@translatabledebugoptions|可翻译调试选项屏幕 (TranslatableDebugOptions) + +suppressopengl1280@|OpenGL错误隐藏 (Suppress OpenGL Error 128(0-2)) +fexs-vehicle-mod@fvtm +otterlib +dimban|维度禁用 (DimBan) +onlyoneitem|仅存一物 (Only One Item) +bedload|自定义区块加载器 (BedLoad) +@unreachable-nether + +@detail-armor-bar-reconstructed +@auto-fishing + +indrevjei@|工业革命JEI (IndRevJEI) +fpresearchpatcher|飞向未来研究数据包兼容 (Futurepack Research Patcher) +me-infinity-cell|ME无限元件 (ME Infinity Cell) +air-jump +more-sniffer-flowers@|更多嗅探兽花卉* + +enchantability|附魔能力* +ender-lead|末影拴绳* + + +create-growableores-compat@creategrowableores-compat + +gears-n-kinetics@ +valkyrien-sails@ + +moo-fluids-modern +yan-from-ff14@|FF14的羊 + + +tfc-cuisine@|群峦料理 (TFC Cuisine) +tfc-no-collapse|群峦传说:塌方禁止 (TFC No Collapse) +no-sleep-chat-overlay-nsco@no-sleep-chat-overlay-(nsco) +funkys-fancy-furnaces@ +timber-enchantment +@alternative-chunkloading +@particulate +haydenapi +spore-inquisition +ice-and-fire-spartan-weaponry-extras +@vanitydungeons +@fcitx5-enhancer +kubejs-actually-additions@ +vibrative-voice@ +spawnlock +enchantment-custom-table@|自定义附魔台* + + + +keeping-inventory-reborn +camel-extend|骆驼拓展* +confluence-music|汇流音乐* +cancel-attack-cooldown|取消冷却间隔* +surprising-loot|藏奇速释/惊喜战利品* +@onlykeys +flair +minecraft-comes-alive-reforged|凡家物语·重制* +cave-mita-dweller@mita-dweller +eeeabs-mobs-death-mode +revivetoken@|复活币 (ReviveToken) +real-filing-reborn-cabinet-based-item-storage@ +quivers +infused-crystals@ +mendingremover@ +auto-vanilla@autovanilla +underground-villages-stoneholm@underground-village,-stoneholm +gep@ + +more-harnesses@|更多挽具* +vanilla-aiots@ +nether-villager-merchant@ +end-villager-trader@ +deep-dark-trader@ +random-generator-block +devour +quantum-storage-reborn@ +library-section@ +move-subtitles@ +@modrenf3 +modern-inline@ +culler@ +@playernoticeboard + +@ae2uel-extra + +gems-realm@ +arrowcollect@|箭矢回收修复 (ArrowCollect) +@primitivemobsrevival +steampunk-clocktower +piglin-outpost +nolando-structurez +skinlayers3d-customskinloader-bridge@ +puffish-biome-dither@biome-dither +voracious +@minecraft-dungeons-weaponry +gentech-block-generation@ +byzantine-styles-pack-for-minecolonies + +aquaculture-tinkers@ +cups +@blue-archive-halo|仿碧蓝档案光环 (Blue Archive Halo) + +ecological-replenishment-station|生态补给站* +@advancedhostility|进阶仇恨 (Advanced Hostility) +aggro-fix@ +chest-curio-items@|箱子里的小饰品* +galaxysimagination|Galaxy的奇思妙想 (Galaxy's Imagination) +delightful-cuisine-for-woodheads-reforged|懒人厨房·重制 (Delightful Cuisine for Woodheads Reforged) +@loans-cinema +banana-baking@ +duacan-delight|岩城乐事* +slashblade-refabricated@|拔刀剑:重织 (SlashBlade: Refabricated) +ender-dragon-fix + +detailab-compat@|细节盔甲兼容 (Detail Armor Bar Compat) +calypsos-mobs@|卡里普索的生物 (Calypso's Mobs) +dirtcraft-unoffical|泥土工艺-非官方版 (DirtCraft-Unofficial) + +@onlytpnext|仅传送Next (OnlyTPNext) +easy-survival-command|简单生存指令TP (EasySurvivalCommand) +@gensokyo-reverie-of-lost-dreams +reliquified-ars-nouveau@ +requesting-interface@irequesting +applied-experienced@ +plain-cars-pack@plaincars +gt-craft-iv-mts@gt-craft-ivmts +ad-astra-more-structures@ +perfect-dodge|完美闪避* +simple-paraglider@|简易滑翔翼* +human-gunner +tacz-headshot-extension|TaCZ 爆头机制扩展 (TaCZ Headshot Extension) +dream-in-a-manhunt + + +@thesunrise +more-critters +oelib@ +itemblacklistfixed|物品黑名单修复版 (ItemBlackListFIXED) + +voxelmap-patch +xaero-train-map@ +traversable-leaves-neoforge¨traversable-leaves¨traversable-leaves-fabric +@etched-extension +@vs-addition-continue + +clavis@ +luckyblock-lib + +nopackcompatcheck@|禁用资源包兼容性检查 (NoPackCompatCheck) + + +crabbers-delight-expand|蟹农乐事:渔冶锭整 (Crabber's Delight Expand) + +offlinegrowth@ + +fauna-and-orchestra@faunaandorchestra + + +contact-love-rekindled|往来:旧情复燃 (Contact: LoveRekindled) +autosow-by-neni-o@ +piglin-join-wto|猪灵加入了WTO (Piglin Join WTO) +@keyboard-detector|键位检测器 (Keyboard Detector) +potion-pill|药水药丸* +inhrtburys-coin|Inhrtbury的金币 (Inhrtbury's Coin) +@firesight +epic-villages +@brainfk-in-minecraft +@aviator-dreams-reloaded +russian-dream + +cbc-ballistics +create-programmable-pals +create-ore-excavation-plus@ +@createtreadmill|机械动力:跑步机 (Create:Treadmill) +create-more-package-couriers@ +regius + + + + + +peyros-scythe|佩伊洛的镰刀 (Peyro's Scythe) + +@readstar|占星术 (ReadStar) +illage-and-legend +giant-dead-oak-tree@ + +ascension-nether +ascension-end +@echovoids +@soulfulnether +zefs-nether +the-end-update-concept +@ending-biomes +charred-horizons@ +@nether-update-expanded +i-see-my-armored-hand@ismah +naturally-spawning-withers@|自然生成凋灵* +wither-infection@ +creeper-infection@ +long-books@ +ftb-chunks-recoloured +jei-hover-search@ +rollable@ +invisibility-aggro-fix@ +quick-skin@ +countryball@minecraft-countryball-mod +tinkers-construct-emergence +pointblank-jelly +terrabnormals +extra-mining-world +herobrine011840s-nether-biomes +my-end +update-ender-a-reimagine-of-the-end +@air-puffers +companions-mod@ +@ai-chat|AI聊天 (AI Chat) +apothic-attributes-extension|神化属性拓展* +databank@ +randomtp@ +@kium-optimizer +instantunify-reforged +@blockfire +morphoverlay +display-prior-work-count +extended-scripts +natures-aura-simplified|自然灵气:简化 (Nature's Aura Simplified) +bed-spawn-backport@|想设就设* +visible-effect@|药水效果显示* +@config-manager|设置管理器 (Config Manager) +simple-datapacks@ +shulkerrrt@srrt +custom-icon-sets +gregic-probe-ceu +@antilootgame +@graphene|石墨烯 (Graphene) + +talents-fabric@talents +@combat-maids +voxelfurni +voxels +voxel-pairing +no-stuck-healing +pixelcompress +matrinoxes-village-names +redlink-wireless-redstone-control@ +@nekohashizuku-blade|雫拔刀剑 (shizukublade) +xtones-reworked@ +@tinkers-enchanting +new-woods +ennuis-bigger-inventories@ennuis_bigger_inventories +@soldisco +missing-cookery +@farmers-tea-resteeped +peruvians-delight@ +productivefarming +brewin-compat-delight@cocktails-delight|鸡尾酒乐事 (Cocktails Delight) +kubejs-tweaks +krypton-fnp@ +neoauth@ +crystalix +chisel-chipped-integration +refined-types +mystical-automation@ +utility-vest +little-big-redstone@ +@stone-is-stone +almost-unified-ie +@trident-are-weapon +collect-seeds +create-delicious-teas@ +@showtime +@windup-music-box +babylonian-empire-origins +mc-mods-the-arcanist-gear +epitaphs +biologist|生物学家* +wandering-trapper2|流浪猎人2* +skiing2 +backported-animal-variants@ +explore-ruins-aether@ +warpdrive-extras@ +@chest-transport-reloaded +lrs-blockollection +@the-what-tools +tacz-chaoscrafts-apocalyptic-arsernal +detail-armor-bar-re-equipment@|细节盔甲条:重装 (Detail Armor Bar Re-equepment) +@jeac +boat-fly|船飞行* +finite-water-infinite-lava +transfer-labels@ +growth-accelerator-tiers +astages-curios +astages-pufferfishs-skills +sound-physics-perfected@ +the-sound-of-rain@ + +greekfantasy-unofficial +musketeer-illager-port +cataclysm-apotheosis-addon-neo +iron-apples-iron-series@ + +tinkers-oreganized@ +simple-basketballs@simple-basketball +decorative-blocks-repurposed +create-mechanical-botany@ + + +naked-gauntlet|焰眼掉装* +only-one-item@ + +aimbow-bow-hit-indicator|弓箭瞄准 (AimBow) + +@tt20forged +@worldfarview +@fixmcstats +soul-return-scroll|还魂符* +slashblade-murasame@|拔刀剑:丛雨丸* + +dungeon-tools-2|地牢工具 2* +gregtech-beyond-coremod +create-compatible-storage@ +antique-rockets@ +meta-changer +chromatic-enchantments@ + + +tfc-detail-armor-bar@|群峦传说:细节盔甲条 (TFC Detail Armor Bar) + +tinkers-compatibility@ +fabric-trade@ +giant-nether-tree@ +gamerule-doblockupdates +terralith-restoned +guitas-woodworks@ +power-grid@|机械动力:交错电网 (Create: Power Grid) + +audaki-cart-engine@ + + +humansmod@humans-mod +aquaacrobaticslegacy + +armored-arms@ +armored-golems +disable-fog@ +accrelatedrendering-refabricated@|加速渲染 重织版 (AccrelatedRendering-reFabricated) +minemenureforged|我的菜单 重制 (MineMenuReforged) +function-command-deduplicator|函数命令去重器* +@hide-armour +ftb-quests-freeze-fix@|FTB任务冻结修复 (FTB Quests Freeze Fix) +playeranimatorapi@ +strictly-origins@ +born-in-configuration@ +eyes-of-ice-and-fire@ +better-hp@betterhp +jsonate +plsgodie|残血了就给我乖乖去死啊! (PlsGoDie) +tetra-patchouli-book|Tetra帕秋莉手册* + +configurable-death@ +invenaudio@ +client-curse-of-binding-prevention +simpleclockmod + +audio-improvements@ + + + +simple-crop-harvester|简易农作物收割* +darkness-curse|暗影诅咒·重生 (Darkness Curse • Reborn) +@scroll-whell-command|滚轮命令 (Scroll Wheel Command) +infoplus@ +@bin-slot +cobblemon-utils +flamingo-mania + +endangered-plants|濒危植物* + +better-melon@|更好的西瓜* +better-snifferty@|更好的嗅探* +@bobs-originium-dream|鲍勃之源石梦想 (BOB's Originium Dream) + +minibloom +teatime@tea_time +garden-trails + +@gd656killicon|GD656击杀图标 (GD656Killicon) +itemcleaner@|掉落物清洁者 (ItemCleaner) +@tinkers-dolabrae +metallic-tinkering@ +kubejs-draconic-evolution +kubejs-mystical-agriculture@ +modern-beta-forge-islands +modern-beta-forge-release-plus +pomkots-world@ +black-dragon-armor|怪物猎人·世界:黑龙盔甲* +kimetsu-no-yaiba-reload|鬼灭之刃 重制* +resonantisshelomoh|同调造物·所罗门 (Fabrica Resonantis:Shelomoh) +withering-away-reborn +villager-extend-localbully +dancing-villagers +natures-of-witchery +tct-flashlight@tctflashlight +mace-attack-assistance +changed-doomsday +changed-ice +tile-entity-breaker +frozen-ocean +portable-pet-sphere@ +@thaumicthermae +bbl-routers +bed-sapwnpoint +wans-storage-shelves@storage-shelves +@winefoxs_spellbooks|酒狐的魔法书 (Winefox's Spellbooks) +better-allay@ +codechicken-lib-cre@ +solcarrot-fix|生活调味料:胡萝卜版 - 死吃修复* + +nanexs-cherry-biome +digital-botania + +crab-city-facilities|螃蟹的城建 (Crab city facilities) +bbl-casting@casting +bbl-smart-crafting +foolproof@ +realistic-terrain-generation-community +more-outlines@ +phantomtweaks|移除幻翼 (RemovePhantoms) +simple-item-blacklist +titlescreen-cleaner + + +manaitaplus-legacy|砧板传承 (ManaitaPlus Legacy) + +blockoffensive@|方块攻势 (BlockOffensive) + +hugme-forge-edition|抱抱我!Forge 版* + + + + + + + +better-turtle-scute@|更好的海龟壳 (Better turtle scute) + +floatable-pressure-plates@|可浮空压力板* + + +more-rust@rust|锈 (Rust) +@cryofloric-arts|严寒插花术 (Cryofloric Arts) +tacz-refabricated@|TaCZ:重织 (Timeless and Classics Zero: Refabricated) + +@ashen-witch-broom|魔女的扫帚:重制版 (Ashen Witch Broom) +maidswordsoaring|女仆御剑 (Maid Sword Soaring) +lembrary +optiboxes@ +create-furnace-lava-adapter +create-trainutilities@ +@createtrainscustomportalapi1.20.1 +create-xptool@cxptcreatexptool|机械动力:经验抽取 (Create:XPTool) +graveyard-death-mode +beautiful-campfires +kimetsu-no-yaiba-final-hope +legend-ary@ +tfc-hourglass|床起速时 / 梦该醒了 (TFC Hourglass)¨@tfc-hourglass|床起速时 / 梦该醒了 (TFC Hourglass) - 审核中 + + + + + +@alan-unified-ui +@cntiertagger + +jei-tetra +eyes-in-the-shadows@ +bandagez +kineticdamage@ +underwater-village@ +dimension-parasite|维度寄生者* +@realistic-stamina +ciscos-content +pet-home@pet_home +changed-xtras@ +alexs-caves-enriched@|Alex 的洞穴:浓缩 (Alex's Caves: Enriched) +@fps-to-the-trash +@entity-desync-viewer +re-entity-outliner +@c2mef +hellspawn-starting-dimension@hellspawn +jj-weapon-config +bbl-compat@opolis-compat +tinkertime-geology +spectral-arrow-tweaks +too-many-bows@ + +@soulbinditem|灵魂绑定 (Soulbind) + + + +maid-beside|女仆伴行* +key|键* +vzlinglib +@signfinder|告示牌查找器 (Signfinder) + + + +@colorful-leaves-mod +decorative-computers@|装饰性电脑 (DECORATIVE COMPUTERS) +landk-furniture@ +derfl007s-road-mod +medieval-paintings@ +glymworld-deepslate + +roadarchitect@ + + +lazybank|咸鱼物品银行* + + + + +@projectile-ding +client-time@clienttime +replanter-plus@ +ae2-retex-port +tinker-tantrum +amethyst-additions +@tetravsikt +@advanced-netherite-upgrade-templates +@falling-sand +gregified-energistics@ +scalable-storage-ceu@ +luminous-blocks@ +epic-structures-witch-huts + +void-block-fabric@void-block-fabric-1.21.1 +more-stews-mod +@ported +better-music-player +berries-and-mobs +applied-soul +packagedfluidcrafting|封包流体合成 (PackagedFluidCrafting) +packagingprovider|封包供应器 (PackagingProvider) +mts-iv-recurring-air-war + + + +@explodee +swift-wheel|迅捷轮盘*¨@swift-wheel|迅捷轮盘 (Swift Wheel) - 审核中 + +better-spectral-arrows +polymorphic-tom@polymorphic-toms-simple-storag +shoulder-surfing-ultimine@ +artisan-pottery@ +epic-fight-avalon +@neospeedzero|自定义速通挑战:Zero (NeoSpeed Zero) +fluffy-dandelions@ +nether-update-concept-mod +@little-maid-mood-flutter|车万女仆:情绪萌动 (Little Maid: Mood Flutter) +mobvotes-sniffer@sniffer +ae-wireless-transceiver@|AE无线收发器 (AE Wireless Transceiver) +create-train-lights@ +kabooms +zoo-architect@ + +@noverifychat|无验证聊天 (NoVerifyChat / FixChatVerify) +giant-cherry-blossom-bonsai-tree@ +lemonskin@|柠檬皮 (LemonSkin) +water-erosion +@undertale-healthbars + +tetra-gui-compat +@shortratio-mod +quantumchunks@ + +@citrus +@accessory-api +@catalyst +@day-counter-enhanced +messenger-crow +epic-fight-super-warden|史诗战斗:超级监守者 (Epic Fight - Super Warden) +armageddon +cobblemon-mega-showdown@ +golem-dungeons@|傀儡地牢* + +explosion-overhaul-a-new-level-of-destruction +@skillz + +can-you-survive +eternal-shield-bead|永御盾珠* +slashnouveau +@aether-stapi +@notenoughminecraft +originiumarts@|源石技艺 (OriginiumArts) +simplebackhome|简单回家 (SimpleBackHome) +@qismorecommands|齐的更多命令 (Qi's More Commands) +polymorphic-travelers-backpack@ +playernbt-quests-ftb-quests +interactive-block-prompt@ibp|可交互物品提示* +@timeshifts +@weathershift +chat-binds@chatbinds +tridentperf-1-0-0 +maid-s-bakeries|女仆的烘焙坊 (Maid's Bakeries) + +elytraautocollect@|鞘翅自动搜集 (ElytraAutoCollect) +@banitems|物品封禁 (Banitems) + +tt20-forged@tt20-reforged +default-server-properties|默认服务器属性* +f3-lite@ +text-animator@ + +yet-another-furniture@ + +qarsans-insurgency +yum-delicious|Yum!美味! (yum! delicious!) +lunarinispread@|丰盛晚宴 (Lunarini Spread) + +rs-backpack +no-template-netherite@ + +@mobcapture +koifish + + +@mcinspects +@csgo-inspects +armor-hud-by-mcjunky33@ +@can-i-sleep-now +@zenxarch-damage-vignette +@teleportg +too-many-binds +too-many-binds-plus +astral-dragon +middle-earth-additions-npc@|中洲拓展:NPC (Middle-Earth Additions: Npc) +middle-earth-additions-factions@|中洲拓展:派系 (Middle-Earth Additions: Factions) +middle-earth-additions-war@|中洲拓展:战争 (Middle-Earth Additions: War) +brews-and-bevs@|酿造与饮料 (Brews and Bevs) +@kimetsu-no-yaiba-x-epic-fight + +lrs-dynamic-dungeon +@buff-mobs +banner-claim@|旗帜圈地* +@reformundyingtotem|不死图腾重制 (ReformUndyingTotem) + +more-world-crafting|更多世界合成*¨@more-world-crafting|更多世界合成 (More World Crafting) - 审核中 +@simplelocks|简单的锁 (SimpleLocks) +luminous-helmet +steel-armor-blocks@sab|机械动力:钢铁装甲方块 (Create: Steel armor blocks) +create-armor-blocks@|机械动力:装甲方块 (Create: Armor Blocks) +rolled-homogenous|轧压均质装甲* +elemental-end-portal +unseens-better-stronghold +salutation +minecraft-1-7-10-reloaded +global-customnpc-data +modern-beta-forge-rivers +zombie-improvements@zombieimprovements +rewithered@ +better-lost-candle +openblocks-reopened +@rc-toys +fancy-toasts + + +@trueuuid|正版离线共存 (TrueUUID) +owapote-bc-exlaser +truly-custom-horse-tack@ +@tacz-applied-ammo-box +azurum-miner@ +thermal-recycling|热力回收* + +revoted-wildfire@ \ No newline at end of file