diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb index a025af9..e595d11 100644 --- a/Plain Craft Launcher 2/Application.xaml.vb +++ b/Plain Craft Launcher 2/Application.xaml.vb @@ -198,7 +198,7 @@ WaitRetry: Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean Public Shared Function AssemblyResolve(sender As Object, Args As ResolveEventArgs) As Assembly '缓存 - Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "System.Net.Http.Formatting"} + Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "System.Net.Http.Formatting", "System.ValueTuple"} Static Locks As New Dictionary(Of String, Object)(StringComparer.Ordinal) Static LoadedAssembly As New Dictionary(Of String, Assembly)(StringComparer.Ordinal) '查找对应的 DLL diff --git a/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb b/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb index b145b27..1daef36 100644 --- a/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb +++ b/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb @@ -35,7 +35,7 @@ Namespace Controls.Behaviors End Sub Public Shared Function GetEnableSafeClipboard(element As DependencyObject) As Boolean - Return CBool(element.GetValue(EnableSafeClipboardProperty)) + Return element.GetValue(EnableSafeClipboardProperty) End Function Private Shared Sub OnEnableSafeClipboardChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) @@ -97,6 +97,10 @@ Namespace Controls.Behaviors If Forms.Clipboard.ContainsText() Then Dim pasteText = Forms.Clipboard.GetText() + + '将换行符替换为空格(#7825) + If Not tb.AcceptsReturn Then pasteText = pasteText.Replace(vbCrLf, " ").Replace(vbCr, " ").Replace(vbLf, " ") + Dim start = tb.SelectionStart tb.SelectedText = pasteText @@ -162,14 +166,15 @@ Namespace Controls.Behaviors Dim dg = TryCast(sender, DataGrid) If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return - Dim sb = New System.Text.StringBuilder() + Dim sb = New StringBuilder Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item) For Each row In rowGroups - Dim rowText = String.Join(vbTab, row.Select(Function(cell) - Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock) - Return If(tb IsNot Nothing, tb.Text, "") - End Function)) + Dim rowText = String.Join(vbTab, row.Select( + Function(cell) + Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock) + Return If(tb IsNot Nothing, tb.Text, "") + End Function)) sb.AppendLine(rowText) Next diff --git a/Plain Craft Launcher 2/Controls/Behaviors/LazyLoadBehavior.vb b/Plain Craft Launcher 2/Controls/Behaviors/LazyLoadBehavior.vb new file mode 100644 index 0000000..c9e2990 --- /dev/null +++ b/Plain Craft Launcher 2/Controls/Behaviors/LazyLoadBehavior.vb @@ -0,0 +1,47 @@ +Public Module LazyLoadBehavior + + ''' + ''' 指定首次进入 ScrollViewer 的可视范围时执行的操作。 + ''' + + Public Sub OnFirstEnterScrollViewerViewport(obj As DependencyObject, value As Action) + obj.SetValue(IsInViewportProperty, value) + End Sub + + Private ReadOnly IsInViewportProperty As DependencyProperty = + DependencyProperty.RegisterAttached("IsInViewport", GetType(Action), GetType(LazyLoadBehavior), + New PropertyMetadata(Nothing, AddressOf OnIsInViewportChanged)) + + Private Sub OnIsInViewportChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) + Dim Element = TryCast(d, FrameworkElement) + If Element Is Nothing OrElse e.NewValue Is Nothing Then Return + + Dim Handled As Boolean = False + Dim Handler As EventHandler = + Sub() + If Handled Then Return + '判断元素是否可见 + If Element.RenderSize.Width < Double.Epsilon Then Return + If Not Element.IsVisible Then Return + '判断是否在 ScrollViewer 的可视区域内 + Dim Scroll = FindParentScrollViewer(Element) + If Scroll Is Nothing Then Return + If Not New Rect(0, 0, Scroll.ViewportWidth, Scroll.ViewportHeight).IntersectsWith( + Element.TransformToAncestor(Scroll).TransformBounds(New Rect(New Point(0, 0), Element.RenderSize))) Then Return + '执行 + Handled = True + CType(e.NewValue, Action).Invoke() + RemoveHandler Element.LayoutUpdated, Handler + End Sub + AddHandler Element.LayoutUpdated, Handler + End Sub + + Private Function FindParentScrollViewer(d As DependencyObject) As ScrollViewer + While d IsNot Nothing + If TypeOf d Is ScrollViewer Then Return CType(d, ScrollViewer) + d = VisualTreeHelper.GetParent(d) + End While + Return Nothing + End Function + +End Module diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb index b84cae2..0eb46a6 100644 --- a/Plain Craft Launcher 2/Controls/MyCard.vb +++ b/Plain Craft Launcher 2/Controls/MyCard.vb @@ -92,6 +92,7 @@ '改变默认的折叠 If IsSwapped AndAlso SwapControl IsNot Nothing Then MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0)) + SwapControl.Visibility = Visibility.Collapsed '取消由于高度变化被迫触发的高度动画 Dim RawUseAnimation As Boolean = UseAnimation UseAnimation = False @@ -109,17 +110,21 @@ '这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎 '但是现在……反正勉强能用……懒得改了就这样吧.jpg '别骂了别骂了.jpg - If IsNothing(Stack.Tag) Then Return + If Stack.Tag Is Nothing Then Return '排序 + Dim HasDuplicates As Boolean = False Select Case Type Case 3 - Stack.Tag = CType(Stack.Tag, List(Of DlOptiFineListEntry)).Sort(Function(a, b) VersionSortBoolean(a.NameDisplay, b.NameDisplay)) + Stack.Tag = CType(Stack.Tag, List(Of DlOptiFineListEntry)).SortByComparison(Function(a, b) CompareVersionGE(a.DisplayName, b.DisplayName)) Case 4, 10 - Stack.Tag = CType(Stack.Tag, List(Of DlLiteLoaderListEntry)).Sort(Function(a, b) VersionSortBoolean(a.Inherit, b.Inherit)) + Stack.Tag = CType(Stack.Tag, List(Of DlLiteLoaderListEntry)).SortByComparison(Function(a, b) CompareVersionGE(a.Inherit, b.Inherit)) Case 6 - Stack.Tag = CType(Stack.Tag, List(Of DlForgeVersionEntry)).Sort(Function(a, b) a.Version > b.Version) + Stack.Tag = CType(Stack.Tag, List(Of DlForgeVersionEntry)).OrderByDescending(Function(a) a).ToList Case 8, 9 - Stack.Tag = CType(Stack.Tag, List(Of CompFile)).Sort(Function(a, b) a.ReleaseDate > b.ReleaseDate) + Stack.Tag = CType(Stack.Tag, List(Of CompFile)).SortByComparison(Function(a, b) a.ReleaseDate > b.ReleaseDate) + HasDuplicates = + CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <> + CType(Stack.Tag, List(Of CompFile)).Count End Select '控件转换 Select Case Type @@ -132,9 +137,7 @@ For Each Data As Object In Stack.Tag Select Case Type Case 0 - Dim VersionItem As MyListItem = CType(Data, McVersion).ToListItem - VersionItem.ContentHandler = AddressOf PageSelectRight.McVersionListContent - Stack.Children.Add(VersionItem) + Stack.Children.Add(CType(Data, McInstance).ToListItem(AddressOf PageSelectRight.McInstanceListContent)) Case 2 Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True)) Case 3 @@ -145,23 +148,13 @@ '不能使用 AddressOf,这导致了 #535,原因完全不明,疑似是编译器 Bug Stack.Children.Add(McDownloadListItem(Data, Sub(sender, e) FrmDownloadInstall.MinecraftSelected(sender, e), False)) Case 8 - If CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <> - CType(Stack.Tag, List(Of CompFile)).Count Then - '存在重复的名称(#1344) - Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=True)) - Else - '不存在重复的名称,正常加载 - Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click)) - End If + '若存在重复的版本名,则显示文件名而非版本名(#1344) + Stack.Children.Add(CType(Data, CompFile).ToListItem( + AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=HasDuplicates)) Case 9 - If CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <> - CType(Stack.Tag, List(Of CompFile)).Count Then - '存在重复的名称(#1344) - Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=True)) - Else - '不存在重复的名称,正常加载 - Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click)) - End If + '若存在重复的版本名,则显示文件名而非版本名(#1344) + Stack.Children.Add(CType(Data, CompFile).ToListItem( + AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=HasDuplicates)) Case 10 Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf LiteLoaderSave_Click, True)) Case 11 @@ -268,7 +261,7 @@ Height = PreviousHeight End Sub ''' - ''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。 + ''' 通知 MyCard,控件内容已改变,需要中断动画并瞬间更新高度。 ''' Public Sub TriggerForceResize() Height = If(IsSwapped, SwapedHeight, Double.NaN) @@ -282,7 +275,7 @@ '若设置了 CanSwap,或 SwapControl 不为空,则判定为会进行折叠 '这是因为不能直接在 XAML 中设置 SwapControl - Public SwapControl As Object + Public SwapControl As FrameworkElement Public Property CanSwap As Boolean = False ''' ''' 被折叠的种类,用于控件虚拟化。 diff --git a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb index 9498b37..7171cab 100644 --- a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb @@ -102,7 +102,7 @@ Public Class MyHint If CanClose AndAlso Setup.Get(RelativeSetup) Then Visibility = Visibility.Collapsed End Sub Private Sub BtnClose_Click(sender As Object, e As EventArgs) Handles BtnClose.Click - Setup.Set(RelativeSetup, True) + Setup.SetSafe(RelativeSetup, True) AniDispose(Me, False) End Sub diff --git a/Plain Craft Launcher 2/Controls/MyImage.vb b/Plain Craft Launcher 2/Controls/MyImage.vb index f869400..13a2cf1 100644 --- a/Plain Craft Launcher 2/Controls/MyImage.vb +++ b/Plain Craft Launcher 2/Controls/MyImage.vb @@ -85,8 +85,8 @@ If _ActualSource = value Then Return _ActualSource = value Try - Dim Bitmap As MyBitmap = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件 - RunInUiWait(Sub() MyBase.Source = Bitmap) + Dim Bitmap As ImageSource = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件 + RunInUi(Sub() MyBase.Source = Bitmap) Catch ex As Exception Log(ex, $"加载图片失败({value})") Try @@ -112,29 +112,24 @@ End If '从缓存加载网络图片 Dim EnableCache As Boolean = Me.EnableCache - Dim TempPath As String = GetTempPath(Source) & If(EnableCache, "", GetUuid()) '不启用缓存时加上随机字符串,避免冲突 + Dim TempPath As String = $"{PathTemp}MyImage\{GetHash(Source)}.png{If(EnableCache, "", GetUuid())}" '不启用缓存时在末尾加上随机字符串,避免冲突 Dim TempFile As New FileInfo(TempPath) If EnableCache AndAlso TempFile.Exists Then - ActualSource = TempPath + ActualSource = TempPath '显示缓存的图片 If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Return '无需刷新缓存 + Else + ActualSource = LoadingSource '显示加载中图片 End If RunInNewThread( Sub() Dim IsLocalFallback As Boolean = Not String.IsNullOrEmpty(FallbackSource) AndAlso Not FallbackSource.StartsWithF("http") Try '下载 - ActualSource = LoadingSource '显示加载中的占位图片 NetDownloadByLoader( - If(FallbackSource?.StartsWithF("http"), {Source, FallbackSource}, {Source}), + If(FallbackSource?.StartsWithF("http") AndAlso Source <> FallbackSource, {Source, FallbackSource}, {Source}), TempPath, SimulateBrowserHeaders:=True) - If EnableCache Then - '保存缓存并显示 - RunInUi(Sub() ActualSource = TempPath) - Else - '直接显示 - RunInUiWait(Sub() ActualSource = TempPath) - File.Delete(TempPath) - End If + ActualSource = TempPath + If Not EnableCache Then File.Delete(TempPath) Catch ex As Exception Try If TempPath IsNot Nothing Then File.Delete(TempPath) @@ -143,7 +138,7 @@ '加载本地备用图片 If IsLocalFallback Then Try - RunInUiWait(Sub() ActualSource = FallbackSource) + ActualSource = FallbackSource Log(ex, $"下载图片失败,使用本地备用图片({Source})") Return Catch exx As Exception @@ -156,8 +151,5 @@ End Try End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal) End Sub - Public Shared Function GetTempPath(Url As String) As String - Return $"{PathTemp}MyImage\{GetHash(Url)}.png" - End Function End Class \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb index cf462ec..b0ff29f 100644 --- a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb @@ -396,6 +396,7 @@ Public Class MyListItem Dim CheckedCount As Integer = 0 '收集控件列表与选中个数 For Each Control In CType(Parent, Object).Children + Control = MyVirtualizingElement.TryInit(Control) If TypeOf Control Is MyListItem AndAlso CType(Control, MyListItem).Type = CheckType.RadioBox Then RadioboxList.Add(Control) If Control.Checked Then CheckedCount += 1 @@ -544,7 +545,7 @@ Public Class MyListItem Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp '菜单虚拟化检测 If ContentHandler IsNot Nothing Then - ContentHandler(sender, e) + ContentHandler(Me, e) ContentHandler = Nothing End If '判断当前颜色 diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb index 59bb7fc..72e72ec 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb @@ -21,7 +21,9 @@ ShapeLine.StrokeThickness = GetWPFSize(1) '添加选择控件 Btn1.IsEnabled = False - For Each Selection As IMyRadio In Converter.Content + For Each Content In Converter.Content + Content = MyVirtualizingElement.TryInit(Content) + Dim Selection As IMyRadio = Content PanSelection.Children.Add(Selection) AddHandler Selection.Check, AddressOf OnChecked If TypeOf Selection Is MyListItem Then diff --git a/Plain Craft Launcher 2/Controls/MyPageLeft.vb b/Plain Craft Launcher 2/Controls/MyPageLeft.vb index afc047f..aceae13 100644 --- a/Plain Craft Launcher 2/Controls/MyPageLeft.vb +++ b/Plain Craft Launcher 2/Controls/MyPageLeft.vb @@ -30,6 +30,7 @@ Dim AniList As New List(Of AniData) Dim Id As Integer = 0, Delay As Integer = 0 For Each Element As FrameworkElement In GetAllAnimControls(True) + Element = MyVirtualizingElement.TryInit(Element) If Element.Visibility = Visibility.Collapsed Then '还原之前的隐藏动画可能导致的改变(#2436) Element.Opacity = 1 diff --git a/Plain Craft Launcher 2/Controls/MyPageRight.vb b/Plain Craft Launcher 2/Controls/MyPageRight.vb index a3e85a6..b7ee462 100644 --- a/Plain Craft Launcher 2/Controls/MyPageRight.vb +++ b/Plain Craft Launcher 2/Controls/MyPageRight.vb @@ -135,7 +135,7 @@ TriggerEnterAnimation(PanAlways, If(PanContent, Child)) ElseIf PageLoader.State = LoadState.Loading Then PageState = PageStates.LoaderWait - AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid) + AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 400), "PageRight PageChange " & PageUuid) Else 'PageLoader.State = LoadState.Failed PageState = PageStates.LoaderEnter TriggerEnterAnimation(PanAlways, PanLoader) @@ -147,7 +147,7 @@ TriggerEnterAnimation(If(PanContent, Child)) ElseIf PageLoader.State = LoadState.Loading Then PageState = PageStates.LoaderWait - AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid) + AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 400), "PageRight PageChange " & PageUuid) Else 'PageLoader.State = LoadState.Failed PageState = PageStates.LoaderEnter TriggerEnterAnimation(PanLoader) @@ -378,7 +378,7 @@ End If '结束 AniList.Add(AaCode(Sub() PageOnEnterAnimationFinished(),, True)) - AniStart(AniList, "PageRight PageChange " & PageUuid) + AniStart(AniList, "PageRight PageChange " & PageUuid, True) End Sub '逐个退出动画 diff --git a/Plain Craft Launcher 2/Controls/MyVirtualizingElement.vb b/Plain Craft Launcher 2/Controls/MyVirtualizingElement.vb new file mode 100644 index 0000000..056cd9d --- /dev/null +++ b/Plain Craft Launcher 2/Controls/MyVirtualizingElement.vb @@ -0,0 +1,70 @@ +Public Class MyVirtualizingElement(Of T As FrameworkElement) + Inherits FrameworkElement + + Private Initializer As Func(Of T) + Public Sub New(Initializer As Func(Of T)) + Me.Initializer = Initializer + OnFirstEnterScrollViewerViewport(AddressOf Init) + End Sub + + ''' + ''' 实例化此控件。 + ''' + Public Function Init() As T + Dim Element As T = Initializer() + If Parent IsNot Nothing Then + If TypeOf Parent IsNot Panel Then Throw New Exception("MyVirtualizingElement 的父级必须是一个 Panel") + Dim ParentPanel As Panel = Parent + Dim CurrentIndex As Integer = ParentPanel.Children.IndexOf(Me) + ParentPanel.Children.RemoveAt(CurrentIndex) + ParentPanel.Children.Insert(CurrentIndex, Element) + End If + Return Element + End Function + Public Shared Widening Operator CType(Virtualized As MyVirtualizingElement(Of T)) As T + Return Virtualized.Init() + End Operator + +End Class + +'非泛型形式 +Public Class MyVirtualizingElement + Inherits FrameworkElement + + Private Initializer As Func(Of FrameworkElement) + Public Sub New(Initializer As Func(Of FrameworkElement)) + Me.Initializer = Initializer + OnFirstEnterScrollViewerViewport(AddressOf Init) + End Sub + + ''' + ''' 实例化此控件。 + ''' + Public Function Init() As FrameworkElement + Dim Element As FrameworkElement = Initializer() + If Parent IsNot Nothing Then + If TypeOf Parent IsNot Panel Then Throw New Exception("MyVirtualizingElement 的父级必须是一个 Panel") + Dim ParentPanel As Panel = Parent + Dim CurrentIndex As Integer = ParentPanel.Children.IndexOf(Me) + ParentPanel.Children.RemoveAt(CurrentIndex) + ParentPanel.Children.Insert(CurrentIndex, Element) + End If + Return Element + End Function + + ''' + ''' 获取实例化后的控件。 + ''' 如果该控件没有实例化,则会立即实例化。 + ''' 如果类型错误,则返回原值。 + ''' + Public Shared Function TryInit(Element As FrameworkElement) As FrameworkElement + If GetType(MyVirtualizingElement(Of )).IsInstanceOfGenericType(Element) Then + Return CType(Element, Object).Init() + ElseIf TypeOf Element Is MyVirtualizingElement Then + Return CType(Element, MyVirtualizingElement).Init() + Else + Return Element + End If + End Function + +End Class diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index cb0da8d..62bff68 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -11,6 +11,11 @@ Public Class FormMain Dim FeatureList As New List(Of KeyValuePair(Of Integer, String)) '统计更新日志条目 #If BETA Then + If LastVersion < 376 Then 'Release 2.11.2 + FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:联机功能!")) + FeatureCount += 32 + BugCount += 21 + End If If LastVersion < 372 Then 'Release 2.10.9 FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:如果版本设置了自定义描述,会在标题后面以淡灰色显示其版本号")) FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "新增:支持为一个控件设置多个自定义事件")) @@ -113,27 +118,37 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- - If LastVersion < 375 Then 'Snapshot 2.11.2 + If LastVersion < 377 Then 'Snapshot 2.12.0 If LastVersion >= 373 Then - FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:对联机进行了各种各样的优化,以改善稳定性")) - FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:若有加入者的网络环境比房主更好,会提示可以让那位加入者担任房主")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "删除:暂时隐藏联机入口……不过只是暂时关闭,它还会回来的!")) End If + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:适配新的 Minecraft 版本号系统与 Unobfuscated 版本")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:巨幅优化各个下载页面和 Mod 管理页面的性能")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:更换披风时会显示当前使用的披风")) + FeatureCount += 31 + BugCount += 27 + End If + If LastVersion < 375 Then 'Snapshot 2.11.2 + 'If LastVersion >= 373 Then + ' FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:对联机进行了各种各样的优化,以改善稳定性")) + ' FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:若有加入者的网络环境比房主更好,会提示可以让那位加入者担任房主")) + 'End If FeatureCount += 16 BugCount += 4 End If If LastVersion < 374 Then 'Snapshot 2.11.1 - If LastVersion >= 373 Then - FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:使用离线登录也可以直接加入联机房间了")) - FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:会从所有共享节点中自动选择负载最低的进行中继连接")) - FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:若复制了邀请码,则可以直接快速加入房间")) - FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:关闭 PCL 时总是会提示是否退出联机,防止在关闭 PCL 时无意地关闭或退出了房间")) - FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "新增:允许自定义要连接的节点")) - End If + 'If LastVersion >= 373 Then + ' FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:使用离线登录也可以直接加入联机房间了")) + ' FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:会从所有共享节点中自动选择负载最低的进行中继连接")) + ' FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:若复制了邀请码,则可以直接快速加入房间")) + ' FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:关闭 PCL 时总是会提示是否退出联机,防止在关闭 PCL 时无意地关闭或退出了房间")) + ' FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "新增:允许自定义要连接的节点")) + 'End If FeatureCount += 9 BugCount += 7 End If If LastVersion < 373 Then 'Snapshot 2.11.0 - FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:联机功能!")) + 'FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:联机功能!")) FeatureCount += 7 BugCount += 10 End If @@ -725,9 +740,9 @@ Public Class FormMain '按 ESC 返回上一级 If e.Key = Key.Escape Then TriggerPageBack() '更改隐藏版本可见性 - If e.Key = Key.F11 AndAlso PageCurrent = FormMain.PageType.VersionSelect Then + If e.Key = Key.F11 AndAlso PageCurrent = FormMain.PageType.InstanceSelect Then FrmSelectRight.ShowHidden = Not FrmSelectRight.ShowHidden - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") Return End If '更改功能隐藏可见性 @@ -772,12 +787,12 @@ Public Class FormMain '切回窗口 Private Sub FormMain_Activated() Handles Me.Activated Try - If PageCurrent = PageType.VersionSetup AndAlso PageCurrentSub = PageSubType.VersionMod Then + If PageCurrent = PageType.InstanceSetup AndAlso PageCurrentSub = PageSubType.InstanceMod Then 'Mod 管理自动刷新 - FrmVersionMod.ReloadModList() - ElseIf PageCurrent = PageType.VersionSelect Then + FrmInstanceMod.ReloadModList() + ElseIf PageCurrent = PageType.InstanceSelect Then '版本选择自动刷新 - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.RunOnUpdated, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.RunOnUpdated, MaxDepth:=1, ExtraPath:="versions\") End If Catch ex As Exception Log(ex, "切回窗口时出错", LogLevel.Feedback) @@ -851,36 +866,36 @@ Public Class FormMain Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Red) Return End If - Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent) - If TargetVersion Is Nothing Then + Dim Target = If(PageCurrent = PageType.InstanceSetup, PageInstanceLeft.Instance, McInstanceSelected) + If Target Is Nothing Then Hint("请先下载游戏,再设置第三方登录!", HintType.Red) Return End If If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then 'LittleSkin - If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启 LittleSkin 登录?" & vbCrLf & + If MyMsgBox($"是否要在版本 {Target.Name} 中开启 LittleSkin 登录?" & vbCrLf & "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then Return End If - Setup.Set("VersionServerLogin", 4, Version:=TargetVersion) - Setup.Set("VersionServerAuthServer", "https://littleskin.cn/api/yggdrasil", Version:=TargetVersion) - Setup.Set("VersionServerAuthRegister", "https://littleskin.cn/auth/register", Version:=TargetVersion) - Setup.Set("VersionServerAuthName", "LittleSkin 登录", Version:=TargetVersion) + Setup.Set("VersionServerLogin", 4, Instance:=Target) + Setup.Set("VersionServerAuthServer", "https://littleskin.cn/api/yggdrasil", Instance:=Target) + Setup.Set("VersionServerAuthRegister", "https://littleskin.cn/auth/register", Instance:=Target) + Setup.Set("VersionServerAuthName", "LittleSkin 登录", Instance:=Target) Else '第三方 Authlib 服务器 - If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启第三方登录?" & vbCrLf & + If MyMsgBox($"是否要在版本 {Target.Name} 中开启第三方登录?" & vbCrLf & $"登录服务器:{AuthlibServer}" & vbCrLf & vbCrLf & "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then Return End If - Setup.Set("VersionServerLogin", 4, Version:=TargetVersion) - Setup.Set("VersionServerAuthServer", AuthlibServer, Version:=TargetVersion) - Setup.Set("VersionServerAuthRegister", AuthlibServer.Replace("api/yggdrasil", "auth/register"), Version:=TargetVersion) - Setup.Set("VersionServerAuthName", "", Version:=TargetVersion) + Setup.Set("VersionServerLogin", 4, Instance:=Target) + Setup.Set("VersionServerAuthServer", AuthlibServer, Instance:=Target) + Setup.Set("VersionServerAuthRegister", AuthlibServer.Replace("api/yggdrasil", "auth/register"), Instance:=Target) + Setup.Set("VersionServerAuthName", "", Instance:=Target) End If - If PageCurrent = PageType.VersionSetup AndAlso PageCurrentSub = PageSubType.VersionSetup Then + If PageCurrent = PageType.InstanceSetup AndAlso PageCurrentSub = PageSubType.InstanceSetup Then '正在服务器选项页,需要刷新设置项显示 - FrmVersionSetup.Reload() + FrmInstanceSetup.Reload() ElseIf PageCurrent = PageType.Launch Then '正在主页,需要刷新左边栏 FrmLaunchLeft.RefreshPage(True, False) @@ -953,7 +968,7 @@ Public Class FormMain Return End If '安装 Mod - If PageVersionMod.InstallMods(FilePathList) Then Return + If PageInstanceMod.InstallMods(FilePathList) Then Return '安装整合包 If {"zip", "rar", "mrpack"}.Any(Function(t) t = Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试 Log("[System] 文件为压缩包,尝试作为整合包安装") @@ -1086,7 +1101,7 @@ Public Class FormMain ''' ''' 版本选择。这是一个副页面。 ''' - VersionSelect = 5 + InstanceSelect = 5 ''' ''' 下载管理。这是一个副页面。 ''' @@ -1094,7 +1109,7 @@ Public Class FormMain ''' ''' 版本设置。这是一个副页面。 ''' - VersionSetup = 7 + InstanceSetup = 7 ''' ''' 资源工程详情。这是一个副页面。 ''' @@ -1124,23 +1139,23 @@ Public Class FormMain OtherHelp = 0 OtherAbout = 1 OtherTest = 2 - VersionOverall = 0 - VersionSetup = 1 - VersionMod = 2 - VersionModDisabled = 3 - VersionExport = 4 + InstanceOverall = 0 + InstanceSetup = 1 + InstanceMod = 2 + InstanceModDisabled = 3 + InstanceExport = 4 End Enum ''' ''' 获取次级页面的名称。若并非次级页面则返回空字符串,故可以以此判断是否为次级页面。 ''' Private Function PageNameGet(Stack As PageStackData) As String Select Case Stack.Page - Case PageType.VersionSelect + Case PageType.InstanceSelect Return "版本选择" Case PageType.DownloadManager Return "下载管理" - Case PageType.VersionSetup - Return "版本设置 - " & If(PageVersionLeft.Version Is Nothing, "未知版本", PageVersionLeft.Version.Name) + Case PageType.InstanceSetup + Return "版本设置 - " & If(PageInstanceLeft.Instance Is Nothing, "未知版本", PageInstanceLeft.Instance.Name) Case PageType.CompDetail Return "资源下载 - " & CType(Stack.Additional(0), CompProject).TranslatedName Case PageType.HelpDetail @@ -1186,9 +1201,9 @@ Public Class FormMain Case PageType.Other If FrmOtherLeft Is Nothing Then FrmOtherLeft = New PageOtherLeft Return FrmOtherLeft.PageID - Case PageType.VersionSetup - If FrmVersionLeft Is Nothing Then FrmVersionLeft = New PageVersionLeft - Return FrmVersionLeft.PageID + Case PageType.InstanceSetup + If FrmInstanceLeft Is Nothing Then FrmInstanceLeft = New PageInstanceLeft + Return FrmInstanceLeft.PageID Case Else Return 0 '没有子页面 End Select @@ -1262,9 +1277,9 @@ Public Class FormMain Else '切换到次页面 Select Case Stack.Page - Case PageType.VersionSetup - If FrmVersionLeft Is Nothing Then FrmVersionLeft = New PageVersionLeft - CType(FrmVersionLeft.PanItem.Children(SubType), MyListItem).SetChecked(True, True, Stack = PageCurrent) + Case PageType.InstanceSetup + If FrmInstanceLeft Is Nothing Then FrmInstanceLeft = New PageInstanceLeft + CType(FrmInstanceLeft.PanItem.Children(SubType), MyListItem).SetChecked(True, True, Stack = PageCurrent) End Select PageChangeActual(Stack, SubType) End If @@ -1356,7 +1371,7 @@ Public Class FormMain Case PageType.Other '更多 If FrmOtherLeft Is Nothing Then FrmOtherLeft = New PageOtherLeft PageChangeAnim(FrmOtherLeft, FrmOtherLeft.PageGet(SubType)) - Case PageType.VersionSelect '版本选择 + Case PageType.InstanceSelect '版本选择 If FrmSelectLeft Is Nothing Then FrmSelectLeft = New PageSelectLeft If FrmSelectRight Is Nothing Then FrmSelectRight = New PageSelectRight PageChangeAnim(FrmSelectLeft, FrmSelectRight) @@ -1364,9 +1379,9 @@ Public Class FormMain If FrmSpeedLeft Is Nothing Then FrmSpeedLeft = New PageSpeedLeft If FrmSpeedRight Is Nothing Then FrmSpeedRight = New PageSpeedRight PageChangeAnim(FrmSpeedLeft, FrmSpeedRight) - Case PageType.VersionSetup '版本设置 - If FrmVersionLeft Is Nothing Then FrmVersionLeft = New PageVersionLeft - PageChangeAnim(FrmVersionLeft, FrmVersionLeft.PageGet(SubType)) + Case PageType.InstanceSetup '版本设置 + If FrmInstanceLeft Is Nothing Then FrmInstanceLeft = New PageInstanceLeft + PageChangeAnim(FrmInstanceLeft, FrmInstanceLeft.PageGet(SubType)) Case PageType.CompDetail 'Mod 信息 If FrmDownloadCompDetail Is Nothing Then FrmDownloadCompDetail = New PageDownloadCompDetail PageChangeAnim(New MyPageLeft, FrmDownloadCompDetail) diff --git a/Plain Craft Launcher 2/Modules/Base/ModAnimation.vb b/Plain Craft Launcher 2/Modules/Base/ModAnimation.vb index 4248ea4..4761708 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModAnimation.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModAnimation.vb @@ -840,7 +840,7 @@ Sleeper: Public Sub AniTimer(DeltaTick As Integer) Try - If DeltaTick / AniSpeed > 200 Then Log("[Animation] 两个动画帧间隔 " & DeltaTick & " ms", LogLevel.Developer) + If DeltaTick / AniSpeed > 100 Then Log("[Animation] 两个动画帧间隔 " & DeltaTick & " ms", LogLevel.Developer) Dim i As Integer = -1 '循环每个动画组 Do While i + 1 < AniGroups.Count diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index c54156d..34014f3 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -14,13 +14,13 @@ Public Module ModBase #Region "声明" '下列版本信息由更新器自动修改 - Public Const VersionBaseName As String = "2.11.2" '不含分支前缀的显示用版本名 - Public Const VersionStandardCode As String = "2.11.2." & VersionBranchCode '标准格式的四段式版本号 + Public Const VersionBaseName As String = "2.12.0" '不含分支前缀的显示用版本名 + Public Const VersionStandardCode As String = "2.12.0." & VersionBranchCode '标准格式的四段式版本号 Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换 #If BETA Then Public Const VersionCode As Integer = 372 'Release #Else - Public Const VersionCode As Integer = 375 'Snapshot + Public Const VersionCode As Integer = 377 'Snapshot #End If '自动生成的版本信息 Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName @@ -1211,14 +1211,17 @@ Re: Public Class FileChecker ''' ''' 文件的准确大小。 + ''' 不检查则为 -1。 ''' Public ActualSize As Long = -1 ''' ''' 文件的最小大小。 + ''' 不检查则为 -1。 ''' Public MinSize As Long = -1 ''' ''' 文件的 MD5、SHA1 或 SHA256。会根据输入字符串的长度自动判断种类。 + ''' 不检查则为 Nothing。 ''' Public Hash As String = Nothing ''' @@ -1226,7 +1229,8 @@ Re: ''' Public CanUseExistsFile As Boolean = True ''' - ''' 是否为 Json 文件。 + ''' 是否要求为 JSON 文件。 + ''' 即,开头结尾必须为 {} 或 []。 ''' Public IsJson As Boolean = False Public Sub New(Optional MinSize As Long = -1, Optional ActualSize As Long = -1, Optional Hash As String = Nothing, Optional CanUseExistsFile As Boolean = True, Optional IsJson As Boolean = False) @@ -1429,6 +1433,7 @@ RetryDir: If Ex Is Nothing Then Return "无可用错误信息!" '获取最底层的异常 + Dim OuterEx As Exception = Ex Dim InnerEx As Exception = Ex Do Until InnerEx.InnerException Is Nothing InnerEx = InnerEx.InnerException @@ -1452,7 +1457,7 @@ RetryDir: Loop '构造输出信息 - Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList) + Dim UsualReason As String = AnalyzeUsualReason(InnerEx, OuterEx, DescList) If UsualReason Is Nothing Then Return DescList.Join(vbCrLf) Else @@ -1466,6 +1471,7 @@ RetryDir: If Ex Is Nothing Then Return "无可用错误信息!" '获取最底层的异常 + Dim OuterEx As Exception = Ex Dim InnerEx As Exception = Ex Do Until InnerEx.InnerException Is Nothing InnerEx = InnerEx.InnerException @@ -1481,7 +1487,7 @@ RetryDir: Dim Desc As String = Join(DescList, vbCrLf & "→ ") '构造输出信息 - Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList) + Dim UsualReason As String = AnalyzeUsualReason(InnerEx, OuterEx, DescList) If UsualReason IsNot Nothing Then Return UsualReason & "详细错误:" & DescList.First Else @@ -1580,16 +1586,6 @@ RetryDir: Return Mid(Str.PadRight(Length, Code), Str.Length + 1) & Str End Function ''' - ''' 将一个小数显示为固定的小数点后位数形式,将向零取整。 - ''' 如 12 保留 2 位则输出 12.00,而 95.678 保留 2 位则输出 95.67。 - ''' - Public Function StrFillNum(Num As Double, Length As Integer) As String - Num = Math.Round(Num, Length, MidpointRounding.AwayFromZero) - StrFillNum = Num - If Not StrFillNum.Contains(".") Then Return (StrFillNum & ".").PadRight(StrFillNum.Length + 1 + Length, "0") - Return StrFillNum.PadRight(StrFillNum.Split(".")(0).Length + 1 + Length, "0") - End Function - ''' ''' 移除字符串首尾的标点符号、回车,以及括号中、冒号后的补充说明内容。 ''' Public Function StrTrim(Str As String, Optional RemoveQuote As Boolean = True) @@ -1964,7 +1960,7 @@ RetryDir: Function(Source) Source.Key.Replace(" ", "").ContainsF(QueryPart, True))) Next '按照相似度进行排序 - Entries = Entries.Sort( + Entries = Entries.SortByComparison( Function(Left, Right) As Boolean If Left.AbsoluteRight Xor Right.AbsoluteRight Then Return Left.AbsoluteRight @@ -2031,7 +2027,7 @@ RetryDir: Implements IDictionary(Of TKey, TValue) Implements IEnumerable(Of KeyValuePair(Of TKey, TValue)) - Private ReadOnly SyncRoot As New Object + Public ReadOnly SyncRoot As New Object Private ReadOnly _Dictionary As New Dictionary(Of TKey, TValue) '构造函数 @@ -2229,6 +2225,19 @@ RetryDir: Return CultureInfo.CurrentCulture.Name.StartsWithF("zh-") OrElse CultureInfo.CurrentUICulture.Name.StartsWithF("zh-") End Function + ''' + ''' 判断对象是否为某个泛型类型的实例。 + ''' + Public Function IsInstanceOfGenericType(genericType As Type, obj As Object) As Boolean + If obj Is Nothing Then Return False + Dim t = obj.GetType() + While t IsNot Nothing + If t.IsGenericType AndAlso t.GetGenericTypeDefinition() Is genericType Then Return True + t = t.BaseType + End While + Return False + End Function + Private Uuid As Integer = 1 Private UuidLock As Object ''' @@ -2256,7 +2265,7 @@ RetryDir: Next i End Function ''' - ''' 数组去重。 + ''' 去重。 ''' Public Function Distinct(Of T)(Arr As ICollection(Of T), IsEqual As ComparisonBoolean(Of T)) As List(Of T) Dim ResultArray As New List(Of T) @@ -2272,6 +2281,15 @@ NextElement: Public Function Any(Arr As UIElementCollection) As Boolean Return Arr?.Count > 0 End Function + ''' + ''' 对集合的每个元素执行指定操作。 + ''' + Public Function ForEach(Of T)(Collection As IEnumerable(Of T), Action As Action(Of T)) As IEnumerable(Of T) + For Each Item As T In Collection + Action(Item) + Next + Return Collection + End Function ''' ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。 @@ -2494,10 +2512,46 @@ NextElement: End Sub ''' - ''' 按照既定的函数进行选择排序。 - ''' 传入两个对象,若第一个对象应该排在前面,则返回 True。 + ''' 选择最大值对应的对象。 + ''' 若没有元素则返回 Nothing。 ''' - Public Function Sort(Of T)(List As IList(Of T), SortRule As ComparisonBoolean(Of T)) As List(Of T) + Public Function MaxOf(Of T, C As IComparable(Of C))(Source As IEnumerable(Of T), Selector As Func(Of T, C)) As T + Using Enumerator = Source.GetEnumerator() + If Not Enumerator.MoveNext() Then Return Nothing + Dim MaxItem As T = Enumerator.Current + Dim MaxValue As C = Selector(MaxItem) + While Enumerator.MoveNext() + Dim Value = Selector(Enumerator.Current) + If Value.CompareTo(MaxValue) <= 0 Then Continue While + MaxItem = Enumerator.Current + MaxValue = Value + End While + Return MaxItem + End Using + End Function + ''' + ''' 选择最小值对应的对象。 + ''' 若没有元素则返回 Nothing。 + ''' + Public Function MinOf(Of T, C As IComparable(Of C))(List As IEnumerable(Of T), Selector As Func(Of T, C)) As T + Using Enumerator = List.GetEnumerator() + If Not Enumerator.MoveNext() Then Return Nothing + Dim MinItem As T = Enumerator.Current + Dim MinValue As C = Selector(MinItem) + While Enumerator.MoveNext() + Dim Value = Selector(Enumerator.Current) + If Value.CompareTo(MinValue) >= 0 Then Continue While + MinItem = Enumerator.Current + MinValue = Value + End While + Return MinItem + End Using + End Function + ''' + ''' 按照既定的函数进行选择排序。 + ''' 返回第一个对象是否应该排在前面(a > b)。 + ''' + Public Function SortByComparison(Of T)(List As IList(Of T), SortRule As ComparisonBoolean(Of T)) As List(Of T) Dim NewList As New List(Of T) While List.Any Dim Highest = List(0) @@ -2769,19 +2823,25 @@ Retry: ''' ''' 将 XML 转换为对应 UI 对象。 + ''' 注意:性能较差,不应大量使用。 ''' Public Function GetObjectFromXML(Str As XElement) Return GetObjectFromXML(Str.ToString) End Function ''' ''' 将 XML 转换为对应 UI 对象。 + ''' 注意:性能较差,不应大量使用。 ''' Public Function GetObjectFromXML(Str As String) As Object Str = Str. '兼容旧版自定义事件写法 Replace("EventType=""", "local:CustomEventService.EventType="""). Replace("EventData=""", "local:CustomEventService.EventData="""). + Replace("EventType='", "local:CustomEventService.EventType='"). + Replace("EventData='", "local:CustomEventService.EventData='"). Replace("Property=""EventType""", "Property=""local:CustomEventService.EventType"""). - Replace("Property=""EventData""", "Property=""local:CustomEventService.EventData""") + Replace("Property=""EventData""", "Property=""local:CustomEventService.EventData"""). + Replace("Property='EventType'", "Property='local:CustomEventService.EventType'"). + Replace("Property='EventData'", "Property='local:CustomEventService.EventData'") Using Stream As New MemoryStream(Encoding.UTF8.GetBytes(Str)) '类型检查 Using Reader As New XamlXmlReader(Stream) @@ -3114,7 +3174,7 @@ Retry: "操作系统:" & My.Computer.Info.OSFullName & "(32 位:" & Is32BitSystem & ")" & vbCrLf & "剩余内存:" & Int(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024) & " M / " & Int(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024) & " M" & vbCrLf & "DPI:" & DPI & "(" & Math.Round(DPI / 96, 2) * 100 & "%)" & vbCrLf & - "MC 文件夹:" & If(PathMcFolder, "Nothing") & vbCrLf & + "MC 文件夹:" & If(McFolderSelected, "Nothing") & vbCrLf & "文件位置:" & Path) End Sub diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb index 401e8fa..b371126 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb @@ -242,7 +242,7 @@ RequestFinished: If Header.Key.ToLower = "content-type" Then Request.Content?.Headers.TryAddWithoutValidation(Header.Key, Header.Value) Else - Request.Headers.Add(Header.Key, Header.Value) + Request.Headers.TryAddWithoutValidation(Header.Key, Header.Value) End If Next SecretHeadersSign(Url, Request, SimulateBrowserHeaders) @@ -257,7 +257,7 @@ RequestFinished: '延迟初始化,以避免在程序启动前加载 CacheCow 导致 DLL 加载失败 If RequestClient Is Nothing Then RequestClient = CacheCow.Client.ClientExtensions.CreateClient(New CacheCow.Client.FileCacheStore.FileStore(PathTemp & "Cache/Http/"), New HttpClientHandler With { - .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None, + .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip, .UseCookies = False '不设为 False 就不能从 Header 手动传入 Cookies }) End If @@ -308,15 +308,25 @@ RequestFinished: ''' 若解析失败,则返回 Nothing。 ''' Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationTokenSource) As String - Dim GetIPReliability = Function(Key) IPReliability.GetOrDefault(Key.ToString, 0) - '初步 DNS 解析 + '若一分钟内已失败过,则不再重复尝试解析,以减少断网时的 Exception 数量 Dim Host = Request.RequestUri.Host + SyncLock DNSFailureRecord.SyncRoot + If DNSFailureRecord.ContainsKey(Host) Then + If Date.Now - DNSFailureRecord(Host) < New TimeSpan(0, 1, 0) Then + Return Nothing + Else + DNSFailureRecord.Remove(Host) + End If + End If + End SyncLock + '初步 DNS 解析 Dim DnsTask As Task(Of IPAddress()) = Nothing Try DnsTask = Dns.GetHostAddressesAsync(Host) DnsTask.Wait(CancelToken.Token) Catch ex As Exception Log(ex, $"DNS 解析失败({Host})") + DNSFailureRecord(Host) = Date.Now Return Nothing End Try Dim Candidates As IPAddress() = DnsTask.Result.ToArray @@ -325,6 +335,7 @@ RequestFinished: Return Nothing End If '若同时存在 IPv4 和 IPv6 地址,仅选择其中一类(因为 GFW 可能只屏蔽了 IPv4 或 IPv6) + Dim GetIPReliability = Function(Key) IPReliability.GetOrDefault(Key.ToString, 0) 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 @@ -347,6 +358,7 @@ RequestFinished: End If Return TargetIp End Function + Private DNSFailureRecord As New SafeDictionary(Of String, Date) ''' ''' 记录每个 IP 地址的请求可靠度。 ''' 通常取值 -1 ~ +0.5,越高越好。未尝试过的 IP 应视为 0。 @@ -387,7 +399,7 @@ RequestFinished: #Region "多线程下载引擎" Private ThreadClient As New HttpClient(New HttpClientHandler With { - .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None + .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip }) ''' @@ -407,7 +419,6 @@ RequestFinished: ''' Public NetTaskSpeedLimitLeft As Long = -1 Private ReadOnly NetTaskSpeedLimitLeftLock As New Object - Private NetTaskSpeedLimitLeftLast As Long ''' ''' 正在运行中的线程数。 ''' @@ -438,39 +449,35 @@ RequestFinished: ''' ''' 尚未进行已存在检查。 ''' - WaitForCheck = -1 + WaitingToCheck = -1 ''' ''' 尚未开始。 ''' - WaitForDownload = 0 + WaitingToDownload = 0 ''' ''' 正在连接,尚未获取文件大小。 ''' - Connect = 1 + Connecting = 1 ''' - ''' 已获取文件大小,尚未有有效下载。 + ''' 已连接,正在尝试接收首个下载数据包。 ''' - [Get] = 2 + Reading = 2 ''' ''' 正在下载。 ''' - Download = 3 + Downloading = 3 ''' ''' 正在合并文件。 ''' - Merge = 4 - ''' - ''' 不进行下载,因为已发现现存的文件。 - ''' - WaitForCopy = 5 + Merging = 4 ''' ''' 已完成。 ''' - Finish = 6 + Finished = 5 ''' ''' 已失败或中断。 ''' - [Error] = 7 + Interrupted = 6 End Enum ''' ''' 预下载检查行为。 @@ -612,13 +619,13 @@ RequestFinished: ''' ''' 当前线程的状态。 ''' - Public State As NetState = NetState.WaitForDownload + Public State As NetState = NetState.WaitingToDownload ''' ''' 是否已经结束。 ''' Public ReadOnly Property IsEnded As Boolean Get - Return State = NetState.Finish OrElse State = NetState.Error + Return State = NetState.Finished OrElse State = NetState.Interrupted End Get End Property @@ -716,7 +723,7 @@ RequestFinished: ''' ''' 当前的下载状态。 ''' - Public State As NetState = NetState.WaitForCheck + Public State As NetState = NetState.WaitingToCheck ''' ''' 导致下载失败的原因。 ''' @@ -741,7 +748,7 @@ RequestFinished: ''' Public ReadOnly Property IsNoSplit As Boolean Get - Return IsUnknownSize OrElse FileSize < FilePieceLimit + Return IsUnknownSize OrElse FileSize < 1024 * 1024 '小于 1MB 的文件不分割 End Get End Property ''' @@ -797,24 +804,22 @@ RequestFinished: Public ReadOnly Property Progress As Double Get Select Case State - Case NetState.WaitForCheck + Case NetState.WaitingToCheck Return 0 - Case NetState.WaitForCopy - Return 0.2 - Case NetState.WaitForDownload + Case NetState.WaitingToDownload Return 0.01 - Case NetState.Connect + Case NetState.Connecting Return 0.02 - Case NetState.Get + Case NetState.Reading Return 0.04 - Case NetState.Download + Case NetState.Downloading '正在下载中,对应 5% ~ 98% Dim OriginalProgress As Double = If(IsUnknownSize, 0.5, DownloadDone / Math.Max(FileSize, 1)) OriginalProgress = 1 - (1 - OriginalProgress) ^ 0.9 Return OriginalProgress * 0.93 + 0.05 - Case NetState.Merge + Case NetState.Merging Return 0.99 - Case NetState.Finish, NetState.Error + Case NetState.Finished, NetState.Interrupted Return 1 Case Else Throw New ArgumentOutOfRangeException("文件状态未知:" & State) @@ -859,7 +864,7 @@ RequestFinished: ''' 新建一个需要下载的文件。 ''' ''' 包含文件名的本地地址。 - Public Sub New(Urls As IEnumerable(Of String), LocalPath As String, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False) + Public Sub New(Urls As IEnumerable(Of String), LocalPath As String, Optional Checker As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False) Dim Sources As New List(Of NetSource) Dim Count As Integer = 0 Urls = Urls.Distinct.ToArray @@ -869,7 +874,7 @@ RequestFinished: Next Me.Sources = Sources Me.LocalPath = LocalPath - Me.Check = Check + Me.Check = Checker Me.SimulateBrowserHeaders = SimulateBrowserHeaders Me.LocalName = GetFileNameFromPath(LocalPath) End Sub @@ -886,10 +891,10 @@ RequestFinished: '条件检测 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 + (IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Interrupted) Then Return Nothing + If State >= NetState.Merging OrElse State = NetState.WaitingToCheck Then Return Nothing SyncLock LockState - If State < NetState.Connect Then State = NetState.Connect + If State < NetState.Connecting Then State = NetState.Connecting End SyncLock '初始化参数 '获取线程起点与下载源 @@ -897,10 +902,10 @@ RequestFinished: If IsNoSplit Then GoTo StartSingleThreadDownload '只剩下单线程可用点 If Not HasAvailableSource(False) Then - If SourcesOnce.First.SingleThread IsNot Nothing AndAlso SourcesOnce.First.SingleThread.State <> NetState.Error Then Return Nothing + If SourcesOnce.First.SingleThread IsNot Nothing AndAlso SourcesOnce.First.SingleThread.State <> NetState.Interrupted Then Return Nothing StartSingleThreadDownload: Reset() - State = NetState.Get + State = NetState.Reading End If End SyncLock SyncLock LockChain @@ -913,7 +918,7 @@ StartSingleThreadDownload: End If '寻找失败点 For Each Thread As NetThread In Threads - If Thread.State = NetState.Error AndAlso Thread.DownloadUndone > 0 Then + If Thread.State = NetState.Interrupted AndAlso Thread.DownloadUndone > 0 Then StartPosition = Thread.DownloadStart + Thread.DownloadDone StartSource = GetSource(Thread.Source.Id + 1) GoTo StartThread @@ -942,7 +947,7 @@ StartThread: Dim ThreadUuid As Integer = GetUuid() If Not Tasks.Any() Then Return Nothing '由于中断,已没有可用任务 Th = New Thread(AddressOf Thread) With {.Name = $"下载 {Tasks(0).Uuid}/{Uuid}/{ThreadUuid}#", .Priority = ThreadPriority.BelowNormal} - ThreadInfo = New NetThread With {.Uuid = ThreadUuid, .DownloadStart = StartPosition, .Thread = Th, .Source = StartSource, .Task = Me, .State = NetState.WaitForDownload} + ThreadInfo = New NetThread With {.Uuid = ThreadUuid, .DownloadStart = StartPosition, .Thread = Th, .Source = StartSource, .Task = Me, .State = NetState.WaitingToDownload} '链表处理 If ThreadInfo.IsFirstThread OrElse Threads Is Nothing Then Threads = ThreadInfo @@ -981,8 +986,8 @@ StartThread: CancelToken As CancellationTokenSource = Nothing, HostIp As String = Nothing '部分下载源真的特别慢,并且只需要一个请求,例如 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) - Th.State = NetState.Connect + Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 10000) * (1 + Th.Source.FailCount), 30000) + Th.State = NetState.Connecting Try Dim HttpDataCount As Integer = 0 If SourcesOnce.Contains(Th.Source) AndAlso Th <> Th.Source.SingleThread Then GoTo SourceBreak @@ -994,7 +999,7 @@ StartThread: Dim ContentLength As Long = 0 Response = ThreadClient.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout) If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查 - If State = NetState.Error Then GoTo SourceBreak '快速中断 + If State = NetState.Interrupted 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) @@ -1045,9 +1050,9 @@ NotSupportRange: Throw New RangeNotSupportedException($"获取到的分段大小不一致: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 + Th.State = NetState.Reading SyncLock LockState - If State < NetState.Get Then State = NetState.Get + If State < NetState.Reading Then State = NetState.Reading End SyncLock '创建缓存文件 If IsNoSplit Then @@ -1065,7 +1070,7 @@ NotSupportRange: Dim ResponseBytes As Byte() = New Byte(16384) {} HttpDataCount = ResponseStream.ReadAsync(ResponseBytes, 0, 16384, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout) 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) + HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merging AndAlso (Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th) '限速 While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0 Threading.Thread.Sleep(16) @@ -1080,9 +1085,9 @@ NotSupportRange: '有数据 If Th.DownloadDone = 0 Then '第一次接受到数据 - Th.State = NetState.Download + Th.State = NetState.Downloading SyncLock LockState - If State < NetState.Download Then State = NetState.Download + If State < NetState.Downloading Then State = NetState.Downloading End SyncLock SyncLock LockCount ConnectCount += 1 @@ -1116,16 +1121,16 @@ NotSupportRange: HttpDataCount = ResponseStream.ReadAsync(ResponseBytes, 0, 16384, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout) End While SourceBreak: - If State = NetState.Error OrElse (Th.Source.IsFailed AndAlso Th.Source.SingleThread <> Th) OrElse (Th.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then + If State = NetState.Interrupted OrElse (Th.Source.IsFailed AndAlso Th.Source.SingleThread <> Th) OrElse (Th.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then '被外部中断 - Th.State = NetState.Error + Th.State = NetState.Interrupted Log($"[Download] {LocalName}:中断") ElseIf HttpDataCount = 0 AndAlso Th.DownloadUndone > 0 AndAlso Not IsUnknownSize Then '服务器无返回数据 Throw New Exception($"返回的 ContentLength 过多:ContentLength 为 {ContentLength},但获取到的总数据量仅为 {Th.DownloadDone}(全文件总数据量 {DownloadDone})") Else '本线程完成 - Th.State = NetState.Finish + Th.State = NetState.Finished If ModeDebug Then Log($"[Download] {LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})") RecordIPReliability(HostIp, 0.5) End If @@ -1147,7 +1152,7 @@ SourceBreak: End SyncLock '合并 If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso - State < NetState.Merge AndAlso Th.State <> NetState.Error Then Merge(Th) + State < NetState.Merging AndAlso Th.State <> NetState.Interrupted Then Merge(Th) End Try End Sub Private Sub SourceFail(Th As NetThread, ex As Exception, IsMergeFailure As Boolean) @@ -1158,7 +1163,7 @@ SourceBreak: Task.FailCount += 1 Next End SyncLock - Th.State = NetState.Error + Th.State = NetState.Interrupted Th.Source.Ex = ex '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传) Dim IsRangeNotSupported As Boolean = TypeOf ex Is RangeNotSupportedException OrElse ex.Message.Contains("(416)") @@ -1202,13 +1207,13 @@ SourceBreak: End SyncLock Reset() SyncLock LockState - State = NetState.WaitForDownload + State = NetState.WaitingToDownload End SyncLock ElseIf HasAvailableSource() AndAlso IsMergeFailure Then '合并失败且单个源失败:继续下一个源 Reset() SyncLock LockState - State = NetState.WaitForDownload + State = NetState.WaitingToDownload End SyncLock Else '失败 @@ -1251,8 +1256,8 @@ SourceBreak: Private Sub Merge(Th As NetThread) '状态判断 SyncLock LockState - If State < NetState.Merge Then - State = NetState.Merge + If State < NetState.Merging Then + State = NetState.Merging Else Return End If @@ -1323,7 +1328,11 @@ Retry: GoTo Retry End If '失败,禁用当前下载源并重启下载 - If File.Exists(LocalPath) Then File.Delete(LocalPath) + Try + If File.Exists(LocalPath) Then File.Delete(LocalPath) + Catch exx As Exception + Log(ex, $"删除合并失败的文件出错({LocalPath})") + End Try SourceFail(Th, ex, True) Finally Cache?.Dispose() @@ -1334,10 +1343,10 @@ Retry: ''' Private Sub Fail(Optional RaiseEx As Exception = Nothing) SyncLock LockState - If State >= NetState.Finish Then Return + If State >= NetState.Finished Then Return If RaiseEx IsNot Nothing Then Ex.Add(RaiseEx) '凉凉 - State = NetState.Error + State = NetState.Interrupted End SyncLock AbortInternal() For Each Task In Tasks @@ -1353,8 +1362,8 @@ Retry: If Tasks.Any Then Return '确认中断 SyncLock LockState - If State >= NetState.Finish Then Return - State = NetState.Error + If State >= NetState.Finished Then Return + State = NetState.Interrupted End SyncLock AbortInternal() End Sub @@ -1373,8 +1382,8 @@ Retry: ''' Public Sub Finish(Optional PrintLog As Boolean = True) SyncLock LockState - If State >= NetState.Finish Then Return - State = NetState.Finish + If State >= NetState.Finished Then Return + State = NetState.Finished End SyncLock SyncLock NetManager.LockRemain NetManager.FileRemain -= 1 @@ -1483,20 +1492,10 @@ FinishExCatch: Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False) If Input IsNot Nothing Then Files = New SafeList(Of NetFile)(Input) '去重 - Dim ResultArray As New SafeList(Of NetFile) - For i = 0 To Files.Count - 1 - For ii = i + 1 To Files.Count - 1 - If Files(i).LocalPath = Files(ii).LocalPath Then GoTo NextElement - Next - ResultArray.Add(Files(i)) -NextElement: - Next - Files = ResultArray + Files = New SafeList(Of NetFile)(Files.Distinct(Function(a, b) a.LocalPath = b.LocalPath)) '设置剩余文件数 SyncLock FileRemainLock - For Each File In Files - If File.State <> NetState.Finish Then FileRemain += 1 - Next + FileRemain += Files.Where(Function(f) f.State <> NetState.Finished).Count End SyncLock State = LoadState.Loading '开始执行 @@ -1517,109 +1516,162 @@ NextElement: End If Next If Not File.HasAvailableSource() Then Throw New ArgumentException("输入的下载链接不正确!") + File.LocalPath = File.LocalPath.Replace("/", "\") 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) + Directory.CreateDirectory(GetPathFromFullPath(File.LocalPath)) '创建目标文件夹 Next '接入下载管理器 NetManager.Start(Me) - '将文件分配给多个线程以进行已存在查找 - Dim Folders As New List(Of String) '可能会用于已存在查找的文件夹列表 - Dim FoldersFinal As New List(Of String) '最终用于查找的列表 - If Not Setup.Get("SystemDebugSkipCopy") Then '在设置中禁用复制 - Folders.Add(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\") '总是添加官启文件夹,因为 HMCL 会把所有文件存在这里 - For Each Folder In McFolderList - Folders.Add(Folder.Path) - Next - Folders = Folders.Distinct.ToList - For Each Folder In Folders - If Folder <> PathMcFolder AndAlso Directory.Exists(Folder) Then FoldersFinal.Add(Folder) - Next - End If - '最多 5 个线程,最少每个线程分配 10 个文件 - Dim FilesPerThread As Integer = Math.Max(5, Files.Count / 10 + 1) - Dim FilesInThread As New List(Of NetFile) + + '==================================== + ' 已存在文件查找 + '==================================== + + '整理允许进行查找的文件 + Dim FilesToCheck As New List(Of NetFile) + Dim DisabledCopy As Boolean = Setup.Get("SystemDebugSkipCopy") '在设置中禁用了复制 For Each File In Files - FilesInThread.Add(File) - If FilesInThread.Count = FilesPerThread Then - Dim FilesToRun As New List(Of NetFile) - FilesToRun.AddRange(FilesInThread) - RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "下载 文件复制 " & Uuid) - FilesInThread.Clear() + If Not DisabledCopy AndAlso File.Check?.CanUseExistsFile Then + FilesToCheck.Add(File) + Else '不允许,直接开始下载 + SyncLock LockState + File.State = NetState.WaitingToDownload + File.IsCopy = False + End SyncLock End If Next - If FilesInThread.Any Then - Dim FilesToRun As New List(Of NetFile) - FilesToRun.AddRange(FilesInThread) - RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "下载 文件复制 " & Uuid) - FilesInThread.Clear() + If Not FilesToCheck.Any Then Return + '获取 MC 文件夹列表 + Dim Folders As New List(Of String) + Folders.Add(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\") '总是添加官启文件夹,因为 HMCL 会把所有文件存在这里 + Folders.AddRange(McFolderList.Select(Function(f) f.Location)) + Folders = Folders.Distinct.Where(Function(f) Directory.Exists(f)).ToList + '平均分配到多个检查线程 + Dim ThreadCount As Integer = MathClamp(FilesToCheck.Count \ 40, 1, 8) '每个线程至少 40 个文件,最多 8 线程 + If ThreadCount = 1 Then '只有一个线程,直接执行 + CheckExistingFiles(FilesToCheck, Folders) + Else + Dim BaseSize = FilesToCheck.Count \ ThreadCount + Dim Remainder = FilesToCheck.Count Mod ThreadCount + Dim Index = 0 + For i = 0 To ThreadCount - 1 + Dim Size = BaseSize + If(i < Remainder, 1, 0) + Dim ThreadFiles = FilesToCheck.GetRange(Index, Size) + Index += Size + RunInNewThread(Sub() CheckExistingFiles(ThreadFiles, Folders), $"下载 文件复制 {Uuid}/{GetUuid()}") + Next End If Catch ex As Exception - OnFail(New List(Of Exception) From {ex}) + OnFail(New List(Of Exception) From {New Exception("下载初始化失败", ex)}) End Try End Sub, "L/下载 " & Uuid) End Sub - Private Sub StartCopy(Files As List(Of NetFile), FolderList As List(Of String)) + Private Sub CheckExistingFiles(Files As List(Of NetFile), FolderList As List(Of String)) Try - If ModeDebug Then Log($"[Download] 检查线程分配文件数:{Files.Count},线程名:{Thread.CurrentThread.Name}") - '试图从已存在的 Minecraft 文件夹中寻找目标文件 - Dim ExistFiles As New List(Of KeyValuePair(Of NetFile, String)) '{NetFile, Target As String} - For Each File As NetFile In Files - Dim ExistFilePath As String = Nothing - '判断是否有已存在的文件 - If File.Check IsNot Nothing AndAlso McFolderList IsNot Nothing AndAlso PathMcFolder IsNot Nothing AndAlso - File.Check.CanUseExistsFile AndAlso File.LocalPath.StartsWithF(PathMcFolder) Then - Dim Relative = File.LocalPath.Replace(PathMcFolder, "") - For Each Folder In FolderList - Dim Target = Folder & Relative - If File.Check.Check(Target) Is Nothing Then - ExistFilePath = Target - Exit For - End If + If ModeDebug Then Log($"[Download] 文件检查线程已启动,分配的文件数:{Files.Count}") + '列出 MC 文件夹中的各个版本文件夹 + Dim VersionFolders As New List(Of String) + For Each McFolder In FolderList + Dim VersionsFolder As New DirectoryInfo(McFolder & "versions\") + If VersionsFolder.Exists() Then + For Each VersionFolder In VersionsFolder.GetDirectories + VersionFolders.Add(VersionFolder.FullName & "\") Next End If - '若存在,则改变状态 - SyncLock LockState - If ExistFilePath IsNot Nothing Then - File.State = NetState.WaitForCopy - File.IsCopy = True - ExistFiles.Add(New KeyValuePair(Of NetFile, String)(File, ExistFilePath)) - Else - File.State = NetState.WaitForDownload - File.IsCopy = False - End If - End SyncLock Next - '复制已存在的文件 - For Each FileToken In ExistFiles - Dim File As NetFile = FileToken.Key - SyncLock LockState - If File.State > NetState.WaitForCopy Then Return - End SyncLock - Dim LocalPath As String = FileToken.Value - Dim RetryCount As Integer = 0 + '处理每个文件 + For Each File As NetFile In Files + Dim Target As String = CheckExistingFile(FolderList, VersionFolders, File) + If File.State >= NetState.WaitingToDownload Then Return '中断 + If Target Is Nothing Then + '未找到相同文件 + SyncLock LockState + File.State = NetState.WaitingToDownload + File.IsCopy = False + End SyncLock + Else + '已找到相同文件 + File.IsCopy = True + Dim RetryCount As Integer = 0 Retry: - Try - Log("[Download] 复制已存在的文件(" & LocalPath & ")") - CopyFile(LocalPath, File.LocalPath) - File.Finish(False) - Catch ex As Exception - RetryCount += 1 - Log(ex, $"复制已存在的文件失败,重试第 {RetryCount} 次({LocalPath} -> {File.LocalPath})") - If RetryCount < 3 Then - Thread.Sleep(200) - GoTo Retry - End If - File.State = NetState.WaitForDownload - File.IsCopy = False - End Try + Try + If Target <> File.LocalPath Then + Log($"[Download] 复制已存在的文件:{Target} → {File.LocalPath}") + CopyFile(Target, File.LocalPath) + End If + File.Finish(False) + Catch ex As Exception + RetryCount += 1 + Log(ex, $"复制已存在的文件失败,第 {RetryCount} 次重试({Target} → {File.LocalPath})") + If RetryCount < 3 Then + Thread.Sleep(200) + GoTo Retry + End If + '失败,回退到下载 + SyncLock LockState + File.State = NetState.WaitingToDownload + File.IsCopy = False + End SyncLock + End Try + End If Next Catch ex As Exception - Log(ex, "下载已存在文件查找失败", LogLevel.Feedback) + OnFail(New List(Of Exception) From {New Exception("下载已存在文件查找失败", ex)}) End Try End Sub + Private Function CheckExistingFile(FolderList As List(Of String), VersionFolders As List(Of String), File As NetFile) As String + '目标文件已存在 + If File.Check.Check(File.LocalPath) Is Nothing Then Return File.LocalPath + '没有可用的检查规则,只能开始下载 + If File.Check.Hash Is Nothing AndAlso File.Check.ActualSize < 0 Then Return Nothing + '大致判断文件类别 + Dim TypeIndexes = + {"\assets\", "\libraries\", "\versions\", "\mods\", "\coremods\", "\lib\", "\resourcepacks\", "\texturepacks\", "\shaderpacks\"}. + Select(Function(FolderName) (FolderName, File.LocalPath.IndexOfF(FolderName, True))). + Where(Function(kv) kv.Item2 >= 0).ToList + If Not TypeIndexes.Any Then + If File.LocalName.EndsWithF(".jar") Then + TypeIndexes.Add(("\versions\", 1)) '总是对 jar 进行版本文件检查,以包括另存为 jar 的情况 + Else + Return Nothing + End If + End If + Dim Type = TypeIndexes.MaxOf(Function(kv) kv.Item2).FolderName.TrimStart("\"c) + '根据类别进行查找 + Select Case Type + Case "assets\", "libraries\" + 'assets/libraries:查找 MC 文件夹下的相同路径 + For Each Folder In FolderList + Dim Candidate = Folder & Type & File.LocalPath.AfterFirst(Type) + If File.Check.Check(Candidate) Is Nothing Then Return Candidate + Next + Case "versions\" + '版本 jar 或 json:查找 MC 文件夹下的各个版本文件夹 + For Each VersionFolder In VersionFolders + For Each Candidate In Directory.GetFiles(VersionFolder, + "*." & GetFileNameFromPath(File.LocalPath).AfterLast(".").ToLower, SearchOption.TopDirectoryOnly) + If File.Check.Check(Candidate) Is Nothing Then Return Candidate + Next + Next + Case Else + '社区资源 + If File.Check.ActualSize < 0 OrElse File.Check.Hash Is Nothing Then Return Nothing '必须要求指定了文件大小和 Hash + For Each Folder In FolderList.Concat(VersionFolders) + Dim TargetFolder = Folder & Type + If Not Directory.Exists(TargetFolder) Then Continue For + For Each Candidate In Directory.GetFiles(TargetFolder) + '快速进行大小校验 + Static Sizes As New SafeDictionary(Of String, Long) + If Not Sizes.ContainsKey(Candidate) Then Sizes(Candidate) = New FileInfo(Candidate).Length + If File.Check.ActualSize <> Sizes(Candidate) Then Continue For + 'Hash 校验 + If File.Check.Check(Candidate) Is Nothing Then Return Candidate + Next + Next + End Select + Return Nothing + End Function Public Sub OnFileFinish(File As NetFile) '要求全部文件完成 @@ -1655,7 +1707,7 @@ Retry: [Error] = If(UsefulExs.FirstOrDefault, ExList.FirstOrDefault) '获取实际失败的文件 For Each File In Files - If File.State <> NetState.Error Then Continue For + If File.State <> NetState.Interrupted Then Continue For If File.Sources.All(Function(s) TypeOf s.Ex Is OperationCanceledException OrElse TypeOf s.Ex Is TimeoutException OrElse TypeOf s.Ex Is ThreadInterruptedException) Then Continue For 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 & @@ -1736,14 +1788,6 @@ Retry: Public FileRemain As Integer = 0 Public ReadOnly LockRemain As New Object - ''' - ''' 上次记速时的已下载大小。 - ''' - Private SpeedLastDone As Long = 0 - ''' - ''' 至多最近 30 次下载速度的记录,较新的在前面。 - ''' - Private SpeedLast As New List(Of Long) '这些属性由 RefreshStat 刷新 ''' ''' 当前的全局下载速度,单位为 Byte / 秒。 @@ -1764,6 +1808,8 @@ Retry: RefreshStatLast += DeltaTime #Region "刷新整体速度" '计算瞬时速度 + Static SpeedLast As New List(Of Long) '记录至多最近 30 次下载速度的记录,较新的在前面 + Static SpeedLastDone As Long = 0 '上次记速时的已下载大小 Dim ActualSpeed As Double = Math.Max(0, (DownloadDone - SpeedLastDone) / (DeltaTime / 1000)) SpeedLast.Insert(0, ActualSpeed) If SpeedLast.Count >= 31 Then SpeedLast.RemoveAt(30) @@ -1799,8 +1845,9 @@ Retry: ''' 启动监控线程,用于新增下载线程。 ''' Private Sub StartManager() - If IsManagerStarted Then Return - IsManagerStarted = True + Static IsStarted As Boolean = False + If IsStarted Then Return + IsStarted = True Dim ThreadStarter = Sub(Id As Integer) '0 或 1 Try @@ -1816,9 +1863,9 @@ Retry: Dim OngoingFiles As New List(Of NetFile) For Each File As NetFile In AllFiles If File.Uuid Mod 2 = Id Then Continue For - If File.State = NetState.WaitForDownload Then + If File.State = NetState.WaitingToDownload Then WaitingFiles.Add(File) - ElseIf File.State < NetState.Merge Then + ElseIf File.State < NetState.Merging Then OngoingFiles.Add(File) End If Next @@ -1826,7 +1873,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(40) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) + If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(100) '减少 BMCLAPI 请求频率 Next '为进行中的文件追加线程 If Speed >= NetTaskSpeedLimitLow Then Continue While '下载速度足够,无需新增 @@ -1836,9 +1883,9 @@ Retry: Dim PreparingCount = 0, DownloadingCount = 0 If File.Threads IsNot Nothing Then For Each Thread As NetThread In File.Threads.ToList - If Thread.State < NetState.Download Then + If Thread.State < NetState.Downloading Then PreparingCount += 1 - ElseIf Thread.State = NetState.Download Then + ElseIf Thread.State = NetState.Downloading Then DownloadingCount += 1 End If Next @@ -1846,7 +1893,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(40) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) + If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(100) '减少 BMCLAPI 请求频率 Next End While Catch ex As Exception @@ -1858,27 +1905,26 @@ Retry: RunInNewThread( Sub() Try - Dim LastLoopTime As Long - NetTaskSpeedLimitLeftLast = GetTimeTick() + Dim NextTick As Long = GetTimeTick() While True - Dim TimeNow = GetTimeTick() - LastLoopTime = TimeNow '增加限速余量 - If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft = NetTaskSpeedLimitHigh / 1000 * (TimeNow - NetTaskSpeedLimitLeftLast) - NetTaskSpeedLimitLeftLast = TimeNow + If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft = NetTaskSpeedLimitHigh / 10 '刷新公开属性 RefreshStat() - '等待直至 80 ms - Do While GetTimeTick() - LastLoopTime < 80 - Thread.Sleep(10) - Loop + '等待 100 ms + NextTick += 100 + Dim SleepTime = NextTick - GetTimeTick() + If SleepTime > 0 Then + Thread.Sleep(SleepTime) + Else + NextTick = GetTimeTick() '超时,直接追帧,不等待 + End If End While Catch ex As Exception Log(ex, "下载管理刷新线程出错", LogLevel.Critical) End Try End Sub, "NetManager StatRefresher") End Sub - Private IsManagerStarted As Boolean = False Private DownloadCacheLock As New Object Private IsDownloadCacheCleared As Boolean = False @@ -1907,7 +1953,7 @@ Retry: Dim File = Task.Files(i) If Files.ContainsKey(File.LocalPath) Then '已有该文件 - If Files(File.LocalPath).State >= NetState.Finish Then + If Files(File.LocalPath).State >= NetState.Finished Then '该文件已经下载过一次,且下载完成 '将已下载的文件替换成当前文件,重新下载 File.Tasks.Add(Task) @@ -1915,7 +1961,7 @@ Retry: SyncLock LockRemain FileRemain += 1 End SyncLock - If ModeDebug Then Log($"[Download] {File.LocalName}:重新加入下载列表") + If ModeDebug Then Log($"[Download] {File.LocalName}:重新加入下载列表,目标 {File.LocalPath}") Else '该文件正在下载中 '将当前文件替换成下载中的文件,即两个任务指向同一个文件 @@ -1929,7 +1975,7 @@ Retry: SyncLock LockRemain FileRemain += 1 End SyncLock - If ModeDebug Then Log($"[Download] {File.LocalName}:已加入下载列表") + If ModeDebug Then Log($"[Download] {File.LocalName}:已加入下载列表,目标 {File.LocalPath}") End If Task.Files(i) = File '回设 Next @@ -2017,7 +2063,15 @@ Retry: ''' 判断某个 Exception 是否为网络问题所导致。 ''' Public Function IsNetworkRelated(Ex As Exception) As Boolean - Dim Detail = Ex.GetDetail() + '提取 Message(不能用 GetDetail,因为堆栈的方法参数中就有 timeout 字样) + Dim Detail As String = "" + Dim InnerEx As Exception = Ex + Detail += InnerEx.Message + Do Until InnerEx.InnerException Is Nothing + InnerEx = InnerEx.InnerException + Detail += InnerEx.Message + Loop + '判断 If Detail.Contains("(403)") Then Return False Return { "(408)", "超时", "timeout", "网络请求失败", "连接尝试失败", "远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", diff --git a/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb b/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb index c5303d6..bc36a53 100644 --- a/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb +++ b/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb @@ -4,11 +4,6 @@ Imports System.Drawing.Imaging Public Class MyBitmap - ''' - ''' 位图缓存。 - ''' - Public Shared BitmapCache As New Concurrent.ConcurrentDictionary(Of String, MyBitmap) - ''' ''' 存储的图片 ''' @@ -30,14 +25,17 @@ Public Class MyBitmap End Operator Public Shared Widening Operator CType(Image As MyBitmap) As ImageSource If Image Is Nothing Then Return Nothing - Dim Bitmap = Image.Pic - Dim rect = New System.Drawing.Rectangle(0, 0, Bitmap.Width, Bitmap.Height) - Dim bitmapData = Bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb) + Dim BitmapPic As System.Drawing.Bitmap = Image.Pic + Dim rect = New System.Drawing.Rectangle(0, 0, BitmapPic.Width, BitmapPic.Height) + Dim bitmapData = BitmapPic.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb) Try - Dim size = rect.Width * rect.Height * 4 - Return BitmapSource.Create(Bitmap.Width, Bitmap.Height, Bitmap.HorizontalResolution, Bitmap.VerticalResolution, PixelFormats.Bgra32, Nothing, bitmapData.Scan0, size, bitmapData.Stride) + Dim Result = BitmapSource.Create(BitmapPic.Width, BitmapPic.Height, BitmapPic.HorizontalResolution, BitmapPic.VerticalResolution, + PixelFormats.Bgra32, Nothing, bitmapData.Scan0, + rect.Width * rect.Height * 4, bitmapData.Stride) + Result.Freeze() + Return Result Finally - Bitmap.UnlockBits(bitmapData) + BitmapPic.UnlockBits(bitmapData) End Try End Operator Public Shared Widening Operator CType(Image As System.Drawing.Bitmap) As MyBitmap @@ -65,11 +63,12 @@ Public Class MyBitmap FilePathOrResourceName = FilePathOrResourceName.Replace("pack://application:,,,/images/", PathImage) If FilePathOrResourceName.StartsWithF(PathImage) Then '使用缓存 - If BitmapCache.ContainsKey(FilePathOrResourceName) Then - Pic = BitmapCache(FilePathOrResourceName).Pic + Static Cache As New Concurrent.ConcurrentDictionary(Of String, MyBitmap) + If Cache.ContainsKey(FilePathOrResourceName) Then + Pic = Cache(FilePathOrResourceName).Pic Else Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePathOrResourceName), ImageSource)) - BitmapCache.TryAdd(FilePathOrResourceName, Pic) + Cache.TryAdd(FilePathOrResourceName, Pic) End If Else '使用这种自己接管 FileStream 的方法加载才能解除文件占用 @@ -92,7 +91,11 @@ Public Class MyBitmap Pic = My.Application.TryFindResource(FilePathOrResourceName) If Pic Is Nothing Then Pic = New System.Drawing.Bitmap(1, 1) - Throw New Exception($"加载 MyBitmap 失败({FilePathOrResourceName})", ex) + If TypeOf ex Is ArgumentException Then + Throw New Exception($"图片格式不支持,或图片文件损坏({FilePathOrResourceName})", ex) + Else + Throw New Exception($"加载 MyBitmap 意外失败({FilePathOrResourceName})", ex) + End If Else Log(ex, $"指定类型有误的 MyBitmap 加载({FilePathOrResourceName})", LogLevel.Developer) Exit Try diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb index f36ed60..77aa6fa 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb @@ -177,13 +177,15 @@ ''' Public ReadOnly Tags As List(Of String) ''' - ''' Logo 图片的下载地址。若为 Nothing 则没有。 + ''' Logo 图片的下载地址。 + ''' 若为 Nothing 则没有,保证不为空字符串。 ''' Public LogoUrl As String = Nothing ''' - ''' 游戏大版本列表。例如:18, 16, 15…… + ''' 支持的 Drop 编号,从高到低排序,不为 Nothing。 + ''' 例如:261(26.1.x)、180(1.18.x)。 ''' - Public ReadOnly GameVersions As List(Of Integer) + Public ReadOnly Drops As List(Of Integer) '数据库信息 @@ -249,10 +251,10 @@ End If Tags = CType(Data("Tags"), JArray).Select(Function(t) t.ToString).ToList If Data.ContainsKey("LogoUrl") Then LogoUrl = Data("LogoUrl") - If Data.ContainsKey("GameVersions") Then - GameVersions = CType(Data("GameVersions"), JArray).Select(Function(t) t.ToObject(Of Integer)).ToList + If Data.ContainsKey("Drops") Then + Drops = CType(Data("Drops"), JArray).Select(Function(t) t.ToObject(Of Integer)).ToList Else - GameVersions = New List(Of Integer) + Drops = New List(Of Integer) End If #End Region Else @@ -274,6 +276,7 @@ LogoUrl = Data("logo")("thumbnailUrl") End If End If + If LogoUrl = "" Then LogoUrl = Nothing 'Type If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then Type = CompType.Mod @@ -288,7 +291,7 @@ Else Type = CompType.DataPack End If - 'FileIndexes / GameVersions / ModLoaders + 'FileIndexes / VanillaMajorVersions / ModLoaders ModLoaders = New List(Of CompModLoaderType) Dim Files As New List(Of KeyValuePair(Of Integer, List(Of String))) 'FileId, GameVersions For Each File In If(Data("latestFiles"), New JArray) @@ -296,17 +299,16 @@ If Not NewFile.Available Then Continue For ModLoaders.AddRange(NewFile.ModLoaders) Dim GameVersions = File("gameVersions").ToObject(Of List(Of String)) - If Not GameVersions.Any(Function(v) v.StartsWithF("1.")) Then Continue For + If Not GameVersions.Any(Function(v) McVersion.IsFormatFit(v)) Then Continue For Files.Add(New KeyValuePair(Of Integer, List(Of String))(File("id"), GameVersions)) Next For Each File In If(Data("latestFilesIndexes"), New JArray) '这俩玩意儿包含的文件不一样,见 #3599 - If Not File("gameVersion").ToString.StartsWithF("1.") Then Continue For + If Not McVersion.IsFormatFit(File("gameVersion")) Then Continue For Files.Add(New KeyValuePair(Of Integer, List(Of String))(File("fileId"), {File("gameVersion").ToString}.ToList)) Next CurseForgeFileIds = Files.Select(Function(f) f.Key).Distinct.ToList - GameVersions = Files.SelectMany(Function(f) f.Value).Where(Function(v) v.StartsWithF("1.")). - Select(Function(v) CInt(Val(v.Split(".")(1).BeforeFirst("-")))).Where(Function(v) v > 0). - Distinct.OrderByDescending(Function(v) v).ToList + Drops = Files.SelectMany(Function(f) f.Value). + Select(Function(v) McVersion.VersionToDrop(v)).Where(Function(v) v > 0).Distinct.OrderByDescending(Function(v) v).ToList ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList 'Tags Tags = New List(Of String) @@ -333,6 +335,8 @@ Case 424 : Tags.Add("装饰") Case 411 : Tags.Add("生物") Case 434 : Tags.Add("装备") + Case 6814 : Tags.Add("性能优化") + Case 9026 : Tags.Add("创造模式") Case 423 : Tags.Add("信息显示") Case 435 : Tags.Add("服务器") Case 5191 : Tags.Add("改良") @@ -399,10 +403,8 @@ Website = $"https://modrinth.com/{Data("project_type")}/{Slug}" 'GameVersions '搜索结果的键为 versions,获取特定工程的键为 game_versions - GameVersions = If(CType(If(Data("game_versions"), Data("versions")), JArray), New JArray). - Select(Function(v) v.ToString).Where(Function(v) v.StartsWithF("1.")). - Select(Of Integer)(Function(v) Val(v.Split(".")(1).BeforeFirst("-"))).Where(Function(v) v > 0). - Distinct.OrderByDescending(Function(v) v).ToList + Drops = If(CType(If(Data("game_versions"), Data("versions")), JArray), New JArray). + Select(Function(v) McVersion.VersionToDrop(v)).Where(Function(v) v > 0).Distinct.OrderByDescending(Function(v) v).ToList 'Type Select Case Data("project_type").ToString Case "modpack" : Type = CompType.ModPack @@ -523,58 +525,60 @@ Json("DownloadCount") = DownloadCount If ModLoaders IsNot Nothing AndAlso ModLoaders.Any Then Json("ModLoaders") = New JArray(ModLoaders.Select(Function(m) CInt(m))) Json("Tags") = New JArray(Tags) - If Not String.IsNullOrEmpty(LogoUrl) Then Json("LogoUrl") = LogoUrl - If GameVersions.Any Then Json("GameVersions") = New JArray(GameVersions) + If LogoUrl IsNot Nothing Then Json("LogoUrl") = LogoUrl + If Drops.Any Then Json("Drops") = New JArray(Drops) Json("CacheTime") = Date.Now '用于检查缓存时间 Return Json End Function ''' ''' 将当前工程信息实例化为控件。 ''' - Public Function ToCompItem(ShowMcVersionDesc As Boolean, ShowLoaderDesc As Boolean) As MyCompItem + Public Function ToCompItem(ShowMcVersionDesc As Boolean, ShowLoaderDesc As Boolean) As MyVirtualizingElement(Of MyCompItem) '获取版本描述 Dim GameVersionDescription As String - If GameVersions Is Nothing OrElse Not GameVersions.Any() Then + If Drops Is Nothing OrElse Not Drops.Any() Then GameVersionDescription = "仅快照版本" '#5412 Else - Dim SpaVersions As New List(Of String) + Dim Segments As New List(Of String) Dim IsOld As Boolean = False - For i = 0 To GameVersions.Count - 1 '版本号一定为降序 + For i = 0 To Drops.Count - 1 '版本号一定为降序 '获取当前连续的版本号段 - Dim StartVersion As Integer = GameVersions(i), EndVersion As Integer = GameVersions(i) - If StartVersion < 10 Then '如果支持新版本,则不显示 1.9- - If SpaVersions.Any() AndAlso Not IsOld Then + Dim StartDrop As Integer = Drops(i), EndDrop As Integer = Drops(i) + If StartDrop < 100 Then '如果支持新版本,则不显示 1.9- + If Segments.Any() AndAlso Not IsOld Then Exit For Else IsOld = True End If End If - For ii = i + 1 To GameVersions.Count - 1 - If GameVersions(ii) <> EndVersion - 1 Then Exit For - EndVersion = GameVersions(ii) + For ii = i + 1 To Drops.Count - 1 + If AllDrops Is Nothing OrElse AllDrops.IndexOf(Drops(ii)) <> AllDrops.IndexOf(EndDrop) + 1 Then Exit For + EndDrop = Drops(ii) i = ii Next '将版本号段转为描述文本 - If StartVersion = EndVersion Then - SpaVersions.Add("1." & StartVersion) - ElseIf McVersionHighest > -1 AndAlso StartVersion >= McVersionHighest Then - If EndVersion < 10 Then - SpaVersions.Clear() - SpaVersions.Add("全版本") + Dim StartName = McVersion.DropToVersion(StartDrop) + Dim EndName = McVersion.DropToVersion(EndDrop) + If StartDrop = EndDrop Then + Segments.Add(StartName) + ElseIf AllDrops?.Any AndAlso StartDrop >= AllDrops.First Then + If EndDrop < 100 Then + Segments.Clear() + Segments.Add("全版本") Exit For Else - SpaVersions.Add("1." & EndVersion & "+") + Segments.Add(EndName & "+") End If - ElseIf EndVersion < 10 Then - SpaVersions.Add("1." & StartVersion & "-") + ElseIf EndDrop < 100 Then + Segments.Add(StartName & "-") Exit For - ElseIf StartVersion - EndVersion = 1 Then - SpaVersions.Add("1." & StartVersion & ", 1." & EndVersion) + ElseIf AllDrops Is Nothing OrElse AllDrops.IndexOf(EndDrop) - AllDrops.IndexOf(StartDrop) = 1 Then + Segments.Add(StartName & ", " & EndName) Else - SpaVersions.Add("1." & StartVersion & "~1." & EndVersion) + Segments.Add(StartName & "~" & EndName) End If Next - GameVersionDescription = SpaVersions.Join(", ") + GameVersionDescription = Segments.Join(", ") End If '获取 Mod 加载器描述 Dim ModLoaderDescriptionFull As String, ModLoaderDescriptionPart As String @@ -593,11 +597,11 @@ ModLoaderDescriptionFull = "仅 " & ModLoadersForDesc.Single.ToString ModLoaderDescriptionPart = ModLoadersForDesc.Single.ToString Case Else - Dim MaxVersion As Integer = If(GameVersions.Any, GameVersions.Max, 99) + Dim NewestDrop As Integer = If(Drops.Any, Drops.First, 9999) If ModLoaders.Contains(CompModLoaderType.Forge) AndAlso - (MaxVersion < 14 OrElse ModLoaders.Contains(CompModLoaderType.Fabric)) AndAlso - (MaxVersion < 20 OrElse ModLoaders.Contains(CompModLoaderType.NeoForge)) AndAlso - (MaxVersion < 14 OrElse ModLoaders.Contains(CompModLoaderType.Quilt) OrElse Setup.Get("ToolDownloadIgnoreQuilt")) Then + (NewestDrop < 140 OrElse ModLoaders.Contains(CompModLoaderType.Fabric)) AndAlso + (NewestDrop < 200 OrElse ModLoaders.Contains(CompModLoaderType.NeoForge)) AndAlso + (NewestDrop < 140 OrElse ModLoaders.Contains(CompModLoaderType.Quilt) OrElse Setup.Get("ToolDownloadIgnoreQuilt")) Then ModLoaderDescriptionFull = "任意" ModLoaderDescriptionPart = "" Else @@ -606,55 +610,60 @@ End If End Select '实例化 UI - Dim NewItem As New MyCompItem With {.Tag = Me, .Logo = GetControlLogo()} - Dim Title = GetControlTitle(True) - NewItem.Title = Title.Key - If Title.Value = "" Then - CType(NewItem.LabTitleRaw.Parent, StackPanel).Children.Remove(NewItem.LabTitleRaw) - Else - NewItem.SubTitle = Title.Value - End If - NewItem.Tags = Tags - NewItem.Description = Description.Replace(vbCr, "").Replace(vbLf, "") - '下边栏 - If Not ShowMcVersionDesc AndAlso Not ShowLoaderDesc Then - '全部隐藏 - CType(NewItem.PathVersion.Parent, Grid).Children.Remove(NewItem.PathVersion) - CType(NewItem.LabVersion.Parent, Grid).Children.Remove(NewItem.LabVersion) - NewItem.ColumnVersion1.Width = New GridLength(0) - NewItem.ColumnVersion2.MaxWidth = 0 - NewItem.ColumnVersion3.Width = New GridLength(0) - ElseIf ShowMcVersionDesc AndAlso ShowLoaderDesc Then - '全部显示 - NewItem.LabVersion.Text = If(ModLoaderDescriptionPart = "", "", ModLoaderDescriptionPart & " ") & GameVersionDescription - ElseIf ShowMcVersionDesc Then - '仅显示版本 - NewItem.LabVersion.Text = GameVersionDescription - Else - '仅显示 Mod 加载器 - NewItem.LabVersion.Text = ModLoaderDescriptionFull - End If - NewItem.LabSource.Text = If(FromCurseForge, "CurseForge", "Modrinth") - If LastUpdate IsNot Nothing Then - NewItem.LabTime.Text = GetTimeSpanString(LastUpdate - Date.Now, True) - Else - NewItem.LabTime.Visibility = Visibility.Collapsed - NewItem.ColumnTime1.Width = New GridLength(0) - NewItem.ColumnTime2.Width = New GridLength(0) - NewItem.ColumnTime3.Width = New GridLength(0) - End If - NewItem.LabDownload.Text = - If(DownloadCount > 100000000, Math.Round(DownloadCount / 100000000, 2) & " 亿", - If(DownloadCount > 100000, Math.Floor(DownloadCount / 10000) & " 万", DownloadCount)) - Return NewItem + Return New MyVirtualizingElement(Of MyCompItem)( + Function() + Dim NewItem As New MyCompItem With {.Tag = Me} + ApplyLogoToMyImage(NewItem.PathLogo) + Dim Title = GetControlTitle(True) + NewItem.Title = Title.Key + If Title.Value = "" Then + CType(NewItem.LabTitleRaw.Parent, StackPanel).Children.Remove(NewItem.LabTitleRaw) + Else + NewItem.SubTitle = Title.Value + End If + NewItem.Tags = Tags + NewItem.Description = Description.Replace(vbCr, "").Replace(vbLf, "") + '下边栏 + If Not ShowMcVersionDesc AndAlso Not ShowLoaderDesc Then + '全部隐藏 + CType(NewItem.PathVersion.Parent, Grid).Children.Remove(NewItem.PathVersion) + CType(NewItem.LabVersion.Parent, Grid).Children.Remove(NewItem.LabVersion) + NewItem.ColumnVersion1.Width = New GridLength(0) + NewItem.ColumnVersion2.MaxWidth = 0 + NewItem.ColumnVersion3.Width = New GridLength(0) + ElseIf ShowMcVersionDesc AndAlso ShowLoaderDesc Then + '全部显示 + NewItem.LabVersion.Text = If(ModLoaderDescriptionPart = "", "", ModLoaderDescriptionPart & " ") & GameVersionDescription + ElseIf ShowMcVersionDesc Then + '仅显示版本 + NewItem.LabVersion.Text = GameVersionDescription + Else + '仅显示 Mod 加载器 + NewItem.LabVersion.Text = ModLoaderDescriptionFull + End If + NewItem.LabSource.Text = If(FromCurseForge, "CurseForge", "Modrinth") + If LastUpdate IsNot Nothing Then + NewItem.LabTime.Text = GetTimeSpanString(LastUpdate - Date.Now, True) + Else + NewItem.LabTime.Visibility = Visibility.Collapsed + NewItem.ColumnTime1.Width = New GridLength(0) + NewItem.ColumnTime2.Width = New GridLength(0) + NewItem.ColumnTime3.Width = New GridLength(0) + End If + NewItem.LabDownload.Text = + If(DownloadCount > 100000000, Math.Round(DownloadCount / 100000000, 2) & " 亿", + If(DownloadCount > 100000, Math.Floor(DownloadCount / 10000) & " 万", DownloadCount)) + Return NewItem + End Function) With {.Height = 64} End Function - Public Function GetControlLogo() As String + Public Sub ApplyLogoToMyImage(Img As MyImage) If String.IsNullOrEmpty(LogoUrl) Then - Return PathImage & "Icons/NoIcon.png" + Img.Source = PathImage & "Icons/NoIcon.png" Else - Return LogoUrl + Img.Source = LogoUrl + Img.FallbackSource = DlSourceModGet(LogoUrl) End If - End Function + End Sub Public Function GetControlTitle(HasModLoaderDescription As Boolean) As KeyValuePair(Of String, String) '检查下列代码时可以参考 #1567 的测试例 Dim Title As String = RawName @@ -755,7 +764,7 @@ NoSubtitle: 'Mod 加载器一致 If ModLoaders.Count <> Project.ModLoaders.Count OrElse ModLoaders.Except(Project.ModLoaders).Any() Then Return False '若不为光影,则要求 MC 版本一致 - If Type <> CompType.Shader AndAlso (GameVersions.Count <> Project.GameVersions.Count OrElse GameVersions.Except(Project.GameVersions).Any()) Then Return False + If Type <> CompType.Shader AndAlso (Drops.Count <> Project.Drops.Count OrElse Drops.Except(Project.Drops).Any()) Then Return False '最近更新时间差距在一周以内 If LastUpdate IsNot Nothing AndAlso Project.LastUpdate IsNot Nothing AndAlso Math.Abs((LastUpdate - Project.LastUpdate).Value.TotalDays) > 7 Then Return False @@ -967,7 +976,7 @@ NoSubtitle: #Region "拒绝 1.13- Quilt(这个版本根本没有 Quilt)" - If Request.ModLoader = CompModLoaderType.Quilt AndAlso VersionSortInteger(If(Request.GameVersion, "1.15"), "1.14") = -1 Then + If Request.ModLoader = CompModLoaderType.Quilt AndAlso CompareVersion(If(Request.GameVersion, "1.15"), "1.14") = -1 Then Throw New Exception("Quilt 不支持 Minecraft " & Request.GameVersion) End If @@ -1060,7 +1069,7 @@ Retry: '在 1.14-,部分老 Mod 没有设置支持的加载器,因此添加 Forge 筛选就会出现遗漏 '所以,在发起请求时不筛选加载器,然后在返回的结果中自行筛除不是 Forge 的 Mod - Dim IsOldForgeRequest = Request.ModLoader = CompModLoaderType.Forge AndAlso Request.GameVersion?.Contains(".") AndAlso Val(Request.GameVersion.Split(".")(1)) < 14 + Dim IsOldForgeRequest = Request.ModLoader = CompModLoaderType.Forge AndAlso McVersion.VersionToDrop(Request.GameVersion, True) < 140 If IsOldForgeRequest Then Request.ModLoader = CompModLoaderType.Any Dim CurseForgeUrl As String = Request.GetCurseForgeAddress() Dim ModrinthUrl As String = Request.GetModrinthAddress() @@ -1176,9 +1185,9 @@ Retry: #Region "提取非重复项,存储于 RealResults" - '将 Modrinth 排在 CurseForge 的前面,避免加载结束顺序不同导致排名不同 - '这样做的话,去重后将优先保留 CurseForge 内容 - RawResults = RawResults.Where(Function(x) Not x.FromCurseForge).Concat(RawResults.Where(Function(x) x.FromCurseForge)).ToList + '将 CurseForge 排在 Modrinth 的前面,避免加载结束顺序不同导致排名不同 + '这样做的话,去重后将优先保留 Modrinth 的内容 + RawResults = RawResults.Where(Function(x) x.FromCurseForge).Concat(RawResults.Where(Function(x) Not x.FromCurseForge)).ToList 'RawResults 去重 RawResults = RawResults.Distinct(Function(a, b) a.IsLike(b)) '已有内容去重 @@ -1292,11 +1301,16 @@ Retry: ''' Public ReadOnly DownloadCount As Integer ''' + ''' Mod 版本号。 + ''' 不一定是标准格式。CurseForge 上默认为 Nothing。 + ''' + Public Version As String + ''' ''' 支持的 Mod 加载器列表。可能为空。 ''' Public ReadOnly ModLoaders As List(Of CompModLoaderType) ''' - ''' 支持的游戏版本列表。类型包括:"1.18.5","1.18","1.18 预览版","21w15a","未知版本"。 + ''' 支持的游戏版本列表。类型包括:"26.1.5","26.1","26.1 预览版","1.18.5","1.18","1.18 预览版","21w15a","未知版本"。 ''' Public ReadOnly GameVersions As List(Of String) ''' @@ -1369,6 +1383,7 @@ Retry: FromCurseForge = Data("FromCurseForge").ToObject(Of Boolean) Id = Data("Id").ToString DisplayName = Data("DisplayName").ToString + If Data.ContainsKey("Version") Then Version = Data("Version").ToString ReleaseDate = Data("ReleaseDate").ToObject(Of Date) DownloadCount = Data("DownloadCount").ToObject(Of Integer) Status = CType(Data("Status").ToObject(Of Integer), CompFileStatus) @@ -1387,6 +1402,7 @@ Retry: '简单信息 Id = Data("id") DisplayName = Data("displayName").ToString.Replace(" ", "").Trim(" ") + Version = Nothing ReleaseDate = Data("fileDate") Status = CType(Data("releaseType").ToObject(Of Integer), CompFileStatus) DownloadCount = Data("downloadCount") @@ -1408,9 +1424,9 @@ Retry: End If 'GameVersions Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.ToLower).ToList - GameVersions = RawVersions.Where(Function(v) v.StartsWithF("1.")).Select(Function(v) v.Replace("-snapshot", " 预览版")).ToList + GameVersions = RawVersions.Where(Function(v) McVersion.IsFormatFit(v)).Select(Function(v) v.Replace("-snapshot", " 预览版")).ToList If GameVersions.Count > 1 Then - GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList + GameVersions = GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本 ElseIf GameVersions.Count = 1 Then GameVersions = GameVersions.ToList @@ -1429,6 +1445,7 @@ Retry: '简单信息 Id = Data("id") DisplayName = Data("name").ToString.Replace(" ", "").Trim(" ") + Version = Data("version_number") ReleaseDate = Data("date_published") Status = If(Data("version_type").ToString = "release", CompFileStatus.Release, If(Data("version_type").ToString = "beta", CompFileStatus.Beta, CompFileStatus.Alpha)) DownloadCount = Data("downloads") @@ -1467,15 +1484,15 @@ Retry: End If 'GameVersions Dim RawVersions As List(Of String) = Data("game_versions").Select(Function(t) t.ToString.Trim.ToLower).ToList - GameVersions = RawVersions.Where(Function(v) v.StartsWithF("1.") OrElse v.StartsWithF("b1.")). + GameVersions = RawVersions.Where(Function(v) v.Contains(".")). Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).ToList If GameVersions.Count > 1 Then - GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList + GameVersions = GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本 ElseIf GameVersions.Count = 1 Then '无需处理 - ElseIf RawVersions.Any(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]{1}")) Then - GameVersions = RawVersions.Where(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]{1}")).ToList + ElseIf RawVersions.Any(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]")) Then + GameVersions = RawVersions.Where(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]")).ToList Else GameVersions = New List(Of String) From {"未知版本"} End If @@ -1504,6 +1521,7 @@ Retry: Dim Json As New JObject Json.Add("FromCurseForge", FromCurseForge) Json.Add("Id", Id) + If Version IsNot Nothing Then Json.Add("Version", Version) Json.Add("DisplayName", DisplayName) Json.Add("ReleaseDate", ReleaseDate) Json.Add("DownloadCount", DownloadCount) @@ -1521,56 +1539,56 @@ Retry: ''' 将当前文件信息实例化为控件。 ''' Public Function ToListItem(OnClick As MyListItem.ClickEventHandler, Optional OnSaveClick As MyIconButton.ClickEventHandler = Nothing, - Optional BadDisplayName As Boolean = False) As MyListItem + Optional BadDisplayName As Boolean = False) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + '获取描述信息 + Dim Title As String = If(BadDisplayName, FileName, DisplayName) + Dim Info As New List(Of String) + If Title <> FileName.BeforeLast(".") Then Info.Add(FileName.BeforeLast(".")) + If Dependencies.Any Then Info.Add(Dependencies.Count & " 项前置") + If GameVersions.All( + Function(VerName) + Return Not VerName.Contains(".") OrElse {"w", "snapshot", "rc", "pre", "experimental", "-"}.Any(Function(s) VerName.ContainsF(s, True)) + End Function) Then Info.Add($"游戏版本 {Join(GameVersions, "、")}") + If DownloadCount > 0 Then 'CurseForge 的下载次数经常错误地返回 0 + Info.Add("下载 " & If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次", DownloadCount & " 次")) + End If + Info.Add("更新于 " & GetTimeSpanString(ReleaseDate - Date.Now, False)) + If Status <> CompFileStatus.Release Then Info.Add(StatusDescription) - '获取描述信息 - Dim Title As String = If(BadDisplayName, FileName, DisplayName) - Dim Info As New List(Of String) - If Title <> FileName.BeforeLast(".") Then Info.Add(FileName.BeforeLast(".")) - If Dependencies.Any Then Info.Add(Dependencies.Count & " 项前置") - If GameVersions.All(Function(v) Not IsVersionNameLikeRelease(v)) Then Info.Add($"游戏版本 {Join(GameVersions, "、")}") - If DownloadCount > 0 Then 'CurseForge 的下载次数经常错误地返回 0 - Info.Add("下载 " & If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次", DownloadCount & " 次")) - End If - Info.Add("更新于 " & GetTimeSpanString(ReleaseDate - Date.Now, False)) - If Status <> CompFileStatus.Release Then Info.Add(StatusDescription) + '建立控件 + Dim NewItem As New MyListItem With { + .Title = Title, + .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Me, + .Info = Info.Join(",") + } + Select Case Status + Case CompFileStatus.Release + NewItem.Logo = PathImage & "Icons/R.png" + Case CompFileStatus.Beta + NewItem.Logo = PathImage & "Icons/B.png" + Case Else 'Alpha + NewItem.Logo = PathImage & "Icons/A.png" + End Select + AddHandler NewItem.Click, OnClick - '建立控件 - Dim NewItem As New MyListItem With { - .Title = Title, - .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Me, - .Info = Info.Join(",") - } - Select Case Status - Case CompFileStatus.Release - NewItem.Logo = PathImage & "Icons/R.png" - Case CompFileStatus.Beta - NewItem.Logo = PathImage & "Icons/B.png" - Case Else 'Alpha - NewItem.Logo = PathImage & "Icons/A.png" - End Select - AddHandler NewItem.Click, OnClick - - '建立另存为按钮 - If OnSaveClick IsNot Nothing Then - Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"} - ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center) - ToolTipService.SetVerticalOffset(BtnSave, 30) - ToolTipService.SetHorizontalOffset(BtnSave, 2) - AddHandler BtnSave.Click, OnSaveClick - NewItem.Buttons = {BtnSave} - End If - - '结束 - Return NewItem + '建立另存为按钮 + If OnSaveClick IsNot Nothing Then + Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"} + ToolTipService.SetPlacement(BtnSave, Primitives.PlacementMode.Center) + ToolTipService.SetVerticalOffset(BtnSave, 30) + ToolTipService.SetHorizontalOffset(BtnSave, 2) + AddHandler BtnSave.Click, OnSaveClick + NewItem.Buttons = {BtnSave} + End If + Return NewItem + End Function) With {.Height = 42} End Function - '辅助函数 - Public Overrides Function ToString() As String Return $"{Id}: {FileName}" End Function - End Class '获取 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb index 8b97b0f..69e3d8d 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb @@ -2,9 +2,9 @@ '构造函数 Private TempFolder As String - Private TargetVersion As McVersion - Public Sub New(Optional TargetVersion As McVersion = Nothing) - Me.TargetVersion = TargetVersion + Private TargetInstance As McInstance + Public Sub New(Optional TargetInstance As McInstance = Nothing) + Me.TargetInstance = TargetInstance '构建文件结构 TempFolder = RequestTaskTempFolder() Directory.CreateDirectory(TempFolder & "Temp\") @@ -391,7 +391,7 @@ Extracted: ''' ''' 根据 AnalyzeLogs 与可能的版本信息分析崩溃原因。 ''' - Public Sub Analyze(Optional Version As McVersion = Nothing) + Public Sub Analyze() Log("[Crash] 步骤 3:分析崩溃原因") LogAll = If(LogMc, If(LogMcDebug, "")) & If(LogHs, "") & If(LogCrash, "") @@ -538,7 +538,7 @@ Done: '确定的 Mod 导致崩溃 If LogMc.Contains("Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(RegexSeek(LogMc, "(?<=Caught exception from )[^\n]+?")?.TrimEnd((vbCrLf & " ").ToCharArray))) 'Mod 重复 / 前置问题 - If LogMc.Contains("DuplicateModsFoundException") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=\n\t[\w]+ : [A-Z]{1}:[^\n]+(/|\\))[^/\\\n]+?.jar", RegularExpressions.RegexOptions.IgnoreCase)) + If LogMc.Contains("DuplicateModsFoundException") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=\n\t[\w]+ : [A-Z]:[^\n]+(/|\\))[^/\\\n]+?.jar", RegularExpressions.RegexOptions.IgnoreCase)) 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)) @@ -607,7 +607,7 @@ Done: Return True End If 'JSON 名称匹配 - For Each JsonName In RegexSearch(LogText, "(?<=^[^\t]+[ \[{(]{1})[^ \[{(]+\.[^ ]+(?=\.json)", RegularExpressions.RegexOptions.Multiline) + For Each JsonName In RegexSearch(LogText, "(?<=^[^\t]+[ \[{(])[^ \[{(]+\.[^ ]+(?=\.json)", RegularExpressions.RegexOptions.Multiline) AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(JsonName.Replace("mixins", "mixin").Replace(".mixin", "").Replace("mixin.", ""))) Return True @@ -926,7 +926,7 @@ NextStack: '上报 If IsManualAnalyze Then Return Telemetry("Minecraft 崩溃", - "Version", If(TargetVersion Is Nothing, "null", TargetVersion.Version.ToString), + "Version", If(TargetInstance Is Nothing, "null", TargetInstance.VersionDisplayName), "Detail", FilterUserName(Detail, "*")) End Sub ''' diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb index db474ec..0e97030 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb @@ -6,40 +6,40 @@ ''' 返回某 Minecraft 版本对应的原版主 Jar 文件的下载信息,要求对应依赖版本已存在。 ''' 失败则抛出异常,不需要下载则返回 Nothing。 ''' - Public Function DlClientJarGet(Version As McVersion, ReturnNothingOnFileUseable As Boolean) As NetFile + Public Function DlClientJarGet(Instance As McInstance, ReturnNothingOnFileUseable As Boolean) As NetFile '获取底层继承版本 Try - Do While Not String.IsNullOrEmpty(Version.InheritVersion) - Version = New McVersion(Version.InheritVersion) + Do While Not String.IsNullOrEmpty(Instance.InheritName) + Instance = New McInstance(Instance.InheritName) Loop Catch ex As Exception Log(ex, "获取底层继承版本失败") End Try '检查 Json 是否标准 - If Version.JsonObject("downloads") Is Nothing OrElse Version.JsonObject("downloads")("client") Is Nothing OrElse Version.JsonObject("downloads")("client")("url") Is Nothing Then - Throw New Exception("底层版本 " & Version.Name & " 中无 jar 文件下载信息") + If Instance.JsonObject("downloads") Is Nothing OrElse Instance.JsonObject("downloads")("client") Is Nothing OrElse Instance.JsonObject("downloads")("client")("url") Is Nothing Then + Throw New Exception("底层版本 " & Instance.Name & " 中无 jar 文件下载信息") End If '检查文件 - Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(Version.JsonObject("downloads")("client")("size"), -1), Hash:=Version.JsonObject("downloads")("client")("sha1")) - If ReturnNothingOnFileUseable AndAlso Checker.Check(Version.Path & Version.Name & ".jar") Is Nothing Then Return Nothing '通过校验 + Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(Instance.JsonObject("downloads")("client")("size"), -1), Hash:=Instance.JsonObject("downloads")("client")("sha1")) + If ReturnNothingOnFileUseable AndAlso Checker.Check(Instance.PathVersion & Instance.Name & ".jar") Is Nothing Then Return Nothing '通过校验 '返回下载信息 - Dim JarUrl As String = Version.JsonObject("downloads")("client")("url") - Return New NetFile(DlSourceLauncherOrMetaGet(JarUrl), Version.Path & Version.Name & ".jar", Checker) + Dim JarUrl As String = Instance.JsonObject("downloads")("client")("url") + Return New NetFile(DlSourceLauncherOrMetaGet(JarUrl), Instance.PathVersion & Instance.Name & ".jar", Checker) End Function ''' ''' 返回某 Minecraft 版本对应的原版主 AssetIndex 文件的下载信息,要求对应依赖版本已存在。 ''' 若未找到,则会返回 Legacy 资源文件或 Nothing。 ''' - Public Function DlClientAssetIndexGet(Version As McVersion) As NetFile + Public Function DlClientAssetIndexGet(Instance As McInstance) As NetFile '获取底层继承版本 - Do While Not String.IsNullOrEmpty(Version.InheritVersion) - Version = New McVersion(Version.InheritVersion) + Do While Not String.IsNullOrEmpty(Instance.InheritName) + Instance = New McInstance(Instance.InheritName) Loop '获取信息 - Dim IndexInfo = McAssetsGetIndex(Version, True, True) - Dim IndexAddress As String = PathMcFolder & "assets\indexes\" & IndexInfo("id").ToString & ".json" - Log("[Download] 版本 " & Version.Name & " 对应的资源文件索引为 " & IndexInfo("id").ToString) + Dim IndexInfo = McAssetsGetIndex(Instance, True, True) + Dim IndexAddress As String = McFolderSelected & "assets\indexes\" & IndexInfo("id").ToString & ".json" + Log("[Download] 版本 " & Instance.Name & " 对应的资源文件索引为 " & IndexInfo("id").ToString) Dim IndexUrl As String = If(IndexInfo("url"), "") If IndexUrl = "" Then Return Nothing @@ -51,12 +51,12 @@ ''' ''' 构造补全某 Minecraft 版本的所有文件的加载器列表。失败会抛出异常。 ''' - Public Function DlClientFix(Version As McVersion, CheckAssetsHash As Boolean, AssetsIndexBehaviour As AssetsIndexExistsBehaviour) As List(Of LoaderBase) + Public Function DlClientFix(Instance As McInstance, CheckAssetsHash As Boolean, AssetsIndexBehaviour As AssetsIndexExistsBehaviour) As List(Of LoaderBase) Dim Loaders As New List(Of LoaderBase) #Region "下载支持库文件" Dim LoadersLib As New List(Of LoaderBase) From { - New LoaderTask(Of String, List(Of NetFile))("分析缺失支持库文件", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(Version)) With {.ProgressWeight = 1}, + New LoaderTask(Of String, List(Of NetFile))("分析缺失支持库文件", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibNetFilesFromInstance(Instance)) With {.ProgressWeight = 1}, New LoaderDownload("下载支持库文件", New List(Of NetFile)) With {.ProgressWeight = 15} } '构造加载器 @@ -64,7 +64,7 @@ #End Region #Region "下载资源文件" - If ShouldIgnoreFileCheck(Version) Then + If ShouldIgnoreFileCheck(Instance) Then Log("[Download] 已跳过所有 Assets 检查") Else Dim LoadersAssets As New List(Of LoaderBase) @@ -72,10 +72,10 @@ LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析资源文件索引地址", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Try - Dim IndexFile = DlClientAssetIndexGet(Version) + Dim IndexFile = DlClientAssetIndexGet(Instance) If IndexFile Is Nothing Then Task.Output = New List(Of NetFile) - Log("[Download] 未找到版本 " & Version.Name & " 的合适的资源索引下载地址,游戏 assets 可能缺失", LogLevel.Debug) + Log("[Download] 未找到版本 " & Instance.Name & " 的合适的资源索引下载地址,游戏 assets 可能缺失", LogLevel.Debug) ElseIf AssetsIndexBehaviour <> AssetsIndexExistsBehaviour.AlwaysDownload AndAlso IndexFile.Check.Check(IndexFile.LocalPath) Is Nothing Then Task.Output = New List(Of NetFile) Else @@ -94,9 +94,9 @@ Dim RealAddress As String = Nothing LoadersAssetsUpdate.Add(New LoaderTask(Of String, List(Of NetFile))("后台分析资源文件索引地址", Sub(Task As LoaderTask(Of String, List(Of NetFile))) - Dim BackAssetsFile As NetFile = DlClientAssetIndexGet(Version) + Dim BackAssetsFile As NetFile = DlClientAssetIndexGet(Instance) If BackAssetsFile Is Nothing Then - Log("[Download] 未找到版本 " & Version.Name & " 的合适的资源索引下载地址,游戏 assets 可能缺失", LogLevel.Debug) + Log("[Download] 未找到版本 " & Instance.Name & " 的合适的资源索引下载地址,游戏 assets 可能缺失", LogLevel.Debug) Task.Abort() Throw New ThreadInterruptedException End If @@ -121,14 +121,12 @@ Updater.Start() End If '获取资源文件地址 - LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析缺失资源文件", - Sub(Task As LoaderTask(Of String, List(Of NetFile))) - Task.Output = McAssetsFixList(Version, CheckAssetsHash, Task) - End Sub) With {.ProgressWeight = 3}) + LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("获取资源文件列表", + Sub(Task) Task.Output = McAssetsFixList(Instance, CheckAssetsHash, Task)) With {.ProgressWeight = 0.01}) '下载资源文件 LoadersAssets.Add(New LoaderDownload("下载资源文件", New List(Of NetFile)) With {.ProgressWeight = 25}) '构造加载器 - Loaders.Add(New LoaderCombo(Of String)("下载资源文件(主加载器)", LoadersAssets) With {.Block = False, .Show = False, .ProgressWeight = 30.5}) + Loaders.Add(New LoaderCombo(Of String)("下载资源文件(主加载器)", LoadersAssets) With {.Block = False, .Show = False, .ProgressWeight = 27}) End If #End Region @@ -153,6 +151,23 @@ #Region "DlClientList | Minecraft 客户端 版本列表" + ''' + ''' 所有正式版的 Minecraft Drop 序数,按从高到低排列。 + ''' Nothing 代表从未完成过获取。 + ''' + Public Property AllDrops As List(Of Integer) + Get + If _AllDrops Is Nothing Then _AllDrops = Setup.Get("CacheDrops").ToString.Split(","c).Select(Function(d) CInt(d)).ToList() + If Not _AllDrops.Any Then _AllDrops = Nothing + Return _AllDrops + End Get + Set(value As List(Of Integer)) + _AllDrops = value + Setup.Set("CacheDrops", value.Join(",")) + End Set + End Property + Private _AllDrops As List(Of Integer) = Nothing + '主加载器 Public Structure DlClientListResult ''' @@ -167,10 +182,6 @@ ''' 获取到的 Json 数据。 ''' Public Value As JObject - '''' - '''' 官方源的失败原因。若没有则为 Nothing。 - '''' - 'Public OfficialError As Exception End Structure ''' ''' Minecraft 客户端 版本列表,主加载器。 @@ -195,6 +206,12 @@ New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListBmclapiLoader, 60 + 60) }, Loader.IsForceRestarting) End Select + '提取所有 Drop 序数 + Dim Drops As New List(Of Integer) + For Each Version As JObject In Loader.Output.Value("versions") + Drops.Add(McVersion.VersionToDrop(Version("id"))) + Next + AllDrops = Drops.Distinct.OrderByDescending(Function(d) d).ToList() End Sub '各个下载源的分加载器 @@ -202,8 +219,6 @@ ''' Minecraft 客户端 版本列表,Mojang 官方源加载器。 ''' Public DlClientListMojangLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain) - Private IsNewClientVersionHinted As Boolean = False - Private ReadOnly HintLock As New Object() ' 线程安全锁 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", RequireJson:=True)) @@ -220,29 +235,25 @@ If File.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(GetJson(ReadFile(PathTemp & "Cache\download.json"))) '返回 Loader.Output = New DlClientListResult With {.IsOfficial = True, .SourceName = "Mojang 官方源", .Value = Json} - '解析更新提示(Release) - Dim Version As String = Json("latest")("release") - If Setup.Get("ToolUpdateRelease") AndAlso Not Setup.Get("ToolUpdateReleaseLast") = "" AndAlso Version IsNot Nothing AndAlso Not Setup.Get("ToolUpdateReleaseLast") = Version Then - SyncLock HintLock - If Not IsNewClientVersionHinted Then ' 在锁内再次检查 - McDownloadClientUpdateHint(Version, Json) - IsNewClientVersionHinted = True - End If - End SyncLock - End If - McVersionHighest = Version.Split(".")(1) - Setup.Set("ToolUpdateReleaseLast", Version) - '解析更新提示(Snapshot) + 'MC 更新提示 + Static IsHinted As Boolean = False + Dim Version As String + '快照版 Version = Json("latest")("snapshot") - If Setup.Get("ToolUpdateSnapshot") AndAlso Not Setup.Get("ToolUpdateSnapshotLast") = "" AndAlso Version IsNot Nothing AndAlso Not Setup.Get("ToolUpdateSnapshotLast") = Version Then - SyncLock HintLock - If Not IsNewClientVersionHinted Then ' 在锁内再次检查 - McDownloadClientUpdateHint(Version, Json) - IsNewClientVersionHinted = True - End If - End SyncLock + If Setup.Get("ToolUpdateSnapshot") AndAlso Not Setup.Get("ToolUpdateSnapshotLast") = "" AndAlso + Setup.Get("ToolUpdateSnapshotLast") <> Version AndAlso Not IsHinted Then + IsHinted = True + McDownloadClientUpdateHint(Version, Json) End If Setup.Set("ToolUpdateSnapshotLast", If(Version, "Nothing")) + '正式版 + Version = Json("latest")("release") + If Setup.Get("ToolUpdateRelease") AndAlso Not Setup.Get("ToolUpdateReleaseLast") = "" AndAlso + Setup.Get("ToolUpdateReleaseLast") <> Version AndAlso Not IsHinted Then + IsHinted = True + McDownloadClientUpdateHint(Version, Json) + End If + Setup.Set("ToolUpdateReleaseLast", Version) Catch ex As Exception Throw New Exception("Minecraft 官方源版本列表解析失败", ex) End Try @@ -329,15 +340,15 @@ ''' ''' 显示名称,已去除 HD_U 字样,如“1.12.2 C8”。 ''' - Public NameDisplay As String + Public DisplayName As String ''' ''' 原始文件名称,如“preview_OptiFine_1.11_HD_U_E1_pre.jar”。 ''' - Public NameFile As String + Public FileName As String ''' ''' 对应的版本名称,如“1.13.2-OptiFine_HD_U_E6”。 ''' - Public NameVersion As String + Public InstanceName As String ''' ''' 是否为测试版。 ''' @@ -409,14 +420,14 @@ For i = 0 To ReleaseTime.Count - 1 Name(i) = Name(i).Replace("_", " ") Dim Entry As New DlOptiFineListEntry With { - .NameDisplay = Name(i).Replace("HD U ", "").Replace(".0 ", " "), - .ReleaseTime = Join({ReleaseTime(i).Split(".")(2), ReleaseTime(i).Split(".")(1), ReleaseTime(i).Split(".")(0)}, "/"), - .IsPreview = Name(i).ContainsF("pre", True), - .Inherit = Name(i).ToString.Split(" ")(0), - .NameFile = If(Name(i).ContainsF("pre", True), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar", - .RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")} + .DisplayName = Name(i).Replace("HD U ", "").Replace(".0 ", " "), + .ReleaseTime = Join({ReleaseTime(i).Split(".")(2), ReleaseTime(i).Split(".")(1), ReleaseTime(i).Split(".")(0)}, "/"), + .IsPreview = Name(i).ContainsF("pre", True), + .Inherit = Name(i).ToString.Split(" ")(0), + .FileName = If(Name(i).ContainsF("pre", True), "preview_", "") & "OptiFine_" & Name(i).Replace(" ", "_") & ".jar", + .RequiredForgeVersion = Forge(i).Replace("Forge ", "").Replace("#", "")} If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing - Entry.NameVersion = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "") + Entry.InstanceName = Entry.Inherit & "-OptiFine_" & Name(i).ToString.Replace(" ", "_").Replace(Entry.Inherit & "_", "") Versions.Add(Entry) Next Loader.Output = New DlOptiFineListResult With {.IsOfficial = True, .SourceName = "OptiFine 官方源", .Value = Versions} @@ -435,15 +446,15 @@ Dim Versions As New List(Of DlOptiFineListEntry) For Each Token As JObject In Json Dim Entry As New DlOptiFineListEntry With { - .NameDisplay = (Token("mcversion").ToString & Token("type").ToString.Replace("HD_U", "").Replace("_", " ") & " " & Token("patch").ToString).Replace(".0 ", " "), - .ReleaseTime = "", - .IsPreview = Token("patch").ToString.ContainsF("pre", True), - .Inherit = Token("mcversion").ToString, - .NameFile = Token("filename").ToString, - .RequiredForgeVersion = If(Token("forge"), "").ToString.Replace("Forge ", "").Replace("#", "") - } + .DisplayName = (Token("mcversion").ToString & Token("type").ToString.Replace("HD_U", "").Replace("_", " ") & " " & Token("patch").ToString).Replace(".0 ", " "), + .ReleaseTime = "", + .IsPreview = Token("patch").ToString.ContainsF("pre", True), + .Inherit = Token("mcversion").ToString, + .FileName = Token("filename").ToString, + .RequiredForgeVersion = If(Token("forge"), "").ToString.Replace("Forge ", "").Replace("#", "") + } If Entry.RequiredForgeVersion.Contains("N/A") Then Entry.RequiredForgeVersion = Nothing - Entry.NameVersion = Entry.Inherit & "-OptiFine_" & (Token("type").ToString & " " & Token("patch").ToString).Replace(".0 ", " ").Replace(" ", "_").Replace(Entry.Inherit & "_", "") + Entry.InstanceName = Entry.Inherit & "-OptiFine_" & (Token("type").ToString & " " & Token("patch").ToString).Replace(".0 ", " ").Replace(" ", "_").Replace(Entry.Inherit & "_", "") Versions.Add(Entry) Next Loader.Output = New DlOptiFineListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Versions} @@ -503,7 +514,7 @@ 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)") + Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+((_pre|[_-]snapshot[_-]?)[0-9]?)?(?=.html)") Names.Add("1.2.4") '1.2.4 不会被匹配上 If Names.Count < 10 Then Throw New Exception("获取到的版本数量不足(" & Result & ")") Loader.Output = New DlForgeListResult With {.IsOfficial = True, .SourceName = "Forge 官方源", .Value = Names} @@ -514,11 +525,10 @@ ''' Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain) Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult)) - Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding:=Encoding.Default) - If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") - '获取所有版本信息 - Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?") - If Names.Count < 10 Then Throw New Exception("获取到的版本数量不足(" & Result & ")") + Dim Result As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft", + Encoding:=Encoding.Default, RequireJson:=True)) + Dim Names As List(Of String) = Result.Select(Function(v) v.ToString).ToList + If Names.Count < 10 Then Throw New Exception("获取到的版本数量不足") Loader.Output = New DlForgeListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Names} End Sub @@ -527,60 +537,47 @@ #Region "DlForgeVersion | Forge 版本列表" Public MustInherit Class DlForgelikeEntry + Implements IComparable(Of DlForgelikeEntry) + Public IsNeoForge As Boolean ''' ''' 加载器名称。Forge 或 NeoForge。 ''' - Public ReadOnly Property LoaderName As String - Get - Return If(IsNeoForge, "NeoForge", "Forge") - End Get - End Property + Public MustOverride ReadOnly Property LoaderName As String ''' ''' 文件扩展名。不以小数点开头。 ''' - Public ReadOnly Property FileExtension As String - Get - If IsNeoForge Then - Return "jar" - Else - Return If(CType(Me, DlForgeVersionEntry).Category = "installer", "jar", "zip") - End If - End Get - End Property + Public MustOverride ReadOnly Property FileExtension As String ''' - ''' Forge:MC 版本是否小于 1.13。 - ''' NeoForge:MC 版本是否为 1.20.1。 - ''' - Public ReadOnly Property IsLegacy As Boolean - Get - '虽然很抽象,但确实可以这样判断 - 'Forge:1.13+ 的版本号首位都大于 20 - 'NeoForge:1.20.1 的版本号首位人为规定为 19 开头 - Return Version.Major < 20 - End Get - End Property - ''' - ''' 标准化后的版本号,仅可用于比较与排序。 + ''' 标准化后的版本号,仅可用于比较与排序,且 NeoForge 项不包含 alpha/beta 编号! ''' 格式:Major.Minor.Build.Revision ''' Forge:如 “50.1.9.0”(最后一位固定为 0)、“14.22.1.2478”(Legacy)。 ''' NeoForge:如 “20.4.30.0”(最后一位固定为 0)、“19.47.1.99”(Legacy:第一位固定为 19)。 ''' Public Version As Version ''' - ''' 可对玩家显示的非格式化版本名。 + ''' 可读的非格式化版本名。 ''' Forge:如 “50.1.9”、“14.22.1.2478”(Legacy)。 ''' NeoForge:如 “20.4.30-beta”、“47.1.99”(Legacy)。 ''' Public VersionName As String ''' - ''' 对应的 Minecraft 版本,如“1.12.2”。 + ''' 对应的 Minecraft 版本,如 1.12.2、26.1-snapshot-1。 ''' Public Inherit As String + + Public Function CompareTo(other As DlForgelikeEntry) As Integer Implements IComparable(Of DlForgelikeEntry).CompareTo + If Version <> other.Version Then + Return Version.CompareTo(other.Version) + Else + Return CompareVersion(VersionName, other.VersionName) + End If + End Function End Class Public Class DlForgeVersionEntry Inherits DlForgelikeEntry + ''' ''' 发布时间,格式为“yyyy/MM/dd HH:mm”。 ''' @@ -602,6 +599,23 @@ ''' Public FileVersion As String + ''' + ''' 加载器名称。Forge 或 NeoForge。 + ''' + Public Overrides ReadOnly Property LoaderName As String + Get + Return "Forge" + End Get + End Property + ''' + ''' 文件扩展名。不以小数点开头。 + ''' + Public Overrides ReadOnly Property FileExtension As String + Get + Return If(Category = "installer", "jar", "zip") + End Get + End Property + Public Sub New(Version As String, Branch As String, Inherit As String) '司马版本的特殊处理 If Version = "11.15.1.2318" OrElse Version = "11.15.1.1902" OrElse Version = "11.15.1.1890" Then Branch = "1.8.9" @@ -613,6 +627,7 @@ Me.Inherit = Inherit FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch) End Sub + End Class ''' @@ -651,7 +666,7 @@ ".html", SimulateBrowserHeaders:=True) Catch ex As Exception If ex.GetBrief().Contains("(404)") Then - Throw New Exception("不可用") + Throw New Exception("无") Else Throw End If @@ -709,7 +724,7 @@ Catch ex As Exception Throw New Exception("Forge 官方源版本列表解析失败(" & Result & ")", ex) End Try - If Not Versions.Any() Then Throw New Exception("不可用") + If Not Versions.Any() Then Throw New Exception("无") Loader.Output = Versions End Sub @@ -718,7 +733,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("-", "_"), RequireJson:=True)) '兼容 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) @@ -763,7 +778,7 @@ Catch ex As Exception Throw New Exception("Forge BMCLAPI 版本列表解析失败(" & Json.ToString & ")", ex) End Try - If Not Versions.Any() Then Throw New Exception("不可用") + If Not Versions.Any() Then Throw New Exception("无") Loader.Output = Versions End Sub @@ -788,6 +803,7 @@ Public Class DlNeoForgeListEntry Inherits DlForgelikeEntry + ''' ''' 是否是 Beta 版。 ''' @@ -801,11 +817,28 @@ ''' Public ReadOnly Property UrlBase As String Get - Dim PackageName As String = If(IsLegacy, "forge", "neoforge") + Dim PackageName As String = If(Inherit = "1.20.1", "forge", "neoforge") Return $"https://maven.neoforged.net/releases/net/neoforged/{PackageName}/{ApiName}/{PackageName}-{ApiName}" End Get End Property + ''' + ''' 加载器名称。Forge 或 NeoForge。 + ''' + Public Overrides ReadOnly Property LoaderName As String + Get + Return "NeoForge" + End Get + End Property + ''' + ''' 文件扩展名。不以小数点开头。 + ''' + Public Overrides ReadOnly Property FileExtension As String + Get + Return "jar" + End Get + End Property + Public Sub New(ApiName As String) IsNeoForge = True Me.ApiName = ApiName @@ -816,12 +849,18 @@ Inherit = "1.20.1" ElseIf ApiName.StartsWith("0.") Then '0.25w14craftmine.3-beta VersionName = ApiName - Version = New Version("0.0." & ApiName.BeforeFirst("-").AfterFirst(".").AfterFirst(".") & ".0") 'Minor 里有字母,但 Version 类的 Minor 是 Int 类型,只能这样写了 - Inherit = ApiName.BeforeFirst("-").AfterFirst(".").BeforeFirst(".") - Else '20.4.30-beta + Dim Segments = ApiName.BeforeFirst("-").Split("."c) + Version = New Version(0, 0, Segments.Last) + Inherit = Segments(1) + Else '20.4.30-beta;26.1.0.0-alpha.1+snapshot-1 VersionName = ApiName Version = New Version(ApiName.BeforeFirst("-")) - Inherit = $"1.{Version.Major}" & If(Version.Minor = 0, "", "." & Version.Minor) + If Version.Major >= 24 Then + Inherit = Version.ToString.Replace(".0", "") + Else + Inherit = "1." & Version.Major & If(Version.Minor > 0, "." & Version.Minor, "") + End If + If VersionName.Contains("+") Then Inherit &= "-" & VersionName.AfterFirst("+") End If End Sub End Class @@ -888,12 +927,12 @@ Private Function GetNeoForgeEntries(LatestJson As String, LatestLegacyJson As String) As List(Of DlNeoForgeListEntry) Dim VersionNames = RegexSearch(LatestLegacyJson & LatestJson, - "(?<="")(?:(1\.20\.1-)?\d+\.\d+\.\d+|0\.[^.]+\.\d+)(-beta)?(?="")") '我寻思直接正则就行.jpg + "(?<="")(1\.20\.1-)?\d+\.[^\.]+\.\d+(\.\d+)?(-(beta|alpha)(\.\d+)?)?(\+snapshot-\d+)?(?="")") Dim Versions = VersionNames. Where(Function(name) name <> "47.1.82"). '这个版本虽然在版本列表中,但不能下载 - Select(Function(name) New DlNeoForgeListEntry(name)).ToList - If Not Versions.Any() Then Throw New Exception("不可用") - Versions = Versions.OrderByDescending(Function(a) a.Version).ToList + Select(Function(name) New DlNeoForgeListEntry(name)). + OrderByDescending(Function(a) a).ToList + If Not Versions.Any() Then Throw New Exception("无") Return Versions End Function @@ -1308,7 +1347,7 @@ For ii = 0 To LoaderList.Count - 1 LoaderList(ii).Key.Input = Nothing '重置输入,以免以同样的输入“重试加载”时直接失败 If LoaderList(ii).Key.Error IsNot Nothing Then - If ErrorInfo Is Nothing OrElse LoaderList(ii).Key.Error.Message.Contains("不可用") Then + If ErrorInfo Is Nothing OrElse LoaderList(ii).Key.Error.Message = "无" Then ErrorInfo = LoaderList(ii).Key.Error End If End If diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb index d84592b..4d0613d 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb @@ -35,7 +35,7 @@ ''' ''' Java 的大版本号。 ''' - Public ReadOnly Property VersionCode As Integer + Public ReadOnly Property MajorVersion As Integer Get Return Version.Minor End Get @@ -71,7 +71,7 @@ Public Overrides Function ToString() As String Dim VersionString = Version.ToString If VersionString.StartsWithF("1.") Then VersionString = Mid(VersionString, 3) - Return If(IsJre, "JRE ", "JDK ") & VersionCode & " (" & VersionString & ")" & If(Is64Bit, "", ",32 位") & If(IsUserImport, ",手动导入", "") & ":" & PathFolder + Return If(IsJre, "JRE ", "JDK ") & MajorVersion & " (" & VersionString & ")" & If(Is64Bit, "", ",32 位") & If(IsUserImport, ",手动导入", "") & ":" & PathFolder End Function '构造 @@ -124,7 +124,7 @@ '#3649:在 64 位系统上禁用 32 位 Java If Not Is64Bit AndAlso Not Is32BitSystem Then Throw New Exception("该 Java 为 32 位版本,请安装 64 位的 Java") '#2249:JRE 17 似乎会导致 Forge 安装失败,干脆禁用更多版本的 JRE - If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!") + If IsJre AndAlso MajorVersion >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!") Catch ex As ApplicationException Throw ex Catch ex As ThreadInterruptedException @@ -199,19 +199,19 @@ ''' 必须在工作线程调用,且必须包括 SyncLock JavaLock。 ''' Public Function JavaSelect(CancelException As String, Optional MinVersion As Version = Nothing, Optional MaxVersion As Version = Nothing, - Optional RelatedVersion As McVersion = Nothing) As JavaEntry + Optional GameInstance As McInstance = Nothing) As JavaEntry Try Dim AllowedJavaList As New List(Of JavaEntry) '添加特定的 Java Dim JavaPreList As New Dictionary(Of String, Boolean) - If PathMcFolder.Split("\").Count > 3 AndAlso Not PathMcFolder.Contains("AppData\Roaming") Then - JavaSearchFolder(GetPathFromFullPath(PathMcFolder), JavaPreList, False, True) 'Minecraft 文件夹的父文件夹(如果不是根目录或 %APPDATA% 的话) + If McFolderSelected.Split("\").Count > 3 AndAlso Not McFolderSelected.Contains("AppData\Roaming") Then + JavaSearchFolder(GetPathFromFullPath(McFolderSelected), JavaPreList, False, True) 'Minecraft 文件夹的父文件夹(如果不是根目录或 %APPDATA% 的话) End If - JavaSearchFolder(PathMcFolder, JavaPreList, False, True) 'Minecraft 文件夹 + JavaSearchFolder(McFolderSelected, JavaPreList, False, True) 'Minecraft 文件夹 JavaPreList = JavaPreList.Where(Function(j) Not j.Key.Contains(".minecraft\runtime")). ToDictionary(Function(j) j.Key, Function(j) j.Value) '排除官启自带 Java(#4286) - If RelatedVersion IsNot Nothing Then JavaSearchFolder(RelatedVersion.Path, JavaPreList, False, True) '所选版本文件夹 + If GameInstance IsNot Nothing Then JavaSearchFolder(GameInstance.PathVersion, JavaPreList, False, True) '所选版本文件夹 Dim TargetJavaList As New List(Of JavaEntry) For Each Entry In JavaPreList TargetJavaList.Add(New JavaEntry(Entry.Key, Entry.Value)) @@ -232,8 +232,8 @@ '获取版本独立设置中指定的 Java Dim VersionSelect As String = "" - If RelatedVersion IsNot Nothing Then - VersionSelect = Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion) + If GameInstance IsNot Nothing Then + VersionSelect = Setup.Get("VersionArgumentJavaSelect", Instance:=GameInstance) If VersionSelect.StartsWithF("{") Then Try UserJava = JavaEntry.FromJson(GetJson(VersionSelect)) @@ -242,7 +242,7 @@ Throw Catch ex As Exception UserJava = Nothing - Setup.Reset("VersionArgumentJavaSelect", Version:=RelatedVersion) + Setup.Reset("VersionArgumentJavaSelect", Instance:=GameInstance) Log(ex, "版本独立设置中指定的 Java 已无法使用,此设置已重置", LogLevel.Hint) End Try End If @@ -330,8 +330,8 @@ RetryGet: Dim Right As String = MaxVersion.Minor & If(ShowRevision, "." & MaxVersion.MajorRevision & "." & MaxVersion.MinorRevision, "") Requirement = "需要 Java " & If(Left = Right, Left, Left & " ~ " & Right) End If - Dim JavaCurrent As String = UserJava.VersionCode & If(ShowRevision, "." & UserJava.Version.MajorRevision & "." & UserJava.Version.MinorRevision, "") - If RelatedVersion IsNot Nothing AndAlso Setup.Get("VersionAdvanceJava", RelatedVersion) Then + Dim JavaCurrent As String = UserJava.MajorVersion & If(ShowRevision, "." & UserJava.Version.MajorRevision & "." & UserJava.Version.MinorRevision, "") + If GameInstance IsNot Nothing AndAlso Setup.Get("VersionAdvanceJava", GameInstance) Then '直接跳过弹窗 Log("[Java] 设置中指定了使用 Java " & JavaCurrent & ",但当前版本" & Requirement & ",这可能会导致游戏崩溃!", LogLevel.Debug) AllowedJavaList = New List(Of JavaEntry) From {UserJava} @@ -372,7 +372,7 @@ ExitUserJavaCheck: UserPass: '对适合的 Java 进行排序 - AllowedJavaList = AllowedJavaList.Sort(AddressOf JavaSorter) + AllowedJavaList = AllowedJavaList.SortByComparison(AddressOf JavaSorter) Log($"[Java] 排序后的 Java 优先顺序:") For Each Java In AllowedJavaList Log($"[Java] - {Java}") @@ -408,12 +408,12 @@ UserPass: ''' ''' 是否强制指定了 64 位 Java。如果没有强制指定,返回是否安装了 64 位 Java。 ''' - Public Function JavaIs64Bit(Optional RelatedVersion As McVersion = Nothing) As Boolean + Public Function JavaIs64Bit(Optional GameInstance As McInstance = Nothing) As Boolean Try '检查强制指定 Dim UserSetup As String = Setup.Get("LaunchArgumentJavaSelect") - If RelatedVersion IsNot Nothing Then - Dim UserSetupVersion As String = Setup.Get("VersionArgumentJavaSelect", Version:=RelatedVersion) + If GameInstance IsNot Nothing Then + Dim UserSetupVersion As String = Setup.Get("VersionArgumentJavaSelect", Instance:=GameInstance) If UserSetupVersion <> "使用全局设置" Then UserSetup = UserSetupVersion End If If UserSetup <> "" Then @@ -422,7 +422,7 @@ UserPass: UserJava = JavaEntry.FromJson(GetJson(UserSetup)) Catch ex As Exception Log(ex, "版本指定的 Java 信息已损坏,已重置版本设置中指定的 Java") - Setup.Set("VersionArgumentJavaSelect", "使用全局设置", Version:=RelatedVersion) + Setup.Set("VersionArgumentJavaSelect", "使用全局设置", Instance:=GameInstance) GoTo NoUserJava End Try For Each Java In JavaList @@ -446,14 +446,14 @@ NoUserJava: ''' Public Function JavaSorter(Left As JavaEntry, Right As JavaEntry) As Boolean Dim PathInfo As New DirectoryInfo(Path) - Dim PathMcInfo As New DirectoryInfo(PathMcFolder) + Dim PathMcInfo As New DirectoryInfo(McFolderSelected) '1. 尽量在当前文件夹或当前 Minecraft 文件夹 Dim ProgramPathParent As String, MinecraftPathParent As String = "" ProgramPathParent = If(PathInfo.Parent, PathInfo).FullName - If PathMcFolder <> "" Then MinecraftPathParent = If(PathMcInfo.Parent, PathMcInfo).FullName + If McFolderSelected <> "" Then MinecraftPathParent = If(PathMcInfo.Parent, PathMcInfo).FullName If Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Not Right.PathFolder.StartsWithF(ProgramPathParent) Then Return True If Not Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Right.PathFolder.StartsWithF(ProgramPathParent) Then Return False - If PathMcFolder <> "" Then + If McFolderSelected <> "" Then If Left.PathFolder.StartsWithF(MinecraftPathParent) AndAlso Not Right.PathFolder.StartsWithF(MinecraftPathParent) Then Return True If Not Left.PathFolder.StartsWithF(MinecraftPathParent) AndAlso Right.PathFolder.StartsWithF(MinecraftPathParent) Then Return False End If @@ -464,10 +464,10 @@ NoUserJava: If Left.IsJre AndAlso Not Right.IsJre Then Return True If Not Left.IsJre AndAlso Right.IsJre Then Return False '4. Java 大版本 - If Left.VersionCode <> Right.VersionCode Then + If Left.MajorVersion <> Right.MajorVersion Then ' Java 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23... Dim Weight = {0, 1, 2, 3, 4, 5, 6, 14, 30, 10, 12, 15, 13, 9, 8, 7, 11, 31, 29, 16, 17, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18} - Return Weight.ElementAtOrDefault(Left.VersionCode) >= Weight.ElementAtOrDefault(Right.VersionCode) + Return Weight.ElementAtOrDefault(Left.MajorVersion) >= Weight.ElementAtOrDefault(Right.MajorVersion) End If '5. 最次级版本号更接近 51 Return Math.Abs(Left.Version.Revision - 51) <= Math.Abs(Right.Version.Revision - 51) @@ -487,11 +487,11 @@ NoUserJava: FrmSetupLaunch.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True}) End Sub) End If - If FrmVersionSetup IsNot Nothing Then + If FrmInstanceSetup IsNot Nothing Then RunInUiWait( Sub() - FrmVersionSetup.ComboArgumentJava.Items.Clear() - FrmVersionSetup.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True}) + FrmInstanceSetup.ComboArgumentJava.Items.Clear() + FrmInstanceSetup.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True}) End Sub) End If @@ -525,8 +525,8 @@ NoUserJava: '查找启动器目录中的 Java JavaSearchFolder(Path, JavaPreList, False, IsFullSearch:=True) '查找所选 Minecraft 文件夹中的 Java - If Not String.IsNullOrWhiteSpace(PathMcFolder) AndAlso Path <> PathMcFolder Then - JavaSearchFolder(PathMcFolder, JavaPreList, False, IsFullSearch:=True) + If Not String.IsNullOrWhiteSpace(McFolderSelected) AndAlso Path <> McFolderSelected Then + JavaSearchFolder(McFolderSelected, JavaPreList, False, IsFullSearch:=True) End If '若不全为符号链接,则清除符号链接的地址 @@ -579,7 +579,7 @@ NoUserJava: For Each Entry In JavaPreList.Distinct(Function(a, b) a.Key.ToLower = b.Key.ToLower) '#794 NewJavaList.Add(New JavaEntry(Entry.Key, Entry.Value)) Next - NewJavaList = JavaCheckList(NewJavaList).Sort(AddressOf JavaSorter) + NewJavaList = JavaCheckList(NewJavaList).SortByComparison(AddressOf JavaSorter) '修改设置项 Dim AllList As New JArray @@ -596,7 +596,7 @@ NoUserJava: Log("[Java] Java 搜索完成,发现 " & JavaList.Count & " 个 Java") If FrmSetupLaunch IsNot Nothing Then RunInUi(Sub() FrmSetupLaunch.RefreshJavaComboBox()) - If FrmVersionSetup IsNot Nothing Then RunInUi(Sub() FrmVersionSetup.RefreshJavaComboBox()) + If FrmInstanceSetup IsNot Nothing Then RunInUi(Sub() FrmInstanceSetup.RefreshJavaComboBox()) End Sub ''' @@ -695,28 +695,12 @@ Wait: #Region "下载" ''' - ''' 提示 Java 缺失,并弹窗确认是否自动下载。返回玩家选择是否下载。 + ''' 获取下载 Java 的加载器。需要开启 IsForceRestart 以正常刷新 Java 列表。 ''' - Public Function JavaDownloadConfirm(VersionDescription As String, Optional ForcedManualDownload As Boolean = False) As Boolean - If ForcedManualDownload Then - MyMsgBox($"PCL 未找到 {VersionDescription}。" & vbCrLf & - $"请自行搜索并安装 {VersionDescription},安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。", - "未找到 Java") - Return False - Else - Return MyMsgBox($"PCL 未找到 {VersionDescription},是否需要 PCL 自动下载?" & vbCrLf & - $"如果你已经安装了 {VersionDescription},可以在 设置 → 启动选项 → 游戏 Java 中手动导入。", - "自动下载 Java?", "自动下载", "取消") = 1 - End If - End Function - - ''' - ''' 获取下载 Java 8/14/17/21 的加载器。需要开启 IsForceRestart 以正常刷新 Java 列表。 - ''' - Public Function JavaFixLoaders(Version As Integer) As LoaderCombo(Of Integer) + Public Function GetJavaDownloadLoader() As LoaderCombo(Of String) Dim JavaDownloadLoader As New LoaderDownload("下载 Java 文件", New List(Of NetFile)) With {.ProgressWeight = 10} - Dim Loader = New LoaderCombo(Of Integer)($"下载 Java {Version}", { - New LoaderTask(Of Integer, List(Of NetFile))("获取 Java 下载信息", AddressOf JavaFileList) With {.ProgressWeight = 2}, + Dim Loader = New LoaderCombo(Of String)($"下载 Java", { + New LoaderTask(Of String, List(Of NetFile))("获取 Java 下载信息", AddressOf JavaFileList) With {.ProgressWeight = 2}, JavaDownloadLoader, JavaSearchLoader }) @@ -733,22 +717,29 @@ Wait: Return Loader End Function Private LastJavaBaseDir As String = Nothing '用于在下载中断或失败时删除未完成下载的 Java 文件夹,防止残留只下了一半但 -version 能跑的 Java - Private Sub JavaFileList(Loader As LoaderTask(Of Integer, List(Of NetFile))) + Private Sub JavaFileList(Loader As LoaderTask(Of String, List(Of NetFile))) Log("[Java] 开始获取 Java 下载信息") Dim IndexFileStr As String = NetRequestByLoader(DlVersionListOrder( {"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}, {"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"} ), IsJson:=True) - '获取下载地址 - Dim MainEntry As JObject = CType(GetJson(IndexFileStr), JObject)($"windows-x{If(Is32BitSystem, "86", "64")}") - Dim Entries = MainEntry.Children.Reverse. '选择最靠后的一项(最新) - SelectMany(Function(e As JProperty) CType(e.Value, JArray).Select(Function(v) New KeyValuePair(Of String, JObject)(e.Name, v))) - Dim TargetEntry = Entries.First(Function(t) t.Value("version")("name").ToString.StartsWithF(Loader.Input)) - Dim Address As String = TargetEntry.Value("manifest")("url") - Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}") + '查找要下载的目标 Java + Dim TargetEntry As JProperty = Nothing + Dim Components As JObject = CType(GetJson(IndexFileStr), JObject)($"windows-x{If(Is32BitSystem, "86", "64")}") + If Components.ContainsKey(Loader.Input) Then '精确匹配 + TargetEntry = Components.Property(Loader.Input) + Else '模糊匹配 + TargetEntry = Components.Properties.FirstOrDefault( + Function(c) c.Value?.ToArray.FirstOrDefault()?("version")("name").ToString.StartsWithF(Loader.Input)) + If TargetEntry Is Nothing Then Throw New Exception($"未能找到所需的 Java {Loader.Input}") + End If + Dim TargetComponent = TargetEntry.Value.ToArray.FirstOrDefault + If TargetComponent Is Nothing Then Throw New Exception($"Mojang 未提供所需的 Java {Loader.Input}") '获取文件列表 + Dim Address As String = TargetComponent("manifest")("url") + McLaunchLog($"准备下载 Java {TargetComponent("version")("name")}({TargetEntry.Name}):{Address}") Dim ListFileStr As String = NetRequestByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True) - LastJavaBaseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Key & "\" + LastJavaBaseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Name & "\" Dim Results As New List(Of NetFile) For Each File As JProperty In CType(GetJson(ListFileStr), JObject)("files") If CType(File.Value, JObject)("downloads")?("raw") Is Nothing Then Continue For diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb index 531f034..58654a4 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb @@ -18,9 +18,9 @@ Public Module ModLaunch Public SaveBatch As String = Nothing ''' ''' 强行指定启动的 MC 版本。 - ''' 默认值:Nothing。使用 McVersionCurrent。 + ''' 默认值:Nothing。使用 McInstanceCurrent。 ''' - Public Version As McVersion = Nothing + Public Instance As McInstance = Nothing ''' ''' 额外的启动参数。 ''' @@ -39,23 +39,30 @@ Public Module ModLaunch Return False End If '强制切换需要启动的版本 - If CurrentLaunchOptions.Version IsNot Nothing AndAlso McVersionCurrent <> CurrentLaunchOptions.Version Then - McLaunchLog("在启动前切换到版本 " & CurrentLaunchOptions.Version.Name) + If CurrentLaunchOptions.Instance IsNot Nothing AndAlso McInstanceSelected <> CurrentLaunchOptions.Instance Then + McLaunchLog("在启动前切换到版本 " & CurrentLaunchOptions.Instance.Name) + '检查版本是否存在 + Try + CurrentLaunchOptions.Instance.GetJsonPath() + Catch + Hint($"无法启动 {CurrentLaunchOptions.Instance.Name}:当前 Minecraft 文件夹中尚未安装该版本!", HintType.Red) + Return False + End Try '检查版本 - CurrentLaunchOptions.Version.Load() - If CurrentLaunchOptions.Version.State = McVersionState.Error Then - Hint("无法启动 Minecraft:" & CurrentLaunchOptions.Version.Info, HintType.Red) + CurrentLaunchOptions.Instance.Load() + If CurrentLaunchOptions.Instance.State = McInstanceState.Error Then + Hint($"无法启动 {CurrentLaunchOptions.Instance.Name}:{CurrentLaunchOptions.Instance.Info}", HintType.Red) Return False End If '切换版本 - McVersionCurrent = CurrentLaunchOptions.Version - Setup.Set("LaunchVersionSelect", McVersionCurrent.Name) + McInstanceSelected = CurrentLaunchOptions.Instance + Setup.Set("LaunchVersionSelect", McInstanceSelected.Name) FrmLaunchLeft.RefreshButtonsUI() FrmLaunchLeft.RefreshPage(False, False) End If FrmMain.AprilGiveup() - '禁止进入版本选择页面(否则就可以在启动中切换 McVersionCurrent 了) - FrmMain.PageStack = FrmMain.PageStack.Where(Function(p) p.Page <> FormMain.PageType.VersionSelect).ToList + '禁止进入版本选择页面(否则就可以在启动中切换 McInstanceSelected 了) + FrmMain.PageStack = FrmMain.PageStack.Where(Function(p) p.Page <> FormMain.PageType.InstanceSelect).ToList '实际启动加载器 McLaunchLoader.Start(Options, IsForceRestart:=True) Return True @@ -109,7 +116,7 @@ Public Module ModLaunch Dim Loaders As New List(Of LoaderBase) From { New LoaderTask(Of Integer, Integer)("获取 Java", AddressOf McLaunchJava) With {.ProgressWeight = 4, .Block = False}, McLoginLoader, '.ProgressWeight = 15, .Block = False - New LoaderCombo(Of String)("补全文件", DlClientFix(McVersionCurrent, False, AssetsIndexExistsBehaviour.DownloadInBackground)) With {.ProgressWeight = 15, .Show = False}, + New LoaderCombo(Of String)("补全文件", DlClientFix(McInstanceSelected, False, AssetsIndexExistsBehaviour.DownloadInBackground)) With {.ProgressWeight = 15, .Show = False}, New LoaderTask(Of String, List(Of McLibToken))("获取启动参数", AddressOf McLaunchArgumentMain) With {.ProgressWeight = 2}, New LoaderTask(Of List(Of McLibToken), Integer)("解压文件", AddressOf McLaunchNatives) With {.ProgressWeight = 2}, New LoaderTask(Of Integer, Integer)("预启动处理", AddressOf McLaunchPrerun) With {.ProgressWeight = 1}, @@ -119,7 +126,7 @@ Public Module ModLaunch New LoaderTask(Of Integer, Integer)("结束处理", AddressOf McLaunchEnd) With {.ProgressWeight = 1} } '内存优化 - Select Case Setup.Get("VersionRamOptimize", Version:=McVersionCurrent) + Select Case Setup.Get("VersionRamOptimize", Instance:=McInstanceSelected) Case 0 '全局 If Setup.Get("LaunchArgumentRam") Then '使用全局设置 CType(Loaders(2), LoaderCombo(Of String)).Block = False @@ -140,17 +147,17 @@ Public Module ModLaunch LoaderTaskbarAdd(LaunchLoader) Do While LaunchLoader.State = LoadState.Loading FrmLaunchLeft.Dispatcher.Invoke(AddressOf FrmLaunchLeft.LaunchingRefresh) - Thread.Sleep(200) + Thread.Sleep(100) Loop FrmLaunchLeft.Dispatcher.Invoke(AddressOf FrmLaunchLeft.LaunchingRefresh) '成功与失败处理 Select Case LaunchLoader.State Case LoadState.Finished - Hint(McVersionCurrent.Name & " 启动成功!", HintType.Green) + Hint(McInstanceSelected.Name & " 启动成功!", HintType.Green) '上报 If Not IsSavingBatch Then Telemetry("Minecraft 启动成功", - "Version", McVersionCurrent.Version.ToString, + "Version", McInstanceSelected.VersionDisplayName, "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType))) End If Case LoadState.Aborted @@ -185,7 +192,7 @@ NextInner: '上报 If Not IsSavingBatch Then Telemetry("Minecraft 启动失败", - "Version", McVersionCurrent.Version.ToString, + "Version", McInstanceSelected.VersionDisplayName, "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType)), "Exception", FilterUserName(FilterAccessToken(ex.GetDetail(), "*"), "*")) End If @@ -223,12 +230,12 @@ NextInner: Private Sub McLaunchPrecheck() If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000)) '检查路径 - If McVersionCurrent.PathIndie.Contains("!") OrElse McVersionCurrent.PathIndie.Contains(";") Then Throw New Exception("游戏路径中不可包含 ! 或 ;(" & McVersionCurrent.PathIndie & ")") - If McVersionCurrent.Path.Contains("!") OrElse McVersionCurrent.Path.Contains(";") Then Throw New Exception("游戏路径中不可包含 ! 或 ;(" & McVersionCurrent.Path & ")") + If McInstanceSelected.PathIndie.Contains("!") OrElse McInstanceSelected.PathIndie.Contains(";") Then Throw New Exception("游戏路径中不可包含 ! 或 ;(" & McInstanceSelected.PathIndie & ")") + If McInstanceSelected.PathVersion.Contains("!") OrElse McInstanceSelected.PathVersion.Contains(";") Then Throw New Exception("游戏路径中不可包含 ! 或 ;(" & McInstanceSelected.PathVersion & ")") '检查版本 - If McVersionCurrent Is Nothing Then Throw New Exception("未选择 Minecraft 版本!") - McVersionCurrent.Load() - If McVersionCurrent.State = McVersionState.Error Then Throw New Exception("Minecraft 存在问题:" & McVersionCurrent.Info) + If McInstanceSelected Is Nothing Then Throw New Exception("未选择 Minecraft 版本!") + McInstanceSelected.Load() + If McInstanceSelected.State = McInstanceState.Error Then Throw New Exception("Minecraft 存在问题:" & McInstanceSelected.Info) '检查输入信息 Dim CheckResult As String = "" RunInUiWait(Sub() CheckResult = McLoginAble(McLoginInput())) @@ -817,7 +824,7 @@ LoginFinish: Dim ErrorMessage As String = Nothing Try Dim Response = DirectCast(ex, ResponsedWebException).Response - If ErrorMessage.ContainsF("errorMessage", True) Then ErrorMessage = GetJson(Response)("errorMessage") + If Response.ContainsF("errorMessage", True) Then ErrorMessage = GetJson(Response)("errorMessage").ToString() Catch End Try If Not String.IsNullOrWhiteSpace(ErrorMessage) Then @@ -940,24 +947,20 @@ Retry: }" Dim Result As String = NetRequestByClientMultiple("https://user.auth.xboxlive.com/user/authenticate", HttpMethod.Post, Content:=Request, ContentType:="application/json", RequireJson:=True) - Dim ResultJson As JObject = GetJson(Result) - Dim XBLToken As String = ResultJson("Token").ToString - Return XBLToken + Return GetJson(Result)("Token").ToString End Function '微软登录步骤 3:从 XBLToken 获取 {XSTSToken, UHS} Private Function MsLoginStep3(XBLToken As String) As String() McLaunchLog("开始微软登录步骤 3/6") Dim Request As String = "{ - ""Properties"": { - ""SandboxId"": ""RETAIL"", - ""UserTokens"": [ - """ & XBLToken & """ - ] - }, - ""RelyingParty"": ""rp://api.minecraftservices.com/"", - ""TokenType"": ""JWT"" - }" + ""Properties"": { + ""SandboxId"": ""RETAIL"", + ""UserTokens"": [""" & XBLToken & """] + }, + ""RelyingParty"": ""rp://api.minecraftservices.com/"", + ""TokenType"": ""JWT"" + }" Dim Result As String Try Result = NetRequestByClientMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post, @@ -1098,7 +1101,7 @@ Retry: '使用正版用户名 Try If SkinName <> "" AndAlso - McVersionCurrent IsNot Nothing AndAlso McVersionCurrent.Version.McCodeMain < 20 Then '1.20+ 或快照版不能使用该项(#3746) + McInstanceSelected IsNot Nothing AndAlso McInstanceSelected.Version.Vanilla.Major < 20 Then '1.20+ 或快照版不能使用该项(#3746) Log("[Skin] 由于离线皮肤设置,使用正版 UUID:" & SkinName) Uuid = McLoginMojangUuid(SkinName, False) End If @@ -1151,85 +1154,94 @@ Retry: Private Sub McLaunchJava(Task As LoaderTask(Of Integer, Integer)) Dim MinVer As New Version(0, 0, 0, 0), MaxVer As New Version(999, 999, 999, 999) - 'MC 大版本检测 - If (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2024, 4, 2)) OrElse - (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 20, 5)) Then + '原版 26-:通过版本号判断 + If (Not McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.ReleaseTime >= New Date(2024, 4, 2)) OrElse + (McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.Version.Vanilla >= New Version(20, 0, 5)) Then '1.20.5+(24w14a+):至少 Java 21 MinVer = New Version(1, 21, 0, 0) - ElseIf (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2021, 11, 16)) OrElse - (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 18)) Then + ElseIf (Not McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.ReleaseTime >= New Date(2021, 11, 16)) OrElse + (McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.Version.Vanilla.Major >= 18) Then '1.18 pre2+:至少 Java 17 MinVer = New Version(1, 17, 0, 0) - ElseIf (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2021, 5, 11)) OrElse - (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 17)) Then + ElseIf (Not McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.ReleaseTime >= New Date(2021, 5, 11)) OrElse + (McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.Version.Vanilla.Major >= 17) Then '1.17+ (21w19a+):至少 Java 16 MinVer = New Version(1, 16, 0, 0) - ElseIf McVersionCurrent.ReleaseTime.Year >= 2017 Then 'Minecraft 1.12 与 1.11 的分界线正好是 2017 年,太棒了 + ElseIf McInstanceSelected.ReleaseTime.Year >= 2017 Then 'Minecraft 1.12 与 1.11 的分界线正好是 2017 年,太棒了 '1.12+:至少 Java 8 MinVer = New Version(1, 8, 0, 0) - ElseIf McVersionCurrent.ReleaseTime <= New Date(2013, 5, 1) AndAlso McVersionCurrent.ReleaseTime.Year >= 2001 Then '避免某些版本写个 1960 年 - '1.5.2-:最高 Java 12 - MaxVer = New Version(1, 12, 999, 999) + ElseIf McInstanceSelected.ReleaseTime <= New Date(2013, 5, 1) AndAlso McInstanceSelected.ReleaseTime.Year >= 2001 Then '避免某些版本写个 1960 年 + '1.5.2-:最高 Java 8 + MaxVer = New Version(1, 8, 999, 999) End If - If McVersionCurrent.JsonVersion?("java_version") IsNot Nothing Then - Dim RecommendedJava As Integer = McVersionCurrent.JsonVersion("java_version").ToObject(Of Integer) - McLaunchLog("Mojang 推荐使用 Java " & RecommendedJava) - If RecommendedJava >= 22 Then MinVer = New Version(1, RecommendedJava, 0, 0) '潜在的向后兼容 + + '原版 26+:获取 Mojang 要求的 Java 版本 + Dim RecommendedComponent As String = Nothing + Dim RecommendedCode As Integer = + If(McInstanceSelected.JsonObject?("javaVersion")?("majorVersion")?.ToObject(Of Integer), + If(McInstanceSelected.JsonVersion?("java_version")?.ToObject(Of Integer), 0)) + If RecommendedCode >= 22 Then + McLaunchLog("Mojang 要求至少使用 Java " & RecommendedCode) + MinVer = New Version(1, RecommendedCode, 0, 0) + RecommendedComponent = + If(McInstanceSelected.JsonObject?("javaVersion")?("component")?.ToString, + McInstanceSelected.JsonVersion?("java_component")?.ToString) + If RecommendedComponent = "" Then RecommendedComponent = Nothing End If 'OptiFine 检测 - If McVersionCurrent.Version.HasOptiFine AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本 - If McVersionCurrent.Version.McVersion < New Version(1, 7) Then + If McInstanceSelected.Version.HasOptiFine AndAlso McInstanceSelected.Version.Vaild Then '不管非标准版本 + If McInstanceSelected.Version.Vanilla.Major < 7 Then '<1.7:至多 Java 8 MaxVer = New Version(1, 8, 999, 999) - ElseIf McVersionCurrent.Version.McVersion >= New Version(1, 8) AndAlso McVersionCurrent.Version.McVersion < New Version(1, 12) Then + ElseIf McInstanceSelected.Version.Vanilla.Major >= 8 AndAlso McInstanceSelected.Version.Vanilla.Major < 12 Then '1.8 - 1.11:必须恰好 Java 8 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 8, 999, 999) - ElseIf McVersionCurrent.Version.McCodeMain = 12 Then + ElseIf McInstanceSelected.Version.Vanilla.Major = 12 Then '1.12:最高 Java 8 MaxVer = New Version(1, 8, 999, 999) End If End If 'Forge 检测 - If McVersionCurrent.Version.HasForge Then - If McVersionCurrent.Version.McVersion >= New Version(1, 6, 1) AndAlso McVersionCurrent.Version.McVersion <= New Version(1, 7, 2) Then + If McInstanceSelected.Version.HasForge Then + If McInstanceSelected.Version.Vanilla >= New Version(6, 0, 1) AndAlso McInstanceSelected.Version.Vanilla <= New Version(7, 0, 2) Then '1.6.1 - 1.7.2:必须 Java 7 MinVer = If(New Version(1, 7, 0, 0) > MinVer, New Version(1, 7, 0, 0), MinVer) MaxVer = If(New Version(1, 7, 999, 999) < MaxVer, New Version(1, 7, 999, 999), MaxVer) - ElseIf McVersionCurrent.Version.McCodeMain <= 12 OrElse Not McVersionCurrent.Version.IsStandardVersion Then '非标准版本 + ElseIf McInstanceSelected.Version.Vanilla.Major <= 12 OrElse Not McInstanceSelected.Version.Vaild Then '非标准版本 '<=1.12:Java 8 MaxVer = New Version(1, 8, 999, 999) - ElseIf McVersionCurrent.Version.McCodeMain <= 14 Then + ElseIf McInstanceSelected.Version.Vanilla.Major <= 14 Then '1.13 - 1.14:Java 8 - 10 MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer) MaxVer = If(New Version(1, 10, 999, 999) < MaxVer, New Version(1, 10, 999, 999), MaxVer) - ElseIf McVersionCurrent.Version.McCodeMain = 15 Then + ElseIf McInstanceSelected.Version.Vanilla.Major = 15 Then '1.15:Java 8 - 15 MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer) MaxVer = If(New Version(1, 15, 999, 999) < MaxVer, New Version(1, 15, 999, 999), MaxVer) - ElseIf VersionSortBoolean(McVersionCurrent.Version.ForgeVersion, "34.0.0") AndAlso VersionSortBoolean("36.2.25", McVersionCurrent.Version.ForgeVersion) Then + ElseIf CompareVersionGE(McInstanceSelected.Version.Forge, "34.0.0") AndAlso CompareVersionGE("36.2.25", McInstanceSelected.Version.Forge) Then '1.16,Forge 34.X ~ 36.2.25:最高 Java 8u320 MaxVer = If(New Version(1, 8, 0, 320) < MaxVer, New Version(1, 8, 0, 320), MaxVer) - ElseIf McVersionCurrent.Version.McCodeMain >= 18 AndAlso McVersionCurrent.Version.McCodeMain < 19 AndAlso McVersionCurrent.Version.HasOptiFine Then '#305 + ElseIf McInstanceSelected.Version.Vanilla.Major >= 18 AndAlso McInstanceSelected.Version.Vanilla.Major < 19 AndAlso McInstanceSelected.Version.HasOptiFine Then '#305 '1.18:若安装了 OptiFine,最高 Java 18 MaxVer = If(New Version(1, 18, 999, 999) < MaxVer, New Version(1, 18, 999, 999), MaxVer) End If End If 'Fabric 检测 - If McVersionCurrent.Version.HasFabric AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本 - If McVersionCurrent.Version.McCodeMain >= 15 AndAlso McVersionCurrent.Version.McCodeMain <= 16 Then + If McInstanceSelected.Version.HasFabric AndAlso McInstanceSelected.Version.Vaild Then '不管非标准版本 + If McInstanceSelected.Version.Vanilla.Major >= 15 AndAlso McInstanceSelected.Version.Vanilla.Major <= 16 Then '1.15 - 1.16:Java 8+ MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer) - ElseIf McVersionCurrent.Version.McCodeMain >= 18 Then + ElseIf McInstanceSelected.Version.Vanilla.Major >= 18 Then '1.18+:Java 17+ MinVer = If(New Version(1, 17, 0, 0) > MinVer, New Version(1, 17, 0, 0), MinVer) End If End If 'LiteLoader 检测 - If McVersionCurrent.Version.HasLiteLoader AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本 + If McInstanceSelected.Version.HasLiteLoader AndAlso McInstanceSelected.Version.Vaild Then '不管非标准版本 '最高 Java 8 MaxVer = If(New Version(1, 8, 999, 999) < MaxVer, New Version(1, 8, 999, 999), MaxVer) End If @@ -1244,7 +1256,7 @@ Retry: '选择 Java McLaunchLog("Java 版本需求:最低 " & MinVer.ToString & ",最高 " & MaxVer.ToString) - McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent) + McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McInstanceSelected) If Task.IsAborted Then Return If McLaunchJavaSelected IsNot Nothing Then McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString) @@ -1255,40 +1267,25 @@ Retry: If Task.IsAborted Then Return '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java McLaunchLog("无合适的 Java,需要确认是否自动下载") Dim JavaCode As String - If MinVer >= New Version(1, 22) Then '潜在的向后兼容 + If MinVer >= New Version(1, 9) Then JavaCode = MinVer.Minor - If Not JavaDownloadConfirm("Java " & JavaCode) Then Throw New Exception("$$") - ElseIf MinVer >= New Version(1, 21) Then - JavaCode = 21 - If Not JavaDownloadConfirm("Java 21") Then Throw New Exception("$$") - ElseIf MinVer >= New Version(1, 9) Then - JavaCode = 17 - If Not JavaDownloadConfirm("Java 17") Then Throw New Exception("$$") ElseIf MaxVer < New Version(1, 8) Then - JavaCode = 7 - If McVersionCurrent.Version.HasForge Then - MyMsgBox("你需要先安装 LegacyJavaFixer Mod,或自行安装 Java 7,然后才能启动该版本。", "未找到 Java") - Else - If Not JavaDownloadConfirm("Java 7", True) Then Throw New Exception("$$") - End If + MyMsgBox($"你需要安装 Java 7 才能启动该版本。{vbCrLf}请自行搜索并安装 Java 7,安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。", "未找到 Java") + Throw New Exception("$$") ElseIf MinVer > New Version(1, 8, 0, 140) AndAlso MaxVer < New Version(1, 8, 0, 321) Then - JavaCode = "8u141" - If Not JavaDownloadConfirm("Java 8.0.141 ~ 8.0.320", True) Then Throw New Exception("$$") + MyMsgBox($"你需要安装 Java 8u141 ~ 8u320 才能启动该版本。{vbCrLf}请自行搜索并安装,安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。", "未找到 Java") + Throw New Exception("$$") ElseIf MinVer > New Version(1, 8, 0, 140) Then - JavaCode = "8u141" - If Not JavaDownloadConfirm("Java 8.0.141 或更高版本的 Java 8", True) Then Throw New Exception("$$") - ElseIf MaxVer < New Version(1, 8, 0, 321) Then - JavaCode = 8 - If Not JavaDownloadConfirm("Java 8.0.320 或更低版本的 Java 8") Then Throw New Exception("$$") + MyMsgBox($"你需要安装 Java 8u141 或更高版本的 Java 8 才能启动该版本。{vbCrLf}请自行搜索并安装,安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。", "未找到 Java") + Throw New Exception("$$") Else JavaCode = 8 - If Not JavaDownloadConfirm("Java 8") Then Throw New Exception("$$") End If '开始自动下载 - Dim JavaLoader = JavaFixLoaders(JavaCode) + Dim JavaLoader = GetJavaDownloadLoader() Try - JavaLoader.Start(JavaCode, IsForceRestart:=True) + JavaLoader.Start(If(RecommendedComponent, JavaCode), IsForceRestart:=True) '在 Java 22+ 时优先使用 Mojang 提供的 Component 字段 Do While JavaLoader.State = LoadState.Loading AndAlso Not Task.IsAborted Task.Progress = JavaLoader.Progress Thread.Sleep(10) @@ -1299,7 +1296,7 @@ Retry: '检查下载结果 If JavaSearchLoader.State <> LoadState.Loading Then JavaSearchLoader.State = LoadState.Waiting '2872# - McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent) + McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McInstanceSelected) If Task.IsAborted Then Return If McLaunchJavaSelected IsNot Nothing Then McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString) @@ -1356,32 +1353,32 @@ Retry: McLaunchLog("开始获取 Minecraft 启动参数") '获取基准字符串与参数信息 Dim Arguments As String - If McVersionCurrent.JsonObject("arguments") IsNot Nothing AndAlso McVersionCurrent.JsonObject("arguments")("jvm") IsNot Nothing Then + If McInstanceSelected.JsonObject("arguments") IsNot Nothing AndAlso McInstanceSelected.JsonObject("arguments")("jvm") IsNot Nothing Then McLaunchLog("获取新版 JVM 参数") - Arguments = McLaunchArgumentsJvmNew(McVersionCurrent) + Arguments = McLaunchArgumentsJvmNew(McInstanceSelected) McLaunchLog("新版 JVM 参数获取成功:") McLaunchLog(Arguments) Else McLaunchLog("获取旧版 JVM 参数") - Arguments = McLaunchArgumentsJvmOld(McVersionCurrent) + Arguments = McLaunchArgumentsJvmOld(McInstanceSelected) McLaunchLog("旧版 JVM 参数获取成功:") McLaunchLog(Arguments) End If - If Not String.IsNullOrEmpty(McVersionCurrent.JsonObject("minecraftArguments")) Then '有的版本是空字符串 + If Not String.IsNullOrEmpty(McInstanceSelected.JsonObject("minecraftArguments")) Then '有的版本是空字符串 McLaunchLog("获取旧版 Game 参数") - Arguments += " " & McLaunchArgumentsGameOld(McVersionCurrent) + Arguments += " " & McLaunchArgumentsGameOld(McInstanceSelected) McLaunchLog("旧版 Game 参数获取成功") End If - If McVersionCurrent.JsonObject("arguments") IsNot Nothing AndAlso McVersionCurrent.JsonObject("arguments")("game") IsNot Nothing Then + If McInstanceSelected.JsonObject("arguments") IsNot Nothing AndAlso McInstanceSelected.JsonObject("arguments")("game") IsNot Nothing Then McLaunchLog("获取新版 Game 参数") - Arguments += " " & McLaunchArgumentsGameNew(McVersionCurrent) + Arguments += " " & McLaunchArgumentsGameNew(McInstanceSelected) McLaunchLog("新版 Game 参数获取成功") End If '编码参数(#4700、#5892、#5909) - If McLaunchJavaSelected.VersionCode >= 18 Then 'Dfile.encoding 需要放在 Dstdout.encoding 后面(#6934) + If McLaunchJavaSelected.MajorVersion >= 18 Then 'Dfile.encoding 需要放在 Dstdout.encoding 后面(#6934) If Not Arguments.Contains("-Dfile.encoding=") Then Arguments = "-Dfile.encoding=COMPAT " & Arguments End If - If McLaunchJavaSelected.VersionCode > 8 Then + If McLaunchJavaSelected.MajorVersion > 8 Then If Not Arguments.Contains("-Dstdout.encoding=") Then Arguments = "-Dstdout.encoding=UTF-8 " & Arguments If Not Arguments.Contains("-Dstderr.encoding=") Then Arguments = "-Dstderr.encoding=UTF-8 " & Arguments End If @@ -1394,10 +1391,10 @@ Retry: Arguments += " " & Arg.Trim Next '自定义 - Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent) + Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Instance:=McInstanceSelected) Arguments += " " & ArgumentReplace(If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame)) '进行参数替换 - Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader) + Dim ReplaceArguments = McLaunchArgumentsReplace(McInstanceSelected, Loader) If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then '若自定义信息为空,则去掉该部分 Arguments = Arguments.Replace(" --versionType ${version_type}", "") @@ -1414,11 +1411,11 @@ Retry: FinalArguments = FinalArguments.TrimEnd '替换 Module Path 为短路径(Forge 会在版本 JSON 硬编码这些路径) FinalArguments = FinalArguments.RegexReplaceEach("(?<= (-p|--module-path) "")[^""]+(?="" )", - Function(ModulePath) ModulePath.Value.RegexReplaceEach("[A-Z]{1}:\\[^;]+", Function(m) ShortenPath(m.Value))) + Function(ModulePath) ModulePath.Value.RegexReplaceEach("[A-Z]:\\[^;]+", Function(m) ShortenPath(m.Value))) '进服 - Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp) + Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McInstanceSelected), CurrentLaunchOptions.ServerIp) If Server.Length > 0 Then - If McVersionCurrent.ReleaseTime > New Date(2023, 4, 4) Then + If McInstanceSelected.ReleaseTime > New Date(2023, 4, 4) Then 'QuickPlay FinalArguments += $" --quickPlayMultiplayer ""{Server}""" Else @@ -1430,7 +1427,7 @@ Retry: '不包含端口号 FinalArguments += $" --server {Server} --port 25565" End If - If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Red) + If McInstanceSelected.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Red) End If End If '输出 @@ -1440,30 +1437,30 @@ Retry: End Sub 'Jvm 部分(第一段) - Private Function McLaunchArgumentsJvmOld(Version As McVersion) As String + Private Function McLaunchArgumentsJvmOld(Instance As McInstance) As String '存储以空格为间隔的启动参数列表 Dim DataList As New List(Of String) '输出固定参数 DataList.Add("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump") - Dim ArgumentJvm As String = Setup.Get("VersionAdvanceJvm", Version:=McVersionCurrent) + Dim ArgumentJvm As String = Setup.Get("VersionAdvanceJvm", Instance:=McInstanceSelected) If ArgumentJvm = "" Then ArgumentJvm = Setup.Get("LaunchAdvanceJvm") If Not ArgumentJvm.Contains("-Dlog4j2.formatMsgNoLookups=true") Then ArgumentJvm += " -Dlog4j2.formatMsgNoLookups=true" ArgumentJvm = ArgumentJvm.Replace(" -XX:MaxDirectMemorySize=256M", "") '#3511 的清理 DataList.Insert(0, ArgumentReplace(ArgumentJvm)) '可变 JVM 参数 - DataList.Add("-Xmn" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024 * 0.15) & "m") - DataList.Add("-Xmx" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024) & "m") + DataList.Add("-Xmn" & Math.Floor(PageInstanceSetup.GetRam(McInstanceSelected, Not McLaunchJavaSelected.Is64Bit) * 1024 * 0.15) & "m") + DataList.Add("-Xmx" & Math.Floor(PageInstanceSetup.GetRam(McInstanceSelected, Not McLaunchJavaSelected.Is64Bit) * 1024) & "m") DataList.Add("""-Djava.library.path=" & GetNativesFolder() & """") DataList.Add("-cp ${classpath}") '把支持库添加进启动参数表 '统一通行证 If McLoginLoader.Output.Type = "Nide" Then - DataList.Insert(0, "-Dnide8auth.client=true -javaagent:""" & PathAppdata & "nide8auth.jar""=" & Setup.Get("VersionServerNide", Version:=McVersionCurrent)) + DataList.Insert(0, "-Dnide8auth.client=true -javaagent:""" & PathAppdata & "nide8auth.jar""=" & Setup.Get("VersionServerNide", Instance:=McInstanceSelected)) End If 'Authlib-Injector If McLoginLoader.Output.Type = "Auth" Then - If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#) - Dim Server As String = Setup.Get("VersionServerAuthServer", McVersionCurrent) + If McLaunchJavaSelected.MajorVersion >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#) + Dim Server As String = Setup.Get("VersionServerAuthServer", McInstanceSelected) Try Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8) DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server & @@ -1475,17 +1472,17 @@ Retry: End If '添加 Java Wrapper 作为主 Jar - If Not Setup.Get("LaunchAdvanceDisableJLW") AndAlso Not Setup.Get("VersionAdvanceDisableJLW", McVersionCurrent) Then - If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED") + If Not Setup.Get("LaunchAdvanceDisableJLW") AndAlso Not Setup.Get("VersionAdvanceDisableJLW", McInstanceSelected) Then + If McLaunchJavaSelected.MajorVersion >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED") DataList.Add("-Doolloo.jlw.tmpdir=""" & PathPure.TrimEnd("\") & """") DataList.Add("-jar """ & ExtractJavaWrapper() & """") End If '添加 MainClass - If Version.JsonObject("mainClass") Is Nothing Then + If Instance.JsonObject("mainClass") Is Nothing Then Throw New Exception("版本 json 中没有 mainClass 项!") Else - DataList.Add(Version.JsonObject("mainClass")) + DataList.Add(Instance.JsonObject("mainClass")) End If '处理 DataList 的换行符 @@ -1493,14 +1490,14 @@ Retry: Return Join(DataList, " ") End Function - Private Function McLaunchArgumentsJvmNew(Version As McVersion) As String + Private Function McLaunchArgumentsJvmNew(Instance As McInstance) As String Dim DataList As New List(Of String) '获取 Json 中的 DataList - Dim CurrentVersion As McVersion = Version -NextVersion: - If CurrentVersion.JsonObject("arguments") IsNot Nothing AndAlso CurrentVersion.JsonObject("arguments")("jvm") IsNot Nothing Then - For Each SubJson As JToken In CurrentVersion.JsonObject("arguments")("jvm") + Dim CurrentInstance As McInstance = Instance +NextInstance: + If CurrentInstance.JsonObject("arguments") IsNot Nothing AndAlso CurrentInstance.JsonObject("arguments")("jvm") IsNot Nothing Then + For Each SubJson As JToken In CurrentInstance.JsonObject("arguments")("jvm") If SubJson.Type = JTokenType.String Then '字符串类型 DataList.Add(SubJson.ToString) @@ -1519,9 +1516,9 @@ NextVersion: End If Next End If - If CurrentVersion.InheritVersion <> "" Then - CurrentVersion = New McVersion(CurrentVersion.InheritVersion) - GoTo NextVersion + If CurrentInstance.InheritName <> "" Then + CurrentInstance = New McInstance(CurrentInstance.InheritName) + GoTo NextInstance End If '内存、Log4j 防御参数等 @@ -1529,12 +1526,12 @@ NextVersion: '统一通行证 If McLoginLoader.Output.Type = "Nide" Then - DataList.Insert(0, "-javaagent:""" & PathAppdata & "nide8auth.jar""=" & Setup.Get("VersionServerNide", Version:=McVersionCurrent)) + DataList.Insert(0, "-javaagent:""" & PathAppdata & "nide8auth.jar""=" & Setup.Get("VersionServerNide", Instance:=McInstanceSelected)) End If 'Authlib-Injector If McLoginLoader.Output.Type = "Auth" Then - If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#) - Dim Server As String = Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent) + If McLaunchJavaSelected.MajorVersion >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#) + Dim Server As String = Setup.Get("VersionServerAuthServer", Instance:=McInstanceSelected) Try Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8) DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server & @@ -1546,8 +1543,8 @@ NextVersion: End If '添加 Java Wrapper 作为主 Jar - If Not Setup.Get("LaunchAdvanceDisableJLW") AndAlso Not Setup.Get("VersionAdvanceDisableJLW", McVersionCurrent) Then - If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED") + If Not Setup.Get("LaunchAdvanceDisableJLW") AndAlso Not Setup.Get("VersionAdvanceDisableJLW", McInstanceSelected) Then + If McLaunchJavaSelected.MajorVersion >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED") DataList.Add("-Doolloo.jlw.tmpdir=""" & PathPure.TrimEnd("\") & """") DataList.Add("-jar """ & ExtractJavaWrapper() & """") End If @@ -1575,28 +1572,28 @@ NextVersion: Dim Result As String = DeDuplicateDataList.Distinct.Join(" ") '添加 MainClass - If Version.JsonObject("mainClass") Is Nothing Then + If Instance.JsonObject("mainClass") Is Nothing Then Throw New Exception("版本 json 中没有 mainClass 项!") Else - Result += " " & Version.JsonObject("mainClass").ToString + Result += " " & Instance.JsonObject("mainClass").ToString End If Return Result End Function 'Game 部分(第二段) - Private Function McLaunchArgumentsGameOld(Version As McVersion) As String + Private Function McLaunchArgumentsGameOld(Instance As McInstance) As String Dim DataList As New List(Of String) '本地化 Minecraft 启动信息 - Dim BasicString As String = Version.JsonObject("minecraftArguments").ToString + Dim BasicString As String = Instance.JsonObject("minecraftArguments").ToString If Not BasicString.Contains("--height") Then BasicString += " --height ${resolution_height} --width ${resolution_width}" DataList.Add(BasicString) Dim Result As String = DataList.Join(" ") '特别改变 OptiFineTweaker - If (Version.Version.HasForge OrElse Version.Version.HasLiteLoader) AndAlso Version.Version.HasOptiFine Then + If (Instance.Version.HasForge OrElse Instance.Version.HasLiteLoader) AndAlso Instance.Version.HasOptiFine Then '把 OptiFineForgeTweaker 放在最后,不然会导致崩溃! If Result.Contains("--tweakClass optifine.OptiFineForgeTweaker") Then Log("[Launch] 发现正确的 OptiFineForge TweakClass,目前参数:" & Result) @@ -1606,7 +1603,7 @@ NextVersion: Log("[Launch] 发现错误的 OptiFineForge TweakClass,目前参数:" & Result) Result = Result.Replace(" --tweakClass optifine.OptiFineTweaker", "").Replace("--tweakClass optifine.OptiFineTweaker ", "") & " --tweakClass optifine.OptiFineForgeTweaker" Try - WriteFile(Version.Path & Version.Name & ".json", ReadFile(Version.Path & Version.Name & ".json").Replace("optifine.OptiFineTweaker", "optifine.OptiFineForgeTweaker")) + WriteFile(Instance.PathVersion & Instance.Name & ".json", ReadFile(Instance.PathVersion & Instance.Name & ".json").Replace("optifine.OptiFineTweaker", "optifine.OptiFineForgeTweaker")) Catch ex As Exception Log(ex, "替换 OptiFineForge TweakClass 失败") End Try @@ -1615,14 +1612,14 @@ NextVersion: Return Result End Function - Private Function McLaunchArgumentsGameNew(Version As McVersion) As String + Private Function McLaunchArgumentsGameNew(Instance As McInstance) As String Dim DataList As New List(Of String) '获取 Json 中的 DataList - Dim CurrentVersion As McVersion = Version -NextVersion: - If CurrentVersion.JsonObject("arguments") IsNot Nothing AndAlso CurrentVersion.JsonObject("arguments")("game") IsNot Nothing Then - For Each SubJson As JToken In CurrentVersion.JsonObject("arguments")("game") + Dim CurrentInstance As McInstance = Instance +NextInstance: + If CurrentInstance.JsonObject("arguments") IsNot Nothing AndAlso CurrentInstance.JsonObject("arguments")("game") IsNot Nothing Then + For Each SubJson As JToken In CurrentInstance.JsonObject("arguments")("game") If SubJson.Type = JTokenType.String Then '字符串类型 DataList.Add(SubJson.ToString) @@ -1641,9 +1638,9 @@ NextVersion: End If Next End If - If CurrentVersion.InheritVersion <> "" Then - CurrentVersion = New McVersion(CurrentVersion.InheritVersion) - GoTo NextVersion + If CurrentInstance.InheritName <> "" Then + CurrentInstance = New McInstance(CurrentInstance.InheritName) + GoTo NextInstance End If '将 "-XXX" 与后面 "XXX" 合并到一起 @@ -1667,7 +1664,7 @@ NextVersion: McLaunchArgumentsGameNew = Join(DeDuplicateDataList.Distinct.ToList, " ") '特别改变 OptiFineTweaker - If (Version.Version.HasForge OrElse Version.Version.HasLiteLoader) AndAlso Version.Version.HasOptiFine Then + If (Instance.Version.HasForge OrElse Instance.Version.HasLiteLoader) AndAlso Instance.Version.HasOptiFine Then '把 OptiFineForgeTweaker 放在最后,不然会导致崩溃! If McLaunchArgumentsGameNew.Contains("--tweakClass optifine.OptiFineForgeTweaker") Then Log("[Launch] 发现正确的 OptiFineForge TweakClass,目前参数:" & McLaunchArgumentsGameNew) @@ -1677,7 +1674,7 @@ NextVersion: Log("[Launch] 发现错误的 OptiFineForge TweakClass,目前参数:" & McLaunchArgumentsGameNew) McLaunchArgumentsGameNew = McLaunchArgumentsGameNew.Replace(" --tweakClass optifine.OptiFineTweaker", "").Replace("--tweakClass optifine.OptiFineTweaker ", "") & " --tweakClass optifine.OptiFineForgeTweaker" Try - WriteFile(Version.Path & Version.Name & ".json", ReadFile(Version.Path & Version.Name & ".json").Replace("optifine.OptiFineTweaker", "optifine.OptiFineForgeTweaker")) + WriteFile(Instance.PathVersion & Instance.Name & ".json", ReadFile(Instance.PathVersion & Instance.Name & ".json").Replace("optifine.OptiFineTweaker", "optifine.OptiFineForgeTweaker")) Catch ex As Exception Log(ex, "替换 OptiFineForge TweakClass 失败") End Try @@ -1686,21 +1683,21 @@ NextVersion: End Function '替换 Arguments - Private Function McLaunchArgumentsReplace(Version As McVersion, ByRef Loader As LoaderTask(Of String, List(Of McLibToken))) As Dictionary(Of String, String) + Private Function McLaunchArgumentsReplace(Instance As McInstance, ByRef Loader As LoaderTask(Of String, List(Of McLibToken))) As Dictionary(Of String, String) Dim GameArguments As New Dictionary(Of String, String) '基础参数 GameArguments.Add("${classpath_separator}", ";") GameArguments.Add("${natives_directory}", ShortenPath(GetNativesFolder())) - GameArguments.Add("${library_directory}", ShortenPath(PathMcFolder & "libraries")) - GameArguments.Add("${libraries_directory}", ShortenPath(PathMcFolder & "libraries")) + GameArguments.Add("${library_directory}", ShortenPath(McFolderSelected & "libraries")) + GameArguments.Add("${libraries_directory}", ShortenPath(McFolderSelected & "libraries")) GameArguments.Add("${launcher_name}", "PCL") GameArguments.Add("${launcher_version}", VersionCode) - GameArguments.Add("${version_name}", Version.Name) - Dim ArgumentInfo As String = Setup.Get("VersionArgumentInfo", Version:=McVersionCurrent) + GameArguments.Add("${version_name}", Instance.Name) + Dim ArgumentInfo As String = Setup.Get("VersionArgumentInfo", Instance:=McInstanceSelected) GameArguments.Add("${version_type}", ArgumentReplace(If(ArgumentInfo = "", Setup.Get("LaunchArgumentInfo"), ArgumentInfo))) - GameArguments.Add("${game_directory}", ShortenPath(Left(McVersionCurrent.PathIndie, McVersionCurrent.PathIndie.Count - 1))) - GameArguments.Add("${assets_root}", ShortenPath(PathMcFolder & "assets")) + GameArguments.Add("${game_directory}", ShortenPath(Left(McInstanceSelected.PathIndie, McInstanceSelected.PathIndie.Count - 1))) + GameArguments.Add("${assets_root}", ShortenPath(McFolderSelected & "assets")) GameArguments.Add("${user_properties}", "{}") GameArguments.Add("${auth_player_name}", McLoginLoader.Output.Name) GameArguments.Add("${auth_uuid}", McLoginLoader.Output.Uuid) @@ -1708,7 +1705,7 @@ NextVersion: GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken) GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken) GameArguments.Add("${user_type}", "msa") '#1221 - GameArguments.Add("${primary_jar}", ShortenPath(Version.Path & Version.Name & ".jar")) '#6942 + GameArguments.Add("${primary_jar}", ShortenPath(Instance.PathVersion & Instance.Name & ".jar")) '#6942 '窗口尺寸参数 Dim GameSize As Size @@ -1723,10 +1720,10 @@ NextVersion: Case Else GameSize = New Size(854, 480) End Select - If McVersionCurrent.Version.McCodeMain <= 12 AndAlso - McLaunchJavaSelected.VersionCode <= 8 AndAlso McLaunchJavaSelected.Version.Revision >= 200 AndAlso McLaunchJavaSelected.Version.Revision <= 321 AndAlso - Not McVersionCurrent.Version.HasOptiFine AndAlso Not McVersionCurrent.Version.HasForge Then - '修复 #3463:1.12.2-,JRE 8u200~321 下窗口大小为设置大小的 DPI% 倍 + '修复 1.12.2- 下 JRE 8u200~321 下窗口大小为设置大小的 DPI% 倍(#3463) + If McInstanceSelected.Version.Vanilla.Major <= 12 AndAlso + McLaunchJavaSelected.MajorVersion <= 8 AndAlso McLaunchJavaSelected.Version.Revision >= 200 AndAlso McLaunchJavaSelected.Version.Revision <= 321 AndAlso + Not McInstanceSelected.Version.HasOptiFine AndAlso Not McInstanceSelected.Version.HasForge Then McLaunchLog($"已应用窗口大小过大修复({McLaunchJavaSelected.Version.Revision})") GameSize.Width /= DPI / 96 GameSize.Height /= DPI / 96 @@ -1735,11 +1732,11 @@ NextVersion: GameArguments.Add("${resolution_height}", Math.Round(GameSize.Height)) 'Assets 相关参数 - GameArguments.Add("${game_assets}", ShortenPath(PathMcFolder & "assets\virtual\legacy")) '1.5.2 的 pre-1.6 资源索引应与 legacy 合并 - GameArguments.Add("${assets_index_name}", McAssetsGetIndexName(Version)) + GameArguments.Add("${game_assets}", ShortenPath(McFolderSelected & "assets\virtual\legacy")) '1.5.2 的 pre-1.6 资源索引应与 legacy 合并 + GameArguments.Add("${assets_index_name}", McAssetsGetIndexName(Instance)) '支持库参数 - Dim LibList As List(Of McLibToken) = McLibListGet(Version, True) + Dim LibList As List(Of McLibToken) = McLibListGet(Instance, True) Loader.Output = LibList Dim CpStrings As New List(Of String) Dim OptiFineCp As String = Nothing @@ -1827,7 +1824,7 @@ NextVersion: ''' 获取 Natives 文件夹路径,不以 \ 结尾。 ''' Private Function GetNativesFolder() As String - Dim Result As String = McVersionCurrent.Path & McVersionCurrent.Name & "-natives" + Dim Result As String = McInstanceSelected.PathVersion & McInstanceSelected.Name & "-natives" If Not (IsGBKEncoding OrElse Result.IsASCII()) Then Result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\bin\natives" If Not Result.IsASCII() Then @@ -1844,7 +1841,7 @@ NextVersion: Private Sub McLaunchPrerun() - '要求 Java 使用高性能显卡 + '使用高性能显卡 If Setup.Get("LaunchAdvanceGraphicCard") Then Try SetGPUPreference(McLaunchJavaSelected.PathJava) @@ -1871,7 +1868,7 @@ NextVersion: Try '确保可用 If Not McLoginLoader.Output.Type = "Microsoft" Then Exit Try - McFolderLauncherProfilesJsonCreate(PathMcFolder) + McFolderLauncherProfilesJsonCreate(McFolderSelected) '构建需要替换的 Json 对象 Dim ReplaceJsonString As String = " { @@ -1893,15 +1890,15 @@ NextVersion: }" Dim ReplaceJson As JObject = GetJson(ReplaceJsonString) '更新文件 - Dim Profiles As JObject = GetJson(ReadFile(PathMcFolder & "launcher_profiles.json")) + Dim Profiles As JObject = GetJson(ReadFile(McFolderSelected & "launcher_profiles.json")) Profiles.Merge(ReplaceJson) - WriteFile(PathMcFolder & "launcher_profiles.json", Profiles.ToString, Encoding:=Encoding.GetEncoding("GB18030")) + WriteFile(McFolderSelected & "launcher_profiles.json", Profiles.ToString, Encoding:=Encoding.GetEncoding("GB18030")) McLaunchLog("已更新 launcher_profiles.json") Catch ex As Exception Log(ex, "更新 launcher_profiles.json 失败,将在删除文件后重试") Try - File.Delete(PathMcFolder & "launcher_profiles.json") - McFolderLauncherProfilesJsonCreate(PathMcFolder) + File.Delete(McFolderSelected & "launcher_profiles.json") + McFolderLauncherProfilesJsonCreate(McFolderSelected) '构建需要替换的 Json 对象 Dim ReplaceJsonString As String = " { @@ -1923,9 +1920,9 @@ NextVersion: }" Dim ReplaceJson As JObject = GetJson(ReplaceJsonString) '更新文件 - Dim Profiles As JObject = GetJson(ReadFile(PathMcFolder & "launcher_profiles.json")) + Dim Profiles As JObject = GetJson(ReadFile(McFolderSelected & "launcher_profiles.json")) Profiles.Merge(ReplaceJson) - WriteFile(PathMcFolder & "launcher_profiles.json", Profiles.ToString, Encoding:=Encoding.GetEncoding("GB18030")) + WriteFile(McFolderSelected & "launcher_profiles.json", Profiles.ToString, Encoding:=Encoding.GetEncoding("GB18030")) McLaunchLog("已在删除后更新 launcher_profiles.json") Catch exx As Exception Log(exx, "更新 launcher_profiles.json 失败", LogLevel.Feedback) @@ -1933,10 +1930,10 @@ NextVersion: End Try '更新 options.txt - Dim SetupFileAddress As String = McVersionCurrent.PathIndie & "options.txt" + Dim SetupFileAddress As String = McInstanceSelected.PathIndie & "options.txt" If Not File.Exists(SetupFileAddress) Then 'Yosbr Mod 兼容(#2385):https://www.curseforge.com/minecraft/mc-mods/yosbr - Dim YosbrFileAddress As String = McVersionCurrent.PathIndie & "config\yosbr\options.txt" + Dim YosbrFileAddress As String = McInstanceSelected.PathIndie & "config\yosbr\options.txt" If File.Exists(YosbrFileAddress) Then McLaunchLog("将修改 Yosbr Mod 中的 options.txt") SetupFileAddress = YosbrFileAddress @@ -1951,9 +1948,9 @@ NextVersion: '1.11 ~ 12:zh_cn 时正常,zh_CN 时虽然显示了中文但语言设置会错误地显示选择英文 '1.13+ :zh_cn 时正常,zh_CN 时自动切换为英文 Dim CurrentLang As String = ReadIni(SetupFileAddress, "lang", "none") - Dim RequiredLang As String = If(CurrentLang = "none" OrElse Not Directory.Exists(McVersionCurrent.PathIndie & "saves"), '#3844,整合包可能已经自带了 options.txt + Dim RequiredLang As String = If(CurrentLang = "none" OrElse Not Directory.Exists(McInstanceSelected.PathIndie & "saves"), '#3844,整合包可能已经自带了 options.txt If(Setup.Get("ToolHelpChinese"), "zh_cn", "en_us"), CurrentLang.ToLower) - If McVersionCurrent.Version.McCodeMain < 12 Then '注意老版本(包含 MC 1.1)的 McCodeMain 可能为 -1 + If Not McInstanceSelected.Version.Vaild OrElse McInstanceSelected.Version.Vanilla.Major <= 10 Then '将最后两位改为大写,前面的部分保留 RequiredLang = RequiredLang.Substring(0, RequiredLang.Length - 2) & RequiredLang.Substring(RequiredLang.Length - 2).ToUpper End If @@ -1965,7 +1962,7 @@ NextVersion: McLaunchLog($"已将语言从 {CurrentLang} 修改为 {RequiredLang}") End If ''如果是初次设置,一并修改 forceUnicodeFont - 'If Setup.Get("ToolHelpChinese") AndAlso (CurrentLang = "none" OrElse Not Directory.Exists(McVersionCurrent.PathIndie & "saves")) Then + 'If Setup.Get("ToolHelpChinese") AndAlso (CurrentLang = "none" OrElse Not Directory.Exists(McInstanceSelected.PathIndie & "saves")) Then ' WriteIni(SetupFileAddress, "forceUnicodeFont", "true") ' McLaunchLog("已开启 forceUnicodeFont") 'End If @@ -1982,7 +1979,7 @@ NextVersion: End Try '离线皮肤 Alex 警告 - If McVersionCurrent.Version.McCodeMain <= 7 AndAlso McVersionCurrent.Version.McCodeMain >= 2 AndAlso '1.2 ~ 1.7 + If McInstanceSelected.Version.Vanilla.Major <= 7 AndAlso McInstanceSelected.Version.Vanilla.Major >= 2 AndAlso '1.2 ~ 1.7 McLoginLoader.Input.Type = McLoginType.Legacy AndAlso '离线登录 (Setup.Get("LaunchSkinType") = 2 OrElse '强制 Alex (Setup.Get("LaunchSkinType") = 4 AndAlso Setup.Get("LaunchSkinSlim"))) Then '或选用 Alex 皮肤 @@ -1991,17 +1988,17 @@ NextVersion: '离线皮肤资源包 Try - Directory.CreateDirectory(McVersionCurrent.PathIndie & "resourcepacks\") - Dim ZipFileAddress As String = McVersionCurrent.PathIndie & "resourcepacks\PCL2 Skin.zip" - Dim NewTypeSetup As Boolean = McVersionCurrent.Version.McCodeMain >= 13 OrElse McVersionCurrent.Version.McCodeMain < 6 + Directory.CreateDirectory(McInstanceSelected.PathIndie & "resourcepacks\") + Dim ZipFileAddress As String = McInstanceSelected.PathIndie & "resourcepacks\PCL2 Skin.zip" + Dim NewTypeSetup As Boolean = McInstanceSelected.Version.Vanilla.Major >= 13 OrElse McInstanceSelected.Version.Vanilla.Major < 6 If McLoginLoader.Input.Type = McLoginType.Legacy AndAlso Setup.Get("LaunchSkinType") = 4 AndAlso File.Exists(PathAppdata & "CustomSkin.png") Then Directory.CreateDirectory(PathTemp) Dim MetaFileAddress As String = PathTemp & "pack.mcmeta" Dim PackPicAddress As String = PathTemp & "pack.png" Dim PackFormat As Integer - Select Case McVersionCurrent.Version.McCodeMain + Select Case McInstanceSelected.Version.Vanilla.Major Case 0, 1, 2, 3, 4, 5 - '更早的版本没有资源包;如果判断失败该值为 -1,不会跑到这 + '更早的版本没有资源包 McLaunchLog("Minecraft 版本过老,尚不支持自定义离线皮肤") GoTo IgnoreCustomSkin Case 6, 7, 8 @@ -2019,24 +2016,24 @@ NextVersion: Case 17 PackFormat = 7 Case 18 - If McVersionCurrent.Version.McCodeSub <= 2 Then + If McInstanceSelected.Version.Vanilla.Minor <= 2 Then PackFormat = 8 Else PackFormat = 9 End If Case 19 - If McVersionCurrent.Version.McCodeSub <= 3 Then + If McInstanceSelected.Version.Vanilla.Minor <= 3 Then PackFormat = 9 Else PackFormat = 12 End If Case 20 - If McVersionCurrent.Version.McCodeSub <= 1 Then + If McInstanceSelected.Version.Vanilla.Minor <= 1 Then PackFormat = 15 Else PackFormat = 17 End If - Case Else '快照版是 99 + Case Else '快照版是 9999 PackFormat = 17 'https://zh.minecraft.wiki/w/数据包#数据包版本 End Select @@ -2046,7 +2043,7 @@ NextVersion: Bit.Save(PackPicAddress) WriteFile(MetaFileAddress, "{""pack"":{""pack_format"":" & PackFormat & ",""description"":""PCL 自定义离线皮肤资源包""}}") Dim Skin As New MyBitmap(PathAppdata & "CustomSkin.png") - If (McVersionCurrent.Version.McCodeMain = 6 OrElse McVersionCurrent.Version.McCodeMain = 7) AndAlso Skin.Pic.Height = 64 Then + If (McInstanceSelected.Version.Vanilla.Major = 6 OrElse McInstanceSelected.Version.Vanilla.Major = 7) AndAlso Skin.Pic.Height = 64 Then McLaunchLog("该 Minecraft 版本不支持双层皮肤,已进行裁剪") Skin = Skin.Clip(0, 0, 64, 32) End If @@ -2057,22 +2054,13 @@ NextVersion: ZipAr.CreateEntryFromFile(MetaFileAddress, "pack.mcmeta") ZipAr.CreateEntryFromFile(PackPicAddress, "pack.png") '1.19.3+ 使用复杂版本的替换 - Dim IsOldType As Boolean - Select Case McVersionCurrent.Version.McCodeMain - Case Is < 19 - IsOldType = True - Case 19 - IsOldType = McVersionCurrent.Version.McCodeSub <= 2 - Case Else - IsOldType = False - End Select - If IsOldType Then - ZipAr.CreateEntryFromFile(Path & "PCL\CustomSkin_Cliped.png", "assets/minecraft/textures/entity/" & If(Setup.Get("LaunchSkinSlim"), "alex.png", "steve.png")) - Else + If McInstanceSelected.Version.Vanilla >= New Version(19, 0, 3) Then For Each SkinName In {"alex", "ari", "efe", "kai", "makena", "noor", "steve", "sunny", "zuri"} ZipAr.CreateEntryFromFile(Path & "PCL\CustomSkin_Cliped.png", $"assets/minecraft/textures/entity/player/{If(Setup.Get("LaunchSkinSlim"), "slim", "wide")}/{SkinName}.png") Next + Else + ZipAr.CreateEntryFromFile(Path & "PCL\CustomSkin_Cliped.png", "assets/minecraft/textures/entity/" & If(Setup.Get("LaunchSkinSlim"), "alex.png", "steve.png")) End If End Using End Using @@ -2131,24 +2119,24 @@ IgnoreCustomSkin: '获取自定义命令 Dim CustomCommandGlobal As String = Setup.Get("LaunchAdvanceRun") If CustomCommandGlobal <> "" Then CustomCommandGlobal = ArgumentReplace(CustomCommandGlobal) - Dim CustomCommandVersion As String = Setup.Get("VersionAdvanceRun", Version:=McVersionCurrent) + Dim CustomCommandVersion As String = Setup.Get("VersionAdvanceRun", Instance:=McInstanceSelected) If CustomCommandVersion <> "" Then CustomCommandVersion = ArgumentReplace(CustomCommandVersion) '输出 bat Try Dim CmdString As String = - $"{If(McLaunchJavaSelected.VersionCode > 8, "chcp 65001>nul" & vbCrLf, "")}" & + $"{If(McLaunchJavaSelected.MajorVersion > 8, "chcp 65001>nul" & vbCrLf, "")}" & "@echo off" & vbCrLf & - $"title 启动 - {McVersionCurrent.Name}" & vbCrLf & + $"title 启动 - {McInstanceSelected.Name}" & vbCrLf & "echo 游戏正在启动,请稍候。" & vbCrLf & - $"cd /D ""{ShortenPath(McVersionCurrent.PathIndie)}""" & vbCrLf & + $"cd /D ""{ShortenPath(McInstanceSelected.PathIndie)}""" & vbCrLf & CustomCommandGlobal & vbCrLf & CustomCommandVersion & vbCrLf & $"""{McLaunchJavaSelected.PathJava}"" {McLaunchArgument}" & vbCrLf & "echo 游戏已退出。" & vbCrLf & "pause" WriteFile(If(CurrentLaunchOptions.SaveBatch, Path & "PCL\LatestLaunch.bat"), FilterAccessToken(CmdString, "F"), - Encoding:=If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Encoding.Default)) + Encoding:=If(McLaunchJavaSelected.MajorVersion > 8, Encoding.UTF8, Encoding.Default)) If CurrentLaunchOptions.SaveBatch IsNot Nothing Then McLaunchLog("导出启动脚本完成,强制结束启动过程") AbortHint = "导出启动脚本成功!" @@ -2169,7 +2157,7 @@ IgnoreCustomSkin: CustomProcess = StartProcess(New ProcessStartInfo With { .FileName = "cmd.exe", .Arguments = $"/c ""{CustomCommandGlobal}""", - .WorkingDirectory = PathMcFolder, + .WorkingDirectory = McFolderSelected, .UseShellExecute = False, .CreateNoWindow = True }) @@ -2194,11 +2182,11 @@ IgnoreCustomSkin: CustomProcess = StartProcess(New ProcessStartInfo With { .FileName = "cmd.exe", .Arguments = $"/c ""{CustomCommandVersion}""", - .WorkingDirectory = PathMcFolder, + .WorkingDirectory = McFolderSelected, .UseShellExecute = False, .CreateNoWindow = True }) - If Setup.Get("VersionAdvanceRunWait", Version:=McVersionCurrent) Then + If Setup.Get("VersionAdvanceRunWait", Instance:=McInstanceSelected) Then Do Until CustomProcess.HasExited OrElse Loader.IsAborted Thread.Sleep(10) Loop @@ -2225,10 +2213,10 @@ IgnoreCustomSkin: If Not String.IsNullOrEmpty(PathEnv) Then Paths.AddRange(PathEnv.Split(";")) Paths.Add(ShortenPath(McLaunchJavaSelected.PathFolder)) StartInfo.EnvironmentVariables("Path") = Join(Paths.Distinct.ToList, ";") - StartInfo.EnvironmentVariables("appdata") = ShortenPath(PathMcFolder) + StartInfo.EnvironmentVariables("appdata") = ShortenPath(McFolderSelected) '设置其他参数 - StartInfo.WorkingDirectory = McVersionCurrent.PathIndie + StartInfo.WorkingDirectory = McInstanceSelected.PathIndie StartInfo.UseShellExecute = False StartInfo.RedirectStandardOutput = True StartInfo.RedirectStandardError = True @@ -2267,14 +2255,14 @@ IgnoreCustomSkin: McLaunchLog("") McLaunchLog("~ 基础参数 ~") McLaunchLog("PCL 版本:" & VersionDisplayName & " (" & VersionCode & ")") - McLaunchLog("游戏版本:" & McVersionCurrent.Version.ToString & "(识别为 1." & McVersionCurrent.Version.McCodeMain & "." & McVersionCurrent.Version.McCodeSub & ")") - McLaunchLog("资源版本:" & McAssetsGetIndexName(McVersionCurrent)) - McLaunchLog("版本继承:" & If(McVersionCurrent.InheritVersion = "", "无", McVersionCurrent.InheritVersion)) - McLaunchLog("分配的内存:" & PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) & " GB(" & Math.Round(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024) & " MB)") - McLaunchLog("MC 文件夹:" & PathMcFolder) - McLaunchLog("版本文件夹:" & McVersionCurrent.Path) - McLaunchLog("版本隔离:" & (McVersionCurrent.PathIndie = McVersionCurrent.Path)) - McLaunchLog("HMCL 格式:" & McVersionCurrent.IsHmclFormatJson) + McLaunchLog($"游戏版本:{McInstanceSelected.VersionDisplayName}({McInstanceSelected.Version.Vanilla},Drop {McInstanceSelected.Version.Drop}{If(McInstanceSelected.Version.Reliable, "", ",无法完全确定")})") + McLaunchLog("资源版本:" & McAssetsGetIndexName(McInstanceSelected)) + McLaunchLog("版本继承:" & If(McInstanceSelected.InheritName = "", "无", McInstanceSelected.InheritName)) + McLaunchLog("分配的内存:" & PageInstanceSetup.GetRam(McInstanceSelected, Not McLaunchJavaSelected.Is64Bit) & " GB(" & Math.Round(PageInstanceSetup.GetRam(McInstanceSelected, Not McLaunchJavaSelected.Is64Bit) * 1024) & " MB)") + McLaunchLog("MC 文件夹:" & McFolderSelected) + McLaunchLog("版本文件夹:" & McInstanceSelected.PathVersion) + McLaunchLog("版本隔离:" & (McInstanceSelected.PathIndie = McInstanceSelected.PathVersion)) + McLaunchLog("HMCL 格式:" & McInstanceSelected.IsHmclFormatJson) McLaunchLog("Java 信息:" & If(McLaunchJavaSelected IsNot Nothing, McLaunchJavaSelected.ToString, "无可用 Java")) McLaunchLog("环境变量:" & If(McLaunchJavaSelected IsNot Nothing, If(McLaunchJavaSelected.HasEnvironment, "已设置", "未设置"), "未设置")) McLaunchLog("Natives 文件夹:" & GetNativesFolder()) @@ -2288,12 +2276,12 @@ IgnoreCustomSkin: McLaunchLog("") '获取窗口标题 - Dim WindowTitle As String = Setup.Get("VersionArgumentTitle", Version:=McVersionCurrent) + Dim WindowTitle As String = Setup.Get("VersionArgumentTitle", Instance:=McInstanceSelected) If WindowTitle = "" Then WindowTitle = Setup.Get("LaunchArgumentTitle") WindowTitle = ArgumentReplace(WindowTitle, ReplaceTime:=False) '初始化等待 - Dim Watcher As New Watcher(Loader, McVersionCurrent, WindowTitle) + Dim Watcher As New Watcher(Loader, McInstanceSelected, WindowTitle) McLaunchWatcher = Watcher '等待 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb index 00f2b11..45cd93f 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb @@ -7,30 +7,35 @@ Public Module ModMinecraft ''' ''' 当前的 Minecraft 文件夹路径,以“\”结尾。 ''' - Public PathMcFolder As String + Public McFolderSelected As String ''' ''' 当前的 Minecraft 文件夹列表。 ''' Public McFolderList As New List(Of McFolder) - Public Class McFolder '必须是 Class,否则不是引用类型,在 ForEach 中不会得到刷新 + Public Class McFolder Public Name As String - Public Path As String - Public Type As McFolderType + ''' + ''' 文件夹路径。 + ''' 以 \ 结尾,例如 "D:\Game\MC\.minecraft\"。 + ''' + Public Location As String + Public Type As Types + Public Enum Types + Original + RenamedOriginal + Custom + End Enum + Public Overrides Function Equals(obj As Object) As Boolean If Not (TypeOf obj Is McFolder) Then Return False Dim folder = DirectCast(obj, McFolder) - Return Name = folder.Name AndAlso Path = folder.Path AndAlso Type = folder.Type + Return Name = folder.Name AndAlso Location = folder.Location AndAlso Type = folder.Type End Function Public Overrides Function ToString() As String - Return Path + Return Location End Function End Class - Public Enum McFolderType - Original - RenamedOriginal - Custom - End Enum ''' ''' 加载 Minecraft 文件夹列表。 @@ -45,9 +50,9 @@ Public Module ModMinecraft '扫描当前文件夹 Try - If Directory.Exists(Path & "versions\") Then CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Path = Path, .Type = McFolderType.Original}) + If Directory.Exists(Path & "versions\") Then CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Location = Path, .Type = McFolder.Types.Original}) For Each Folder As DirectoryInfo In New DirectoryInfo(Path).GetDirectories - If Directory.Exists(Folder.FullName & "versions\") OrElse Folder.Name = ".minecraft" Then CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Path = Folder.FullName & "\", .Type = McFolderType.Original}) + If Directory.Exists(Folder.FullName & "versions\") OrElse Folder.Name = ".minecraft" Then CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Location = Folder.FullName & "\", .Type = McFolder.Types.Original}) Next Catch ex As Exception Log(ex, "扫描 PCL 所在文件夹中是否有 MC 文件夹失败") @@ -55,9 +60,9 @@ Public Module ModMinecraft '扫描官启文件夹 Dim MojangPath As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\" - If (Not CacheMcFolderList.Any OrElse MojangPath <> CacheMcFolderList(0).Path) AndAlso '当前文件夹不是官启文件夹 + If (Not CacheMcFolderList.Any OrElse MojangPath <> CacheMcFolderList(0).Location) AndAlso '当前文件夹不是官启文件夹 Directory.Exists(MojangPath & "versions\") Then '具有权限且存在 versions 文件夹 - CacheMcFolderList.Add(New McFolder With {.Name = "官方启动器文件夹", .Path = MojangPath, .Type = McFolderType.Original}) + CacheMcFolderList.Add(New McFolder With {.Name = "官方启动器文件夹", .Location = MojangPath, .Type = McFolder.Types.Original}) End If #End Region @@ -78,13 +83,13 @@ Public Module ModMinecraft '若已有该文件夹,则直接重命名;没有则添加 Dim Renamed As Boolean = False For Each OriginalFolder As McFolder In CacheMcFolderList - If OriginalFolder.Path = Path Then + If OriginalFolder.Location = Path Then OriginalFolder.Name = Name - OriginalFolder.Type = McFolderType.RenamedOriginal + OriginalFolder.Type = McFolder.Types.RenamedOriginal Renamed = True End If Next - If Not Renamed Then CacheMcFolderList.Add(New McFolder With {.Name = Name, .Path = Path, .Type = McFolderType.Custom}) + If Not Renamed Then CacheMcFolderList.Add(New McFolder With {.Name = Name, .Location = Path, .Type = McFolder.Types.Custom}) Catch ex As Exception MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & ex.GetBrief(), "Minecraft 文件夹失效", IsWarn:=True) Log(ex, $"无法访问 Minecraft 文件夹 {Path}") @@ -94,7 +99,7 @@ Public Module ModMinecraft '将自定义文件夹情况同步到设置 Dim NewSetup As New List(Of String) For Each Folder As McFolder In CacheMcFolderList - If Not Folder.Type = McFolderType.Original Then NewSetup.Add(Folder.Name & ">" & Folder.Path) + If Not Folder.Type = McFolder.Types.Original Then NewSetup.Add(Folder.Name & ">" & Folder.Location) Next If Not NewSetup.Any() Then NewSetup.Add("") '防止 0 元素 Join 返回 Nothing Setup.Set("LaunchFolders", Join(NewSetup, "|")) @@ -104,12 +109,12 @@ Public Module ModMinecraft '若没有可用文件夹,则创建 .minecraft If Not CacheMcFolderList.Any() Then Directory.CreateDirectory(Path & ".minecraft\versions\") - CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Path = Path & ".minecraft\", .Type = McFolderType.Original}) + CacheMcFolderList.Add(New McFolder With {.Name = "当前文件夹", .Location = Path & ".minecraft\", .Type = McFolder.Types.Original}) End If For Each Folder As McFolder In CacheMcFolderList #Region "更新 launcher_profiles.json" - McFolderLauncherProfilesJsonCreate(Folder.Path) + McFolderLauncherProfilesJsonCreate(Folder.Location) #End Region Next If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(200, 2000)) @@ -153,79 +158,79 @@ Public Module ModMinecraft #Region "版本处理" - Public Const McVersionCacheVersion As Integer = 30 + Public Const McInstanceCacheVersion As Integer = 32 - Private _McVersionCurrent As McVersion - Private _McVersionLast = 0 '为 0 以保证与 Nothing 不相同,使得 UI 显示可以正常初始化 + Private _McInstanceSelected As McInstance ''' ''' 当前的 Minecraft 版本。 ''' - Public Property McVersionCurrent As McVersion + Public Property McInstanceSelected As McInstance Get - Return _McVersionCurrent + Return _McInstanceSelected End Get - Set(value As McVersion) - If ReferenceEquals(_McVersionLast, value) Then Return - _McVersionCurrent = value '由于有可能是 Nothing,导致无法初始化,才得这样弄一圈 - _McVersionLast = value + Set(value As McInstance) + Static _McInstanceSelectedLast = 0 '为 0 以保证与 Nothing 不相同,使得 UI 显示可以正常初始化 + If ReferenceEquals(_McInstanceSelectedLast, value) Then Return + _McInstanceSelected = value '由于有可能是 Nothing,导致无法初始化,才得这样弄一圈 + _McInstanceSelectedLast = value If value Is Nothing Then Return '重置缓存的下载文件夹 PageDownloadCompDetail.CachedFolder.Clear() '统一通行证重判 If AniControlEnabled = 0 AndAlso - Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso - Setup.Get("VersionServerLogin", Version:=value) = 3 Then + Setup.Get("VersionServerNide", Instance:=value) <> Setup.Get("CacheNideServer") AndAlso + Setup.Get("VersionServerLogin", Instance:=value) = 3 Then Setup.Set("CacheNideAccess", "") Log("[Launch] 服务器改变,要求重新登录统一通行证") End If - If Setup.Get("VersionServerLogin", Version:=value) = 3 Then - Setup.Set("CacheNideServer", Setup.Get("VersionServerNide", Version:=value)) + If Setup.Get("VersionServerLogin", Instance:=value) = 3 Then + Setup.Set("CacheNideServer", Setup.Get("VersionServerNide", Instance:=value)) End If 'Authlib-Injector 重判 If AniControlEnabled = 0 AndAlso - Setup.Get("VersionServerAuthServer", Version:=value) <> Setup.Get("CacheAuthServerServer") AndAlso - Setup.Get("VersionServerLogin", Version:=value) = 4 Then + Setup.Get("VersionServerAuthServer", Instance:=value) <> Setup.Get("CacheAuthServerServer") AndAlso + Setup.Get("VersionServerLogin", Instance:=value) = 4 Then Setup.Set("CacheAuthAccess", "") Log("[Launch] 服务器改变,要求重新登录 Authlib-Injector") End If - If Setup.Get("VersionServerLogin", Version:=value) = 4 Then - Setup.Set("CacheAuthServerServer", Setup.Get("VersionServerAuthServer", Version:=value)) - Setup.Set("CacheAuthServerName", Setup.Get("VersionServerAuthName", Version:=value)) - Setup.Set("CacheAuthServerRegister", Setup.Get("VersionServerAuthRegister", Version:=value)) + If Setup.Get("VersionServerLogin", Instance:=value) = 4 Then + Setup.Set("CacheAuthServerServer", Setup.Get("VersionServerAuthServer", Instance:=value)) + Setup.Set("CacheAuthServerName", Setup.Get("VersionServerAuthName", Instance:=value)) + Setup.Set("CacheAuthServerRegister", Setup.Get("VersionServerAuthRegister", Instance:=value)) End If End Set End Property - Public Class McVersion + Public Class McInstance ''' - ''' 该版本的版本文件夹,以“\”结尾。 + ''' 版本文件夹路径,以 \ 结尾。 ''' - Public ReadOnly Property Path As String + Public ReadOnly Property PathVersion As String ''' - ''' 应用版本隔离后,该版本所对应的 Minecraft 根文件夹,以“\”结尾。 + ''' 应用版本隔离后,该版本所对应的 Minecraft 根文件夹,以 \ 结尾。 ''' Public ReadOnly Property PathIndie As String Get - If Setup.IsUnset("VersionArgumentIndieV2", Version:=Me) Then + If Setup.IsUnset("VersionArgumentIndieV2", Instance:=Me) Then If Not IsLoaded Then Load() '决定该版本是否应该被隔离 Dim ShouldBeIndie = Function() As Boolean '从老的版本独立设置中迁移:-1 未决定,0 使用全局设置,1 手动开启,2 手动关闭 - If Not Setup.IsUnset("VersionArgumentIndie", Version:=Me) AndAlso Setup.Get("VersionArgumentIndie", Version:=Me) > 0 Then + If Not Setup.IsUnset("VersionArgumentIndie", Instance:=Me) AndAlso Setup.Get("VersionArgumentIndie", Instance:=Me) > 0 Then Log($"[Minecraft] 版本隔离初始化({Name}):从老的版本独立设置中迁移") - Return Setup.Get("VersionArgumentIndie", Version:=Me) = 1 + Return Setup.Get("VersionArgumentIndie", Instance:=Me) = 1 End If '若版本文件夹下包含 mods 或 saves 文件夹,则自动开启版本隔离 - Dim ModFolder As New DirectoryInfo(Path & "mods\") - Dim SaveFolder As New DirectoryInfo(Path & "saves\") + Dim ModFolder As New DirectoryInfo(PathVersion & "mods\") + Dim SaveFolder As New DirectoryInfo(PathVersion & "saves\") If (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Any) OrElse (SaveFolder.Exists AndAlso SaveFolder.EnumerateDirectories.Any) Then Log($"[Minecraft] 版本隔离初始化({Name}):版本文件夹下存在 mods 或 saves 文件夹,自动开启") Return True End If '根据全局的默认设置决定是否隔离 - Dim IsRelease As Boolean = State <> McVersionState.Fool AndAlso State <> McVersionState.Old AndAlso State <> McVersionState.Snapshot + Dim IsRelease As Boolean = State <> McInstanceState.Fool AndAlso State <> McInstanceState.Old AndAlso State <> McInstanceState.Snapshot Log($"[Minecraft] 版本隔离初始化({Name}):从全局默认设置中({Setup.Get("LaunchArgumentIndieV2")})判断,State {GetStringFromEnum(State)},IsRelease {IsRelease},Modable {Modable}") Select Case Setup.Get("LaunchArgumentIndieV2") Case 0 '关闭 @@ -240,9 +245,9 @@ Public Module ModMinecraft Return True End Select End Function - Setup.Set("VersionArgumentIndieV2", ShouldBeIndie(), Version:=Me) + Setup.Set("VersionArgumentIndieV2", ShouldBeIndie(), Instance:=Me) End If - Return If(Setup.Get("VersionArgumentIndieV2", Version:=Me), Path, PathMcFolder) + Return If(Setup.Get("VersionArgumentIndieV2", Instance:=Me), PathVersion, McFolderSelected) End Get End Property @@ -251,7 +256,7 @@ Public Module ModMinecraft ''' Public ReadOnly Property Name As String Get - If _Name Is Nothing AndAlso Not Path = "" Then _Name = GetFolderNameFromPath(Path) + If _Name Is Nothing AndAlso Not PathVersion = "" Then _Name = GetFolderNameFromPath(PathVersion) Return _Name End Get End Property @@ -261,16 +266,11 @@ Public Module ModMinecraft ''' 显示的描述文本。 ''' Public Info As String = "该版本未被加载,请向作者反馈此问题" - Public ReadOnly Property HasCustomInfo As Boolean - Get - Return ReadIni(Path & "PCL\Setup.ini", "CustomInfo") <> "" - End Get - End Property ''' ''' 该版本的列表检查原始结果,不受自定义影响。 ''' - Public State As McVersionState = McVersionState.Error + Public State As McInstanceState = McInstanceState.Error ''' ''' 显示的版本图标。 ''' @@ -282,164 +282,174 @@ Public Module ModMinecraft ''' ''' 强制版本分类,0 为未启用,1 为隐藏,2 及以上为其他普通分类。 ''' - Public DisplayType As McVersionCardType = McVersionCardType.Auto + Public DisplayType As McInstanceCardType = McInstanceCardType.Auto ''' ''' 该版本是否可以安装 Mod。 ''' Public ReadOnly Property Modable As Boolean Get If Not IsLoaded Then Load() - Return Version.HasFabric OrElse Version.HasForge OrElse Version.HasLiteLoader OrElse Version.HasNeoForge OrElse - DisplayType = McVersionCardType.API '#223 + Return Version.HasFabric OrElse Version.HasForgelike OrElse Version.HasLiteLoader OrElse + DisplayType = McInstanceCardType.API '#223 End Get End Property ''' ''' 版本信息。 ''' - Public Property Version As McVersionInfo + Public Property Version As McVersion Get - If _Version Is Nothing Then - _Version = New McVersionInfo + If _Version IsNot Nothing Then Return _Version + _Version = New McVersion #Region "获取游戏版本" + Try + '获取发布时间并判断是否为老版本 Try - - '获取发布时间并判断是否为老版本 - Try - If JsonObject("releaseTime") Is Nothing Then - ReleaseTime = New Date(1970, 1, 1, 15, 0, 0) '未知版本也可能显示为 1970 年 - Else - ReleaseTime = JsonObject("releaseTime").ToObject(Of Date) - End If - If ReleaseTime.Year > 2000 AndAlso ReleaseTime.Year < 2013 Then - _Version.McName = "Old" - GoTo VersionSearchFinish - End If - Catch - ReleaseTime = New Date(1970, 1, 1, 15, 0, 0) - End Try - '实验性快照 - If If(JsonObject("type"), "") = "pending" Then - _Version.McName = "pending" + If JsonObject("releaseTime") Is Nothing Then + ReleaseTime = New Date(1970, 1, 1, 15, 0, 0) '未知版本也可能显示为 1970 年 + Else + ReleaseTime = JsonObject("releaseTime").ToObject(Of Date) + End If + If ReleaseTime.Year > 2000 AndAlso ReleaseTime.Year < 2013 Then + _Version.VanillaName = "Old" GoTo VersionSearchFinish End If - '从 PCL 下载的版本信息中获取版本号 - If JsonObject("clientVersion") IsNot Nothing Then - _Version.McName = JsonObject("clientVersion") - GoTo VersionSearchFinish - End If - '从 HMCL 下载的版本信息中获取版本号 - If JsonObject("patches") IsNot Nothing Then - For Each Patch As JObject In JsonObject("patches") - If If(Patch("id"), "").ToString = "game" AndAlso Patch("version") IsNot Nothing Then - _Version.McName = Patch("version").ToString - GoTo VersionSearchFinish - End If - Next - End If - '从 Forge / NeoForge Arguments 中获取版本号 - If JsonObject("arguments") IsNot Nothing AndAlso JsonObject("arguments")("game") IsNot Nothing Then - Dim Mark As Boolean = False - For Each Argument In JsonObject("arguments")("game") - If Mark Then - _Version.McName = Argument.ToString - GoTo VersionSearchFinish - End If - If Argument.ToString = "--fml.mcVersion" Then Mark = True - Next - End If - '从继承版本中获取版本号 - If Not InheritVersion = "" Then - _Version.McName = If(JsonObject("jar"), "").ToString 'LiteLoader 优先使用 Jar - If _Version.McName = "" Then _Version.McName = InheritVersion - GoTo VersionSearchFinish - End If - '从下载地址中获取版本号 - Dim Regex As String = RegexSeek(If(JsonObject("downloads"), "").ToString, "(?<=launcher.mojang.com/mc/game/)[^/]*") - If Regex IsNot Nothing Then - _Version.McName = Regex - GoTo VersionSearchFinish - End If - '从 Forge 版本中获取版本号 - Dim LibrariesString As String = JsonObject("libraries").ToString - Regex = If(RegexSeek(LibrariesString, "(?<=net.minecraftforge:forge:)1.[0-9+.]+"), RegexSeek(LibrariesString, "(?<=net.minecraftforge:fmlloader:)1.[0-9+.]+")) - If Regex IsNot Nothing Then - _Version.McName = Regex - GoTo VersionSearchFinish - End If - '从 OptiFine 版本中获取版本号 - Regex = RegexSeek(LibrariesString, "(?<=optifine:OptiFine:)1.[0-9+.]+") - If Regex IsNot Nothing Then - _Version.McName = Regex - GoTo VersionSearchFinish - End If - '从 Fabric 版本中获取版本号 - Regex = RegexSeek(LibrariesString, "(?<=((fabricmc)|(quiltmc)):intermediary:)[^""]*") - If Regex IsNot Nothing Then - _Version.McName = Regex - GoTo VersionSearchFinish - End If - '从 jar 项中获取版本号 - If JsonObject("jar") IsNot Nothing Then - _Version.McName = JsonObject("jar").ToString - GoTo VersionSearchFinish - End If - '从 jar 文件的 version.json 中获取版本号 - If JsonVersion?("name") IsNot Nothing Then - Dim JsonVerName As String = JsonVersion("name").ToString - If JsonVerName.Length < 32 Then '因为 wiki 说这玩意儿可能是个 hash,虽然我没发现 - _Version.McName = JsonVerName - Log("[Minecraft] 从版本 jar 中的 version.json 获取到版本号:" & JsonVerName) - GoTo VersionSearchFinish - End If - End If - '非准确的版本判断警告 - Log("[Minecraft] 无法完全确认 MC 版本号的版本:" & Name) - '从文件夹名中获取 - Regex = RegexSeek(Name, "([0-9w]{5}[a-z]{1})|(1\.[0-9]+(\.[0-9]+)?(-(pre|rc)[1-9]?| Pre-Release( [1-9]{1})?)?)", RegularExpressions.RegexOptions.IgnoreCase) - If Regex IsNot Nothing Then - _Version.McName = Regex - GoTo VersionSearchFinish - End If - '从 Json 出现的版本号中获取 - Dim JsonRaw As JObject = JsonObject.DeepClone() - JsonRaw.Remove("libraries") - Dim JsonRawText As String = JsonRaw.ToString - Regex = RegexSeek(JsonRawText, "([0-9w]{5}[a-z]{1})|(1\.[0-9]+(\.[0-9]+)?(-(pre|rc)[1-9]?| Pre-Release( [1-9]{1})?)?)", RegularExpressions.RegexOptions.IgnoreCase) - If Regex IsNot Nothing Then - _Version.McName = Regex - GoTo VersionSearchFinish - End If - '无法获取 - _Version.McName = "Unknown" - Info = "PCL 无法识别该版本的 MC 版本号" - Catch ex As Exception - Log(ex, "识别 Minecraft 版本时出错") - _Version.McName = "Unknown" - Info = "无法识别:" & ex.Message + Catch + ReleaseTime = New Date(1970, 1, 1, 15, 0, 0) End Try -VersionSearchFinish: - '获取版本号 - If _Version.McName.StartsWithF("1.") Then - Dim SplitVersion = _Version.McName.Split(" "c, "_"c, "-"c, "."c) - Dim SplitResult As String - '分割获取信息 - SplitResult = If(SplitVersion.Count >= 2, SplitVersion(1), "0") - _Version.McCodeMain = If(SplitResult.Length <= 2, Val(SplitResult), "0") - SplitResult = If(SplitVersion.Count >= 3, SplitVersion(2), "0") - _Version.McCodeSub = If(SplitResult.Length <= 2, Val(SplitResult), "0") - ElseIf (Not IsVersionNameLikeRelease(_Version.McName)) OrElse _Version.McName = "pending" Then - _Version.McCodeMain = 99 - _Version.McCodeSub = 99 + '实验性快照 + If If(JsonObject("type"), "") = "pending" Then + _Version.VanillaName = "pending" + GoTo VersionSearchFinish End If + '从 PCL 下载的版本信息中获取版本号 + If JsonObject("clientVersion") IsNot Nothing Then + _Version.VanillaName = JsonObject("clientVersion") + GoTo VersionSearchFinish + End If + '从 HMCL 下载的版本信息中获取版本号 + If JsonObject("patches") IsNot Nothing Then + For Each Patch As JObject In JsonObject("patches") + If If(Patch("id"), "").ToString = "game" AndAlso Patch("version") IsNot Nothing Then + _Version.VanillaName = Patch("version").ToString + GoTo VersionSearchFinish + End If + Next + End If + '从 Forge / NeoForge Arguments 中获取版本号 + If JsonObject("arguments") IsNot Nothing AndAlso JsonObject("arguments")("game") IsNot Nothing Then + Dim Mark As Boolean = False + For Each Argument In JsonObject("arguments")("game") + If Mark Then + _Version.VanillaName = Argument.ToString + GoTo VersionSearchFinish + End If + If Argument.ToString = "--fml.mcVersion" Then Mark = True + Next + End If + '从继承版本中获取版本号 + If Not InheritName = "" Then + _Version.VanillaName = If(JsonObject("jar"), "").ToString 'LiteLoader 优先使用 Jar + If _Version.VanillaName = "" Then _Version.VanillaName = InheritName + GoTo VersionSearchFinish + End If + '从下载地址中获取版本号 + Dim Regex As String = RegexSeek(If(JsonObject("downloads"), "").ToString, "(?<=launcher.mojang.com/mc/game/)[^/]*") + If Regex IsNot Nothing Then + _Version.VanillaName = Regex + GoTo VersionSearchFinish + End If + '从 Forge 版本中获取版本号 + Dim LibrariesString As String = JsonObject("libraries").ToString + Regex = If(RegexSeek(LibrariesString, "(?<=net.minecraftforge:forge:)[0-9]{1,2}.[0-9+.]+"), RegexSeek(LibrariesString, "(?<=net.minecraftforge:fmlloader:)[0-9]{1,2}.[0-9+.]+")) + If Regex IsNot Nothing Then + _Version.VanillaName = Regex + GoTo VersionSearchFinish + End If + '从 OptiFine 版本中获取版本号 + Regex = RegexSeek(LibrariesString, "(?<=optifine:OptiFine:)[0-9]{1,2}.[0-9+.]+") + If Regex IsNot Nothing Then + _Version.VanillaName = Regex + GoTo VersionSearchFinish + End If + '从 Fabric 版本中获取版本号 + Regex = RegexSeek(LibrariesString, "(?<=((fabricmc)|(quiltmc)):intermediary:)[^""]*") + If Regex IsNot Nothing Then + _Version.VanillaName = Regex + GoTo VersionSearchFinish + End If + '从 jar 项中获取版本号 + If JsonObject("jar") IsNot Nothing Then + _Version.VanillaName = JsonObject("jar").ToString + GoTo VersionSearchFinish + End If + '从 jar 文件的 version.json 中获取版本号 + If JsonVersion?("name") IsNot Nothing Then + Dim JsonVerName As String = JsonVersion("name").ToString + If JsonVerName.Length < 32 Then '因为 wiki 说这玩意儿可能是个 hash,虽然我没发现 + _Version.VanillaName = JsonVerName + Log("[Minecraft] 从版本 jar 中的 version.json 获取到版本号:" & JsonVerName) + GoTo VersionSearchFinish + End If + End If + '从 JSON 的 ID 中获取 + Static Pattern = "(([1-9][0-9]w[0-9]{2}[a-g])|((1|[2-9][0-9])\.[0-9]+(\.[0-9]+)?(-(pre|rc|snapshot-?)[1-9]*| Pre-Release( [1-9])?)?))(_unobfuscated)?" + Regex = RegexSeek(JsonObject("id"), Pattern, RegularExpressions.RegexOptions.IgnoreCase) + If Regex IsNot Nothing Then + _Version.VanillaName = Regex + GoTo VersionSearchFinish + End If + '非准确的版本判断警告 + Log("[Minecraft] 无法完全确认 MC 版本号的版本:" & Name) + _Version.Reliable = False + '从文件夹名中获取 + Regex = RegexSeek(Name, Pattern, RegularExpressions.RegexOptions.IgnoreCase) + If Regex IsNot Nothing Then + _Version.VanillaName = Regex + GoTo VersionSearchFinish + End If + '从 JSON 出现的版本号中获取 + Dim JsonRaw As JObject = JsonObject.DeepClone() + JsonRaw.Remove("libraries") + Dim JsonRawText As String = JsonRaw.ToString + Regex = RegexSeek(JsonRawText, Pattern, RegularExpressions.RegexOptions.IgnoreCase) + If Regex IsNot Nothing Then + _Version.VanillaName = Regex + GoTo VersionSearchFinish + End If + '无法获取 + _Version.VanillaName = "Unknown" + Info = "PCL 无法识别该版本的 MC 版本号" + Catch ex As Exception + Log(ex, "识别 Minecraft 版本时出错") + _Version.VanillaName = "Unknown" + Info = "无法识别:" & ex.Message + End Try #End Region +VersionSearchFinish: + _Version.VanillaName = _Version.VanillaName.Replace("_unobfuscated", "").Replace(" Unobfuscated", "") + '获取版本号 + If _Version.VanillaName.StartsWithF("1.") Then + Dim Segments = _Version.VanillaName.Split(" _-.".ToCharArray) + _Version.Vanilla = New Version( + Val(If(Segments.Count >= 2, Segments(1), "0")), + 0, + Val(If(Segments.Count >= 3, Segments(2), "0"))) + ElseIf RegexCheck(_Version.VanillaName, "^[2-9][0-9]\.") Then + Dim Segments = _Version.VanillaName.Split(" _-.".ToCharArray) + _Version.Vanilla = New Version( + Val(Segments(0)), + Val(If(Segments.Count >= 2, Segments(1), "0")), + Val(If(Segments.Count >= 3, Segments(2), "0"))) + Else + _Version.Vanilla = New Version(9999, 0, 0) End If Return _Version End Get - Set(value As McVersionInfo) + Set(value As McVersion) _Version = value End Set End Property - Private _Version As McVersionInfo = Nothing + Private _Version As McVersion = Nothing ''' ''' 版本的发布时间。 @@ -450,11 +460,11 @@ VersionSearchFinish: ''' 获取该版本的 JSON 路径。 ''' Public Function GetJsonPath() As String - Dim JsonPath As String = Path & Name & ".json" + Dim JsonPath As String = PathVersion & Name & ".json" If Not File.Exists(JsonPath) Then '尝试寻找 JSON 文件 JsonPath = Nothing - For Each JsonCandidatePath In Directory.GetFiles(Path, "*.json") + For Each JsonCandidatePath In Directory.GetFiles(PathVersion, "*.json") Try Dim JsonCandidate As JObject = GetJson(ReadFile(JsonCandidatePath)) If Not JsonCandidate.ContainsKey("mainClass") Then Continue For @@ -466,7 +476,7 @@ VersionSearchFinish: Exit For Next If JsonPath Is Nothing Then - Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json") + Throw New Exception($"未找到版本 JSON 文件:{PathVersion}{Name}.json") Else Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug) End If @@ -527,7 +537,7 @@ VersionSearchFinish: For Each Subjson As JObject In _JsonObject("patches") SubjsonList.Add(Subjson) Next - SubjsonList = SubjsonList.Sort( + SubjsonList = SubjsonList.SortByComparison( Function(Left, Right) Val(If(Left("priority"), "0").ToString) < Val(If(Right("priority"), "0").ToString)) For Each Subjson As JObject In SubjsonList Dim Id As String = Subjson("id") @@ -549,33 +559,33 @@ VersionSearchFinish: If _JsonObject.ContainsKey("inheritsFrom") Then _JsonObject.Remove("inheritsFrom") End If '与继承版本合并 - Dim InheritVersion = Nothing + Dim InstanceNameInheritFrom = Nothing Try - InheritVersion = If(_JsonObject("inheritsFrom") Is Nothing, "", _JsonObject("inheritsFrom").ToString) - If InheritVersion = Name Then + InstanceNameInheritFrom = If(_JsonObject("inheritsFrom") Is Nothing, "", _JsonObject("inheritsFrom").ToString) + If InstanceNameInheritFrom = Name Then Log("[Minecraft] 自引用的继承版本:" & Name, LogLevel.Debug) - InheritVersion = "" + InstanceNameInheritFrom = "" Exit Try End If Recheck: - If InheritVersion <> "" Then - Dim Inherit As New McVersion(InheritVersion) + If InstanceNameInheritFrom <> "" Then + Dim InheritInstance As New McInstance(InstanceNameInheritFrom) '继续循环 - If Inherit.InheritVersion = InheritVersion Then Throw New Exception("版本依赖项出现嵌套:" & InheritVersion) - InheritVersion = Inherit.InheritVersion + If InheritInstance.InheritName = InstanceNameInheritFrom Then Throw New Exception("版本依赖项出现嵌套:" & InstanceNameInheritFrom) + InstanceNameInheritFrom = InheritInstance.InheritName '合并 Libraries 项:子版本放在前面,父版本放在后面(5978#:如果多个 jar 包中含有相同的类,Java 8 和之前的版本按照 -cp 指定的顺序选择第一个) Dim CurrentLib As JArray = _JsonObject("libraries").DeepClone() - For Each LibToken In Inherit.JsonObject("libraries") + For Each LibToken In InheritInstance.JsonObject("libraries") CurrentLib.Add(LibToken) Next '合并其他项:子版本优先于父版本 - Inherit.JsonObject.Merge(_JsonObject) - _JsonObject = Inherit.JsonObject + InheritInstance.JsonObject.Merge(_JsonObject) + _JsonObject = InheritInstance.JsonObject _JsonObject("libraries") = CurrentLib GoTo Recheck End If Catch ex As Exception - Log(ex, "合并版本依赖项 JSON 失败(" & If(InheritVersion, "null").ToString & ")") + Log(ex, "合并版本依赖项 JSON 失败(" & If(InstanceNameInheritFrom, "null").ToString & ")") End Try Catch ex As Exception Throw New Exception("初始化版本 JSON 时失败(" & If(Name, "null") & ")", ex) @@ -607,11 +617,12 @@ Recheck: ''' Public ReadOnly Property JsonVersion As JObject Get + Static JsonVersionInited As Boolean = False If Not JsonVersionInited Then JsonVersionInited = True Try - If Not File.Exists($"{Path}{Name}.jar") Then Exit Try - Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + If Not File.Exists($"{PathVersion}{Name}.jar") Then Exit Try + Using JarArchive As New ZipArchive(New FileStream(PathVersion & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json") If VersionJson Is Nothing Then Exit Try Using VersionJsonStream As New StreamReader(VersionJson.Open) @@ -619,42 +630,42 @@ Recheck: End Using End Using Catch ex As Exception - Log(ex, $"从版本 jar 中读取 version.json 失败({Path}{Name}.jar)") + Log(ex, $"从版本 jar 中读取 version.json 失败({PathVersion}{Name}.jar)") End Try End If Return _JsonVersion End Get End Property - Private JsonVersionInited As Boolean = False Private _JsonVersion As JObject = Nothing ''' ''' 该版本的依赖版本。若无依赖版本则为空字符串。 ''' - Public ReadOnly Property InheritVersion As String + Public ReadOnly Property InheritName As String Get - If _InheritVersion Is Nothing Then - _InheritVersion = If(JsonObject("inheritsFrom"), "").ToString + If _InheritName Is Nothing Then + _InheritName = If(JsonObject("inheritsFrom"), "").ToString '由于过老的 LiteLoader 中没有 Inherits(例如 1.5.2),需要手动判断以获取真实继承版本 '此外,由于这里的加载早于版本种类判断,所以需要手动判断是否为 LiteLoader '如果版本提供了不同的 Jar,代表所需的 Jar 可能已被更改,则跳过 Inherit 替换 - If JsonText.Contains("liteloader") AndAlso Version.McName <> Name AndAlso Not JsonText.Contains("logging") Then - If If(JsonObject("jar"), Version.McName).ToString = Version.McName Then _InheritVersion = Version.McName + If JsonText.Contains("liteloader") AndAlso Version.VanillaName <> Name AndAlso Not JsonText.Contains("logging") Then + If If(JsonObject("jar"), Version.VanillaName).ToString = Version.VanillaName Then _InheritName = Version.VanillaName End If 'HMCL 版本无 Json - If IsHmclFormatJson Then _InheritVersion = "" + If IsHmclFormatJson Then _InheritName = "" End If - Return _InheritVersion + Return _InheritName End Get End Property - Private _InheritVersion As String = Nothing + Private _InheritName As String = Nothing - ''' - ''' 版本名,或版本文件夹的完整路径(不规定是否以 \ 结尾)。 - Public Sub New(Path As String) - Me.Path = If(Path.Contains(":"), "", PathMcFolder & "versions\") & '补全完整路径 - Path & - If(Path.EndsWithF("\"), "", "\") '补全右划线 + ''' + ''' 从版本名,或版本文件夹的完整路径初始化(不规定是否以 \ 结尾)。 + ''' + Public Sub New(Name As String) + PathVersion = If(Name.Contains(":"), "", McFolderSelected & "versions\") & '补全完整路径 + Name & + If(Name.EndsWithF("\"), "", "\") '补全右划线 End Sub ''' @@ -663,17 +674,17 @@ Recheck: Public Function Check() As Boolean '检查文件夹 - If Not Directory.Exists(Path) Then - State = McVersionState.Error + If Not Directory.Exists(PathVersion) Then + State = McInstanceState.Error Info = "未找到版本 " & Name Return False End If '检查权限 Try - Directory.CreateDirectory(Path & "PCL\") - CheckPermissionWithException(Path & "PCL\") + Directory.CreateDirectory(PathVersion & "PCL\") + CheckPermissionWithException(PathVersion & "PCL\") Catch ex As Exception - State = McVersionState.Error + State = McInstanceState.Error Info = "PCL 没有对该文件夹的访问权限,请右键以管理员身份运行 PCL" Log(ex, "没有访问版本文件夹的权限") Return False @@ -682,226 +693,254 @@ Recheck: Try Dim JsonObjCheck = JsonObject Catch ex As Exception - Log(ex, "版本 JSON 可用性检查失败(" & Path & ")") + Log(ex, "版本 JSON 可用性检查失败(" & PathVersion & ")") JsonText = "" JsonObject = Nothing Info = ex.Message - State = McVersionState.Error + State = McInstanceState.Error + Return False + End Try + '检查版本号获取 + Try + If String.IsNullOrEmpty(Version.VanillaName) Then Throw New Exception("无法获取版本号,结果为空") + Catch ex As Exception + Log(ex, "版本号获取失败(" & Name & ")") + State = McInstanceState.Error + Info = "版本号获取失败:" & ex.GetBrief() Return False End Try '检查依赖版本 Try - If Not InheritVersion = "" Then - If Not File.Exists(GetPathFromFullPath(Path) & InheritVersion & "\" & InheritVersion & ".json") Then - State = McVersionState.Error - Info = "需要安装 " & InheritVersion & " 作为前置版本" + If Not InheritName = "" Then + If Not File.Exists(GetPathFromFullPath(PathVersion) & InheritName & "\" & InheritName & ".json") Then + State = McInstanceState.Error + Info = "需要安装 " & InheritName & " 作为前置版本" Return False End If End If Catch ex As Exception Log(ex, "依赖版本检查出错(" & Name & ")") - State = McVersionState.Error + State = McInstanceState.Error Info = "未知错误:" & ex.GetBrief() Return False End Try - State = McVersionState.Original + State = McInstanceState.Original Return True End Function ''' ''' 加载 Minecraft 版本的详细信息。不使用其缓存,且会更新缓存。 ''' - Public Function Load() As McVersion + Public Function Load() As McInstance Try '检查版本,若出错则跳过数据确定阶段 If Not Check() Then GoTo ExitDataLoad #Region "确定版本分类" - Select Case Version.McName '在获取 Version.Original 对象时会完成它的加载 + Select Case Version.VanillaName '在获取 Version.Original 对象时会完成它的加载 Case "Unknown" - State = McVersionState.Error + State = McInstanceState.Error Case "Old" - State = McVersionState.Old + State = McInstanceState.Old Case Else '根据 API 进行筛选 Dim RealJson As String = If(JsonObject, JsonText).ToString '愚人节与快照版本 - If If(JsonObject("type"), "").ToString = "fool" OrElse GetMcFoolName(Version.McName) <> "" Then - State = McVersionState.Fool - ElseIf Version.McName.ContainsF("w", True) OrElse Name.ContainsF("combat", True) OrElse Version.McName.ContainsF("rc", True) OrElse Version.McName.ContainsF("pre", True) OrElse Version.McName.ContainsF("experimental", True) OrElse If(JsonObject("type"), "").ToString = "snapshot" OrElse If(JsonObject("type"), "").ToString = "pending" Then - State = McVersionState.Snapshot + If If(JsonObject("type"), "").ToString = "fool" OrElse GetMcFoolName(Version.VanillaName) <> "" Then + State = McInstanceState.Fool + ElseIf IsSnapshot() Then + State = McInstanceState.Snapshot End If 'OptiFine If RealJson.Contains("optifine") Then - State = McVersionState.OptiFine + State = McInstanceState.OptiFine Version.HasOptiFine = True - Version.OptiFineVersion = If(RegexSeek(RealJson, "(?<=HD_U_)[^"":/]+"), "未知版本") + Version.OptiFine = If(RegexSeek(RealJson, "(?<=HD_U_)[^"":/]+"), "未知版本") End If 'LiteLoader If RealJson.Contains("liteloader") Then - State = McVersionState.LiteLoader + State = McInstanceState.LiteLoader Version.HasLiteLoader = True End If 'Fabric、Forge If RealJson.Contains("net.fabricmc:fabric-loader") OrElse RealJson.Contains("org.quiltmc:quilt-loader") Then - State = McVersionState.Fabric + State = McInstanceState.Fabric Version.HasFabric = True - Version.FabricVersion = If(RegexSeek(RealJson, "(?<=(net.fabricmc:fabric-loader:)|(org.quiltmc:quilt-loader:))[0-9\.]+(\+build.[0-9]+)?"), "未知版本").Replace("+build", "") + Version.Fabric = If(RegexSeek(RealJson, "(?<=(net.fabricmc:fabric-loader:)|(org.quiltmc:quilt-loader:))[0-9\.]+(\+build.[0-9]+)?"), "未知版本").Replace("+build", "") ElseIf RealJson.Contains("minecraftforge") AndAlso Not RealJson.Contains("net.neoforge") Then - State = McVersionState.Forge + State = McInstanceState.Forge Version.HasForge = True - Version.ForgeVersion = RegexSeek(RealJson, "(?<=forge:[0-9\.]+(_pre[0-9]*)?\-)[0-9\.]+") - If Version.ForgeVersion Is Nothing Then Version.ForgeVersion = RegexSeek(RealJson, "(?<=net\.minecraftforge:minecraftforge:)[0-9\.]+") - If Version.ForgeVersion Is Nothing Then Version.ForgeVersion = If(RegexSeek(RealJson, "(?<=net\.minecraftforge:fmlloader:[0-9\.]+-)[0-9\.]+"), "未知版本") + Version.Forge = RegexSeek(RealJson, "(?<=forge:[0-9\.]+(_pre[0-9]*)?\-)[0-9\.]+") + If Version.Forge Is Nothing Then Version.Forge = RegexSeek(RealJson, "(?<=net\.minecraftforge:minecraftforge:)[0-9\.]+") + If Version.Forge Is Nothing Then Version.Forge = If(RegexSeek(RealJson, "(?<=net\.minecraftforge:fmlloader:[0-9\.]+-)[0-9\.]+"), "未知版本") ElseIf RealJson.Contains("net.neoforge") Then '1.20.1 JSON 范例:"--fml.forgeVersion", "47.1.99" '1.20.2+ JSON 范例:"--fml.neoForgeVersion", "20.6.119-beta" - State = McVersionState.NeoForge + State = McInstanceState.NeoForge Version.HasNeoForge = True - Version.NeoForgeVersion = If(RegexSeek(RealJson, "(?<=orgeVersion"",[^""]*?"")[^""]+(?="",)"), "未知版本") + Version.NeoForge = If(RegexSeek(RealJson, "(?<=orgeVersion"",[^""]*?"")[^""]+(?="",)"), "未知版本") End If - Version.IsApiLoaded = True End Select #End Region ExitDataLoad: '确定版本图标 - Logo = ReadIni(Path & "PCL\Setup.ini", "Logo", "") - If Logo = "" OrElse Not CType(ReadIni(Path & "PCL\Setup.ini", "LogoCustom", False), Boolean) Then + Logo = ReadIni(PathVersion & "PCL\Setup.ini", "Logo", "") + If Logo = "" OrElse Not CType(ReadIni(PathVersion & "PCL\Setup.ini", "LogoCustom", False), Boolean) Then Select Case State - Case McVersionState.Original + Case McInstanceState.Original Logo = PathImage & "Blocks/Grass.png" - Case McVersionState.Snapshot + Case McInstanceState.Snapshot Logo = PathImage & "Blocks/CommandBlock.png" - Case McVersionState.Old + Case McInstanceState.Old Logo = PathImage & "Blocks/CobbleStone.png" - Case McVersionState.Forge + Case McInstanceState.Forge Logo = PathImage & "Blocks/Anvil.png" - Case McVersionState.NeoForge + Case McInstanceState.NeoForge Logo = PathImage & "Blocks/NeoForge.png" - Case McVersionState.Fabric + Case McInstanceState.Fabric Logo = PathImage & "Blocks/Fabric.png" - Case McVersionState.OptiFine + Case McInstanceState.OptiFine Logo = PathImage & "Blocks/GrassPath.png" - Case McVersionState.LiteLoader + Case McInstanceState.LiteLoader Logo = PathImage & "Blocks/Egg.png" - Case McVersionState.Fool + Case McInstanceState.Fool Logo = PathImage & "Blocks/GoldBlock.png" Case Else Logo = PathImage & "Blocks/RedstoneBlock.png" End Select End If '确定版本描述 - Dim CustomInfo As String = ReadIni(Path & "PCL\Setup.ini", "CustomInfo") + Dim CustomInfo As String = ReadIni(PathVersion & "PCL\Setup.ini", "CustomInfo") If CustomInfo <> "" Then Info = CustomInfo Else - Info = GetVersionDescription() - If State = McVersionState.Fool Then - Info = GetMcFoolName(Version.McName) - ElseIf State <> McVersionState.Error Then - If Setup.Get("VersionServerLogin", Version:=Me) = 3 Then Info += ", 统一通行证验证" - If Setup.Get("VersionServerLogin", Version:=Me) = 4 Then Info += ", Authlib 验证" + Info = VersionDisplayName() + If State = McInstanceState.Fool Then + Info = GetMcFoolName(Version.VanillaName) + ElseIf State <> McInstanceState.Error Then + If Setup.Get("VersionServerLogin", Instance:=Me) = 3 Then Info += ", 统一通行证验证" + If Setup.Get("VersionServerLogin", Instance:=Me) = 4 Then Info += ", Authlib 验证" End If End If '确定版本收藏状态 - IsStar = ReadIni(Path & "PCL\Setup.ini", "IsStar", False) + IsStar = ReadIni(PathVersion & "PCL\Setup.ini", "IsStar", False) '确定版本显示种类 - DisplayType = ReadIni(Path & "PCL\Setup.ini", "DisplayType", McVersionCardType.Auto) + DisplayType = ReadIni(PathVersion & "PCL\Setup.ini", "DisplayType", McInstanceCardType.Auto) '写入缓存 - If Directory.Exists(Path) Then - WriteIni(Path & "PCL\Setup.ini", "State", State) - WriteIni(Path & "PCL\Setup.ini", "Info", Info) - WriteIni(Path & "PCL\Setup.ini", "Logo", Logo) + If Directory.Exists(PathVersion) Then + WriteIni(PathVersion & "PCL\Setup.ini", "State", State) + WriteIni(PathVersion & "PCL\Setup.ini", "Info", Info) + WriteIni(PathVersion & "PCL\Setup.ini", "Logo", Logo) End If - If State <> McVersionState.Error Then - WriteIni(Path & "PCL\Setup.ini", "ReleaseTime", ReleaseTime.ToString("yyyy'-'MM'-'dd HH':'mm")) - WriteIni(Path & "PCL\Setup.ini", "VersionFabric", Version.FabricVersion) - WriteIni(Path & "PCL\Setup.ini", "VersionOptiFine", Version.OptiFineVersion) - WriteIni(Path & "PCL\Setup.ini", "VersionLiteLoader", Version.HasLiteLoader) - WriteIni(Path & "PCL\Setup.ini", "VersionForge", Version.ForgeVersion) - WriteIni(Path & "PCL\Setup.ini", "VersionNeoForge", Version.NeoForgeVersion) - WriteIni(Path & "PCL\Setup.ini", "VersionApiCode", Version.SortCode) - WriteIni(Path & "PCL\Setup.ini", "VersionOriginal", Version.McName) - WriteIni(Path & "PCL\Setup.ini", "VersionOriginalMain", Version.McCodeMain) - WriteIni(Path & "PCL\Setup.ini", "VersionOriginalSub", Version.McCodeSub) + If State <> McInstanceState.Error AndAlso Version.Reliable Then + WriteIni(PathVersion & "PCL\Setup.ini", "ReleaseTime", ReleaseTime.ToString("yyyy'-'MM'-'dd HH':'mm")) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionFabric", Version.Fabric) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionOptiFine", Version.OptiFine) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionLiteLoader", Version.HasLiteLoader) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionForge", Version.Forge) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionNeoForge", Version.NeoForge) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionVanillaName", Version.VanillaName) + WriteIni(PathVersion & "PCL\Setup.ini", "VersionVanilla", Version.Vanilla.ToString) End If Catch ex As Exception Info = "未知错误:" & ex.GetBrief() Logo = PathImage & "Blocks/RedstoneBlock.png" - State = McVersionState.Error + State = McInstanceState.Error Log(ex, "加载版本失败(" & Name & ")", LogLevel.Feedback) Finally IsLoaded = True End Try Return Me End Function - Public IsLoaded As Boolean = False - ''' - ''' 获取对该版本的简短描述。 - ''' 例如 “快照 16w01a”、“原版 1.12.2”、“愚人节版本 2.0”。 - ''' - Public Function GetVersionDescription() As String - Dim Info As String - Select Case State - Case McVersionState.Snapshot - If Version.McName.ContainsF("pre", True) Then - Info = "预发布版 " & Version.McName - ElseIf Version.McName.ContainsF("rc", True) Then - Info = "发布候选 " & Version.McName - ElseIf Version.McName.Contains("experimental") OrElse Version.McName = "pending" Then - Info = "实验性快照" - Else - Info = "快照 " & Version.McName - End If - Case McVersionState.Old - Info = "远古版本" - Case McVersionState.Original, McVersionState.Forge, McVersionState.NeoForge, McVersionState.Fabric, McVersionState.OptiFine, McVersionState.LiteLoader - Info = Version.ToString - Case McVersionState.Fool - Info = "愚人节版本 " & Version.McName - Case McVersionState.Error - Return Me.Info '已有错误信息 - Case Else - Info = "发生了未知错误,请向作者反馈此问题" - End Select - Return Info + Private Function IsSnapshot() As Boolean + Return {"w", "snapshot", "rc", "pre", "experimental", "-"}.Any(Function(s) Version.VanillaName.ContainsF(s, True)) OrElse + Name.ContainsF("combat", True) OrElse + If(JsonObject("type"), "").ToString = "snapshot" OrElse If(JsonObject("type"), "").ToString = "pending" End Function - Public Function ToListItem() As MyListItem - Dim NewItem As New MyListItem With {.Info = Info, .Height = 42, .Tag = Me, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable} - '标题 - NewItem.Inlines.Clear() - NewItem.Inlines.Add(New Run(Name)) - If HasCustomInfo Then '如果版本设置了自定义描述,在标题后面以淡灰色显示其版本号 - NewItem.Inlines.Add(New Run(" | " & GetVersionDescription()) With {.Foreground = New MyColor(215, 215, 215), .FontSize = 12}) - End If - 'Logo - Try - If Logo.EndsWith("PCL\Logo.png") Then - NewItem.Logo = Path & "PCL\Logo.png" '修复老版本中,存储的自定义 Logo 使用完整路径,导致移动后无法加载的 Bug - Else - NewItem.Logo = Logo + Public IsLoaded As Boolean = False + ''' + ''' 获取对版本信息的简短描述。 + ''' 例如 “快照 16w01a”、“原版 1.12.2, Forge 1.2.3”、“愚人节版本 2.0”。 + ''' + Public Function VersionDisplayName() As String + 'Mod Loader 信息 + Dim ModLoaderInfo As String = "" + If Version.HasForge Then ModLoaderInfo += ", Forge" & If(Version.Forge = "未知版本", "", " " & Version.Forge) + If Version.HasNeoForge Then ModLoaderInfo += ", NeoForge" & If(Version.NeoForge = "未知版本", "", " " & Version.NeoForge) + If Version.HasFabric Then ModLoaderInfo += ", Fabric" & If(Version.Fabric = "未知版本", "", " " & Version.Fabric) + If Version.HasOptiFine Then ModLoaderInfo += ", OptiFine" & If(Version.OptiFine = "未知版本", "", " " & Version.OptiFine.Replace("-", " ").Replace("_", " ")) + If Version.HasLiteLoader Then ModLoaderInfo += ", LiteLoader" + '基础信息 + Dim Info As String + Select Case State + Case McInstanceState.Snapshot, McInstanceState.Original, McInstanceState.Forge, McInstanceState.NeoForge, McInstanceState.Fabric, McInstanceState.OptiFine, McInstanceState.LiteLoader + If Version.VanillaName.ContainsF("pre", True) Then + Info = "预发布版 " & Version.VanillaName + ElseIf Version.VanillaName.ContainsF("rc", True) Then + Info = "发布候选 " & Version.VanillaName + ElseIf Version.VanillaName.Contains("experimental") Then + Info = "实验性快照" & Version.VanillaName + ElseIf Version.VanillaName = "pending" Then + Info = "实验性快照" + ElseIf IsSnapshot Then + Info = If(Version.Reliable, "快照版 " & Version.VanillaName.Replace("-snapshot", ""), "快照版") + Else + Info = If(Version.Reliable, "正式版 " & Version.VanillaName, "正式版") + End If + Case McInstanceState.Old + Info = "远古版本" + Case McInstanceState.Fool + Info = "愚人节版本 " & Version.VanillaName + Case McInstanceState.Error + Return Me.Info '已有错误信息 + Case Else + Return "发生了未知错误,请向作者反馈此问题" + End Select + Return (Info & ModLoaderInfo).Replace("_", "-") + End Function + + Public Function ToListItem(Optional ContentHandler As Action(Of MyListItem, EventArgs) = Nothing) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + Dim NewItem As New MyListItem With { + .Info = Info, .Height = 42, .Tag = Me, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable, + .ContentHandler = ContentHandler + } + '标题 + NewItem.Inlines.Clear() + NewItem.Inlines.Add(New Run(Name)) + If ReadIni(PathVersion & "PCL\Setup.ini", "CustomInfo") <> "" Then '如果版本设置了自定义描述,在标题后面以淡灰色显示其版本号 + NewItem.Inlines.Add(New Run(" | " & VersionDisplayName()) With {.Foreground = New MyColor(215, 215, 215), .FontSize = 12}) End If - Catch ex As Exception - Log(ex, "加载版本图标失败", LogLevel.Hint) - NewItem.Logo = "pack://application:,,,/images/Blocks/RedstoneBlock.png" - End Try - Return NewItem + 'Logo + Try + If Logo.EndsWith("PCL\Logo.png") Then + NewItem.Logo = PathVersion & "PCL\Logo.png" '修复老版本中,存储的自定义 Logo 使用完整路径,导致移动后无法加载的 Bug + Else + NewItem.Logo = Logo + End If + Catch ex As Exception + Log(ex, "加载版本图标失败", LogLevel.Hint) + NewItem.Logo = "pack://application:,,,/images/Blocks/RedstoneBlock.png" + End Try + Return NewItem + End Function) With {.Height = 42} End Function '运算符支持 Public Overrides Function Equals(obj As Object) As Boolean - Dim version = TryCast(obj, McVersion) - Return version IsNot Nothing AndAlso Path = version.Path + Dim version = TryCast(obj, McInstance) + Return version IsNot Nothing AndAlso PathVersion = version.PathVersion End Function - Public Shared Operator =(a As McVersion, b As McVersion) As Boolean + Public Shared Operator =(a As McInstance, b As McInstance) As Boolean If a Is Nothing AndAlso b Is Nothing Then Return True If a Is Nothing OrElse b Is Nothing Then Return False - Return a.Path = b.Path + Return a.PathVersion = b.PathVersion End Operator - Public Shared Operator <>(a As McVersion, b As McVersion) As Boolean + Public Shared Operator <>(a As McInstance, b As McInstance) As Boolean Return Not (a = b) End Operator End Class - Public Enum McVersionState + Public Enum McInstanceState [Error] Original Snapshot @@ -915,80 +954,130 @@ ExitDataLoad: End Enum ''' - ''' 某个 Minecraft 实例的版本名、附加组件信息。 + ''' Minecraft 版本号和附加组件信息。 ''' - Public Class McVersionInfo - - ''' - ''' 版本的 API 信息是否已加载。 - ''' - Public IsApiLoaded As Boolean = False + Public Class McVersion '原版 ''' - ''' 原版版本名。如 1.12.2,16w01a。 + ''' 原版版本名。 + ''' 如 26.1,26.1-snapshot-1,1.12.2,16w01a。 ''' - Public McName As String + Public VanillaName As String ''' - ''' 原版主版本号,如 12(For 1.12.2),快照则固定为 99。不可用则为 -1。 + ''' 可比较的三段式原版版本号。 + ''' 对老版本格式,例如 1.20.3,会被转换为 20.0.3。 + ''' 若没有版本号,例如旧快照,则为 9999.0.0。 ''' - Public McCodeMain As Integer = -1 + Public Vanilla As Version ''' - ''' 原版次版本号,如 2(For 1.12.2),快照则固定为 99。不可用则为 -1。 + ''' 指示原版版本号是否可靠(不是通过猜测获取)。 ''' - Public McCodeSub As Integer = -1 + Public Reliable As Boolean = True ''' - ''' 是否为非快照版,且读取到了一个有效的原版版本号。 + ''' 原版版本号是否有效。 ''' - Public ReadOnly Property IsStandardVersion As Boolean + Public ReadOnly Property Vaild As Boolean Get - Return McCodeMain > -1 AndAlso McCodeMain < 99 AndAlso McCodeSub > -1 AndAlso McCodeSub < 99 + Return Vanilla.Major < 1000 End Get End Property ''' - ''' 标准的原版版本号。 - ''' 若为快照版或没有有效版本号,则返回 0。 + ''' 可供比较的原版 Drop 序数。 + ''' 例如 26.3.2 为 263,1.21.5 为 210。 + ''' 若没有版本号,例如旧快照,则直接指定为 209。 ''' - Public ReadOnly Property McVersion As Version + Public ReadOnly Property Drop As Integer Get - If Not IsStandardVersion Then Return New Version(0, 0, 0) - Return New Version(1, McCodeMain, McCodeSub) + Return If(Vaild, Vanilla.Major * 10 + Vanilla.Minor, 209) End Get End Property 'OptiFine ''' - ''' 该版本是否通过 Json 安装了 OptiFine。 + ''' 该版本是否通过 JSON 安装了 OptiFine。 ''' Public HasOptiFine As Boolean = False ''' - ''' OptiFine 版本号,如 C8、C9_pre10。 + ''' OptiFine 版本名,如 C8、C9_pre10。 ''' - Public OptiFineVersion As String = "" + Public OptiFine As String = "" + ''' + ''' 可供比较的 OptiFine 版本序数。 + ''' + Public ReadOnly Property OptiFineCode As Integer + Get + If String.IsNullOrEmpty(OptiFine) OrElse OptiFine = "未知版本" Then Return 0 + '字母编号,如 G2 中的 G(7) + Dim Result As Integer = Asc(OptiFine.ToUpper.First) - Asc("A"c) + 1 + '末尾数字,如 C5 beta4 中的 5 + Result *= Result * 100 + Result += Val(RegexSeek(Right(OptiFine, OptiFine.Length - 1), "[0-9]+")) + '测试标记(正式版为 99,Pre[x] 为 50+x,Beta[x] 为 x) + Result *= Result * 100 + If OptiFine.ContainsF("pre", True) Then Result += 50 + If OptiFine.ContainsF("pre", True) OrElse OptiFine.ContainsF("beta", True) Then + If Val(Right(OptiFine, 1)) = 0 AndAlso Right(OptiFine, 1) <> "0" Then + Result += 1 '为 pre 或 beta 结尾,视作 1 + Else + Result += Val(RegexSeek(OptiFine.ToLower, "(?<=((pre)|(beta)))[0-9]+")) + End If + Else + Result += 99 + End If + Return Result + End Get + End Property - 'Forge + 'Forge/NeoForge ''' ''' 该版本是否安装了 Forge。 ''' Public HasForge As Boolean = False - ''' - ''' Forge 版本号,如 31.1.2、14.23.5.2847。 - ''' - Public ForgeVersion As String = "" - - 'NeoForge - ''' ''' 该版本是否安装了 NeoForge。 ''' Public HasNeoForge As Boolean = False ''' - ''' NeoForge 版本号,如 21.0.2-beta、47.1.79。 + ''' 该版本是否安装了 Forge 或 NeoForge。 ''' - Public NeoForgeVersion As String = "" + Public ReadOnly Property HasForgelike As Boolean + Get + Return HasForge OrElse HasNeoForge + End Get + End Property + ''' + ''' Forge 版本名,如 31.1.2、14.23.5.2847。 + ''' + Public Forge As String = "" + ''' + ''' NeoForge 版本名,如 21.0.2-beta、47.1.79。 + ''' + Public NeoForge As String = "" + ''' + ''' 可供比较的类 Forge 版本序数。 + ''' + Public ReadOnly Property ForgelikeCode As Integer + Get + If Not HasForgelike Then Return 0 + If (String.IsNullOrEmpty(Forge) OrElse Forge = "未知版本") AndAlso + (String.IsNullOrEmpty(NeoForge) OrElse NeoForge = "未知版本") Then Return 0 + Dim Segments = RegexSearch(If(HasForge, Forge, NeoForge), "\d+") + Select Case Segments.Count + Case Is > 4 + Return Val(Segments(0)) * 1000000 + Val(Segments(1)) * 10000 + Val(Segments(3)) + Case 3 + Return Val(Segments(0)) * 1000000 + Val(Segments(1)) * 10000 + Val(Segments(2)) + Case 2 + Return Val(Segments(0)) * 1000000 + Val(Segments(1)) * 10000 + Case Else + Return Val(Segments(0)) * 1000000 + End Select + End Get + End Property 'Fabric @@ -997,9 +1086,9 @@ ExitDataLoad: ''' Public HasFabric As Boolean = False ''' - ''' Fabric 版本号,如 0.7.2.175。 + ''' Fabric 版本名,如 0.7.2.175。 ''' - Public FabricVersion As String = "" + Public Fabric As String = "" 'LiteLoader @@ -1008,84 +1097,46 @@ ExitDataLoad: ''' Public HasLiteLoader As Boolean = False - 'API + 'Helpers ''' - ''' 生成对此版本信息的用户友好的描述性字符串。 + ''' 版本字符串是否符合 Minecraft 原版格式,例如 1.x、26.x。 ''' - Public Overrides Function ToString() As String - ToString = "" - If HasForge Then ToString += ", Forge" & If(ForgeVersion = "未知版本", "", " " & ForgeVersion) - If HasNeoForge Then ToString += ", NeoForge" & If(NeoForgeVersion = "未知版本", "", " " & NeoForgeVersion) - If HasFabric Then ToString += ", Fabric" & If(FabricVersion = "未知版本", "", " " & FabricVersion) - If HasOptiFine Then ToString += ", OptiFine" & If(OptiFineVersion = "未知版本", "", " " & OptiFineVersion) - If HasLiteLoader Then ToString += ", LiteLoader" - If ToString = "" Then - Return "原版 " & McName + Public Shared Function IsFormatFit(Version As String) As Boolean + If Version Is Nothing Then Return False + If RegexCheck(Version, "^1\.\d") Then Return True + If Val(RegexSeek(Version, "^[2-9]\d\.\d+")) > 25 Then Return True + Return False + End Function + ''' + ''' 尝试将版本字符串转换为 Drop 序数。 + ''' 若无法转换则返回 0。 + ''' + Public Shared Function VersionToDrop(Version As String, Optional AllowSnapshot As Boolean = False) As Integer + If Not AllowSnapshot AndAlso Version.Contains("-") Then Return 0 + If Version Is Nothing Then Return 0 + Dim Segments = Version.BeforeFirst("-").Split(".") + If Segments.Length < 2 Then Return 0 + Dim Major As Integer = Val(Segments(0)) + Dim Minor As Integer = Val(Segments(1)) + If Major = 1 Then + Return Minor * 10 + ElseIf Major < 25 Then + Return 0 Else - Return McName & ToString + Return Major * 10 + Minor End If End Function - ''' - ''' 用于排序比较的编号。 + ''' 将 Drop 序数转换为版本字符串。 ''' - Public Property SortCode As Integer - Get - If _SortCode = -2 Then - '初始化 - Try - If HasFabric Then - If FabricVersion = "未知版本" Then Return 0 - Dim SubVersions = FabricVersion.Split(".") - If SubVersions.Length >= 3 Then - _SortCode = Val(SubVersions(0)) * 10000 + Val(SubVersions(1)) * 100 + Val(SubVersions(2)) - Else - Throw New Exception("无效的 Fabric 版本:" & ForgeVersion) - End If - ElseIf HasForge OrElse HasNeoForge Then - If ForgeVersion = "未知版本" AndAlso NeoForgeVersion = "未知版本" Then Return 0 - Dim SubVersions = If(HasForge, ForgeVersion.Split("."), NeoForgeVersion.Split(".")) - If SubVersions.Length = 4 Then - _SortCode = Val(SubVersions(0)) * 1000000 + Val(SubVersions(1)) * 10000 + Val(SubVersions(3)) - ElseIf SubVersions.Length = 3 Then - _SortCode = Val(SubVersions(0)) * 1000000 + Val(SubVersions(1)) * 10000 + Val(SubVersions(2)) - Else - Throw New Exception("无效的 Neo/Forge 版本:" & ForgeVersion) - End If - ElseIf HasOptiFine Then - If OptiFineVersion = "未知版本" Then Return 0 - '由对应原版次级版本号(2 位)、字母(2 位)、末尾数字(2 位)、测试标记(2 位,正式版为 99,Pre[x] 为 50+x,Beta[x] 为 x)组成 - _SortCode = - If(McCodeSub >= 0, McCodeSub, 0) * 1000000 + '第一段:原版次级版本号(2 位) - (Asc(CType(Left(OptiFineVersion.ToUpper, 1), Char)) - Asc("A"c) + 1) * 10000 + '第二段:字母编号(2 位),如 G2 中的 G(7) - Val(RegexSeek(Right(OptiFineVersion, OptiFineVersion.Length - 1), "[0-9]+")) * 100 '第三段:末尾数字(2 位),如 C5 beta4 中的 5 - '第三段:测试标记 - If OptiFineVersion.ContainsF("pre", True) Then _SortCode += 50 - If OptiFineVersion.ContainsF("pre", True) OrElse OptiFineVersion.ContainsF("beta", True) Then - If Val(Right(OptiFineVersion, 1)) = 0 AndAlso Right(OptiFineVersion, 1) <> "0" Then - _SortCode += 1 '为 pre 或 beta 结尾,视作 1 - Else - _SortCode += Val(RegexSeek(OptiFineVersion.ToLower, "(?<=((pre)|(beta)))[0-9]+")) - End If - Else - _SortCode += 99 - End If - Else - _SortCode = -1 - End If - Catch ex As Exception - _SortCode = -1 - Log(ex, "获取 API 版本信息失败:" & ToString()) - End Try - End If - Return _SortCode - End Get - Set(value As Integer) - _SortCode = value - End Set - End Property - Private _SortCode As Integer = -2 + Public Shared Function DropToVersion(Drop As Integer) As String + If Drop >= 250 Then + Return $"{Drop \ 10}.{Drop Mod 10}" + Else + Return $"1.{Drop \ 10}" + End If + End Function End Class @@ -1120,7 +1171,7 @@ ExitDataLoad: ''' ''' 当前按卡片分类的所有版本列表。 ''' - Public McVersionList As New Dictionary(Of McVersionCardType, List(Of McVersion)) + Public McInstanceList As New Dictionary(Of McInstanceCardType, List(Of McInstance)) #End Region @@ -1129,91 +1180,87 @@ ExitDataLoad: ''' ''' 是否要求本次加载强制刷新版本列表。 ''' - Public McVersionListForceRefresh As Boolean = False - ''' - ''' 加载 Minecraft 文件夹的版本列表。 - ''' - Public McVersionListLoader As New LoaderTask(Of String, Integer)("Minecraft Version List", AddressOf McVersionListLoad) With {.ReloadTimeout = 1} - + Public McInstanceListForceRefresh As Boolean = False ''' ''' 是否为本次打开 PCL 后第一次加载版本列表。 ''' 这会清理所有 .pclignore 文件,而非跳过这些对应版本。 ''' - Private IsFirstMcVersionListLoad As Boolean = True + Private IsFirstMcInstanceListLoad As Boolean = True ''' - ''' 开始加载当前 Minecraft 文件夹的版本列表。 + ''' 加载 Minecraft 文件夹的版本列表。 ''' - Private Sub McVersionListLoad(Loader As LoaderTask(Of String, Integer)) + Public McInstanceListLoader As New LoaderTask(Of String, Integer)("Minecraft Instance List", AddressOf InitMcInstanceList) With {.ReloadTimeout = 1} + Private Sub InitMcInstanceList(Loader As LoaderTask(Of String, Integer)) '开始加载 - Dim Path As String = Loader.Input + Dim PathMc As String = Loader.Input Try '初始化 - McVersionList = New Dictionary(Of McVersionCardType, List(Of McVersion)) + McInstanceList = New Dictionary(Of McInstanceCardType, List(Of McInstance)) '检测缓存是否需要更新 Dim FolderList As New List(Of String) - If Directory.Exists(Path & "versions") Then '不要使用 CheckPermission,会导致写入时间改变,从而使得文件夹被强制刷新 + If Directory.Exists(PathMc & "versions") Then '不要使用 CheckPermission,会导致写入时间改变,从而使得文件夹被强制刷新 Try - For Each Folder As DirectoryInfo In New DirectoryInfo(Path & "versions").GetDirectories + For Each Folder As DirectoryInfo In New DirectoryInfo(PathMc & "versions").GetDirectories FolderList.Add(Folder.Name) Next Catch ex As Exception - Throw New Exception("无法读取版本文件夹,可能是由于没有权限(" & Path & "versions)", ex) + Throw New Exception("无法读取版本文件夹,可能是由于没有权限(" & PathMc & "versions)", ex) End Try End If '不可用 If Not FolderList.Any() Then - WriteIni(Path & "PCL.ini", "VersionCache", "") '清空缓存 + WriteIni(PathMc & "PCL.ini", "InstanceCache", "") '清空缓存 GoTo OnLoaded End If '有可用版本 - Dim FolderListCheck As Integer = GetHash(McVersionCacheVersion & "#" & Join(FolderList.ToArray, "#")) Mod (Integer.MaxValue - 1) '根据文件夹名列表生成辨识码 - If Not McVersionListForceRefresh AndAlso Val(ReadIni(Path & "PCL.ini", "VersionCache")) = FolderListCheck Then + Dim FolderListCheck As Integer = GetHash(McInstanceCacheVersion & "#" & Join(FolderList.ToArray, "#")) Mod (Integer.MaxValue - 1) '根据文件夹名列表生成辨识码 + If Not McInstanceListForceRefresh AndAlso Val(ReadIni(PathMc & "PCL.ini", "InstanceCache")) = FolderListCheck Then '可以使用缓存 - Dim Result = McVersionListLoadCache(Path) + Dim Result = InitMcInstanceListWithCache(PathMc) If Result Is Nothing Then GoTo Reload Else - McVersionList = Result + McInstanceList = Result End If Else '文件夹列表不符 Reload: - McVersionListForceRefresh = False + McInstanceListForceRefresh = False Log("[Minecraft] 文件夹列表变更,重载所有版本") - WriteIni(Path & "PCL.ini", "VersionCache", FolderListCheck) - McVersionList = McVersionListLoadNoCache(Path) + WriteIni(PathMc & "PCL.ini", "InstanceCache", FolderListCheck) + McInstanceList = InitMcInstanceListWithoutCache(PathMc) End If - IsFirstMcVersionListLoad = False + IsFirstMcInstanceListLoad = False '改变当前选择的版本 OnLoaded: If Loader.IsAborted Then Return - If McVersionList.Any(Function(v) v.Key <> McVersionCardType.Error) Then + If McInstanceList.Any(Function(v) v.Key <> McInstanceCardType.Error) Then '尝试读取已储存的选择 - Dim SavedSelection As String = ReadIni(Path & "PCL.ini", "Version") + Dim SavedSelection As String = ReadIni(PathMc & "PCL.ini", "Version") If SavedSelection <> "" Then - For Each Card As KeyValuePair(Of McVersionCardType, List(Of McVersion)) In McVersionList - For Each Version As McVersion In Card.Value - If Version.Name = SavedSelection AndAlso Not Version.State = McVersionState.Error Then + For Each Card As KeyValuePair(Of McInstanceCardType, List(Of McInstance)) In McInstanceList + For Each Instance As McInstance In Card.Value + If Instance.Name = SavedSelection AndAlso Not Instance.State = McInstanceState.Error Then '使用已储存的选择 - McVersionCurrent = Version - Setup.Set("LaunchVersionSelect", McVersionCurrent.Name) - Log("[Minecraft] 选择该文件夹储存的 Minecraft 版本:" & McVersionCurrent.Path) + McInstanceSelected = Instance + Setup.Set("LaunchVersionSelect", McInstanceSelected.Name) + Log("[Minecraft] 选择该文件夹储存的 Minecraft 版本:" & McInstanceSelected.PathVersion) Return End If Next Next End If - If Not McVersionList.First.Value(0).State = McVersionState.Error Then + If Not McInstanceList.First.Value(0).State = McInstanceState.Error Then '自动选择第一项 - McVersionCurrent = McVersionList.First.Value(0) - Setup.Set("LaunchVersionSelect", McVersionCurrent.Name) - Log("[Launch] 自动选择 Minecraft 版本:" & McVersionCurrent.Path) + McInstanceSelected = McInstanceList.First.Value(0) + Setup.Set("LaunchVersionSelect", McInstanceSelected.Name) + Log("[Launch] 自动选择 Minecraft 版本:" & McInstanceSelected.PathVersion) End If Else - McVersionCurrent = Nothing + McInstanceSelected = Nothing Setup.Set("LaunchVersionSelect", "") Log("[Minecraft] 未找到可用 Minecraft 版本") End If @@ -1221,84 +1268,84 @@ OnLoaded: Catch ex As ThreadInterruptedException Catch ex As Exception If Loader.IsAborted Then Return '#5617 - WriteIni(Path & "PCL.ini", "VersionCache", "") '要求下次重新加载 + WriteIni(PathMc & "PCL.ini", "InstanceCache", "") '要求下次重新加载 Log(ex, "加载 .minecraft 版本列表失败", LogLevel.Feedback) End Try End Sub '获取版本列表 - Private Function McVersionListLoadCache(Path As String) As Dictionary(Of McVersionCardType, List(Of McVersion)) - Dim ResultVersionList As New Dictionary(Of McVersionCardType, List(Of McVersion)) + Private Function InitMcInstanceListWithCache(Path As String) As Dictionary(Of McInstanceCardType, List(Of McInstance)) + Dim Results As New Dictionary(Of McInstanceCardType, List(Of McInstance)) Try Dim CardCount As Integer = ReadIni(Path & "PCL.ini", "CardCount", -1) If CardCount = -1 Then Return Nothing For i = 0 To CardCount - 1 - Dim CardType As McVersionCardType = ReadIni(Path & "PCL.ini", "CardKey" & (i + 1), ":") - Dim VersionList As New List(Of McVersion) + Dim CardType As McInstanceCardType = ReadIni(Path & "PCL.ini", "CardKey" & (i + 1), ":") + Dim InstanceList As New List(Of McInstance) '循环读取版本 For Each Folder As String In ReadIni(Path & "PCL.ini", "CardValue" & (i + 1), ":").Split(":") If Folder = "" Then Continue For - Dim VersionFolder As String = $"{Path}versions\{Folder}\" - If File.Exists(VersionFolder & ".pclignore") Then - If IsFirstMcVersionListLoad Then - Log("[Minecraft] 清理残留的忽略项目:" & VersionFolder) '#2781 - File.Delete(VersionFolder & ".pclignore") + Dim FolderVersions As String = $"{Path}versions\{Folder}\" + If File.Exists(FolderVersions & ".pclignore") Then + If IsFirstMcInstanceListLoad Then + Log("[Minecraft] 清理残留的忽略项目:" & FolderVersions) '#2781 + File.Delete(FolderVersions & ".pclignore") Else - Log("[Minecraft] 跳过要求忽略的项目:" & VersionFolder) + Log("[Minecraft] 跳过要求忽略的项目:" & FolderVersions) Continue For End If End If Try '读取单个版本 - Dim Version As New McVersion(VersionFolder) - VersionList.Add(Version) - Version.Info = ReadIni(Version.Path & "PCL\Setup.ini", "CustomInfo", "") - If Version.Info = "" Then Version.Info = ReadIni(Version.Path & "PCL\Setup.ini", "Info", Version.Info) - Version.Logo = ReadIni(Version.Path & "PCL\Setup.ini", "Logo", Version.Logo) - Version.ReleaseTime = ReadIni(Version.Path & "PCL\Setup.ini", "ReleaseTime", Version.ReleaseTime) - Version.State = ReadIni(Version.Path & "PCL\Setup.ini", "State", Version.State) - Version.IsStar = ReadIni(Version.Path & "PCL\Setup.ini", "IsStar", False) - Version.DisplayType = ReadIni(Path & "PCL\Setup.ini", "DisplayType", McVersionCardType.Auto) - If Version.State <> McVersionState.Error AndAlso - ReadIni(Version.Path & "PCL\Setup.ini", "VersionOriginal", "Unknown") <> "Unknown" Then '旧版本可能没有这一项,导致 Version 不加载(#643) - Dim VersionInfo As New McVersionInfo With { - .FabricVersion = ReadIni(Version.Path & "PCL\Setup.ini", "VersionFabric", ""), - .ForgeVersion = ReadIni(Version.Path & "PCL\Setup.ini", "VersionForge", ""), - .NeoForgeVersion = ReadIni(Version.Path & "PCL\Setup.ini", "VersionNeoForge", ""), - .OptiFineVersion = ReadIni(Version.Path & "PCL\Setup.ini", "VersionOptiFine", ""), - .HasLiteLoader = ReadIni(Version.Path & "PCL\Setup.ini", "VersionLiteLoader", False), - .SortCode = ReadIni(Version.Path & "PCL\Setup.ini", "VersionApiCode", -1), - .McName = ReadIni(Version.Path & "PCL\Setup.ini", "VersionOriginal", "Unknown"), - .McCodeMain = ReadIni(Version.Path & "PCL\Setup.ini", "VersionOriginalMain", -1), - .McCodeSub = ReadIni(Version.Path & "PCL\Setup.ini", "VersionOriginalSub", -1), - .IsApiLoaded = True + Dim Instance As New McInstance(FolderVersions) + InstanceList.Add(Instance) + Instance.Info = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "CustomInfo", "") + If Instance.Info = "" Then Instance.Info = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "Info", Instance.Info) + Instance.Logo = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "Logo", Instance.Logo) + Instance.ReleaseTime = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "ReleaseTime", Instance.ReleaseTime) + Instance.State = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "State", Instance.State) + Instance.IsStar = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "IsStar", False) + Instance.DisplayType = ReadIni(Path & "PCL\Setup.ini", "DisplayType", McInstanceCardType.Auto) + If Instance.State <> McInstanceState.Error AndAlso + ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionVanillaName", "Unknown") <> "Unknown" Then + '读取版本信息 + Dim VersionInfo As New McVersion With { + .Fabric = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionFabric", ""), + .Forge = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionForge", ""), + .NeoForge = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionNeoForge", ""), + .OptiFine = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionOptiFine", ""), + .HasLiteLoader = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionLiteLoader", False), + .VanillaName = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionVanillaName", "Unknown"), + .Vanilla = New Version(ReadIni(Instance.PathVersion & "PCL\Setup.ini", "VersionVanilla", "0.0.0")) } - VersionInfo.HasFabric = VersionInfo.FabricVersion.Any() - VersionInfo.HasForge = VersionInfo.ForgeVersion.Any() - VersionInfo.HasNeoForge = VersionInfo.NeoForgeVersion.Any() - VersionInfo.HasOptiFine = VersionInfo.OptiFineVersion.Any() - Version.Version = VersionInfo + VersionInfo.HasFabric = VersionInfo.Fabric.Any() + VersionInfo.HasForge = VersionInfo.Forge.Any() + VersionInfo.HasNeoForge = VersionInfo.NeoForge.Any() + VersionInfo.HasOptiFine = VersionInfo.OptiFine.Any() + Instance.Version = VersionInfo + Else + Instance.Load() End If '重新检查错误版本 - If Version.State = McVersionState.Error Then - '重新获取版本错误信息 - Dim OldDesc As String = Version.Info - Version.State = McVersionState.Original - Version.Check() + If Instance.State = McInstanceState.Error Then + '重新尝试获取版本信息 + Dim OldDesc As String = Instance.Info + Instance.State = McInstanceState.Original + Instance.Check() '校验错误原因是否改变 - Dim CustomInfo As String = ReadIni(Version.Path & "PCL\Setup.ini", "CustomInfo") - If Version.State = McVersionState.Original OrElse (CustomInfo = "" AndAlso Not OldDesc = Version.Info) Then - Log("[Minecraft] 版本 " & Version.Name & " 的错误状态已变更,新的状态为:" & Version.Info) + Dim CustomInfo As String = ReadIni(Instance.PathVersion & "PCL\Setup.ini", "CustomInfo") + If Instance.State = McInstanceState.Original OrElse (CustomInfo = "" AndAlso Not OldDesc = Instance.Info) Then + Log("[Minecraft] 版本 " & Instance.Name & " 的错误状态已变更,新的状态为:" & Instance.Info) Return Nothing End If End If '校验未加载的版本 - If Version.Logo = "" Then - Log("[Minecraft] 版本 " & Version.Name & " 未被加载") + If Instance.Logo = "" Then + Log("[Minecraft] 版本 " & Instance.Name & " 未被加载") Return Nothing End If @@ -1308,16 +1355,16 @@ OnLoaded: End Try Next - If VersionList.Any Then ResultVersionList.Add(CardType, VersionList) + If InstanceList.Any Then Results.Add(CardType, InstanceList) Next - Return ResultVersionList + Return Results Catch ex As Exception Log(ex, "读取版本缓存失败") Return Nothing End Try End Function - Private Function McVersionListLoadNoCache(Path As String) As Dictionary(Of McVersionCardType, List(Of McVersion)) - Dim VersionList As New List(Of McVersion) + Private Function InitMcInstanceListWithoutCache(Path As String) As Dictionary(Of McInstanceCardType, List(Of McInstance)) + Dim InstanceList As New List(Of McInstance) #Region "循环加载每个版本的信息" For Each Folder As DirectoryInfo In New DirectoryInfo(Path & "versions").GetDirectories @@ -1331,7 +1378,7 @@ OnLoaded: End If Dim VersionFolder As String = Folder.FullName & "\" If File.Exists(VersionFolder & ".pclignore") Then - If IsFirstMcVersionListLoad Then + If IsFirstMcInstanceListLoad Then Log("[Minecraft] 清理残留的忽略项目:" & VersionFolder) '#2781 File.Delete(VersionFolder & ".pclignore") Else @@ -1339,238 +1386,193 @@ OnLoaded: Continue For End If End If - Dim Version As New McVersion(VersionFolder) - VersionList.Add(Version) - Version.Load() + Dim Instance As New McInstance(VersionFolder) + InstanceList.Add(Instance) + Instance.Load() Next #End Region - Dim ResultVersionList As New Dictionary(Of McVersionCardType, List(Of McVersion)) + Dim Results As New Dictionary(Of McInstanceCardType, List(Of McInstance)) #Region "将版本分类到各个卡片" Try '未经过自定义的版本列表 - Dim VersionListOriginal As New Dictionary(Of McVersionCardType, List(Of McVersion)) + Dim InstanceListOriginal As New Dictionary(Of McInstanceCardType, List(Of McInstance)) '单独列出收藏的版本 - Dim StaredVersions As New List(Of McVersion) - For Each Version As McVersion In VersionList.ToList - If Version.IsStar AndAlso Not Version.DisplayType = McVersionCardType.Hidden Then - StaredVersions.Add(Version) - VersionList.Remove(Version) - End If + Dim InstanceStar As New List(Of McInstance) + For Each Instance As McInstance In InstanceList.ToList + If Not Instance.IsStar Then Continue For + If Instance.DisplayType = McInstanceCardType.Hidden Then Continue For + InstanceStar.Add(Instance) + InstanceList.Remove(Instance) Next - If StaredVersions.Any Then VersionListOriginal.Add(McVersionCardType.Star, StaredVersions) + If InstanceStar.Any Then InstanceListOriginal.Add(McInstanceCardType.Star, InstanceStar) '预先筛选出愚人节和错误的版本 - McVersionFilter(VersionList, VersionListOriginal, {McVersionState.Error}, McVersionCardType.Error) - McVersionFilter(VersionList, VersionListOriginal, {McVersionState.Fool}, McVersionCardType.Fool) + McInstanceFilter(InstanceList, InstanceListOriginal, {McInstanceState.Error}, McInstanceCardType.Error) + McInstanceFilter(InstanceList, InstanceListOriginal, {McInstanceState.Fool}, McInstanceCardType.Fool) '筛选 API 版本 - McVersionFilter(VersionList, VersionListOriginal, {McVersionState.Forge, McVersionState.NeoForge, McVersionState.LiteLoader, McVersionState.Fabric}, McVersionCardType.API) + McInstanceFilter(InstanceList, InstanceListOriginal, {McInstanceState.Forge, McInstanceState.NeoForge, McInstanceState.LiteLoader, McInstanceState.Fabric}, McInstanceCardType.API) '将老版本预先分类入不常用,只剩余原版、快照、OptiFine - Dim VersionUseful As New List(Of McVersion) - Dim VersionRubbish As New List(Of McVersion) - McVersionFilter(VersionList, {McVersionState.Old}, VersionRubbish) + Dim InstanceUseful As New List(Of McInstance) + Dim InstanceRubbish As New List(Of McInstance) + McInstanceFilter(InstanceList, {McInstanceState.Old}, InstanceRubbish) '确认最新版本,若为快照则加入常用列表 - Dim LargestVersion As McVersion = Nothing '最新的版本 - For Each Version As McVersion In VersionList - If Version.State = McVersionState.Original OrElse Version.State = McVersionState.Snapshot Then - If LargestVersion Is Nothing OrElse Version.ReleaseTime > LargestVersion.ReleaseTime Then LargestVersion = Version - End If - Next - If LargestVersion IsNot Nothing AndAlso LargestVersion.State = McVersionState.Snapshot Then - VersionUseful.Add(LargestVersion) - VersionList.Remove(LargestVersion) + Dim LargestInstance As McInstance = InstanceList. + Where(Function(v) v.State = McInstanceState.Original OrElse v.State = McInstanceState.Snapshot). + MaxOf(Function(v) v.ReleaseTime) + If LargestInstance IsNot Nothing AndAlso LargestInstance.State = McInstanceState.Snapshot Then + InstanceUseful.Add(LargestInstance) + InstanceList.Remove(LargestInstance) End If '将剩余的快照全部拖进不常用列表 - McVersionFilter(VersionList, {McVersionState.Snapshot}, VersionRubbish) + McInstanceFilter(InstanceList, {McInstanceState.Snapshot}, InstanceRubbish) - '获取每个大版本下最新的原版与 OptiFine - Dim NewerVersion As New Dictionary(Of String, McVersion) - Dim ExistVersion As New List(Of Integer) - For Each Version As McVersion In VersionList - If Version.Version.McCodeMain < 2 Then Continue For '未获取成功的版本 - If Not ExistVersion.Contains(Version.Version.McCodeMain) Then ExistVersion.Add(Version.Version.McCodeMain) - If NewerVersion.ContainsKey(Version.Version.McCodeMain & "-" & Version.State) Then - If Version.Version.HasOptiFine Then - 'OptiFine 根据排序识别号判断 - If Version.Version.SortCode > NewerVersion(Version.Version.McCodeMain & "-" & Version.State).Version.SortCode Then NewerVersion(Version.Version.McCodeMain & "-" & Version.State) = Version - Else - '原版根据发布时间判断 - If Version.ReleaseTime > NewerVersion(Version.Version.McCodeMain & "-" & Version.State).ReleaseTime Then NewerVersion(Version.Version.McCodeMain & "-" & Version.State) = Version - End If + '获取每个 Drop 下最新的原版与 OptiFine + Dim NewerInstance As New Dictionary(Of String, McInstance) + Dim ExistDrops As New List(Of Integer) + For Each Instance As McInstance In InstanceList + If Not Instance.Version.Vaild Then Continue For + If Not ExistDrops.Contains(Instance.Version.Drop) Then ExistDrops.Add(Instance.Version.Drop) + Dim Key As String = Instance.Version.Drop & "-" & Instance.State + If Not NewerInstance.ContainsKey(Key) Then + NewerInstance.Add(Key, Instance) + Continue For + End If + If Instance.Version.HasOptiFine Then + If Instance.Version.OptiFineCode > NewerInstance(Key).Version.OptiFineCode Then NewerInstance(Key) = Instance 'OptiFine 根据版本号判断 Else - NewerVersion.Add(Version.Version.McCodeMain & "-" & Version.State, Version) + If Instance.ReleaseTime > NewerInstance(Key).ReleaseTime Then NewerInstance(Key) = Instance '原版根据发布时间判断 End If Next - '将每个大版本下的最常规版本加入 - For Each Code As Integer In ExistVersion - If NewerVersion.ContainsKey(Code & "-" & McVersionState.OptiFine) AndAlso NewerVersion.ContainsKey(Code & "-" & McVersionState.Original) Then + '将每个 Drop 下的最常规版本加入 + For Each Drop As Integer In ExistDrops + If NewerInstance.ContainsKey(Drop & "-" & McInstanceState.OptiFine) AndAlso NewerInstance.ContainsKey(Drop & "-" & McInstanceState.Original) Then '同时存在 OptiFine 与原版 - Dim OriginalVersion As McVersion = NewerVersion(Code & "-" & McVersionState.Original) - Dim OptiFineVersion As McVersion = NewerVersion(Code & "-" & McVersionState.OptiFine) - If OriginalVersion.Version.McCodeSub > OptiFineVersion.Version.McCodeSub Then + Dim VanillaInstance As McInstance = NewerInstance(Drop & "-" & McInstanceState.Original) + Dim OptiFineInstance As McInstance = NewerInstance(Drop & "-" & McInstanceState.OptiFine) + If VanillaInstance.Version.Drop > OptiFineInstance.Version.Drop Then '仅在原版比 OptiFine 更新时才加入原版 - VersionUseful.Add(OriginalVersion) - VersionList.Remove(OriginalVersion) + InstanceUseful.Add(VanillaInstance) + InstanceList.Remove(VanillaInstance) End If - VersionUseful.Add(OptiFineVersion) - VersionList.Remove(OptiFineVersion) - ElseIf NewerVersion.ContainsKey(Code & "-" & McVersionState.OptiFine) Then + InstanceUseful.Add(OptiFineInstance) + InstanceList.Remove(OptiFineInstance) + ElseIf NewerInstance.ContainsKey(Drop & "-" & McInstanceState.OptiFine) Then '没有原版,直接加入 OptiFine - VersionUseful.Add(NewerVersion(Code & "-" & McVersionState.OptiFine)) - VersionList.Remove(NewerVersion(Code & "-" & McVersionState.OptiFine)) - ElseIf NewerVersion.ContainsKey(Code & "-" & McVersionState.Original) Then + InstanceUseful.Add(NewerInstance(Drop & "-" & McInstanceState.OptiFine)) + InstanceList.Remove(NewerInstance(Drop & "-" & McInstanceState.OptiFine)) + ElseIf NewerInstance.ContainsKey(Drop & "-" & McInstanceState.Original) Then '没有 OptiFine,直接加入原版 - VersionUseful.Add(NewerVersion(Code & "-" & McVersionState.Original)) - VersionList.Remove(NewerVersion(Code & "-" & McVersionState.Original)) + InstanceUseful.Add(NewerInstance(Drop & "-" & McInstanceState.Original)) + InstanceList.Remove(NewerInstance(Drop & "-" & McInstanceState.Original)) End If Next '将剩余的东西添加进去 - VersionRubbish.AddRange(VersionList) - If VersionUseful.Any Then VersionListOriginal.Add(McVersionCardType.OriginalLike, VersionUseful) - If VersionRubbish.Any Then VersionListOriginal.Add(McVersionCardType.Rubbish, VersionRubbish) + InstanceRubbish.AddRange(InstanceList) + If InstanceUseful.Any Then InstanceListOriginal.Add(McInstanceCardType.OriginalLike, InstanceUseful) + If InstanceRubbish.Any Then InstanceListOriginal.Add(McInstanceCardType.Rubbish, InstanceRubbish) '按照自定义版本分类重新添加 - For Each VersionPair In VersionListOriginal - For Each Version As McVersion In VersionPair.Value - Dim RealType = If(Version.DisplayType = 0 OrElse VersionPair.Key = McVersionCardType.Star, VersionPair.Key, Version.DisplayType) - If Not ResultVersionList.ContainsKey(RealType) Then ResultVersionList.Add(RealType, New List(Of McVersion)) - ResultVersionList(RealType).Add(Version) + For Each InstancePair In InstanceListOriginal + For Each Instance As McInstance In InstancePair.Value + Dim RealType = If(Instance.DisplayType = 0 OrElse InstancePair.Key = McInstanceCardType.Star, InstancePair.Key, Instance.DisplayType) + If Not Results.ContainsKey(RealType) Then Results.Add(RealType, New List(Of McInstance)) + Results(RealType).Add(Instance) Next Next Catch ex As Exception - ResultVersionList.Clear() + Results.Clear() Log(ex, "分类版本列表失败", LogLevel.Feedback) End Try #End Region #Region "对卡片与版本进行排序" - '对卡片进行整体排序 - Dim SortedVersionList As New Dictionary(Of McVersionCardType, List(Of McVersion)) - For Each SortRule As String In {McVersionCardType.Star, McVersionCardType.API, McVersionCardType.OriginalLike, McVersionCardType.Rubbish, McVersionCardType.Fool, McVersionCardType.Error, McVersionCardType.Hidden} - If ResultVersionList.ContainsKey(SortRule) Then SortedVersionList.Add(SortRule, ResultVersionList(SortRule)) + '卡片排序 + Dim SortedInstanceList As New Dictionary(Of McInstanceCardType, List(Of McInstance)) + For Each SortRule As String In {McInstanceCardType.Star, McInstanceCardType.API, McInstanceCardType.OriginalLike, McInstanceCardType.Rubbish, McInstanceCardType.Fool, McInstanceCardType.Error, McInstanceCardType.Hidden} + If Results.ContainsKey(SortRule) Then SortedInstanceList.Add(SortRule, Results(SortRule)) Next - ResultVersionList = SortedVersionList + Results = SortedInstanceList - '常规版本:快照放在最上面,此后按版本号从高到低排序 - If ResultVersionList.ContainsKey(McVersionCardType.OriginalLike) Then - Dim OldList As List(Of McVersion) = ResultVersionList(McVersionCardType.OriginalLike) - '提取快照 - Dim Snapshot As McVersion = Nothing - For Each Version As McVersion In OldList - If Version.State = McVersionState.Snapshot Then - Snapshot = Version - Exit For - End If - Next - If Not IsNothing(Snapshot) Then OldList.Remove(Snapshot) - '按版本号排序 - Dim NewList As List(Of McVersion) = OldList.OrderByDescending(Function(v) v.Version.McCodeMain).ToList - '回设 - If Not IsNothing(Snapshot) Then NewList.Insert(0, Snapshot) - ResultVersionList(McVersionCardType.OriginalLike) = NewList - End If - - '不常用版本:按发布时间新旧排序,如果不可用则按名称排序 - If ResultVersionList.ContainsKey(McVersionCardType.Rubbish) Then - ResultVersionList(McVersionCardType.Rubbish) = ResultVersionList(McVersionCardType.Rubbish).Sort( - Function(Left As McVersion, Right As McVersion) - Dim LeftYear As Integer = Left.ReleaseTime.Year '+ If(Left.State = McVersionState.Original OrElse Left.Version.HasOptiFine, 100, 0) - Dim RightYear As Integer = Right.ReleaseTime.Year '+ If(Right.State = McVersionState.Original OrElse Left.Version.HasOptiFine, 100, 0) - If LeftYear > 2000 AndAlso RightYear > 2000 Then - If LeftYear <> RightYear Then - Return LeftYear > RightYear - Else - Return Left.ReleaseTime > Right.ReleaseTime - End If - ElseIf LeftYear > 2000 AndAlso RightYear < 2000 Then - Return True - ElseIf LeftYear < 2000 AndAlso RightYear > 2000 Then - Return False - Else - Return Left.Name > Right.Name - End If + '版本排序 + For Each CardType In {McInstanceCardType.Star, McInstanceCardType.API, McInstanceCardType.OriginalLike, McInstanceCardType.Rubbish, McInstanceCardType.Fool} + If Not Results.ContainsKey(CardType) Then Continue For + Dim GetComponentCode = + Function(Instance As McInstance) As Integer + If Instance.Version.ForgelikeCode > 0 Then Return Instance.Version.ForgelikeCode + If Instance.Version.HasOptiFine Then Return Instance.Version.OptiFineCode + Return 0 + End Function + Results(CardType) = Results(CardType).SortByComparison( + Function(Left As McInstance, Right As McInstance) + '发布时间 + If (Left.ReleaseTime.Year >= 2000 OrElse Right.ReleaseTime.Year >= 2000) AndAlso + Left.ReleaseTime <> Right.ReleaseTime Then Return Left.ReleaseTime > Right.ReleaseTime + '附加组件种类 + If Left.Version.HasFabric <> Right.Version.HasFabric Then Return Left.Version.HasFabric + If Left.Version.HasNeoForge <> Right.Version.HasNeoForge Then Return Left.Version.HasNeoForge + If Left.Version.HasForge <> Right.Version.HasForge Then Return Left.Version.HasForge + If Left.Version.HasOptiFine <> Right.Version.HasOptiFine Then Return Left.Version.HasOptiFine + If Left.Version.HasLiteLoader <> Right.Version.HasLiteLoader Then Return Left.Version.HasLiteLoader + '附加组件版本 + If GetComponentCode(Left) <> GetComponentCode(Right) Then Return GetComponentCode(Left) > GetComponentCode(Right) + '名称 + Return Left.Name > Right.Name End Function) - End If - - 'API 版本:优先按版本排序,此后【先放 Fabric,再放 Neo/Forge(按版本号从高到低排序),最后放 LiteLoader(按名称排序)】 - If ResultVersionList.ContainsKey(McVersionCardType.API) Then - ResultVersionList(McVersionCardType.API) = ResultVersionList(McVersionCardType.API).Sort( - Function(Left As McVersion, Right As McVersion) - Dim Basic = VersionSortInteger(Left.Version.McName, Right.Version.McName) - If Basic <> 0 Then - Return Basic > 0 - Else - If Left.Version.HasFabric Xor Right.Version.HasFabric Then - Return Left.Version.HasFabric - ElseIf Left.Version.HasNeoForge Xor Right.Version.HasNeoForge Then - Return Left.Version.HasNeoForge - ElseIf Left.Version.HasForge Xor Right.Version.HasForge Then - Return Left.Version.HasForge - ElseIf Not Left.Version.SortCode <> Right.Version.SortCode Then - Return Left.Version.SortCode > Right.Version.SortCode - Else - Return Left.Name > Right.Name - End If - End If - End Function) - End If + Next #End Region #Region "保存卡片缓存" - WriteIni(Path & "PCL.ini", "CardCount", ResultVersionList.Count) - For i = 0 To ResultVersionList.Count - 1 - WriteIni(Path & "PCL.ini", "CardKey" & (i + 1), ResultVersionList.Keys(i)) + WriteIni(Path & "PCL.ini", "CardCount", Results.Count) + For i = 0 To Results.Count - 1 + WriteIni(Path & "PCL.ini", "CardKey" & (i + 1), Results.Keys(i)) Dim Value As String = "" - For Each Version As McVersion In ResultVersionList.Values(i) - Value += Version.Name & ":" + For Each Instance As McInstance In Results.Values(i) + Value += Instance.Name & ":" Next WriteIni(Path & "PCL.ini", "CardValue" & (i + 1), Value) Next #End Region - Return ResultVersionList + Return Results End Function ''' ''' 筛选特定种类的版本,并直接添加为卡片。 ''' - ''' 用于筛选的列表。 + ''' 用于筛选的列表。 ''' 需要筛选出的版本类型。-2 代表隐藏的版本。 ''' 卡片的名称。 - Private Sub McVersionFilter(ByRef VersionList As List(Of McVersion), ByRef Target As Dictionary(Of McVersionCardType, List(Of McVersion)), Formula As McVersionState(), CardType As McVersionCardType) - Dim KeepList = VersionList.Where(Function(v) Formula.Contains(v.State)).ToList + Private Sub McInstanceFilter(ByRef InstanceList As List(Of McInstance), ByRef Target As Dictionary(Of McInstanceCardType, List(Of McInstance)), Formula As McInstanceState(), CardType As McInstanceCardType) + Dim KeepList = InstanceList.Where(Function(v) Formula.Contains(v.State)).ToList '加入版本列表,并从剩余中删除 If KeepList.Any Then Target.Add(CardType, KeepList) - For Each Version As McVersion In KeepList - VersionList.Remove(Version) - Next + InstanceList = InstanceList.Except(KeepList).ToList() End If End Sub ''' ''' 筛选特定种类的版本,并增加入一个已有列表中。 ''' - ''' 用于筛选的列表。 + ''' 用于筛选的列表。 ''' 需要筛选出的版本类型。-2 代表隐藏的版本。 ''' 传入需要增加入的列表。 - Private Sub McVersionFilter(ByRef VersionList As List(Of McVersion), Formula As McVersionState(), ByRef KeepList As List(Of McVersion)) - KeepList.AddRange(VersionList.Where(Function(v) Formula.Contains(v.State))) + Private Sub McInstanceFilter(ByRef InstanceList As List(Of McInstance), Formula As McInstanceState(), ByRef KeepList As List(Of McInstance)) + KeepList.AddRange(InstanceList.Where(Function(v) Formula.Contains(v.State))) '加入版本列表,并从剩余中删除 If KeepList.Any Then - For Each Version As McVersion In KeepList - VersionList.Remove(Version) - Next + InstanceList = InstanceList.Except(KeepList).ToList() End If End Sub - Public Enum McVersionCardType + Public Enum McInstanceCardType Star = -1 Auto = 0 '仅用于强制版本分类的自动 Hidden = 1 @@ -1639,9 +1641,9 @@ OnLoaded: Case "Mojang", "Ms" Url = "https://sessionserver.mojang.com/session/minecraft/profile/" Case "Nide" - Url = "https://auth.mc-user.com:233/" & If(McVersionCurrent Is Nothing, Setup.Get("CacheNideServer"), Setup.Get("VersionServerNide", Version:=McVersionCurrent)) & "/sessionserver/session/minecraft/profile/" + Url = "https://auth.mc-user.com:233/" & If(McInstanceSelected Is Nothing, Setup.Get("CacheNideServer"), Setup.Get("VersionServerNide", Instance:=McInstanceSelected)) & "/sessionserver/session/minecraft/profile/" Case "Auth" - Url = If(McVersionCurrent Is Nothing, Setup.Get("CacheAuthServerServer"), Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent)) & "/sessionserver/session/minecraft/profile/" + Url = If(McInstanceSelected Is Nothing, Setup.Get("CacheAuthServerServer"), Setup.Get("VersionServerAuthServer", Instance:=McInstanceSelected)) & "/sessionserver/session/minecraft/profile/" Case Else Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null")) End Select @@ -1825,51 +1827,51 @@ OnLoaded: ''' ''' 递归获取 Minecraft 某一版本的完整支持库列表。 ''' - Public Function McLibListGet(Version As McVersion, IncludeVersionJar As Boolean) As List(Of McLibToken) + Public Function McLibListGet(Instance As McInstance, IncludeMainJar As Boolean) As List(Of McLibToken) '获取当前支持库列表 - Log("[Minecraft] 获取支持库列表:" & Version.Name) - Dim Result = McLibListGetWithJson(Version.JsonObject) + Log("[Minecraft] 获取支持库列表:" & Instance.Name) + Dim Result = McLibListGetWithJson(Instance.JsonObject) '需要添加原版 Jar - If IncludeVersionJar Then - Dim RealVersion As McVersion - Dim RequiredJar As String = Version.JsonObject("jar")?.ToString - If Version.IsHmclFormatJson OrElse RequiredJar Is Nothing Then + If IncludeMainJar Then + Dim RealInstance As McInstance + Dim RequiredJar As String = Instance.JsonObject("jar")?.ToString + If Instance.IsHmclFormatJson OrElse RequiredJar Is Nothing Then 'HMCL 项直接使用自身的 Jar '根据 Inherit 获取最深层版本 - Dim OriginalVersion As McVersion = Version + Dim OriginalInstance As McInstance = Instance '1.17+ 的 Forge 不寻找 Inherit - If Not ((Version.Version.HasForge OrElse Version.Version.HasNeoForge) AndAlso Version.Version.McCodeMain >= 17) Then - Do Until OriginalVersion.InheritVersion = "" - If OriginalVersion.InheritVersion = OriginalVersion.Name Then Exit Do - OriginalVersion = New McVersion(PathMcFolder & "versions\" & OriginalVersion.InheritVersion & "\") + If Not (Instance.Version.HasForgelike AndAlso Instance.Version.Vanilla.Major >= 17) Then + Do Until OriginalInstance.InheritName = "" + If OriginalInstance.InheritName = OriginalInstance.Name Then Exit Do + OriginalInstance = New McInstance(McFolderSelected & "versions\" & OriginalInstance.InheritName & "\") Loop End If - '需要新建对象,否则后面的 Check 会导致 McVersionCurrent 的 State 变回 Original + '需要新建对象,否则后面的 Check 会导致 McInstanceCurrent 的 State 变回 Original '复现:启动一个 Snapshot 版本 - RealVersion = New McVersion(OriginalVersion.Path) + RealInstance = New McInstance(OriginalInstance.PathVersion) Else 'Json 已提供 Jar 字段,使用该字段的信息 - RealVersion = New McVersion(RequiredJar) + RealInstance = New McInstance(RequiredJar) End If Dim ClientUrl As String, ClientSHA1 As String '判断需求的版本是否存在 '不能调用 RealVersion.Check(),可能会莫名其妙地触发 CheckPermission 正被另一进程使用,导致误判前置不存在 - If Not File.Exists(RealVersion.Path & RealVersion.Name & ".json") Then - RealVersion = Version - Log("[Minecraft] 可能缺少前置版本 " & RealVersion.Name & ",找不到对应的 json 文件", LogLevel.Debug) + If Not File.Exists(RealInstance.PathVersion & RealInstance.Name & ".json") Then + RealInstance = Instance + Log("[Minecraft] 可能缺少前置版本 " & RealInstance.Name & ",找不到对应的 json 文件", LogLevel.Debug) End If '获取详细下载信息 - If RealVersion.JsonObject("downloads") IsNot Nothing AndAlso RealVersion.JsonObject("downloads")("client") IsNot Nothing Then - ClientUrl = RealVersion.JsonObject("downloads")("client")("url") - ClientSHA1 = RealVersion.JsonObject("downloads")("client")("sha1") + If RealInstance.JsonObject("downloads") IsNot Nothing AndAlso RealInstance.JsonObject("downloads")("client") IsNot Nothing Then + ClientUrl = RealInstance.JsonObject("downloads")("client")("url") + ClientSHA1 = RealInstance.JsonObject("downloads")("client")("sha1") Else ClientUrl = Nothing ClientSHA1 = Nothing End If '把所需的原版 Jar 添加进去 - Result.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1}) + Result.Add(New McLibToken With {.LocalPath = RealInstance.PathVersion & RealInstance.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1}) End If Return Result @@ -1878,7 +1880,7 @@ OnLoaded: ''' 获取 Minecraft 某一版本忽视继承的支持库列表,即结果中没有继承项。 ''' Public Function McLibListGetWithJson(JsonObject As JObject, Optional KeepSameNameDifferentVersionResult As Boolean = False, Optional CustomMcFolder As String = Nothing) As List(Of McLibToken) - CustomMcFolder = If(CustomMcFolder, PathMcFolder) + CustomMcFolder = If(CustomMcFolder, McFolderSelected) '转换为 LibToken Dim BasicArray As New List(Of McLibToken) @@ -1974,7 +1976,7 @@ OnLoaded: ResultArray.Add(Key & GetUuid(), BasicArray(i)) Else Log($"[Minecraft] 发现重复的支持库:{BasicArray(i)} ({BasicArrayVersion}) 与 {ResultArray(Key)} ({ResultArrayVersion}),已忽略其中之一") - If VersionSortBoolean(BasicArrayVersion, ResultArrayVersion) Then + If CompareVersionGE(BasicArrayVersion, ResultArrayVersion) Then ResultArray(Key) = BasicArray(i) End If End If @@ -1986,58 +1988,59 @@ OnLoaded: End Function ''' - ''' 获取版本缺失的支持库文件所对应的 NetTaskFile。 + ''' 获取版本所需支持库文件的 NetFile。 ''' - Public Function McLibFix(Version As McVersion) As List(Of NetFile) - If Not Version.IsLoaded Then Version.Load() + Public Function McLibNetFilesFromInstance(Instance As McInstance) As List(Of NetFile) + If Not Instance.IsLoaded Then Instance.Load() Dim Result As New List(Of NetFile) '更新此方法时需要同步更新 Forge 新版自动安装方法! '主 Jar 文件 Try - Dim MainJar As NetFile = DlClientJarGet(Version, True) + Dim MainJar As NetFile = DlClientJarGet(Instance, True) If MainJar IsNot Nothing Then Result.Add(MainJar) Catch ex As Exception Log(ex, "版本缺失主 jar 文件所必须的信息", LogLevel.Developer) End Try 'Library 文件 - Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False))) + Result.AddRange(McLibNetFilesFromTokens(McLibListGet(Instance, False))) '统一通行证文件 - If Setup.Get("VersionServerLogin", Version:=Version) = 3 Then - Dim TargetFile = PathAppdata & "nide8auth.jar" - Dim DownloadInfo As JObject = Nothing - '获取下载信息 + If Setup.Get("VersionServerLogin", Instance:=Instance) = 3 Then + Dim TargetFile As String = PathAppdata & "nide8auth.jar" Try - Log("[Minecraft] 开始获取统一通行证下载信息") '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/ - DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version), RequireJson:=True)) + Log("[Minecraft] 开始获取统一通行证下载信息") + Dim DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Instance:=Instance), RequireJson:=True)) + Result.Add(New NetFile( + Urls:={"https://login.mc-user.com:233/index/jar"}, + LocalPath:=TargetFile, + Checker:=New FileChecker(Hash:=DownloadInfo("jarHash").ToString))) Catch ex As Exception - Log(ex, "获取统一通行证下载信息失败") - End Try - '校验文件 - If DownloadInfo IsNot Nothing Then - Dim Checker As New FileChecker(Hash:=DownloadInfo("jarHash").ToString) - If Checker.Check(TargetFile) IsNot Nothing Then - '开始下载 - Log("[Minecraft] 统一通行证需要更新:Hash - " & Checker.Hash, LogLevel.Developer) - Result.Add(New NetFile({"https://login.mc-user.com:233/index/jar"}, TargetFile, Checker)) + If File.Exists(TargetFile) Then + Log(ex, "获取统一通行证下载信息失败") + Else + Throw New Exception("获取统一通行证下载信息失败", ex) End If - End If + End Try End If 'Authlib-Injector 文件 - If Setup.Get("VersionServerLogin", Version:=Version) = 4 Then + If Setup.Get("VersionServerLogin", Instance:=Instance) = 4 Then Dim TargetFile = PathPure & "authlib-injector.jar" - Dim DownloadInfo As JObject = Nothing - '获取下载信息 Try Log("[Minecraft] 开始获取 Authlib-Injector 下载信息") - DownloadInfo = GetJson(NetRequestByClientRetry( + Dim DownloadInfo = GetJson(NetRequestByClientRetry( "https://authlib-injector.yushi.moe/artifact/latest.json", BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json", RequireJson:=True)) + Dim DownloadAddress As String = DownloadInfo("download_url").ToString. + Replace("bmclapi2.bangbang93.com/mirrors/authlib-injector", "authlib-injector.yushi.moe") + Result.Add(New NetFile({ + DownloadAddress, + DownloadAddress.Replace("authlib-injector.yushi.moe", "bmclapi2.bangbang93.com/mirrors/authlib-injector") + }, TargetFile, New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString))) Catch ex As Exception If File.Exists(TargetFile) Then Log(ex, "获取 Authlib-Injector 下载信息失败") @@ -2045,25 +2048,11 @@ OnLoaded: Throw New Exception("获取 Authlib-Injector 下载信息失败", ex) End If End Try - '校验文件 - If DownloadInfo IsNot Nothing Then - Dim Checker As New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString) - If Checker.Check(TargetFile) IsNot Nothing Then - '开始下载 - Dim DownloadAddress As String = DownloadInfo("download_url").ToString. - Replace("bmclapi2.bangbang93.com/mirrors/authlib-injector", "authlib-injector.yushi.moe") - Log("[Minecraft] Authlib-Injector 需要更新:" & DownloadAddress, LogLevel.Developer) - Result.Add(New NetFile({ - DownloadAddress, - DownloadAddress.Replace("authlib-injector.yushi.moe", "bmclapi2.bangbang93.com/mirrors/authlib-injector") - }, TargetFile, New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString))) - End If - End If End If '跳过校验 - If ShouldIgnoreFileCheck(Version) Then - Log("[Minecraft] 用户要求尽量忽略文件检查,这可能会保留有误的文件") + If ShouldIgnoreFileCheck(Instance) Then + Log("[Minecraft] 设置要求尽量忽略文件检查,这可能会保留有误的文件", LogLevel.Debug) Result = Result.Where( Function(f) If File.Exists(f.LocalPath) Then @@ -2078,22 +2067,21 @@ OnLoaded: Return Result End Function ''' - ''' 将 McLibToken 列表转换为 NetFile。无需下载的文件会被自动过滤。 + ''' 将 McLibToken 列表转换为 NetFile。 ''' - Public Function McLibFixFromLibToken(Libs As List(Of McLibToken), Optional CustomMcFolder As String = Nothing) As List(Of NetFile) - CustomMcFolder = If(CustomMcFolder, PathMcFolder) + Public Function McLibNetFilesFromTokens(Libs As List(Of McLibToken), Optional CustomMcFolder As String = Nothing) As List(Of NetFile) + CustomMcFolder = If(CustomMcFolder, McFolderSelected) Dim Result As New List(Of NetFile) '获取 For Each Token As McLibToken In Libs - '检查文件 + '检查器 Dim Checker As FileChecker If Token.Name.ContainsF("labymod") Then Checker = New FileChecker '不检查 LabyMod 的文件,它们提供的文件校验信息是错的(#3225) Else Checker = New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1) End If - If Checker.Check(Token.LocalPath) Is Nothing Then Continue For - '文件不符合,添加下载 + 'URL Dim Urls As New List(Of String) If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then '特判修复 Forge 部分 universal 文件缺失 URL(#5455) Token.Url = "https://maven.minecraftforge.net" & Token.LocalPath.Replace(CustomMcFolder & "libraries", "").Replace("\", "/") @@ -2141,7 +2129,7 @@ OnLoaded: ''' 原始地址,如 com.mumfrey:liteloader:1.12.2-SNAPSHOT。 ''' 是否包含 Lib 文件夹头部,若不包含,则会类似以 com\xxx\ 开头。 Public Function McLibGet(Original As String, Optional WithHead As Boolean = True, Optional IgnoreLiteLoader As Boolean = False, Optional CustomMcFolder As String = Nothing) As String - CustomMcFolder = If(CustomMcFolder, PathMcFolder) + CustomMcFolder = If(CustomMcFolder, McFolderSelected) '有时候原始的有四段,例如 net.minecraftforge:forge:1.21.4-54.0.34:client '文件夹名应该忽略最后一段(client),但文件名需要保留 '参见 #7306(以及 #5376 也是因为这里没有处理好导致的) @@ -2153,8 +2141,8 @@ OnLoaded: ''' ''' 检查设置,是否应当忽略文件检查? ''' - Public Function ShouldIgnoreFileCheck(Version As McVersion) - Return Setup.Get("VersionAdvanceAssetsV2", Version:=Version) OrElse (Setup.Get("VersionAdvanceAssets", Version:=Version) = 2) + Public Function ShouldIgnoreFileCheck(Instance As McInstance) + Return Setup.Get("VersionAdvanceAssetsV2", Instance:=Instance) OrElse (Setup.Get("VersionAdvanceAssets", Instance:=Instance) = 2) End Function #End Region @@ -2165,17 +2153,17 @@ OnLoaded: ''' ''' 获取某版本资源文件索引的对应 Json 项,详见版本 Json 中的 assetIndex 项。失败会抛出异常。 ''' - Public Function McAssetsGetIndex(Version As McVersion, Optional ReturnLegacyOnError As Boolean = False, Optional CheckURLEmpty As Boolean = False) As JToken + Public Function McAssetsGetIndex(Instance As McInstance, Optional ReturnLegacyOnError As Boolean = False, Optional CheckURLEmpty As Boolean = False) As JToken Dim AssetsName As String Try Do While True - Dim Index As JToken = Version.JsonObject("assetIndex") + Dim Index As JToken = Instance.JsonObject("assetIndex") If Index IsNot Nothing AndAlso Index("id") IsNot Nothing Then Return Index - If Version.JsonObject("assets") IsNot Nothing Then AssetsName = Version.JsonObject("assets").ToString + If Instance.JsonObject("assets") IsNot Nothing Then AssetsName = Instance.JsonObject("assets").ToString If CheckURLEmpty AndAlso Index("url") IsNot Nothing Then Return Index '下一个版本 - If Version.InheritVersion = "" Then Exit Do - Version = New McVersion(PathMcFolder & "versions\" & Version.InheritVersion) + If Instance.InheritName = "" Then Exit Do + Instance = New McInstance(McFolderSelected & "versions\" & Instance.InheritName) Loop Catch End Try @@ -2202,17 +2190,17 @@ OnLoaded: ''' ''' 获取某版本资源文件索引名,优先使用 assetIndex,其次使用 assets。失败会返回 legacy。 ''' - Public Function McAssetsGetIndexName(Version As McVersion) As String + Public Function McAssetsGetIndexName(Instance As McInstance) As String Try Do While True - If Version.JsonObject("assetIndex") IsNot Nothing AndAlso Version.JsonObject("assetIndex")("id") IsNot Nothing Then - Return Version.JsonObject("assetIndex")("id").ToString + If Instance.JsonObject("assetIndex") IsNot Nothing AndAlso Instance.JsonObject("assetIndex")("id") IsNot Nothing Then + Return Instance.JsonObject("assetIndex")("id").ToString End If - If Version.JsonObject("assets") IsNot Nothing Then - Return Version.JsonObject("assets").ToString + If Instance.JsonObject("assets") IsNot Nothing Then + Return Instance.JsonObject("assets").ToString End If - If Version.InheritVersion = "" Then Exit Do - Version = New McVersion(PathMcFolder & "versions\" & Version.InheritVersion) + If Instance.InheritName = "" Then Exit Do + Instance = New McInstance(McFolderSelected & "versions\" & Instance.InheritName) Loop Catch ex As Exception Log(ex, "获取资源文件索引名失败") @@ -2246,27 +2234,27 @@ OnLoaded: ''' ''' 获取 Minecraft 的资源文件列表。失败会抛出异常。 ''' - Private Function McAssetsListGet(Version As McVersion) As List(Of McAssetsToken) - Dim IndexName = McAssetsGetIndexName(Version) + Private Function McAssetsListGet(Instance As McInstance) As List(Of McAssetsToken) + Dim IndexName = McAssetsGetIndexName(Instance) Try '初始化 - If Not File.Exists($"{PathMcFolder}assets\indexes\{IndexName}.json") Then Throw New FileNotFoundException("未找到 Asset Index", PathMcFolder & "assets\indexes\" & IndexName & ".json") + If Not File.Exists($"{McFolderSelected}assets\indexes\{IndexName}.json") Then Throw New FileNotFoundException("未找到 Asset Index", McFolderSelected & "assets\indexes\" & IndexName & ".json") Dim Result As New List(Of McAssetsToken) - Dim Json As JObject = GetJson(ReadFile($"{PathMcFolder}assets\indexes\{IndexName}.json")) + Dim Json As JObject = GetJson(ReadFile($"{McFolderSelected}assets\indexes\{IndexName}.json")) '读取列表 For Each File As JProperty In Json("objects").Children Dim LocalPath As String If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToObject(Of Boolean) Then 'Remap - LocalPath = $"{Version.PathIndie}resources\{File.Name}" + LocalPath = $"{Instance.PathIndie}resources\{File.Name}" ElseIf Json("virtual") IsNot Nothing AndAlso Json("virtual").ToObject(Of Boolean) Then 'Virtual - LocalPath = $"{PathMcFolder}assets\virtual\legacy\{File.Name}" + LocalPath = $"{McFolderSelected}assets\virtual\legacy\{File.Name}" Else '正常 - LocalPath = $"{PathMcFolder}assets\objects\{Left(File.Value("hash").ToString, 2)}\{File.Value("hash")}" + LocalPath = $"{McFolderSelected}assets\objects\{Left(File.Value("hash").ToString, 2)}\{File.Value("hash")}" End If Result.Add(New McAssetsToken With { .LocalPath = LocalPath.Replace("/", "\"), @@ -2283,16 +2271,24 @@ OnLoaded: End Try End Function - '获取缺失列表 ''' - ''' 获取版本缺失的资源文件所对应的 NetTaskFile。 + ''' 获取版本资源文件所对应的 NetFile。 ''' - Public Function McAssetsFixList(Version As McVersion, CheckHash As Boolean, Optional ByRef ProgressFeed As LoaderBase = Nothing) As List(Of NetFile) + Public Function McAssetsFixList(Instance As McInstance, CheckHash As Boolean, Optional ByRef ProgressFeed As LoaderBase = Nothing) As List(Of NetFile) + '如果需要检查 Hash,则留到下载时处理,以借助多线程加快检查速度 + If CheckHash Then + Return McAssetsListGet(Instance). + Select(Function(Token As McAssetsToken) New NetFile( + DlSourceAssetsGet($"https://resources.download.minecraft.net/{Left(Token.Hash, 2)}/{Token.Hash}"), + LocalPath:=Token.LocalPath, + Checker:=New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), + Hash:=Token.Hash))).ToList + End If + '如果不检查 Hash,则立即处理 Dim Result As New List(Of NetFile) - Dim AssetsList As List(Of McAssetsToken) Try - AssetsList = McAssetsListGet(Version) + AssetsList = McAssetsListGet(Instance) Dim Token As McAssetsToken If ProgressFeed IsNot Nothing Then ProgressFeed.Progress = 0.04 For i = 0 To AssetsList.Count - 1 @@ -2301,8 +2297,7 @@ OnLoaded: If ProgressFeed IsNot Nothing Then ProgressFeed.Progress = 0.05 + 0.94 * i / AssetsList.Count '检查文件是否存在 Dim File As New FileInfo(Token.LocalPath) - If File.Exists AndAlso (Token.Size = 0 OrElse Token.Size = File.Length) AndAlso - (Not CheckHash OrElse Token.Hash Is Nothing OrElse Token.Hash = GetFileSHA1(Token.LocalPath)) Then Continue For + If File.Exists AndAlso (Token.Size = 0 OrElse Token.Size = File.Length) Then Continue For '文件不存在,添加下载 Result.Add(New NetFile(DlSourceAssetsGet($"https://resources.download.minecraft.net/{Left(Token.Hash, 2)}/{Token.Hash}"), Token.LocalPath, New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.Hash))) Next @@ -2310,7 +2305,6 @@ OnLoaded: Log(ex, "获取版本缺失的资源文件下载列表失败") End Try If ProgressFeed IsNot Nothing Then ProgressFeed.Progress = 0.99 - Return Result End Function @@ -2342,7 +2336,7 @@ OnLoaded: '下载 RunInUi( Sub() - PageDownloadInstall.McVersionWaitingForSelect = VersionName + PageDownloadInstall.VersionWaitingSelect = VersionName FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall) End Sub) End If @@ -2353,17 +2347,19 @@ OnLoaded: End Sub ''' - ''' 比较两个版本名的排序,若 Left 较新或相同则返回 True(Left >= Right)。无法比较两个 Pre 的大小。 - ''' 支持的格式范例:未知版本, 1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6 + ''' 比较两个版本名;等同 Left >= Right。 + ''' 无法比较两个预发布版的大小。 + ''' 支持的格式:未知版本, 1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6 ''' - Public Function VersionSortBoolean(Left As String, Right As String) As Boolean - Return VersionSortInteger(Left, Right) >= 0 + Public Function CompareVersionGE(Left As String, Right As String) As Boolean + Return CompareVersion(Left, Right) >= 0 End Function ''' - ''' 比较两个版本名的排序,若 Left 较新则返回 1,相同则返回 0,Right 较新则返回 -1。 - ''' 支持的格式范例:未知版本, 1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6 + ''' 比较两个版本名,若 Left 较新则返回 1,相同则返回 0,Right 较新则返回 -1;等同 Left - Right。 + ''' 无法比较两个预发布版的大小。 + ''' 支持的格式:未知版本, 26.1-snapshot-1,1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6 ''' - Public Function VersionSortInteger(Left As String, Right As String) As Integer + Public Function CompareVersion(Left As String, Right As String) As Integer If Left = "未知版本" OrElse Right = "未知版本" Then If Left = "未知版本" AndAlso Right <> "未知版本" Then Return 1 If Left = "未知版本" AndAlso Right = "未知版本" Then Return 0 @@ -2377,25 +2373,23 @@ OnLoaded: While True '两边均缺失,感觉是一个东西 If Lefts.Count - 1 < i AndAlso Rights.Count - 1 < i Then - If Left > Right Then - Return 1 - ElseIf Left < Right Then - Return -1 - Else - Return 0 - End If + If Left > Right Then Return 1 + If Left < Right Then Return -1 + Return 0 End If '确定两边的数值 - Dim LeftValue As String = If(Lefts.Count - 1 < i, "-1", Lefts(i)) - Dim RightValue As String = If(Rights.Count - 1 < i, "-1", Rights(i)) + Dim LeftValue As String = If(Lefts.Count - 1 < i, 0, Lefts(i)) + Dim RightValue As String = If(Rights.Count - 1 < i, 0, Rights(i)) If LeftValue = RightValue Then GoTo NextEntry - If LeftValue = "pre" OrElse LeftValue = "snapshot" Then LeftValue = "-3" - If LeftValue = "rc" Then LeftValue = "-2" - If LeftValue = "experimental" Then LeftValue = "-4" + If LeftValue = "rc" Then LeftValue = -1 + If LeftValue = "pre" Then LeftValue = -2 + If LeftValue = "snapshot" Then LeftValue = -3 + If LeftValue = "experimental" Then LeftValue = -4 Dim LeftValValue = Val(LeftValue) - If RightValue = "pre" OrElse RightValue = "snapshot" Then RightValue = "-3" - If RightValue = "rc" Then RightValue = "-2" - If RightValue = "experimental" Then RightValue = "-4" + If RightValue = "rc" Then RightValue = -1 + If RightValue = "pre" Then RightValue = -2 + If RightValue = "snapshot" Then RightValue = -3 + If RightValue = "experimental" Then RightValue = -4 Dim RightValValue = Val(RightValue) If LeftValValue = 0 AndAlso RightValValue = 0 Then '如果没有数值则直接比较字符串 @@ -2424,17 +2418,10 @@ NextEntry: Public Class VersionComparer Implements IComparer(Of String) Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare - Return VersionSortInteger(x, y) + Return CompareVersion(x, y) End Function End Class - ''' - ''' 判断版本名是否类似正式版。 - ''' - Public Function IsVersionNameLikeRelease(VerName As String) As Boolean - Return VerName.StartsWithF("1.") AndAlso Not (VerName.Contains("w") OrElse VerName.Contains("pre") OrElse VerName.Contains("rc") OrElse VerName.Contains("-")) - End Function - ''' ''' 打码字符串中的 AccessToken。 ''' diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb index 764bafb..5cb1c84 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb @@ -1,7 +1,7 @@ Imports System.IO.Compression Public Module ModMod - Private Const LocalModCacheVersion As Integer = 8 + Private Const LocalModCacheVersion As Integer = 10 Public Class McMod @@ -46,10 +46,7 @@ Public Module ModMod ''' Public ReadOnly Property State As McModState Get - Load() - If Not IsFileAvailable Then - Return McModState.Unavailable - ElseIf Path.EndsWithF(".disabled", True) OrElse Path.EndsWithF(".old", True) Then + If Path.EndsWithF(".disabled", True) OrElse Path.EndsWithF(".old", True) Then Return McModState.Disabled Else Return McModState.Fine @@ -59,7 +56,6 @@ Public Module ModMod Public Enum McModState As Integer Fine = 0 Disabled = 1 - Unavailable = 2 End Enum #End Region @@ -67,12 +63,17 @@ Public Module ModMod #Region "信息项" ''' - ''' Mod 的名称。若不可用则为 ModID 或无扩展的文件名。 + ''' 当任何信息在初始化后更新时触发。 ''' - Public Property Name As String + Public Event OnUpdate(sender As McMod) + + ''' + ''' Mod 的可读名称。 + ''' 可能为无扩展的文件名,但不会是 Nothing。 + ''' + Public Property DisplayName As String Get - If _Name Is Nothing Then Load() - If _Name Is Nothing Then _Name = _ModId + If _Name Is Nothing Then _Name = CompFile?.DisplayName If _Name Is Nothing Then _Name = IO.Path.GetFileNameWithoutExtension(Path) Return _Name End Get @@ -87,12 +88,10 @@ Public Module ModMod ''' ''' Mod 的描述信息。 + ''' 可能为 Nothing。 ''' Public Property Description As String Get - If _Description Is Nothing Then Load() - If _Description Is Nothing AndAlso FileUnavailableReason IsNot Nothing Then _Description = FileUnavailableReason.Message - 'If _Description Is Nothing Then _Description = Path Return _Description End Get Set(value As String) @@ -106,11 +105,12 @@ Public Module ModMod Private _Description As String = Nothing ''' - ''' Mod 的版本,不保证符合版本格式规范。 + ''' Mod 的版本。 + ''' 不保证符合版本格式规范,且可能为 Nothing。 ''' Public Property Version As String Get - If _Version Is Nothing Then Load() + If _Version Is Nothing Then _Version = CompFile?.Version Return _Version End Get Set(value As String) @@ -121,167 +121,17 @@ Public Module ModMod End Property Public _Version As String = Nothing - ''' - ''' 用于依赖检查的 ModID。 - ''' - Public Property ModId As String - Get - If _ModId Is Nothing Then Load() - Return _ModId - End Get - Set(value As String) - If value Is Nothing Then Return - value = RegexSeek(value, "[0-9a-zA-Z_-]+") - If value Is Nothing OrElse value.Count <= 1 OrElse Val(value).ToString = value Then Return - If value.ContainsF("name", True) OrElse value.ContainsF("modid", True) Then Return - If Not PossibleModId.Contains(value) Then PossibleModId.Add(value) - If _ModId Is Nothing Then _ModId = value - End Set - End Property - Private _ModId As String = Nothing - ''' - ''' 其他可能的 ModID。 - ''' - Public PossibleModId As New List(Of String) - - ''' - ''' Mod 的主页。 - ''' - Public Property Url As String - Get - If _Url Is Nothing Then Load() - Return _Url - End Get - Set(value As String) - If _Url Is Nothing AndAlso value IsNot Nothing AndAlso value.StartsWithF("http") Then - _Url = value - End If - End Set - End Property - Private _Url As String = Nothing - - ''' - ''' Mod 的作者列表。 - ''' - Public Property Authors As String - Get - If _Authors Is Nothing Then Load() - Return _Authors - End Get - Set(value As String) - If _Authors Is Nothing AndAlso Not String.IsNullOrWhiteSpace(value) Then - _Authors = value - End If - End Set - End Property - Private _Authors As String = Nothing - - ''' - ''' 依赖项,其中包括了 Minecraft 的版本要求。格式为 ModID - VersionRequirement,若无版本要求则为 Nothing。 - ''' - Public ReadOnly Property Dependencies As Dictionary(Of String, String) - Get - Load() - Return _Dependencies - End Get - End Property - Private _Dependencies As New Dictionary(Of String, String) - Private Sub AddDependency(ModID As String, Optional VersionRequirement As String = Nothing) - '确保信息正确 - If ModID Is Nothing OrElse ModID.Count < 2 Then Return - ModID = ModID.ToLower - If ModID = "name" OrElse Val(ModID).ToString = ModID Then Return '跳过 name 与纯数字 id - If VersionRequirement Is Nothing OrElse ((Not VersionRequirement.Contains(".")) AndAlso (Not VersionRequirement.Contains("-"))) OrElse VersionRequirement.Contains("$") Then - VersionRequirement = Nothing - Else - If (Not VersionRequirement.StartsWithF("[")) AndAlso (Not VersionRequirement.StartsWithF("(")) AndAlso (Not VersionRequirement.EndsWithF("]")) AndAlso (Not VersionRequirement.EndsWithF(")")) Then VersionRequirement = "[" & VersionRequirement & ",)" - End If - '向依赖项中添加 - If _Dependencies.ContainsKey(ModID) Then - If _Dependencies(ModID) Is Nothing Then _Dependencies(ModID) = VersionRequirement - Else - _Dependencies.Add(ModID, VersionRequirement) - End If - End Sub - #End Region -#Region "加载步骤标记" - - '1. 进行文件可用性检查 - ' 成功:继续第二步。 - ' 失败:标记 FileUnavailableReason, 并停止后续加载。 - ''' - ''' 是否已进行 Mod 文件的基础加载。(这包括第一步和第二步) - ''' - Private IsLoaded As Boolean = False - ''' - ''' Mod 文件是否可被正常读取。 - ''' - Public ReadOnly Property IsFileAvailable As Boolean - Get - Load() - Return FileUnavailableReason Is Nothing - End Get - End Property - ''' - ''' Mod 文件出错的原因。若无错误,则为 Nothing。 - ''' - Public ReadOnly Property FileUnavailableReason As Exception - Get - Load() - Return _FileUnavailableReason - End Get - End Property - Private _FileUnavailableReason As Exception = Nothing - - '2. 进行 .class 以外的信息获取 - ' 成功:标记 IsInfoWithoutClassAvailable。 - ' 失败:什么也不干。如果需要补充信息的话,检测到 IsInfoWithoutClassAvailable 为 False,会自动继续加载。 - ''' - ''' 是否已在不获取 .class 文件的前提下完成了所需信息的加载。 - ''' - Private IsInfoWithoutClassAvailable As Boolean = False - - '3. 尝试从 .class 文件中获取信息 - ' 成功:标记 IsInfoWithClassAvailable。 - ' 失败:什么也不干。 - ''' - ''' 是否已进行 .class 文件的信息获取。 - ''' - Private IsInfoWithClassLoaded As Boolean = False - ''' - ''' 是否已在 .class 文件中完成了所需信息的加载。 - ''' - Private IsInfoWithClassAvailable As Boolean = False - -#End Region - -#Region "加载" +#Region "从 JAR 获取信息" ''' - ''' 初始化所有数据。 + ''' 从 JAR 文件中获取 Mod 信息。 ''' - Private Sub Init() - _Name = Nothing - _Description = Nothing - _Version = Nothing - _ModId = Nothing - PossibleModId = New List(Of String) - _Dependencies = New Dictionary(Of String, String) - IsLoaded = False - _FileUnavailableReason = Nothing - IsInfoWithClassLoaded = False - IsInfoWithClassAvailable = False - End Sub - - ''' - ''' 进行文件可用性检查与 .class 以外的信息获取。 - ''' - Public Sub Load(Optional ForceReload As Boolean = False) - If IsLoaded AndAlso Not ForceReload Then Return - '初始化 - Init() + Public Sub LoadMetadataFromJar() + Static IsLoaded As Boolean = False + If IsLoaded Then Return + IsLoaded = True Dim Jar As ZipArchive = Nothing Try '基础可用性检查、打开 Jar 文件 @@ -289,24 +139,16 @@ Public Module ModMod If Not File.Exists(Path) Then Throw New FileNotFoundException("未找到 Mod 文件(" & Path & ")") Jar = New ZipArchive(New FileStream(Path, FileMode.Open)) '信息获取 - LookupMetadata(Jar) + LoadMetadataFromJar(Jar) Catch ex As UnauthorizedAccessException Log(ex, "Mod 文件由于无权限无法打开(" & Path & ")", LogLevel.Developer) - _FileUnavailableReason = New UnauthorizedAccessException("没有读取此文件的权限,请尝试右键以管理员身份运行 PCL", ex) Catch ex As Exception Log(ex, "Mod 文件无法打开(" & Path & ")", LogLevel.Developer) - _FileUnavailableReason = ex Finally If Jar IsNot Nothing Then Jar.Dispose() End Try - '完成标记 - IsLoaded = True End Sub - - ''' - ''' 从 jar 文件中获取 Mod 信息。 - ''' - Private Sub LookupMetadata(Jar As ZipArchive) + Private Sub LoadMetadataFromJar(Jar As ZipArchive) #Region "尝试使用 mcmod.info" Try @@ -327,45 +169,9 @@ Public Module ModMod InfoObject = JsonObject("modList")(0) End If '从文件中获取 Mod 信息项 - Name = InfoObject("name") + DisplayName = InfoObject("name") Description = InfoObject("description") Version = InfoObject("version") - Url = InfoObject("url") - ModId = InfoObject("modid") - Dim AuthorJson As JArray = InfoObject("authorList") - If AuthorJson IsNot Nothing Then - Dim Author As New List(Of String) - For Each Token In AuthorJson - Author.Add(Token.ToString) - Next - If Author.Any Then Authors = Join(Author, ", ") - End If - Dim Reqs As JArray = InfoObject("requiredMods") - If Reqs IsNot Nothing Then - For Each Token As String In Reqs - If Not String.IsNullOrEmpty(Token) Then - Token = Token.Substring(Token.IndexOfF(":") + 1) - If Token.Contains("@") Then - AddDependency(Token.Split("@")(0), Token.Split("@")(1)) - Else - AddDependency(Token) - End If - End If - Next - End If - Reqs = InfoObject("dependancies") - If Reqs IsNot Nothing Then - For Each Token As String In Reqs - If Not String.IsNullOrEmpty(Token) Then - Token = Token.Substring(Token.IndexOfF(":") + 1) - If Token.Contains("@") Then - AddDependency(Token.Split("@")(0), Token.Split("@")(1)) - Else - AddDependency(Token) - End If - End If - Next - End If Catch ex As Exception Log(ex, "读取 mcmod.info 时出现未知错误(" & Path & ")", LogLevel.Developer) End Try @@ -383,37 +189,9 @@ Public Module ModMod Dim FabricObject As JObject = GetJson(FabricText) GotFabric: '从文件中获取 Mod 信息项 - If FabricObject.ContainsKey("name") Then Name = FabricObject("name") + If FabricObject.ContainsKey("name") Then DisplayName = FabricObject("name") If FabricObject.ContainsKey("version") Then Version = FabricObject("version") If FabricObject.ContainsKey("description") Then Description = FabricObject("description") - If FabricObject.ContainsKey("id") Then ModId = FabricObject("id") - If FabricObject.ContainsKey("contact") Then Url = If(FabricObject("contact")("homepage"), "") - Dim AuthorJson As JArray = FabricObject("authors") - If AuthorJson IsNot Nothing Then - Dim Author As New List(Of String) - For Each Token In AuthorJson - Author.Add(Token.ToString) - Next - If Author.Any Then Authors = Join(Author, ", ") - End If - 'If (Not FabricObject.ContainsKey("serverSideOnly")) OrElse FabricObject("serverSideOnly")("value").ToObject(Of Boolean) = False Then - ' '添加 Minecraft 依赖 - ' Dim DepMinecraft As String = If(If(FabricObject("acceptedMinecraftVersions") IsNot Nothing, FabricObject("acceptedMinecraftVersions")("value"), ""), "") - ' If DepMinecraft <> "" Then AddDependency("minecraft", DepMinecraft) - ' '添加其他依赖 - ' Dim Deps As String = If(If(FabricObject("dependencies") IsNot Nothing, FabricObject("dependencies")("value"), ""), "") - ' If Deps <> "" Then - ' For Each Dep In Deps.Split(";") - ' If Dep = "" OrElse Not Dep.StartsWithF("required-") Then Continue For - ' Dep = Dep.Substring(Dep.IndexOfF(":") + 1) - ' If Dep.Contains("@") Then - ' AddDependency(Dep.Split("@")(0), Dep.Split("@")(1)) - ' Else - ' AddDependency(Dep) - ' End If - ' Next - ' End If - 'End If '加载成功 GoTo Finished Catch ex As Exception @@ -500,22 +278,9 @@ GotFabric: End If Next If ModEntry Is Nothing OrElse Not ModEntry.ContainsKey("modId") Then Exit Try - ModId = ModEntry("modId") - If _ModId Is Nothing Then Exit Try '设置了无效的 ModID - If ModEntry.ContainsKey("displayName") Then Name = ModEntry("displayName") + If ModEntry.ContainsKey("displayName") Then DisplayName = ModEntry("displayName") If ModEntry.ContainsKey("description") Then Description = ModEntry("description") If ModEntry.ContainsKey("version") Then Version = ModEntry("version") - If TomlData(0).Value.ContainsKey("displayURL") Then Url = TomlData(0).Value("displayURL") - If TomlData(0).Value.ContainsKey("authors") Then Authors = TomlData(0).Value("authors") - For Each TomlSubData In TomlData - If TomlSubData.Key.ToLower = $"dependencies.{ModId.ToLower}" Then - Dim DepEntry As Dictionary(Of String, Object) = TomlSubData.Value - If DepEntry.ContainsKey("modId") AndAlso DepEntry.ContainsKey("mandatory") AndAlso DepEntry("mandatory") AndAlso - DepEntry.ContainsKey("side") AndAlso Not DepEntry("side").ToString.ToLower = "server" Then - AddDependency(DepEntry("modId"), If(DepEntry.ContainsKey("versionRange"), DepEntry("versionRange"), Nothing)) - End If - End If - Next '加载成功 GoTo Finished Catch ex As Exception @@ -551,41 +316,16 @@ GotFabric: Exit Try Got: '从文件中获取 Mod 信息项 - If FmlObject.ContainsKey("useMetadata") AndAlso If(FmlObject("useMetadata")("value"), "").ToString.ToLower = "true" Then - '要求使用 mcmod.info 中的信息 - Dim value As String = FmlObject("modid")("value") - If value Is Nothing Then Exit Try - value = RegexSeek(value.ToLower, "[0-9a-z_]+") - If value IsNot Nothing AndAlso value.ToLower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then - If Not PossibleModId.Contains(value) Then PossibleModId.Add(value) - End If - Exit Try - End If - If FmlObject.ContainsKey("name") Then Name = FmlObject("name")("value") + If FmlObject.ContainsKey("name") Then DisplayName = FmlObject("name")("value") If FmlObject.ContainsKey("version") Then Version = FmlObject("version")("value") - If FmlObject.ContainsKey("modid") Then ModId = FmlObject("modid")("value") - If (Not FmlObject.ContainsKey("serverSideOnly")) OrElse FmlObject("serverSideOnly")("value").ToObject(Of Boolean) = False Then - '添加 Minecraft 依赖 - Dim DepMinecraft As String = If(If(FmlObject("acceptedMinecraftVersions") IsNot Nothing, FmlObject("acceptedMinecraftVersions")("value"), ""), "") - If DepMinecraft <> "" Then AddDependency("minecraft", DepMinecraft) - '添加其他依赖 - Dim Deps As String = If(If(FmlObject("dependencies") IsNot Nothing, FmlObject("dependencies")("value"), ""), "") - If Deps <> "" Then - For Each Dep In Deps.Split(";") - If Dep = "" OrElse Not Dep.StartsWithF("required-") Then Continue For - Dep = Dep.Substring(Dep.IndexOfF(":") + 1) - If Dep.Contains("@") Then - AddDependency(Dep.Split("@")(0), Dep.Split("@")(1)) - Else - AddDependency(Dep) - End If - Next - End If - End If + '加载成功 + GoTo Finished Catch ex As Exception Log(ex, "读取 fml_cache_annotation.json 时出现未知错误(" & Path & ")", LogLevel.Developer) End Try #End Region + Return '没能成功获取任何信息 + Finished: #Region "将 Version 代号转换为 META-INF 中的版本" If _Version = "version" Then @@ -606,6 +346,7 @@ Finished: End If If _Version IsNot Nothing AndAlso Not (_Version.Contains(".") OrElse _Version.Contains("-")) Then Version = Nothing #End Region + RaiseEvent OnUpdate(Me) End Sub @@ -613,11 +354,6 @@ Finished: #Region "网络信息" - ''' - ''' 当任何网络信息更新时触发。 - ''' - Public Event OnCompUpdate(sender As McMod) - ''' ''' 该 Mod 关联的网络项目。 ''' @@ -627,7 +363,7 @@ Finished: End Get Set(value As CompProject) _Comp = value - RaiseEvent OnCompUpdate(Me) + RaiseEvent OnUpdate(Me) End Set End Property Private _Comp As CompProject @@ -646,7 +382,7 @@ Finished: End Get Set(value As CompFile) _UpdateFile = value - RaiseEvent OnCompUpdate(Me) + RaiseEvent OnUpdate(Me) End Set End Property Private _UpdateFile As CompFile @@ -688,7 +424,7 @@ Finished: ''' Public ReadOnly Property CanUpdate As Boolean Get - Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso Not Setup.Get("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version) AndAlso ChangelogUrls.Any() + Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso Not Setup.Get("VersionAdvanceDisableModUpdate", Instance:=PageInstanceLeft.Instance) AndAlso ChangelogUrls.Any() End Get End Property @@ -787,10 +523,10 @@ Finished: #End Region ''' - ''' 是否可能为前置 Mod。 + ''' 是否可能为前置 Mod。目前非常不准确。 ''' Public Function IsPresetMod() As Boolean - Return Not Dependencies.Any() AndAlso Name IsNot Nothing AndAlso (Name.ToLower.Contains("core") OrElse Name.ToLower.Contains("lib")) + Return DisplayName IsNot Nothing AndAlso (DisplayName.ToLower.Contains("core") OrElse DisplayName.ToLower.Contains("lib")) End Function ''' @@ -811,21 +547,21 @@ Finished: Public McModLoader As New LoaderTask(Of String, List(Of McMod))("Mod List Loader", AddressOf McModLoad) Private Sub McModLoad(Loader As LoaderTask(Of String, List(Of McMod))) Try - RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = False) + RunInUiWait(Sub() If FrmInstanceMod IsNot Nothing Then FrmInstanceMod.Load.ShowProgress = False) '等待 Mod 更新完成 - If PageVersionMod.UpdatingVersions.Contains(Loader.Input) Then + If PageInstanceMod.UpdatingInstanceModFolders.Contains(Loader.Input) Then Log($"[Mod] 等待 Mod 更新完成后才能继续加载 Mod 列表:" & Loader.Input) Try - RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在更新 Mod") - Do Until Not PageVersionMod.UpdatingVersions.Contains(Loader.Input) + RunInUiWait(Sub() If FrmInstanceMod IsNot Nothing Then FrmInstanceMod.Load.Text = "正在更新 Mod") + Do Until Not PageInstanceMod.UpdatingInstanceModFolders.Contains(Loader.Input) If Loader.IsAborted Then Return Thread.Sleep(100) Loop Finally - RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在加载 Mod 列表") + RunInUiWait(Sub() If FrmInstanceMod IsNot Nothing Then FrmInstanceMod.Load.Text = "正在加载 Mod 列表") End Try - FrmVersionMod.LoaderRun(LoaderFolderRunType.UpdateOnly) + FrmInstanceMod.LoaderRun(LoaderFolderRunType.UpdateOnly) End If '获取 Mod 文件夹下的可用文件列表 @@ -835,9 +571,9 @@ Finished: For Each File As FileInfo In EnumerateFiles(Loader.Input) If File.DirectoryName.ToLower & "\" <> RawName Then '仅当 Forge 1.13- 且文件夹名与版本号相同时,才加载该子文件夹下的 Mod - If Not (PageVersionLeft.Version IsNot Nothing AndAlso PageVersionLeft.Version.Version.HasForge AndAlso - PageVersionLeft.Version.Version.McCodeMain < 13 AndAlso - File.Directory.Name = $"1.{PageVersionLeft.Version.Version.McCodeMain}.{PageVersionLeft.Version.Version.McCodeSub}") Then + If Not (PageInstanceLeft.Instance IsNot Nothing AndAlso PageInstanceLeft.Instance.Version.HasForge AndAlso + PageInstanceLeft.Instance.Version.Vanilla.Major < 13 AndAlso + File.Directory.Name = $"1.{PageInstanceLeft.Instance.Version.Vanilla.Major}.{PageInstanceLeft.Instance.Version.Vanilla.Build}") Then Continue For End If End If @@ -845,10 +581,6 @@ Finished: Next End If - '确定是否显示进度 - Loader.Progress = 0.05 - If ModFileList.Count > 50 Then RunInUi(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.ShowProgress = True) - '获取本地文件缓存 Dim CachePath As String = PathTemp & "Cache\LocalMod.json" Dim Cache As New JObject @@ -869,13 +601,9 @@ Finished: '加载 Mod 列表 Dim ModList As New List(Of McMod) - Dim ModUpdateList As New List(Of McMod) For Each ModFile As FileInfo In ModFileList - Loader.Progress += 0.94 / ModFileList.Count If Loader.IsAborted Then Return - '加载 McMod 对象 Dim ModEntry As New McMod(ModFile.FullName) - ModEntry.Load() Dim DumpMod As McMod = ModList.FirstOrDefault(Function(m) m.RawFileName = ModEntry.RawFileName) If DumpMod IsNot Nothing Then Dim DisabledMod As McMod = If(DumpMod.State = McMod.McModState.Disabled, DumpMod, ModEntry) @@ -884,31 +612,16 @@ Finished: Continue For Else ModList.Remove(DisabledMod) - ModUpdateList.Remove(DisabledMod) End If End If ModList.Add(ModEntry) - '读取 Comp 缓存 - If ModEntry.State = McMod.McModState.Unavailable Then Continue For - Dim CacheKey = ModEntry.ModrinthHash & PageVersionLeft.Version.Version.McName & GetTargetModLoaders().Join("") - If Cache.ContainsKey(CacheKey) Then - ModEntry.FromJson(Cache(CacheKey)) - '如果缓存中的信息在 6 小时以内更新过,则无需重新获取 - If ModEntry.CompLoaded AndAlso Date.Now - Cache(CacheKey)("Comp")("CacheTime").ToObject(Of Date) < New TimeSpan(6, 0, 0) Then Continue For - End If - ModUpdateList.Add(ModEntry) Next - Loader.Progress = 0.99 - Log($"[Mod] 共有 {ModList.Count} 个 Mod,其中 {ModUpdateList.Where(Function(m) m.Comp Is Nothing).Count} 个需要联网获取信息,{ModUpdateList.Where(Function(m) m.Comp IsNot Nothing).Count} 个需要更新信息") + Log($"[Mod] 共发现 {ModList.Count} 个 Mod") '排序 - ModList = ModList.Sort( - Function(Left As McMod, Right As McMod) As Boolean - If (Left.State = McMod.McModState.Unavailable) <> (Right.State = McMod.McModState.Unavailable) Then - Return Left.State = McMod.McModState.Unavailable - Else - Return Not Right.FileName.CompareTo(Left.FileName) - End If + ModList = ModList.SortByComparison( + Function(Left, Right) As Boolean + Return Not Right.FileName.CompareTo(Left.FileName) End Function) '回设 @@ -916,10 +629,8 @@ Finished: Loader.Output = ModList '开始联网加载 - If ModUpdateList.Any() Then - 'TODO: 添加信息获取中提示 - McModDetailLoader.Start(New KeyValuePair(Of List(Of McMod), JObject)(ModUpdateList, Cache), IsForceRestart:=True) - End If + 'TODO: 添加信息获取中提示 + McModDetailLoader.Start(New KeyValuePair(Of List(Of McMod), JObject)(ModList, Cache), IsForceRestart:=True) Catch ex As Exception Log(ex, "Mod 列表加载失败", LogLevel.Debug) @@ -929,15 +640,31 @@ Finished: '联网加载 Mod 详情 Public McModDetailLoader As New LoaderTask(Of KeyValuePair(Of List(Of McMod), JObject), Integer)("Mod List Detail Loader", AddressOf McModDetailLoad) Private Sub McModDetailLoad(Loader As LoaderTask(Of KeyValuePair(Of List(Of McMod), JObject), Integer)) - Dim Mods As List(Of McMod) = Loader.Input.Key + Dim Mods As New List(Of McMod) Dim Cache As JObject = Loader.Input.Value + '读取 Comp 缓存,获取需要更新的 Mod 列表 + For Each ModEntry As McMod In Loader.Input.Key + If Loader.IsAborted Then Return + Dim CacheKey = ModEntry.ModrinthHash & PageInstanceLeft.Instance.Version.VanillaName & GetTargetModLoaders().Join("") + If Cache.ContainsKey(CacheKey) Then + ModEntry.FromJson(Cache(CacheKey)) + '如果缓存中的信息在 6 小时以内更新过,则无需重新获取 + If ModEntry.CompLoaded AndAlso Date.Now - Cache(CacheKey)("Comp")("CacheTime").ToObject(Of Date) < New TimeSpan(6, 0, 0) Then Continue For + End If + Mods.Add(ModEntry) + Next + Log($"[Mod] 有 {Mods.Where(Function(m) m.Comp Is Nothing).Count} 个 Mod 需要联网获取信息,{Mods.Where(Function(m) m.Comp IsNot Nothing).Count} 个 Mod 需要更新信息") + If Not Mods.Any Then + Loader.Input.Key.Where(Function(m) m.Version Is Nothing).ForEach(Sub(m) m.LoadMetadataFromJar()) '从 JAR 中获取缺失的版本信息(下面有另一个分支) + Return + End If '获取作为检查目标的加载器和版本 '此处不应向下扩展检查的 MC 小版本,例如 Mod 在更新 1.16.5 后,对早期的 1.16.2 版本发布了修补补丁,这会导致 PCL 将 1.16.5 版本的 Mod 降级到 1.16.2 - Dim TargetMcVersion As McVersionInfo = PageVersionLeft.Version.Version + Dim TargetMcVersion As McVersion = PageInstanceLeft.Instance.Version Dim ModLoaders = GetTargetModLoaders() - Dim McVersion = TargetMcVersion.McName + Dim VanillaVersion = TargetMcVersion.VanillaName '开始网络获取 - Log($"[Mod] 目标加载器:{ModLoaders.Join("/")},版本:{McVersion}") + Log($"[Mod] 目标加载器:{ModLoaders.Join("/")},版本:{VanillaVersion}") Dim EndedThreadCount As Integer = 0, IsFailed As Boolean = False Dim CurrentTaskThread As Thread = Thread.CurrentThread '从 Modrinth 获取信息 @@ -961,7 +688,11 @@ Finished: ModrinthMapping(ProjectId).Add(Entry) '记录对应的 CompFile Dim File As New CompFile(ModrinthVersion(Entry.ModrinthHash), CompType.Mod) - If Entry.CompFile Is Nothing OrElse Entry.CompFile.ReleaseDate < File.ReleaseDate Then Entry.CompFile = File + If Entry.CompFile Is Nothing OrElse Entry.CompFile.ReleaseDate < File.ReleaseDate Then + Entry.CompFile = File + Else + Entry.CompFile.Version = File.Version '使用来自 Modrinth 的版本号 + End If Next If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return Log($"[Mod] 需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息") @@ -979,7 +710,7 @@ Finished: '步骤 4:获取更新信息 Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update", HttpMethod.Post, $"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"", - ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json") + ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{VanillaVersion}""]}}", "application/json") For Each Entry In Mods If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.CompFile Is Nothing Then Continue For Dim UpdateFile As New CompFile(ModrinthUpdate(Entry.ModrinthHash), CompType.Mod) @@ -988,11 +719,11 @@ Finished: If Entry.CompFile.ReleaseDate >= UpdateFile.ReleaseDate OrElse Entry.CompFile.Hash = UpdateFile.Hash Then Continue For '设置更新日志与更新文件 If Entry.UpdateFile IsNot Nothing AndAlso UpdateFile.Hash = Entry.UpdateFile.Hash Then '合并 - Entry.ChangelogUrls.Add($"https://modrinth.com/mod/{ModrinthUpdate(Entry.ModrinthHash)("project_id")}/changelog?g={McVersion}") + Entry.ChangelogUrls.Add($"https://modrinth.com/mod/{ModrinthUpdate(Entry.ModrinthHash)("project_id")}/changelog?g={VanillaVersion}") UpdateFile.DownloadUrls.AddRange(Entry.UpdateFile.DownloadUrls) '合并下载源 Entry.UpdateFile = UpdateFile '优先使用 Modrinth 的文件 ElseIf Entry.UpdateFile Is Nothing OrElse UpdateFile.ReleaseDate >= Entry.UpdateFile.ReleaseDate Then '替换 - Entry.ChangelogUrls = New List(Of String) From {$"https://modrinth.com/mod/{ModrinthUpdate(Entry.ModrinthHash)("project_id")}/changelog?g={McVersion}"} + Entry.ChangelogUrls = New List(Of String) From {$"https://modrinth.com/mod/{ModrinthUpdate(Entry.ModrinthHash)("project_id")}/changelog?g={VanillaVersion}"} Entry.UpdateFile = UpdateFile End If Next @@ -1059,9 +790,9 @@ Finished: For Each IndexEntry In ProjectJson("latestFilesIndexes") If IndexEntry("modLoader") Is Nothing OrElse ModLoaders.Single <> IndexEntry("modLoader").ToObject(Of Integer) Then Continue For 'ModLoader 唯一且匹配 Dim IndexVersion As String = IndexEntry("gameVersion") - If IndexVersion <> McVersion Then Continue For 'MC 版本匹配 + If IndexVersion <> VanillaVersion Then Continue For 'MC 版本匹配 '由于 latestFilesIndexes 是按时间从新到老排序的,所以只需取第一个;如果需要检查多个 releaseType 下的文件,将 > -1 改为 = 1,但这应当并不会获取到更新的文件 - If NewestVersion IsNot Nothing AndAlso VersionSortInteger(NewestVersion, IndexVersion) > -1 Then Continue For '只保留最新 MC 版本 + If NewestVersion IsNot Nothing AndAlso CompareVersion(NewestVersion, IndexVersion) > -1 Then Continue For '只保留最新 MC 版本 If NewestVersion <> IndexVersion Then NewestVersion = IndexVersion NewestFileIds.Clear() @@ -1111,6 +842,8 @@ Finished: EndedThreadCount += 1 End Try End Sub, "Mod List Detail Loader CurseForge") + '从 JAR 中获取缺失的版本信息(上面有另一个分支) + Loader.Input.Key.Where(Function(m) m.Version Is Nothing).ForEach(Sub(m) m.LoadMetadataFromJar()) '等待线程结束 Do Until EndedThreadCount = 2 Thread.Sleep(10) @@ -1122,155 +855,25 @@ Finished: If Not Mods.Any() Then Return For Each Entry In Mods Entry.CompLoaded = Not IsFailed - Cache(Entry.ModrinthHash & McVersion & ModLoaders.Join("")) = Entry.ToJson() + Cache(Entry.ModrinthHash & VanillaVersion & ModLoaders.Join("")) = Entry.ToJson() 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) + If FrmInstanceMod?.Filter = PageInstanceMod.FilterType.CanUpdate Then + RunInUi(Sub() FrmInstanceMod?.RefreshUI()) '同步 “可更新” 列表 (#4677) Else - RunInUi(Sub() FrmVersionMod?.RefreshBars()) + RunInUi(Sub() FrmInstanceMod?.RefreshBars()) End If End Sub Public Function GetTargetModLoaders() As List(Of CompModLoaderType) Dim ModLoaders As New List(Of CompModLoaderType) - If PageVersionLeft.Version.Version.HasForge Then ModLoaders.Add(CompModLoaderType.Forge) - If PageVersionLeft.Version.Version.HasNeoForge Then ModLoaders.Add(CompModLoaderType.NeoForge) - If PageVersionLeft.Version.Version.HasFabric Then ModLoaders.Add(CompModLoaderType.Fabric) - If PageVersionLeft.Version.Version.HasLiteLoader Then ModLoaders.Add(CompModLoaderType.LiteLoader) + If PageInstanceLeft.Instance.Version.HasForge Then ModLoaders.Add(CompModLoaderType.Forge) + If PageInstanceLeft.Instance.Version.HasNeoForge Then ModLoaders.Add(CompModLoaderType.NeoForge) + If PageInstanceLeft.Instance.Version.HasFabric Then ModLoaders.Add(CompModLoaderType.Fabric) + If PageInstanceLeft.Instance.Version.HasLiteLoader Then ModLoaders.Add(CompModLoaderType.LiteLoader) If Not ModLoaders.Any() Then ModLoaders.AddRange({CompModLoaderType.Forge, CompModLoaderType.NeoForge, CompModLoaderType.Fabric, CompModLoaderType.LiteLoader, CompModLoaderType.Quilt}) Return ModLoaders End Function -#If DEBUG Then - ''' - ''' 检查 Mod 列表中存在的错误,返回错误信息的集合。 - ''' - Public Function McModCheck(Version As McVersion, Mods As List(Of McMod)) As List(Of String) - Dim Result As New List(Of String) - '令所有 Mod 进行基础检查,并归纳需要检查的 Mod - Dim CurrentModList As New List(Of McMod) - For Each ModEntity In Mods - If Not ModEntity.IsFileAvailable Then - Result.Add("无法读取的 Mod 文件。" & vbCrLf & " - " & ModEntity.Path) - Continue For - End If - If ModEntity.State = McMod.McModState.Fine AndAlso ModEntity.ModId IsNot Nothing Then CurrentModList.Add(ModEntity) - Next - '添加默认依赖 - Dim CurrentDependencies As New Dictionary(Of String, String()) '{DependencyVersion, Path} - If Version.State = McVersionState.Forge Then CurrentDependencies.Add("forge", {Version.Version.ForgeVersion, "Forge"}) - CurrentDependencies.Add("minecraft", {Version.Version.McName, "Minecraft"}) - '检查重复的 Mod,并添加对应的依赖 - For Each ModEntity In CurrentModList - For Each PossibleModId In ModEntity.PossibleModId - If CurrentDependencies.ContainsKey(PossibleModId) Then - If CurrentDependencies(PossibleModId)(2) = 1 Then - Result.Add("重复添加了相同的 Mod,请尝试删除其中一个(ModID:" & PossibleModId & ")。" & vbCrLf & - " - " & ModEntity.FileName & vbCrLf & - " - " & CurrentDependencies(PossibleModId)(1)) - Else - Log("[Minecraft] 由于可能有多个 ModID,跳过疑似的重复项(ModID:" & PossibleModId & ")。" & vbCrLf & - " - " & ModEntity.FileName & vbCrLf & - " - " & CurrentDependencies(PossibleModId)(1), LogLevel.Developer) - End If - Else - CurrentDependencies.Add(PossibleModId, {ModEntity.Version, ModEntity.FileName, ModEntity.PossibleModId.Count}) - End If - Next - Next - '检查依赖 - For Each ModEntity In CurrentModList - Try - For Each Dependency In ModEntity.Dependencies - Dim ReqId As String = Dependency.Key - If ReqId.Count < 2 Then Continue For '确保正常 - If ReqId = ModEntity.ModId Then Continue For '跳过自体引用 - If ReqId = "forgemultipartcbe" Then Continue For '跳过莫名其妙的引用 - If Dependency.Value IsNot Nothing Then - '获取分段后的详细版本信息 - Dim ReqVersion As String = Dependency.Value - Dim ReqVersionHeadCanEqual As Boolean = ReqVersion.StartsWithF("[") - Dim ReqVersionTailCanEqual As Boolean = ReqVersion.EndsWithF("]") - Dim ReqVersionHead As String - Dim ReqVersionTail As String - If ReqVersion.Contains(",") Then - ReqVersionHead = ReqVersion.Split(",")(0).Trim("([ ".ToCharArray()) - ReqVersionTail = ReqVersion.Split(",")(1).Trim("]) ".ToCharArray()) - Else - ReqVersionHead = ReqVersion.Trim("([]) ".ToCharArray()) - ReqVersionTail = ReqVersionHead - If ReqId = "minecraft" AndAlso ReqVersionHead.Split(".").Count = 2 Then - ReqVersionTail = ReqVersionHead.Split(".")(0) & "." & (Val(ReqVersionHead.Split(".")(1)) + 1) - ReqVersionTailCanEqual = False - End If - End If - If ReqVersionHead.StartsWithF("1.") AndAlso ReqVersionHead.Contains("-") Then ReqVersionHead = ReqVersionHead.Substring(ReqVersionHead.LastIndexOfF("-") + 1) - If ReqVersionTail.StartsWithF("1.") AndAlso ReqVersionTail.Contains("-") Then ReqVersionTail = ReqVersionTail.Substring(ReqVersionTail.LastIndexOfF("-") + 1) - '获取报错描述文本 - Dim VersionRequire As String - If ReqVersionHead = ReqVersionTail Then - VersionRequire = "应为 " & ReqVersionHead - ElseIf ReqVersionHead.Contains(".") AndAlso ReqVersionTail.Contains(".") Then - VersionRequire = $"应为 {ReqVersionHead} 至 {ReqVersionTail}" - ElseIf ReqVersionHead.Contains(".") Then - If ReqVersionHeadCanEqual Then - VersionRequire = "最低应为 " & ReqVersionHead - Else - VersionRequire = "应高于 " & ReqVersionHead - End If - ElseIf ReqVersionTail.Contains(".") Then - If ReqVersionTailCanEqual Then - VersionRequire = "最高应为 " & ReqVersionHead - Else - VersionRequire = "应低于 " & ReqVersionHead - End If - Else - VersionRequire = "" - End If - '检查前置 Mod 是否存在,并获取其版本 - If Not CurrentDependencies.ContainsKey(ReqId) Then - Result.Add($"缺少前置 Mod:{ReqId}{If(VersionRequire = "", "", ",其版本" & VersionRequire)}。{vbCrLf} - {ModEntity.FileName}") - Continue For - End If - Dim CurrentVersion As String = If(CurrentDependencies(ReqId)(0), "0.0") - If CurrentVersion.StartsWithF("1.") AndAlso CurrentVersion.Contains("-") Then CurrentVersion = CurrentVersion.Substring(CurrentVersion.LastIndexOfF("-") + 1) - '对比前置 Mod 头部版本 - 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)}", "")) - Continue For - End If - End If - '对比前置 Mod 尾部版本 - 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)}", "")) - Continue For - End If - End If - Else - If Not CurrentDependencies.ContainsKey(Dependency.Key) Then - Result.Add($"缺少前置 Mod:{Dependency.Key}。{vbCrLf} - {ModEntity.FileName}") - Continue For - End If - End If - Next - Catch ex As Exception - 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)}") - End If - Return Result - End Function -#End If - End Module \ No newline at end of file diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb index 5769826..ec94ad6 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb @@ -24,13 +24,13 @@ Public Module ModModpack ''' 必须在工作线程执行。 ''' ''' - Public Function ModpackInstall(File As String, Optional VersionName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String) + Public Function ModpackInstall(File As String, Optional InstanceName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String) Log("[ModPack] 整合包安装请求:" & If(File, "null")) Dim Archive As ZipArchive = Nothing Dim ArchiveBaseFolder As String = "" Try '字符校验 - Dim TargetFolder As String = $"{PathMcFolder}versions\{VersionName}\" + Dim TargetFolder As String = $"{McFolderSelected}versions\{InstanceName}\" If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("游戏路径中不能含有感叹号或分号:" & TargetFolder, HintType.Red) : Throw New CancelledException '获取整合包种类与关键 Json Dim PackType As Integer = -1 @@ -84,7 +84,7 @@ Public Module ModModpack Select Case PackType Case 0 Log("[ModPack] 整合包种类:CurseForge") - Return InstallPackCurseForge(File, Archive, ArchiveBaseFolder, VersionName, Logo) + Return InstallPackCurseForge(File, Archive, ArchiveBaseFolder, InstanceName, Logo) Case 1 Log("[ModPack] 整合包种类:HMCL") Return InstallPackHMCL(File, Archive, ArchiveBaseFolder) @@ -93,10 +93,10 @@ Public Module ModModpack Return InstallPackMMC(File, Archive, ArchiveBaseFolder) Case 3 Log("[ModPack] 整合包种类:MCBBS") - Return InstallPackMCBBS(File, Archive, ArchiveBaseFolder, VersionName) + Return InstallPackMCBBS(File, Archive, ArchiveBaseFolder, InstanceName) Case 4 Log("[ModPack] 整合包种类:Modrinth") - Return InstallPackModrinth(File, Archive, ArchiveBaseFolder, VersionName, Logo) + Return InstallPackModrinth(File, Archive, ArchiveBaseFolder, InstanceName, Logo) Case 9 Log("[ModPack] 整合包种类:带启动器的压缩包") Return InstallPackLauncherPack(File, Archive, ArchiveBaseFolder) @@ -180,7 +180,7 @@ Retry: 'CurseForge Private Function InstallPackCurseForge(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, - Optional VersionName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String) + Optional InstanceName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String) '读取 Json 文件 Dim Json As JObject @@ -192,12 +192,12 @@ Retry: If Json("minecraft") Is Nothing OrElse Json("minecraft")("version") Is Nothing Then Throw New Exception("CurseForge 整合包未提供 Minecraft 版本信息") '获取版本名 - If VersionName Is Nothing Then - VersionName = If(Json("name"), "") - Dim Validate As New ValidateFolderName(PathMcFolder & "versions") - If Validate.Validate(VersionName) <> "" Then VersionName = "" - If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) - If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException + If InstanceName Is Nothing Then + InstanceName = If(Json("name"), "") + Dim Validate As New ValidateFolderName(McFolderSelected & "versions") + If Validate.Validate(InstanceName) <> "" Then InstanceName = "" + If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) + If String.IsNullOrEmpty(InstanceName) Then Throw New CancelledException End If '获取 Mod API 版本信息 @@ -234,7 +234,7 @@ Retry: ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6) CopyOverrideDirectory( InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome), '#5613 - $"{PathMcFolder}versions\{VersionName}", + $"{McFolderSelected}versions\{InstanceName}", Task, 0.4) End Sub) With { .ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s @@ -292,7 +292,7 @@ Retry: Dim File As New CompFile(ModJson, Type) If Not File.Available Then Continue For '实际的添加 - FileList.Add(Id, File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{TargetFolder}\")) + FileList.Add(Id, File.ToNetFile($"{McFolderSelected}versions\{InstanceName}\{TargetFolder}\")) Task.Progress += 1 / (1 + ModList.Count) Next Task.Output = FileList.Values.ToList @@ -306,8 +306,8 @@ Retry: '构造加载器 Dim Request As New McInstallRequest With { - .TargetVersionName = VersionName, - .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", + .NewInstanceName = InstanceName, + .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", .MinecraftName = Json("minecraft")("version").ToString, .ForgeVersion = ForgeVersion, .NeoForgeVersion = NeoForgeVersion, @@ -321,7 +321,7 @@ Retry: Loaders.Add(New LoaderTask(Of String, String)("最终整理文件", Sub(Task As LoaderTask(Of String, String)) '设置图标 - Dim VersionFolder As String = $"{PathMcFolder}versions\{VersionName}\" + Dim VersionFolder As String = $"{McFolderSelected}versions\{InstanceName}\" If Logo IsNot Nothing AndAlso File.Exists(Logo) Then File.Copy(Logo, VersionFolder & "PCL\Logo.png", True) WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png") @@ -342,7 +342,7 @@ Retry: End Sub) With {.ProgressWeight = 0.1, .Show = False}) '重复任务检查 - Dim LoaderName As String = "CurseForge 整合包安装:" & VersionName & " " + Dim LoaderName As String = "CurseForge 整合包安装:" & InstanceName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException @@ -350,7 +350,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(Request.TargetVersionFolder) + Loader.Start(Request.VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager)) @@ -358,7 +358,7 @@ Retry: End Function 'Modrinth - Private Function InstallPackModrinth(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, Optional VersionName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String) + Private Function InstallPackModrinth(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, Optional InstanceName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String) '读取 Json 文件 Dim Json As JObject @@ -392,12 +392,12 @@ Retry: End Select Next '获取版本名 - If VersionName Is Nothing Then - VersionName = If(Json("name"), "") - Dim Validate As New ValidateFolderName(PathMcFolder & "versions") - If Validate.Validate(VersionName) <> "" Then VersionName = "" - If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) - If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException + If InstanceName Is Nothing Then + InstanceName = If(Json("name"), "") + Dim Validate As New ValidateFolderName(McFolderSelected & "versions") + If Validate.Validate(InstanceName) <> "" Then InstanceName = "" + If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) + If String.IsNullOrEmpty(InstanceName) Then Throw New CancelledException End If '解压 Dim InstallTemp As String = RequestTaskTempFolder() @@ -407,11 +407,11 @@ Retry: ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.5) CopyOverrideDirectory( InstallTemp & ArchiveBaseFolder & "overrides", - PathMcFolder & "versions\" & VersionName, + McFolderSelected & "versions\" & InstanceName, Task, 0.4) CopyOverrideDirectory( InstallTemp & ArchiveBaseFolder & "client-overrides", - PathMcFolder & "versions\" & VersionName, + McFolderSelected & "versions\" & InstanceName, Task, 0.1) End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s '获取下载文件列表 @@ -433,8 +433,8 @@ Retry: Dim Urls = File("downloads").SelectMany(Function(t) CompFile.HandleCurseForgeDownloadUrls(t.ToString)).ToList Urls.AddRange(Urls.Select(Function(u) DlSourceModGet(u)).ToList) Urls = Urls.Distinct.ToList() - Dim TargetPath As String = $"{PathMcFolder}versions\{VersionName}\{File("path")}" - If Not IO.Path.GetFullPath(TargetPath).StartsWithF($"{PathMcFolder}versions\{VersionName}\", True) Then + Dim TargetPath As String = $"{McFolderSelected}versions\{InstanceName}\{File("path")}" + If Not IO.Path.GetFullPath(TargetPath).StartsWithF($"{McFolderSelected}versions\{InstanceName}\", True) Then MyMsgBox($"整合包的文件路径超出了版本文件夹,请向整合包作者反馈此问题!" & vbCrLf & "错误的文件:" & TargetPath, "文件路径校验失败", IsWarn:=True) Throw New CancelledException End If @@ -447,8 +447,8 @@ Retry: '构造加载器 Dim Request As New McInstallRequest With { - .TargetVersionName = VersionName, - .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", + .NewInstanceName = InstanceName, + .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", .MinecraftName = MinecraftVersion, .ForgeVersion = ForgeVersion, .NeoForgeVersion = NeoForgeVersion, @@ -462,7 +462,7 @@ Retry: Loaders.Add(New LoaderTask(Of String, String)("最终整理文件", Sub(Task As LoaderTask(Of String, String)) '设置图标 - Dim VersionFolder As String = $"{PathMcFolder}versions\{VersionName}\" + Dim VersionFolder As String = $"{McFolderSelected}versions\{InstanceName}\" If Logo IsNot Nothing AndAlso File.Exists(Logo) Then File.Copy(Logo, VersionFolder & "PCL\Logo.png", True) WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png") @@ -483,7 +483,7 @@ Retry: End Sub) With {.ProgressWeight = 0.1, .Show = False}) '重复任务检查 - Dim LoaderName As String = $"Modrinth 整合包安装:{VersionName} " + Dim LoaderName As String = $"Modrinth 整合包安装:{InstanceName} " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException @@ -491,7 +491,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(Request.TargetVersionFolder) + Loader.Start(Request.VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager)) @@ -508,11 +508,11 @@ Retry: Throw New Exception("HMCL 整合包安装信息存在问题", ex) End Try '获取版本名 - Dim VersionName As String = If(Json("name"), "") - Dim Validate As New ValidateFolderName(PathMcFolder & "versions") - If Validate.Validate(VersionName) <> "" Then VersionName = "" - If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) - If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException + Dim InstanceName As String = If(Json("name"), "") + Dim Validate As New ValidateFolderName(McFolderSelected & "versions") + If Validate.Validate(InstanceName) <> "" Then InstanceName = "" + If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) + If String.IsNullOrEmpty(InstanceName) Then Throw New CancelledException '解压 Dim InstallTemp As String = RequestTaskTempFolder() Dim InstallLoaders As New List(Of LoaderBase) @@ -521,14 +521,14 @@ Retry: ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6) CopyOverrideDirectory( InstallTemp & ArchiveBaseFolder & "minecraft", - PathMcFolder & "versions\" & VersionName, + McFolderSelected & "versions\" & InstanceName, Task, 0.4) End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s '构造游戏本体安装加载器 If Json("gameVersion") Is Nothing Then Throw New Exception("该 HMCL 整合包未提供游戏版本信息,无法安装!") Dim Request As New McInstallRequest With { - .TargetVersionName = VersionName, - .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", + .NewInstanceName = InstanceName, + .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", .MinecraftName = Json("gameVersion").ToString } Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) @@ -538,14 +538,14 @@ Retry: New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)} } '重复任务检查 - Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " " + Dim LoaderName As String = "HMCL 整合包安装:" & InstanceName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException End If '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(Request.TargetVersionFolder) + Loader.Start(Request.VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager)) @@ -563,21 +563,21 @@ Retry: Throw New Exception("MMC 整合包安装信息存在问题", ex) End Try '获取版本名 - Dim VersionName As String = If(RegexSeek(PackInstance, "(?<=\nname\=)[^\n]+"), "") - Dim Validate As New ValidateFolderName(PathMcFolder & "versions") - If Validate.Validate(VersionName) <> "" Then VersionName = "" - If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) - If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException + Dim InstanceName As String = If(RegexSeek(PackInstance, "(?<=\nname\=)[^\n]+"), "") + Dim Validate As New ValidateFolderName(McFolderSelected & "versions") + If Validate.Validate(InstanceName) <> "" Then InstanceName = "" + If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) + If String.IsNullOrEmpty(InstanceName) Then Throw New CancelledException '解压 Dim InstallTemp As String = RequestTaskTempFolder() - Dim SetupFile As String = $"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini" + Dim SetupFile As String = $"{McFolderSelected}versions\{InstanceName}\PCL\Setup.ini" Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.55) CopyOverrideDirectory( InstallTemp & ArchiveBaseFolder & ".minecraft", - PathMcFolder & "versions\" & VersionName, + McFolderSelected & "versions\" & InstanceName, Task, 0.4) '读取 MMC 设置文件(#2655) Try @@ -616,7 +616,7 @@ Retry: If Logo <> "" AndAlso File.Exists($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png") Then WriteIni(SetupFile, "LogoCustom", True) WriteIni(SetupFile, "Logo", "PCL\Logo.png") - CopyFile($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", $"{PathMcFolder}versions\{VersionName}\PCL\Logo.png") + CopyFile($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", $"{McFolderSelected}versions\{InstanceName}\PCL\Logo.png") Log($"[ModPack] 迁移 MultiMC 版本独立设置:版本图标({Logo}.png)") End If 'JVM 参数 @@ -638,7 +638,7 @@ Retry: End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s '构造版本安装请求 If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!") - Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"} + Dim Request As New McInstallRequest With {.NewInstanceName = InstanceName, .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\"} For Each Component As JObject In PackJson("components") If Not Component.ContainsKey("uid") Then Continue For Select Case Component("uid").ToString @@ -665,7 +665,7 @@ Retry: Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}) '重复任务检查 - Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " " + Dim LoaderName As String = "MMC 整合包安装:" & InstanceName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException @@ -673,7 +673,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - Loader.Start(Request.TargetVersionFolder) + Loader.Start(Request.VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager)) @@ -682,7 +682,7 @@ Retry: 'MCBBS Private Function InstallPackMCBBS(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String, - Optional VersionName As String = Nothing) As LoaderCombo(Of String) + Optional InstanceName As String = Nothing) As LoaderCombo(Of String) '读取 Json 文件 Dim Json As JObject Try @@ -692,23 +692,23 @@ Retry: Throw New Exception("MCBBS 整合包安装信息存在问题", ex) End Try '获取版本名 - If VersionName Is Nothing Then - VersionName = If(Json("name"), "") - Dim Validate As New ValidateFolderName(PathMcFolder & "versions") - If Validate.Validate(VersionName) <> "" Then VersionName = "" - If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) - If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException + If InstanceName Is Nothing Then + InstanceName = If(Json("name"), "") + Dim Validate As New ValidateFolderName(McFolderSelected & "versions") + If Validate.Validate(InstanceName) <> "" Then InstanceName = "" + If InstanceName = "" Then InstanceName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) + If String.IsNullOrEmpty(InstanceName) Then Throw New CancelledException End If '解压 Dim InstallTemp As String = RequestTaskTempFolder() - Dim SetupFile As String = $"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini" + Dim SetupFile As String = $"{McFolderSelected}versions\{InstanceName}\PCL\Setup.ini" Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6) CopyOverrideDirectory( InstallTemp & ArchiveBaseFolder & "overrides", - PathMcFolder & "versions\" & VersionName, + McFolderSelected & "versions\" & InstanceName, Task, 0.4) 'JVM 参数 If Json("launchInfo") IsNot Nothing Then @@ -729,8 +729,8 @@ Retry: Throw New CancelledException End If Dim Request As New McInstallRequest With { - .TargetVersionName = VersionName, - .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", + .NewInstanceName = InstanceName, + .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", .MinecraftName = Addons("game"), .OptiFineVersion = If(Addons.ContainsKey("optifine"), Addons("optifine"), Nothing), .ForgeVersion = If(Addons.ContainsKey("forge"), Addons("forge"), Nothing), @@ -744,7 +744,7 @@ Retry: Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}) '重复任务检查 - Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " " + Dim LoaderName As String = "MCBBS 整合包安装:" & InstanceName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException @@ -753,7 +753,7 @@ Retry: '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} 'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件” - Loader.Start(Request.TargetVersionFolder) + Loader.Start(Request.VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager)) @@ -805,11 +805,11 @@ Retry: End If OpenExplorer(TargetFolder) '加入文件夹列表 - Dim VersionName As String = GetFolderNameFromPath(TargetFolder) + Dim InstanceName As String = GetFolderNameFromPath(TargetFolder) Directory.CreateDirectory(TargetFolder & ".minecraft\") PageSelectLeft.AddFolder( TargetFolder & ".minecraft\" & ArchiveBaseFolder.Replace("/", "\").TrimStart("\"), '格式例如:包裹文件夹\.minecraft\(最短为空字符串) - VersionName, False) + InstanceName, False) '调用 modpack 文件进行安装 Dim ModpackFile = Directory.GetFiles(TargetFolder, "modpack.*", SearchOption.AllDirectories).First Log("[Modpack] 调用 modpack 文件继续安装:" & ModpackFile) @@ -835,10 +835,9 @@ Retry: Exit For End If Next - If Match Is Nothing Then Throw New Exception("未能找到适合的文件结构,这可能不是一个 MC 压缩包") '没有匹配 + If Match Is Nothing Then Throw New Exception("文件结构不匹配,这可能不是 Minecraft 客户端压缩包?") '没有匹配 Dim ArchiveBaseFolder As String = Match.Value.Replace("/", "\").TrimStart("\") '格式例如:包裹文件夹\.minecraft\(最短为空字符串) - Dim VersionName As String = Match.Groups(1).Value - Log("[ModPack] 检测到压缩包的 .minecraft 根目录:" & ArchiveBaseFolder & ",命中的版本名:" & VersionName) + Log("[ModPack] 检测到压缩包的 .minecraft 根目录:" & ArchiveBaseFolder & ",命中的版本名:" & Match.Groups(1).Value) '获取解压路径 MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True) Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)") @@ -853,7 +852,7 @@ Retry: '加入文件夹列表 PageSelectLeft.AddFolder(TargetFolder & ArchiveBaseFolder, GetFolderNameFromPath(TargetFolder), False) Thread.Sleep(400) '避免文件争用 - RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.VersionSelect)) + RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.InstanceSelect)) End Sub) }) With {.OnStateChanged = AddressOf McInstallState} Loader.Start(TargetFolder) diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb index d6259a0..38a50b8 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb @@ -58,13 +58,13 @@ '初始化 Public GameProcess As Process - Public Version As McVersion + Public Instance As McInstance Private WindowTitle As String = "" Private PID As Integer Public Loader As LoaderTask(Of Process, Integer) - Public Sub New(Loader As LoaderTask(Of Process, Integer), Version As McVersion, WindowTitle As String) + Public Sub New(Loader As LoaderTask(Of Process, Integer), Instance As McInstance, WindowTitle As String) Me.Loader = Loader - Me.Version = Version + Me.Instance = Instance Me.WindowTitle = WindowTitle Me.PID = Loader.Input.Id WatcherLog("开始 Minecraft 日志监控") @@ -97,7 +97,7 @@ '设置窗口标题 For i = 1 To 3 If IsWindowFinished AndAlso IsWindowAppeared AndAlso WindowTitle <> "" AndAlso State = MinecraftState.Running AndAlso Not GameProcess.HasExited Then - Dim RealTitle As String = WindowTitle.Replace("{date}", Date.Now.ToString("yyyy/M/d")).Replace("{time}", Date.Now.ToString("HH:mm:ss")) + Dim RealTitle As String = WindowTitle.Replace("{date}", Date.Now.ToString("yyyy'/'M'/'d")).Replace("{time}", Date.Now.ToString("HH':'mm':'ss")) SetWindowText(WindowHandle, RealTitle.ToCharArray) End If Thread.Sleep(64) @@ -164,7 +164,7 @@ '窗口未出现 WatcherLog("Minecraft 尚未加载完成,可能已崩溃") Crashed() - ElseIf GameProcess.ExitCode <> 0 AndAlso State = MinecraftState.Running AndAlso Version.ReleaseTime.Year >= 2012 Then + ElseIf GameProcess.ExitCode <> 0 AndAlso State = MinecraftState.Running AndAlso Instance.ReleaseTime.Year >= 2012 Then '返回值不为 0 且未结束 WatcherLog("Minecraft 返回值异常,可能已崩溃") Crashed() @@ -356,12 +356,12 @@ Try Thread.Sleep(2000) WatcherLog("崩溃分析开始") - Dim Analyzer As New CrashAnalyzer(Version) - Analyzer.Collect(Version.PathIndie, LatestLog.ToList) + Dim Analyzer As New CrashAnalyzer(Instance) + Analyzer.Collect(Instance.PathIndie, LatestLog.ToList) Analyzer.Prepare() - Analyzer.Analyze(Version) + Analyzer.Analyze() Analyzer.Output(False, New List(Of String) From - {Version.Path & Version.Name & ".json", Path & "PCL\Log1.txt", Path & "PCL\LatestLaunch.bat"}) + {Instance.PathVersion & Instance.Name & ".json", Path & "PCL\Log1.txt", Path & "PCL\LatestLaunch.bat"}) Catch ex As Exception Log(ex, "崩溃分析失败", LogLevel.Feedback) End Try diff --git a/Plain Craft Launcher 2/Modules/ModEvent.vb b/Plain Craft Launcher 2/Modules/ModEvent.vb index 06204f9..b409989 100644 --- a/Plain Craft Launcher 2/Modules/ModEvent.vb +++ b/Plain Craft Launcher 2/Modules/ModEvent.vb @@ -96,11 +96,36 @@ End Module ''' 自定义事件。 ''' Public Class CustomEvent + Inherits DependencyObject #Region "属性与触发" - Public Property Type As EventType = EventType.None - Public Property Data As String = Nothing + Public Property Type As EventType + Get + Dim Value As EventType = EventType.None + RunInUiWait(Sub() Value = GetValue(TypeProperty)) + Return Value + End Get + Set(value As EventType) + SetValue(TypeProperty, value) + End Set + End Property + Public Shared ReadOnly TypeProperty As DependencyProperty = + DependencyProperty.Register("Type", GetType(EventType), GetType(CustomEvent), New PropertyMetadata(EventType.None)) + + Public Property Data As String + Get + Dim Value As String = Nothing + RunInUiWait(Sub() Value = GetValue(DataProperty)) + Return Value + End Get + Set(value As String) + SetValue(DataProperty, value) + End Set + End Property + Public Shared ReadOnly DataProperty As DependencyProperty = + DependencyProperty.Register("Data", GetType(String), GetType(CustomEvent), New PropertyMetadata(Nothing)) + Public Sub New() End Sub Public Sub New(Type As EventType, Data As String) @@ -190,17 +215,17 @@ Public Class CustomEvent Case EventType.启动游戏 If Args(0) = "\current" Then - If McVersionCurrent Is Nothing Then + If McInstanceSelected Is Nothing Then Hint("请先选择一个 Minecraft 版本!", HintType.Red) Return Else - Args(0) = McVersionCurrent.Name + Args(0) = McInstanceSelected.Name End If End If RunInUi( Sub() If McLaunchStart(New McLaunchOptions With - {.ServerIp = If(Args.Length >= 2, Args(1), Nothing), .Version = New McVersion(Args(0))}) Then + {.ServerIp = If(Args.Length >= 2, Args(1), Nothing), .Instance = New McInstance(Args(0))}) Then Hint($"正在启动 {Args(0)}……") End If End Sub) @@ -276,7 +301,7 @@ Public Class CustomEvent Case EventType.修改设置, EventType.写入设置 If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 UiLauncherTransparent|400") - Setup.SetSafe(Args(0), Args(1), Version:=McVersionCurrent) + Setup.SetSafe(Args(0), Args(1), Instance:=McInstanceSelected) If Args.Length = 2 Then Hint($"已写入设置:{Args(0)} → {Args(1)}", HintType.Green) Case EventType.修改变量, EventType.写入变量 diff --git a/Plain Craft Launcher 2/Modules/ModMain.vb b/Plain Craft Launcher 2/Modules/ModMain.vb index c8c325c..19b86e2 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.vb +++ b/Plain Craft Launcher 2/Modules/ModMain.vb @@ -410,12 +410,12 @@ EndHint: Public FrmLoginMsSkin As PageLoginMsSkin '版本设置页面声明 - Public FrmVersionLeft As PageVersionLeft - Public FrmVersionOverall As PageVersionOverall - Public FrmVersionMod As PageVersionMod - Public FrmVersionModDisabled As PageVersionModDisabled - Public FrmVersionSetup As PageVersionSetup - Public FrmVersionExport As PageVersionExport + Public FrmInstanceLeft As PageInstanceLeft + Public FrmInstanceOverall As PageInstanceOverall + Public FrmInstanceMod As PageInstanceMod + Public FrmInstanceModDisabled As PageInstanceModDisabled + Public FrmInstanceSetup As PageInstanceSetup + Public FrmInstanceExport As PageInstanceExport '资源信息分页声明 Public FrmDownloadCompDetail As PageDownloadCompDetail @@ -834,20 +834,20 @@ NextFile: Text = Text.Replace("{path_temp}", Replacer(PathTemp)) '时间 If ReplaceTime Then '在窗口标题中,时间会被后续动态替换,所以此时不应该替换 - Text = Text.Replace("{date}", Replacer(Date.Now.ToString("yyyy/M/d"))) - Text = Text.Replace("{time}", Replacer(Date.Now.ToString("HH:mm:ss"))) + Text = Text.Replace("{date}", Replacer(Date.Now.ToString("yyyy'/'M'/'d"))) + Text = Text.Replace("{time}", Replacer(Date.Now.ToString("HH':'mm':'ss"))) End If 'Minecraft Text = Text.Replace("{java}", Replacer(McLaunchJavaSelected?.PathFolder)) - Text = Text.Replace("{minecraft}", Replacer(PathMcFolder)) - If McVersionCurrent IsNot Nothing Then - Text = Text.Replace("{version_path}", Replacer(McVersionCurrent.Path)) : Text = Text.Replace("{verpath}", Replacer(McVersionCurrent.Path)) - Text = Text.Replace("{version_indie}", Replacer(McVersionCurrent.PathIndie)) : Text = Text.Replace("{verindie}", Replacer(McVersionCurrent.PathIndie)) - Text = Text.Replace("{name}", Replacer(McVersionCurrent.Name)) - If {"unknown", "old", "pending"}.Contains(McVersionCurrent.Version.McName.ToLower) Then - Text = Text.Replace("{version}", Replacer(McVersionCurrent.Name)) + Text = Text.Replace("{minecraft}", Replacer(McFolderSelected)) + If McInstanceSelected?.IsLoaded Then + Text = Text.Replace("{version_path}", Replacer(McInstanceSelected.PathVersion)) : Text = Text.Replace("{verpath}", Replacer(McInstanceSelected.PathVersion)) + Text = Text.Replace("{version_indie}", Replacer(McInstanceSelected.PathIndie)) : Text = Text.Replace("{verindie}", Replacer(McInstanceSelected.PathIndie)) + Text = Text.Replace("{name}", Replacer(McInstanceSelected.Name)) + If {"unknown", "old", "pending"}.Contains(McInstanceSelected.Version.VanillaName.ToLower) Then + Text = Text.Replace("{version}", Replacer(McInstanceSelected.Name)) Else - Text = Text.Replace("{version}", Replacer(McVersionCurrent.Version.McName)) + Text = Text.Replace("{version}", Replacer(McInstanceSelected.Version.VanillaName)) End If Else Text = Text.Replace("{version_path}", Replacer(Nothing)) : Text = Text.Replace("{verpath}", Replacer(Nothing)) @@ -877,7 +877,7 @@ NextFile: '高级 Text = Text.RegexReplaceEach("\{hint\}", Function() Replacer(PageOtherTest.GetRandomHint())) Text = Text.RegexReplaceEach("\{cave\}", Function() Replacer(PageOtherTest.GetRandomCave())) - Text = Text.RegexReplaceEach("\{setup:([a-zA-Z0-9]+)\}", Function(m) Replacer(Setup.GetSafe(m.Groups(1).Value, McVersionCurrent))) + Text = Text.RegexReplaceEach("\{setup:([a-zA-Z0-9]+)\}", Function(m) Replacer(Setup.GetSafe(m.Groups(1).Value, McInstanceSelected))) Text = Text.RegexReplaceEach("\{varible:([^:\}]+)(?::([^\}]+))?\}", Function(m) Replacer(ReadReg("CustomEvent" & m.Groups(1).Value, m.Groups(2).Value))) Text = Text.RegexReplaceEach("\{variable:([^:\}]+)(?::([^\}]+))?\}", Function(m) Replacer(ReadReg("CustomEvent" & m.Groups(1).Value, m.Groups(2).Value))) Return Text diff --git a/Plain Craft Launcher 2/Modules/ModSecret.vb b/Plain Craft Launcher 2/Modules/ModSecret.vb index 62d425c..df22802 100644 --- a/Plain Craft Launcher 2/Modules/ModSecret.vb +++ b/Plain Craft Launcher 2/Modules/ModSecret.vb @@ -63,11 +63,11 @@ Friend Module ModSecret End Function Friend Sub SecretLaunchJvmArgs(ByRef DataList As List(Of String)) - Dim DataJvmCustom As String = Setup.Get("VersionAdvanceJvm", Version:=McVersionCurrent) + Dim DataJvmCustom As String = Setup.Get("VersionAdvanceJvm", Version:=McInstanceSelected) DataList.Insert(0, ArgumentReplace(If(DataJvmCustom = "", Setup.Get("LaunchAdvanceJvm"), DataJvmCustom))) '可变 JVM 参数 McLaunchLog("当前剩余内存:" & Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024 * 10) / 10 & "G") - DataList.Add("-Xmn" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent) * 1024 * 0.15) & "m") - DataList.Add("-Xmx" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent) * 1024) & "m") + DataList.Add("-Xmn" & Math.Floor(PageInstanceSetup.GetRam(McInstanceSelected) * 1024 * 0.15) & "m") + DataList.Add("-Xmx" & Math.Floor(PageInstanceSetup.GetRam(McInstanceSelected) * 1024) & "m") If Not DataList.Any(Function(d) d.Contains("-Dlog4j2.formatMsgNoLookups=true")) Then DataList.Add("-Dlog4j2.formatMsgNoLookups=true") End Sub diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index 8beb5b3..c8de122 100644 --- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb +++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb @@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 ' 方法是按如下所示使用“*” - - + + diff --git a/Plain Craft Launcher 2/My Project/Resources.Designer.vb b/Plain Craft Launcher 2/My Project/Resources.Designer.vb index dfc568a..f4bdeee 100644 --- a/Plain Craft Launcher 2/My Project/Resources.Designer.vb +++ b/Plain Craft Launcher 2/My Project/Resources.Designer.vb @@ -200,6 +200,16 @@ Namespace My.Resources End Get End Property + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property System_ValueTuple() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("System.ValueTuple", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' diff --git a/Plain Craft Launcher 2/My Project/Resources.resx b/Plain Craft Launcher 2/My Project/Resources.resx index 8db2019..6131341 100644 --- a/Plain Craft Launcher 2/My Project/Resources.resx +++ b/Plain Craft Launcher 2/My Project/Resources.resx @@ -160,6 +160,9 @@ ..\Resources\System.Net.Http.Formatting.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\System.ValueTuple.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\transformer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb index 47b17e9..0a36182 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb @@ -3,16 +3,6 @@ #Region "基础属性" Public Uuid As Integer = GetUuid() - 'Logo - Public Property Logo As String - Get - Return PathLogo.Source - End Get - Set(value As String) - PathLogo.Source = value - End Set - End Property - '标题 Public Property Title As String Get @@ -64,17 +54,17 @@ 'Tag Public WriteOnly Property Tags As List(Of String) - Set(value As List(Of String)) + Set(Tags As List(Of String)) PanTags.Children.Clear() - PanTags.Visibility = If(value.Any(), Visibility.Visible, Visibility.Collapsed) - For Each TagText In value - Dim NewTag = GetObjectFromXML( - " - - ") - PanTags.Children.Add(NewTag) + PanTags.Visibility = If(Tags.Any(), Visibility.Visible, Visibility.Collapsed) + For Each TagText In Tags + Dim BorderTag As New Border With { + .Background = New MyColor("#11000000"), .Padding = New Thickness(3, 1, 3, 1), .CornerRadius = New CornerRadius(3), + .Margin = New Thickness(0, 0, 3, 0), .SnapsToDevicePixels = True, .UseLayoutRounding = False} + Dim LabTag As New TextBlock With { + .Text = TagText, .Foreground = New MyColor("#868686"), .FontSize = 11} + BorderTag.Child = LabTag + PanTags.Children.Add(BorderTag) Next End Set End Property @@ -208,9 +198,9 @@ Dim Ani As New List(Of AniData) If IsMouseOver Then Ani.AddRange({ - AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrushBg1"), Time), - AaOpacity(RectBack, 1 - RectBack.Opacity, Time,, New AniEaseOutFluent) - }) + AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrushBg1"), Time), + AaOpacity(RectBack, 1 - RectBack.Opacity, Time,, New AniEaseOutFluent) + }) If IsMouseDown Then Ani.Add(AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time * 1.2,, New AniEaseOutFluent)) Else @@ -218,11 +208,11 @@ End If Else Ani.AddRange({ - AaOpacity(RectBack, -RectBack.Opacity, Time), - AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrush7"), Time), - AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent), - AaScaleTransform(RectBack, -0.196, 1,,, True) - }) + AaOpacity(RectBack, -RectBack.Opacity, Time), + AaColor(RectBack, Border.BackgroundProperty, If(IsMouseDown, "ColorBrush6", "ColorBrush7"), Time), + AaScaleTransform(RectBack, 0.996 - CType(RectBack.RenderTransform, ScaleTransform).ScaleX, Time,, New AniEaseOutFluent), + AaScaleTransform(RectBack, -0.196, 1,,, True) + }) End If AniStart(Ani, "CompItem Color " & Uuid) Else diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml index 719bbc6..456ca3b 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml @@ -34,16 +34,14 @@ - + - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb index f3d278c..6d3fb6d 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb @@ -67,7 +67,7 @@ Public Class PageComp ''' ''' 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。 ''' - Public Shared TargetVersion As McVersion = Nothing + Public Shared TargetInstance As McInstance = Nothing ''' ''' 在切换到该页面时自动设置的搜索框内的内容。 ''' @@ -76,13 +76,13 @@ Public Class PageComp '在点击 MyCompItem 时会获取 Loader 的输入,以使资源详情页面可以应用相同的筛选项 Public Loader As New LoaderTask(Of CompProjectRequest, Integer)("社区资源获取:XXX", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000} - Private IsLoaderInited As Boolean = False Private Sub PageCompControls_Inited(sender As Object, e As EventArgs) Handles Me.Loaded + Static IsFirstLoaded As Boolean = False '不知道从 Initialized 改成 Loaded 会不会有问题,但用 Initialized 会导致初始的筛选器修改被覆盖回默认值 - If TargetVersion IsNot Nothing Then + If TargetInstance IsNot Nothing Then '设置目标 ResetFilter() '重置筛选器 - TextSearchVersion.Text = TargetVersion.Version.McName + TextSearchVersion.Text = TargetInstance.Version.VanillaName Dim GetTargetItemByName = Function(Name As String) As MyComboBoxItem For Each Item As MyComboBoxItem In ComboSearchLoader.Items @@ -90,32 +90,38 @@ Public Class PageComp Next Return ComboSearchLoader.Items(0) End Function - If TargetVersion.Version.HasForge Then + If TargetInstance.Version.HasForge Then ComboSearchLoader.SelectedItem = GetTargetItemByName("Forge") - ElseIf TargetVersion.Version.HasFabric Then + ElseIf TargetInstance.Version.HasFabric Then ComboSearchLoader.SelectedItem = GetTargetItemByName("Fabric") - ElseIf TargetVersion.Version.HasNeoForge Then + ElseIf TargetInstance.Version.HasNeoForge Then ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge") End If - TargetVersion = Nothing + TargetInstance = Nothing If TargetName IsNot Nothing Then TextSearchName.Text = TargetName TargetName = Nothing End If '如果已经完成请求,则重新开始 - If IsLoaderInited Then StartNewSearch() + If IsFirstLoaded Then StartNewSearch() ScrollToHome() End If '加载器初始化 - If IsLoaderInited Then Return - IsLoaderInited = True + If IsFirstLoaded Then Return + IsFirstLoaded = True CType(Parent, MyPageRight).PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput) - If McVersionHighest = -1 Then McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1))) + '将最高 Drop 加入筛选 + If AllDrops?.Any AndAlso AllDrops.First > 250 Then + Dim HighestVersion As String = McVersion.DropToVersion(AllDrops.First) + If CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString <> HighestVersion Then '0 是全部 + TextSearchVersion.Items.Insert(1, New MyComboBoxItem With {.Content = HighestVersion}) + End If + End If End Sub Private Function LoaderInput() As CompProjectRequest - Dim Request As New CompProjectRequest(PageType, Storage, (Page + 1) * PageSize) + Dim Request As New CompProjectRequest(PageType, Storage, (Page + 1) * PAGE_SIZE) Dim GameVersion As String = If(TextSearchVersion.Text = "全部 (也可自行输入)", Nothing, - If(TextSearchVersion.Text.Contains(".") OrElse TextSearchVersion.Text.Contains("w"), TextSearchVersion.Text, Nothing)) + If(TextSearchVersion.Text.Contains(".") OrElse TextSearchVersion.Text.Contains("w"), TextSearchVersion.Text, Nothing)) With Request .SearchText = TextSearchName.Text .GameVersion = GameVersion @@ -132,7 +138,7 @@ Public Class PageComp ''' ''' 每页展示的结果数量。 ''' - Public Const PageSize = 40 + Public Const PAGE_SIZE = 40 Public Page As Integer = 0 '结果 UI 化 @@ -141,8 +147,9 @@ Public Class PageComp Log($"[Comp] 开始可视化{TypeNameSpaced}列表,已储藏 {Storage.Results.Count} 个结果,当前在第 {Page + 1} 页") '列表项 PanProjects.Children.Clear() - For i = Math.Min(Page * PageSize, Storage.Results.Count - 1) To Math.Min((Page + 1) * PageSize - 1, Storage.Results.Count - 1) - PanProjects.Children.Add(Storage.Results(i).ToCompItem( + Dim Index As Integer = Math.Min(Page * PAGE_SIZE, Storage.Results.Count - 1) + For Each Result In Storage.Results.GetRange(Index, Math.Min(Storage.Results.Count - Index, PAGE_SIZE)) + PanProjects.Children.Add(Result.ToCompItem( ShowMcVersionDesc:=Loader.Input.GameVersion Is Nothing, ShowLoaderDesc:=Loader.Input.ModLoader = CompModLoaderType.Any AndAlso (PageType = CompType.Mod OrElse PageType = CompType.ModPack))) Next @@ -156,7 +163,7 @@ Public Class PageComp BtnPageLeft.IsEnabled = Page > 0 BtnPageLeft.Opacity = If(Page > 0, 1, 0.2) Dim IsRightEnabled As Boolean = '由于 WPF 的未知 bug,读取到的 IsEnabled 可能是错误的值(#3319) - Storage.Results.Count > PageSize * (Page + 1) OrElse + Storage.Results.Count > PAGE_SIZE * (Page + 1) OrElse Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal BtnPageRight.IsEnabled = IsRightEnabled BtnPageRight.Opacity = If(IsRightEnabled, 1, 0.2) 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 fda60b1..4a8ef69 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb @@ -19,12 +19,12 @@ End Sub Public Sub LoadTargetFromAdditional() Handles Me.Loaded Project = FrmMain.PageCurrent.Additional(0) - TargetVersion = FrmMain.PageCurrent.Additional(2) + TargetInstance = FrmMain.PageCurrent.Additional(2) TargetLoader = FrmMain.PageCurrent.Additional(3) PageType = FrmMain.PageCurrent.Additional(4) End Sub Private Project As CompProject - Private TargetVersion As String, TargetLoader As CompModLoaderType + Private TargetInstance As String, TargetLoader As CompModLoaderType ''' ''' 当前页面应展示的内容类别。可能为 Any。 ''' @@ -52,16 +52,16 @@ If x = Topmost Then Return -1 If y = Topmost Then Return 1 '特殊版本 - Dim IsXSpecial As Boolean = x.EndsWithF("版本") - Dim IsYSpecial As Boolean = y.EndsWithF("版本") + Dim IsXSpecial As Boolean = Not x.Contains(".") + Dim IsYSpecial As Boolean = Not y.Contains(".") If IsXSpecial AndAlso IsYSpecial Then Return x.CompareTo(y) If IsXSpecial Then Return 1 If IsYSpecial Then Return -1 '比较版本号 - Dim VersionCodeSort = -VersionSortInteger(x.Replace(x.BeforeFirst(" ") & " ", ""), y.Replace(y.BeforeFirst(" ") & " ", "")) + Dim VersionCodeSort = -CompareVersion(x.Replace(x.BeforeFirst(" ") & " ", ""), y.Replace(y.BeforeFirst(" ") & " ", "")) If VersionCodeSort <> 0 Then Return VersionCodeSort '比较全部 - Return -VersionSortInteger(x, y) + Return -CompareVersion(x, y) End Function Public Sub New(Optional Topmost As String = "") Me.Topmost = If(Topmost, "") @@ -69,7 +69,8 @@ End Class Private VersionFilter As String - Private IsMajorVersionFilter As Boolean '是否按大版本号筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...) + Private GroupedDrop As Boolean '是否按 Drop 筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...) + Private GroupedOld As Boolean '是否折叠远古版本为一个选项 '筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表) Private Function GetResults() As List(Of CompFile) Dim Results As List(Of CompFile) = CompFileLoader.Output @@ -87,32 +88,40 @@ Dim Results = GetResults() '初始化筛选器 - Dim VersionFilters As List(Of String) - - '按小版本号筛选? - IsMajorVersionFilter = False - VersionFilters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)). - Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList - '按大版本号筛选? - If VersionFilters.Count >= 9 Then - IsMajorVersionFilter = True - VersionFilters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)). + Dim Filters As List(Of String) = Nothing + Dim UpdateFilters = + Sub() + Filters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, GroupedDrop, GroupedOld)). Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList - End If + End Sub + + '确定分组方式 + GroupedDrop = False : GroupedOld = False + UpdateFilters() + If Filters.Count < 9 Then GoTo GroupDone + GroupedDrop = True : GroupedOld = False + UpdateFilters() + If Filters.Count < 9 Then GoTo GroupDone + GroupedDrop = False : GroupedOld = True + UpdateFilters() + If Filters.Count < 9 Then GoTo GroupDone + GroupedDrop = True : GroupedOld = True + UpdateFilters() +GroupDone: 'UI 化筛选器 PanFilter.Children.Clear() - If VersionFilters.Count < 2 Then + If Filters.Count < 2 Then CardFilter.Visibility = Visibility.Collapsed VersionFilter = Nothing Else CardFilter.Visibility = Visibility.Visible - VersionFilters.Insert(0, "全部") + Filters.Insert(0, "全部") '转化为按钮 - For Each Version As String In VersionFilters + For Each Version As String In Filters Dim NewButton As New MyRadioButton With { .Text = Version, .Margin = New Thickness(2, 0, 2, 0), .ColorType = MyRadioButton.ColorState.Highlight} - NewButton.LabText.Margin = New Thickness(-2, 0, 8, 0) + NewButton.LabText.Margin = New Thickness(-2, 0, 10, 0) AddHandler NewButton.Check, Sub(sender As MyRadioButton, raiseByMouse As Boolean) VersionFilter = If(sender.Text = "全部", Nothing, sender.Text) @@ -122,10 +131,10 @@ Next '自动选择 Dim ToCheck As MyRadioButton = Nothing - If TargetVersion <> "" Then - Dim TargetFile = Results.FirstOrDefault(Function(v) v.GameVersions.Contains(TargetVersion)) + If TargetInstance <> "" Then + Dim TargetFile = Results.FirstOrDefault(Function(v) v.GameVersions.Contains(TargetInstance)) If TargetFile IsNot Nothing Then - Dim TargetGroup = GetGroupedVersionName(TargetVersion, IsMajorVersionFilter, True) + Dim TargetGroup = GetGroupedVersionName(TargetInstance, GroupedDrop, GroupedOld) For Each Button As MyRadioButton In PanFilter.Children If Button.Text <> TargetGroup Then Continue For ToCheck = Button @@ -143,24 +152,24 @@ Private Sub UpdateFilterResult() Dim Results = GetResults() - Dim TargetCardName As String = If(TargetVersion <> "" OrElse TargetLoader <> CompModLoaderType.Any, - $"所选版本:{If(TargetLoader <> CompModLoaderType.Any, TargetLoader.ToString & " ", "")}{TargetVersion}", "") + Dim TargetCardName As String = If(TargetInstance <> "" OrElse TargetLoader <> CompModLoaderType.Any, + $"{If(TargetLoader <> CompModLoaderType.Any, TargetLoader.ToString & " ", "")}{TargetInstance}(所选版本)", "") '归类到卡片下 Dim Dict As New SortedDictionary(Of String, List(Of CompFile))(New CardSorter(TargetCardName)) - Dict.Add("其他版本", New List(Of CompFile)) + Dict.Add("其他", New List(Of CompFile)) Dim SupportedLoaders As New List(Of Integer)([Enum].GetValues(GetType(CompModLoaderType))) For Each Version As CompFile In Results For Each GameVersion In Version.GameVersions '检查是否符合版本筛选器 If VersionFilter IsNot Nothing AndAlso - GetGroupedVersionName(GameVersion, IsMajorVersionFilter, True) <> VersionFilter Then Continue For + GetGroupedVersionName(GameVersion, GroupedDrop, GroupedOld) <> VersionFilter Then Continue For '决定添加到哪个卡片 Dim VerName As String = GetGroupedVersionName(GameVersion, False, False) '遍历加入的加载器列表 Dim Loaders As New List(Of String) If Project.ModLoaders.Count > 1 AndAlso '工程至少有两个加载器 Version.Type = CompType.Mod AndAlso '是 Mod - VerName.StartsWith("1.") Then '不是 “快照版本” 之类的 + McVersion.IsFormatFit(VerName) Then '不是 “快照版本” 之类的 For Each Loader In Version.ModLoaders If Loader = CompModLoaderType.Quilt AndAlso Setup.Get("ToolDownloadIgnoreQuilt") Then Continue For If SupportedLoaders.Contains(Loader) Then Loaders.Add(Loader.ToString & " ") @@ -176,14 +185,14 @@ Next Next '添加筛选的版本的卡片 - If TargetCardName <> "" AndAlso (VersionFilter Is Nothing OrElse GetGroupedVersionName(TargetVersion, IsMajorVersionFilter, True).StartsWithF(VersionFilter)) Then + If TargetCardName <> "" AndAlso (VersionFilter Is Nothing OrElse GetGroupedVersionName(TargetInstance, GroupedDrop, GroupedOld).StartsWithF(VersionFilter)) Then Dict.Add(TargetCardName, New List(Of CompFile)) For Each Version As CompFile In Results - If Version.GameVersions.Contains(TargetVersion) AndAlso + If Version.GameVersions.Contains(TargetInstance) AndAlso (TargetLoader = CompModLoaderType.Any OrElse Version.ModLoaders.Contains(TargetLoader)) Then '检查是否符合版本筛选器 If VersionFilter IsNot Nothing AndAlso - Not Version.GameVersions.Any(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True) = VersionFilter) Then Continue For + Not Version.GameVersions.Any(Function(v) GetGroupedVersionName(v, GroupedDrop, GroupedOld) = VersionFilter) Then Continue For If Not Dict(TargetCardName).Contains(Version) Then Dict(TargetCardName).Add(Version) End If Next @@ -193,6 +202,7 @@ PanResults.Children.Clear() For Each Pair As KeyValuePair(Of String, List(Of CompFile)) In Dict If Not Pair.Value.Any() Then Continue For + If Pair.Key = TargetCardName.Replace("(所选版本)", "") Then Continue For '增加卡片 Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(PageType = CompType.ModPack, 9, 8)} '9 是安装,8 是另存为 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} @@ -208,7 +218,7 @@ NewCard.IsSwapped = True End If '增加提示 - If Pair.Key = "其他版本" Then + If Pair.Key = "其他" Then NewStack.Children.Add(New MyHint With {.Text = "由于版本信息更新缓慢,可能无法识别刚更新的 MC 版本。几天后即可正常识别。", .Theme = MyHint.Themes.Yellow, .Margin = New Thickness(5, 0, 0, 8)}) End If Next @@ -220,15 +230,17 @@ Log(ex, "可视化工程下载列表出错", LogLevel.Feedback) End Try End Sub - Private Function GetGroupedVersionName(Name As String, MajorOnly As Boolean, FoldOldRelease As Boolean) As String + Private Function GetGroupedVersionName(Name As String, GroupedByDrop As Boolean, FoldOld As Boolean) As String If Name Is Nothing Then - Return "其他版本" + Return "其他" ElseIf Name.Contains("w") Then - Return "快照版本" - ElseIf Name.StartsWith("1.0") OrElse Not Name.StartsWith("1.") OrElse (FoldOldRelease AndAlso Val(Name.Split(".")(1)) < 10) Then - Return "远古版本" + Return "快照版" + ElseIf Not McVersion.IsFormatFit(Name) OrElse (FoldOld AndAlso McVersion.VersionToDrop(Name, True) < 120) Then + Return "远古版" + ElseIf GroupedByDrop Then + Return McVersion.DropToVersion(McVersion.VersionToDrop(Name, True)) Else - Return If(MajorOnly, "1." & Name.Split(".")(1).BeforeFirst(" "), Name) + Return Name End If End Function @@ -250,7 +262,7 @@ '放置当前工程 If CompItem IsNot Nothing Then PanIntro.Children.Remove(CompItem) - CompItem = Project.ToCompItem(True, True) + CompItem = Project.ToCompItem(True, True).Init() CompItem.CanInteraction = False CompItem.Margin = New Thickness(-7, -7, 0, 8) PanIntro.Children.Insert(0, CompItem) @@ -272,18 +284,18 @@ '获取版本名 Dim PackName As String = Project.TranslatedName.Replace(".zip", "").Replace(".rar", "").Replace(".mrpack", "").Replace("\", "\").Replace("/", "/").Replace("|", "|").Replace(":", ":").Replace("<", "<").Replace(">", ">").Replace("*", "*").Replace("?", "?").Replace("""", "").Replace(": ", ":") - Dim Validate As New ValidateFolderName(PathMcFolder & "versions") + Dim Validate As New ValidateFolderName(McFolderSelected & "versions") If Validate.Validate(PackName) <> "" Then PackName = "" - Dim VersionName As String = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate}) - If String.IsNullOrEmpty(VersionName) Then Return + Dim InstanceName As String = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate}) + If String.IsNullOrEmpty(InstanceName) Then Return '构造步骤加载器 Dim Loaders As New List(Of LoaderBase) - Dim Target As String = $"{PathMcFolder}versions\{VersionName}\原始整合包.{If(Project.FromCurseForge, "zip", "mrpack")}" - Dim LogoFileAddress As String = MyImage.GetTempPath(CompItem.Logo) + Dim Target As String = $"{McFolderSelected}versions\{InstanceName}\原始整合包.{If(Project.FromCurseForge, "zip", "mrpack")}" + Dim LogoFileAddress As String = CompItem.PathLogo.ActualSource Loaders.Add(New LoaderDownload("下载整合包文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 10, .Block = True}) Loaders.Add(New LoaderTask(Of Integer, Integer)("准备安装整合包", - Sub() ModpackInstall(Target, VersionName, If(IO.File.Exists(LogoFileAddress), LogoFileAddress, Nothing))) With {.ProgressWeight = 0.1}) + Sub() ModpackInstall(Target, InstanceName, If(IO.File.Exists(LogoFileAddress), LogoFileAddress, Nothing))) With {.ProgressWeight = 0.1}) '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = @@ -298,7 +310,7 @@ End Select McInstallFailedClearFolder(MyLoader) End Sub} - Loader.Start(PathMcFolder & "versions\" & VersionName & "\") + Loader.Start(McFolderSelected & "versions\" & InstanceName & "\") LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() FrmMain.BtnExtraDownload.Ribble() @@ -332,7 +344,7 @@ Case CompType.Shader : SubFolder = "shaderpacks\" Case CompType.DataPack : SubFolder = "" '导航到版本根目录 End Select - Dim IsVersionSuitable As Func(Of McVersion, Boolean) = Nothing + Dim IsInstanceSuitable As Func(Of McInstance, Boolean) = Nothing '获取资源所需的加载器 Dim AllowedLoaders As New List(Of CompModLoaderType) If File.ModLoaders.Any Then @@ -342,51 +354,51 @@ End If Log($"[Comp] {Desc}要求的加载器种类:" & If(AllowedLoaders.Any(), AllowedLoaders.Join(" / "), "无要求")) '判断某个版本是否符合资源要求 - IsVersionSuitable = - Function(Version) - If Not Version.IsLoaded Then Version.Load() + IsInstanceSuitable = + Function(Instance As McInstance) + If Not Instance.IsLoaded Then Instance.Load() '只对 Mod 和数据包进行版本检测 If File.Type = CompType.Mod OrElse File.Type = CompType.DataPack Then If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso - Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Version.Version.McName) Then Return False + Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Instance.Version.VanillaName) Then Return False End If '加载器 If Not AllowedLoaders.Any() Then Return True '无要求 - If AllowedLoaders.Contains(CompModLoaderType.Forge) AndAlso Version.Version.HasForge Then Return True - If AllowedLoaders.Contains(CompModLoaderType.Fabric) AndAlso Version.Version.HasFabric Then Return True - If AllowedLoaders.Contains(CompModLoaderType.NeoForge) AndAlso Version.Version.HasNeoForge Then Return True - If AllowedLoaders.Contains(CompModLoaderType.LiteLoader) AndAlso Version.Version.HasLiteLoader Then Return True + If AllowedLoaders.Contains(CompModLoaderType.Forge) AndAlso Instance.Version.HasForge Then Return True + If AllowedLoaders.Contains(CompModLoaderType.Fabric) AndAlso Instance.Version.HasFabric Then Return True + If AllowedLoaders.Contains(CompModLoaderType.NeoForge) AndAlso Instance.Version.HasNeoForge Then Return True + If AllowedLoaders.Contains(CompModLoaderType.LiteLoader) AndAlso Instance.Version.HasLiteLoader Then Return True Return False End Function '获取常规资源默认下载位置 If CachedFolder.ContainsKey(File.Type) Then DefaultFolder = CachedFolder(File.Type) Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}") - ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then - DefaultFolder = $"{McVersionCurrent.PathIndie}{SubFolder}" + ElseIf McInstanceSelected IsNot Nothing AndAlso IsInstanceSuitable(McInstanceSelected) Then + DefaultFolder = $"{McInstanceSelected.PathIndie}{SubFolder}" Directory.CreateDirectory(DefaultFolder) Log($"[Comp] 使用当前版本作为默认下载位置:{DefaultFolder}") Else '查找所有可能的版本 - Dim NeedLoad As Boolean = McVersionListLoader.State <> LoadState.Finished + Dim NeedLoad As Boolean = McInstanceListLoader.State <> LoadState.Finished If NeedLoad Then Hint("正在查找适合的游戏版本……") - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True) + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True) End If - Dim SuitableVersions = McVersionList.Values.SelectMany(Function(l) l).Where(Function(v) IsVersionSuitable(v)). + Dim SuitableInstanceList = McInstanceList.Values.SelectMany(Function(l) l).Where(Function(v) IsInstanceSuitable(v)). Select(Function(v) New DirectoryInfo($"{v.PathIndie}{SubFolder}")) - If SuitableVersions.Any Then - Dim SelectedVersion = SuitableVersions. + If SuitableInstanceList.Any Then + Dim SelectedInstance = SuitableInstanceList. OrderByDescending(Function(Dir) If(Dir.Exists, Dir.LastWriteTimeUtc, Date.MinValue)). '先按文件夹更改时间降序 ThenByDescending(Function(Dir) If(Dir.Exists, Dir.GetFiles().Length, -1)). '再按文件夹中的文件数量降序 First() - DefaultFolder = SelectedVersion.FullName + DefaultFolder = SelectedInstance.FullName Directory.CreateDirectory(DefaultFolder) Log($"[Comp] 使用适合的游戏版本作为默认下载位置:{DefaultFolder}") Else - DefaultFolder = PathMcFolder + DefaultFolder = McFolderSelected If NeedLoad Then - Hint("当前 MC 文件夹中没有找到适合此资源的版本!") + Hint($"当前 MC 文件夹中没有找到合适的版本!") Else Log("[Comp] 由于当前版本不兼容,使用当前的 MC 文件夹作为默认下载位置") End If @@ -413,6 +425,7 @@ FileName = File.FileName End Select End If + If File.Type = CompType.Mod Then FileName = FileName.Replace("~", "-") '~ 会导致 Mixin 加载失败 RunInUi( Sub() '弹窗要求选择保存位置 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml index d000fd8..4438d0d 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml @@ -27,7 +27,8 @@ - + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index 7d2dae1..c9820c9 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -10,10 +10,6 @@ Public Module ModDownloadLib ''' 如果 OptiFine 与 Forge 同时复制原版 Jar,就会导致复制文件时冲突。 ''' Private VanillaSyncLock As New Object - ''' - ''' 最高的 Minecraft 大版本号,-1 代表尚未获取。 - ''' - Public McVersionHighest As Integer = -1 #Region "Minecraft 下载" @@ -25,7 +21,7 @@ Public Module ModDownloadLib ''' Json 文件的 Mojang 官方地址。 Public Function McDownloadClient(Behaviour As NetPreDownloadBehaviour, Id As String, Optional JsonUrl As String = Nothing) As LoaderCombo(Of String) Try - Dim VersionFolder As String = PathMcFolder & "versions\" & Id & "\" + Dim VersionFolder As String = McFolderSelected & "versions\" & Id & "\" '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() @@ -62,11 +58,11 @@ Public Module ModDownloadLib ''' ''' 获取下载某个 Minecraft 版本的加载器列表。 - ''' 它必须安装到 PathMcFolder,但是可以自定义版本名(不过自定义的版本名不会修改 Json 中的 id 项)。 + ''' 它必须安装到 McFolderSelected,但是可以自定义版本名(不过自定义的版本名不会修改 Json 中的 id 项)。 ''' - Private Function McDownloadClientLoader(Id As String, Optional JsonUrl As String = Nothing, Optional VersionName As String = Nothing) As List(Of LoaderBase) - VersionName = If(VersionName, Id) - Dim VersionFolder As String = PathMcFolder & "versions\" & VersionName & "\" + Private Function McDownloadClientLoader(Id As String, Optional JsonUrl As String = Nothing, Optional InstanceName As String = Nothing) As List(Of LoaderBase) + InstanceName = If(InstanceName, Id) + Dim VersionFolder As String = McFolderSelected & "versions\" & InstanceName & "\" Dim Loaders As New List(Of LoaderBase) @@ -75,11 +71,11 @@ Public Module ModDownloadLib Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("获取原版 json 文件下载地址", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Dim JsonAddress As String = DlClientListGet(Id) - Task.Output = New List(Of NetFile) From {New NetFile(DlSourceLauncherOrMetaGet(JsonAddress), VersionFolder & VersionName & ".json")} + Task.Output = New List(Of NetFile) From {New NetFile(DlSourceLauncherOrMetaGet(JsonAddress), VersionFolder & InstanceName & ".json")} End Sub) With {.ProgressWeight = 2, .Show = False}) End If Loaders.Add(New LoaderDownload(McDownloadClientJsonName, New List(Of NetFile) From { - New NetFile(DlSourceLauncherOrMetaGet(If(JsonUrl, "")), VersionFolder & VersionName & ".json", New FileChecker(CanUseExistsFile:=False, IsJson:=True)) + New NetFile(DlSourceLauncherOrMetaGet(If(JsonUrl, "")), VersionFolder & InstanceName & ".json", New FileChecker(CanUseExistsFile:=False, IsJson:=True)) }) With {.ProgressWeight = 3}) '下载支持库文件 @@ -88,7 +84,7 @@ Public Module ModDownloadLib Sub(Task As LoaderTask(Of String, List(Of NetFile))) Thread.Sleep(50) '等待 JSON 文件实际写入硬盘(#3710) Log("[Download] 开始分析原版支持库文件:" & VersionFolder) - Task.Output = McLibFix(New McVersion(VersionFolder)) + Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder)) End Sub) With {.ProgressWeight = 1, .Show = False}) LoadersLib.Add(New LoaderDownload("下载原版支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 13, .Show = False}) Loaders.Add(New LoaderCombo(Of String)(McDownloadClientLibName, LoadersLib) With {.Block = False, .ProgressWeight = 14}) @@ -98,28 +94,25 @@ Public Module ModDownloadLib LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析资源文件索引地址(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Try - Dim Version As New McVersion(VersionFolder) - Dim AssetIndex = DlClientAssetIndexGet(Version) + Dim AssetIndex = DlClientAssetIndexGet(New McInstance(VersionFolder)) Task.Output = If(AssetIndex Is Nothing, New List(Of NetFile), New List(Of NetFile) From {AssetIndex}) Catch ex As Exception Throw New Exception("分析资源文件索引地址失败", ex) End Try '顺手添加 Json 项目 Try - Dim VersionJson As JObject = GetJson(ReadFile(VersionFolder & VersionName & ".json")) - VersionJson.Add("clientVersion", Id) - WriteFile(VersionFolder & VersionName & ".json", VersionJson.ToString) + Dim InstanceJson As JObject = GetJson(ReadFile(VersionFolder & InstanceName & ".json")) + InstanceJson.Add("clientVersion", Id) + WriteFile($"{VersionFolder}{InstanceName}.json", InstanceJson.ToString) Catch ex As Exception Throw New Exception("添加客户端版本失败", ex) End Try End Sub) With {.ProgressWeight = 1, .Show = False}) LoadersAssets.Add(New LoaderDownload("下载资源文件索引(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 3, .Show = False}) LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析所需资源文件(副加载器)", - Sub(Task As LoaderTask(Of String, List(Of NetFile))) - Task.Output = McAssetsFixList(New McVersion(VersionFolder), True, Task) - End Sub) With {.ProgressWeight = 3, .Show = False}) + Sub(Task) Task.Output = McAssetsFixList(New McInstance(VersionFolder), True, Task)) With {.ProgressWeight = 0.01, .Show = False}) LoadersAssets.Add(New LoaderDownload("下载资源文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 14, .Show = False}) - Loaders.Add(New LoaderCombo(Of String)("下载原版资源文件", LoadersAssets) With {.Block = False, .ProgressWeight = 21}) + Loaders.Add(New LoaderCombo(Of String)("下载原版资源文件", LoadersAssets) With {.Block = False, .ProgressWeight = 18}) Return Loaders @@ -131,36 +124,39 @@ Public Module ModDownloadLib #Region "Minecraft 下载菜单" - Public Function McDownloadListItem(Entry As JObject, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem - '确定图标 - Dim Logo As String - Select Case Entry("type") - Case "release" - Logo = PathImage & "Blocks/Grass.png" - Case "snapshot" - Logo = PathImage & "Blocks/CommandBlock.png" - Case "special" - Logo = PathImage & "Blocks/GoldBlock.png" - Case Else - Logo = PathImage & "Blocks/CobbleStone.png" - End Select - '建立控件 - Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Entry("id").ToString, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry} - If Entry("lore") Is Nothing Then - NewItem.Info = Entry("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm") - Else - NewItem.Info = Entry("lore").ToString - End If - If Entry("url").ToString.Contains("pcl") Then NewItem.Info = "[PCL 特供下载] " & NewItem.Info - AddHandler NewItem.Click, OnClick - '建立菜单 - If IsSaveOnly Then - NewItem.ContentHandler = AddressOf McDownloadSaveMenuBuild - Else - NewItem.ContentHandler = AddressOf McDownloadMenuBuild - End If - '结束 - Return NewItem + Public Function McDownloadListItem(Entry As JObject, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + '确定图标 + Dim Logo As String + Select Case Entry("type") + Case "release" + Logo = PathImage & "Blocks/Grass.png" + Case "snapshot" + Logo = PathImage & "Blocks/CommandBlock.png" + Case "special" + Logo = PathImage & "Blocks/GoldBlock.png" + Case Else + Logo = PathImage & "Blocks/CobbleStone.png" + End Select + '建立控件 + Dim NewItem As New MyListItem With {.Logo = Logo, .SnapsToDevicePixels = True, .Title = Entry("id").ToString, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry} + If Entry("lore") Is Nothing Then + NewItem.Info = Entry("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm") + Else + NewItem.Info = Entry("lore").ToString + End If + If Entry("url").ToString.Contains("pcl") Then NewItem.Info = "[PCL 特供下载] " & NewItem.Info + AddHandler NewItem.Click, OnClick + '建立菜单 + If IsSaveOnly Then + NewItem.ContentHandler = AddressOf McDownloadSaveMenuBuild + Else + NewItem.ContentHandler = AddressOf McDownloadMenuBuild + End If + '结束 + Return NewItem + End Function) With {.Height = 42} End Function Private Sub McDownloadSaveMenuBuild(sender As Object, e As EventArgs) Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"} @@ -234,23 +230,23 @@ Public Module ModDownloadLib }) With {.ProgressWeight = 2}) '构建服务端 Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("构建服务端", - Sub(Task As LoaderTask(Of String, List(Of NetFile))) - '分析服务端 JAR 文件下载地址 - Dim McVersion As New McVersion(VersionFolder) - If McVersion.JsonObject("downloads") Is Nothing OrElse McVersion.JsonObject("downloads")("server") Is Nothing OrElse McVersion.JsonObject("downloads")("server")("url") Is Nothing Then - File.Delete(VersionFolder & Id & ".json") - If Not New DirectoryInfo(VersionFolder).GetFileSystemInfos.Any() Then Directory.Delete(VersionFolder) - Task.Output = New List(Of NetFile) - Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Red) - Thread.Sleep(2000) '等玩家把上一个提示看完 - Task.Abort() - Return - End If - Dim JarUrl As String = McVersion.JsonObject("downloads")("server")("url") - Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(McVersion.JsonObject("downloads")("server")("size"), -1), Hash:=McVersion.JsonObject("downloads")("server")("sha1")) - Task.Output = New List(Of NetFile) From {New NetFile(DlSourceLauncherOrMetaGet(JarUrl), VersionFolder & Id & "-server.jar", Checker)} - '添加启动脚本 - Dim Bat As String = + Sub(Task As LoaderTask(Of String, List(Of NetFile))) + '分析服务端 JAR 文件下载地址 + Dim Instance As New McInstance(VersionFolder) + If Instance.JsonObject("downloads") Is Nothing OrElse Instance.JsonObject("downloads")("server") Is Nothing OrElse Instance.JsonObject("downloads")("server")("url") Is Nothing Then + File.Delete(VersionFolder & Id & ".json") + If Not New DirectoryInfo(VersionFolder).GetFileSystemInfos.Any() Then Directory.Delete(VersionFolder) + Task.Output = New List(Of NetFile) + Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Red) + Thread.Sleep(2000) '等玩家把上一个提示看完 + Task.Abort() + Return + End If + Dim JarUrl As String = Instance.JsonObject("downloads")("server")("url") + Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(Instance.JsonObject("downloads")("server")("size"), -1), Hash:=Instance.JsonObject("downloads")("server")("sha1")) + Task.Output = New List(Of NetFile) From {New NetFile(DlSourceLauncherOrMetaGet(JarUrl), VersionFolder & Id & "-server.jar", Checker)} + '添加启动脚本 + Dim Bat As String = $"@echo off title {Id} 原版服务端 echo 如果服务端立即停止,请右键编辑该脚本,将下一行开头的 java 替换为适合该 Minecraft 版本的完整 java.exe 的路径。 @@ -262,12 +258,11 @@ echo ------------------------------ echo ---------------------- echo 服务端已停止。 pause" - WriteFile(VersionFolder & "Launch Server.bat", Bat, + WriteFile(VersionFolder & "Launch Server.bat", Bat, Encoding:=If(Encoding.Default.Equals(Encoding.UTF8), Encoding.UTF8, Encoding.GetEncoding("GB18030"))) - '删除版本 JSON - File.Delete(VersionFolder & Id & ".json") - End Sub - ) With {.ProgressWeight = 0.5, .Show = False}) + '删除版本 JSON + File.Delete(VersionFolder & Id & ".json") + End Sub) With {.ProgressWeight = 0.5, .Show = False}) '下载服务端文件 Loaders.Add(New LoaderDownload("下载服务端文件", New List(Of NetFile)) With {.ProgressWeight = 5}) @@ -311,7 +306,7 @@ pause" }) With {.ProgressWeight = 2}) '获取支持库文件地址 Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析核心 JAR 文件下载地址", - Sub(Task) Task.Output = New List(Of NetFile) From {DlClientJarGet(New McVersion(VersionFolder), False)} + Sub(Task) Task.Output = New List(Of NetFile) From {DlClientJarGet(New McInstance(VersionFolder), False)} ) With {.ProgressWeight = 0.5, .Show = False}) '下载支持库文件 Loaders.Add(New LoaderDownload("下载核心 JAR 文件", New List(Of NetFile)) With {.ProgressWeight = 5}) @@ -329,10 +324,10 @@ pause" ''' ''' 显示某 Minecraft 版本的更新日志。 ''' - ''' 在 version_manifest.json 中的对应项。 - Public Sub McUpdateLogShow(VersionJson As JToken) + ''' 在 version_manifest.json 中的对应项。 + Public Sub McUpdateLogShow(InstanceJson As JToken) Dim WikiName As String - Dim Id As String = VersionJson("id").ToString.ToLower + Dim Id As String = InstanceJson("id").ToString.ToLower If Id = "3d shareware v1.34" Then WikiName = "3D_Shareware_v1.34" ElseIf Id = "2.0" Then @@ -365,7 +360,7 @@ pause" WikiName = "Java版Beta_1.9_Prerelease_6" ElseIf Id.Contains("b1.9") Then WikiName = "Java版Beta_1.9_Prerelease" - ElseIf VersionJson("type") = "release" OrElse VersionJson("type") = "snapshot" OrElse VersionJson("type") = "special" Then + ElseIf InstanceJson("type") = "release" OrElse InstanceJson("type") = "snapshot" OrElse InstanceJson("type") = "special" Then WikiName = If(Id.Contains("w"), "", "Java版") & Id.Replace(" Pre-Release ", "-pre") ElseIf Id.StartsWithF("b") Then WikiName = "Java版" & Id.TrimEnd("a", "b", "c", "d", "e").Replace("b", "Beta_") @@ -394,18 +389,18 @@ pause" Private Sub McDownloadOptiFineSave(DownloadInfo As DlOptiFineListEntry) Try - Dim Id As String = DownloadInfo.NameVersion - Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.NameFile, "OptiFine Jar (*.jar)|*.jar") + Dim Id As String = DownloadInfo.InstanceName + Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.FileName, "OptiFine Jar (*.jar)|*.jar") If Not Target.Contains("\") Then Return '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() - If OngoingLoader.Name <> $"OptiFine {DownloadInfo.NameDisplay} 下载" Then Continue For + If OngoingLoader.Name <> $"OptiFine {DownloadInfo.DisplayName} 下载" Then Continue For Hint("该版本正在下载中!", HintType.Red) Return Next - Dim Loader As New LoaderCombo(Of DlOptiFineListEntry)("OptiFine " & DownloadInfo.NameDisplay & " 下载", McDownloadOptiFineSaveLoader(DownloadInfo, Target)) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly} + Dim Loader As New LoaderCombo(Of DlOptiFineListEntry)("OptiFine " & DownloadInfo.DisplayName & " 下载", McDownloadOptiFineSaveLoader(DownloadInfo, Target)) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly} Loader.Start(DownloadInfo) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() @@ -421,9 +416,8 @@ pause" SyncLock JavaLock Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0)) If Java Is Nothing Then - If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。") '开始自动下载 - Dim JavaLoader = JavaFixLoaders(17) + Dim JavaLoader = GetJavaDownloadLoader() Try JavaLoader.Start(17, IsForceRestart:=True) Do While JavaLoader.State = LoadState.Loading AndAlso Not Task.IsAborted @@ -445,7 +439,7 @@ pause" Else Arguments = $"-Duser.home=""{BaseMcFolderHome.TrimEnd("\")}"" -cp ""{Target}"" optifine.Installer" End If - If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments + If Java.MajorVersion >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments '开始启动 SyncLock InstallSyncLock Dim Info = New ProcessStartInfo With { @@ -539,14 +533,14 @@ pause" Private Function McDownloadOptiFineLoader(DownloadInfo As DlOptiFineListEntry, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional ClientFolder As String = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase) '参数初始化 - McFolder = If(McFolder, PathMcFolder) - Dim IsCustomFolder As Boolean = McFolder <> PathMcFolder - Dim Id As String = DownloadInfo.NameVersion + McFolder = If(McFolder, McFolderSelected) + Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected + Dim Id As String = DownloadInfo.InstanceName Dim VersionFolder As String = McFolder & "versions\" & Id & "\" - Dim IsNewVersion As Boolean = DownloadInfo.Inherit.Contains("w") OrElse Val(DownloadInfo.Inherit.Split(".")(1)) >= 14 - Dim Target As String = If(IsNewVersion, + Dim IsNewerVersion As Boolean = DownloadInfo.Inherit.Contains("w") OrElse McVersion.VersionToDrop(DownloadInfo.Inherit) >= 140 + Dim Target As String = If(IsNewerVersion, $"{RequestTaskTempFolder()}OptiFine.jar", - $"{McFolder}libraries\optifine\OptiFine\{DownloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "")}\{DownloadInfo.NameFile.Replace("OptiFine_", "OptiFine-").Replace("preview_", "")}") + $"{McFolder}libraries\optifine\OptiFine\{DownloadInfo.FileName.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "")}\{DownloadInfo.FileName.Replace("OptiFine_", "OptiFine-").Replace("preview_", "")}") Dim Loaders As New List(Of LoaderBase) '获取下载地址 @@ -563,20 +557,20 @@ pause" Dim BmclapiInherit As String = DownloadInfo.Inherit If BmclapiInherit = "1.8" OrElse BmclapiInherit = "1.9" Then BmclapiInherit &= ".0" '#4281 If DownloadInfo.IsPreview Then - Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/")) + Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U_" & DownloadInfo.DisplayName.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/")) Else - Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "")) + Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U/" & DownloadInfo.DisplayName.Replace(DownloadInfo.Inherit & " ", "")) End If '官方源 Dim PageData As String Try - PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, + PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.FileName, 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) + Log("[Download] OptiFine " & DownloadInfo.DisplayName & " 官方下载地址:" & Sources.Last) Catch ex As Exception - Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败") + Log(ex, "获取 OptiFine " & DownloadInfo.DisplayName & " 官方下载地址失败") End Try '构造文件请求 Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, Target, New FileChecker(MinSize:=300 * 1024))} @@ -610,7 +604,7 @@ pause" End Sub) With {.ProgressWeight = 0.1, .Show = False}) '安装(新旧方式均需要原版 Jar 和 Json) - If IsNewVersion Then + If IsNewerVersion Then Log("[Download] 检测为新版 OptiFine:" & DownloadInfo.Inherit) Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("安装 OptiFine(方式 A)", Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) @@ -667,24 +661,24 @@ Retry: CopyFile(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar", VersionFolder & Id & ".jar") Task.Progress = 0.7 '建立 Json 文件 - Dim InheritVersion As New McVersion(McFolder & "versions\" & DownloadInfo.Inherit) + Dim InheritInstance As New McInstance(McFolder & "versions\" & DownloadInfo.Inherit) Dim Json As String = "{ ""id"": """ & Id & """, ""inheritsFrom"": """ & DownloadInfo.Inherit & """, - ""time"": """ & If(DownloadInfo.ReleaseTime = "", InheritVersion.ReleaseTime.ToString("yyyy'-'MM'-'dd"), DownloadInfo.ReleaseTime.Replace("/", "-")) & "T23:33:33+08:00"", - ""releaseTime"": """ & If(DownloadInfo.ReleaseTime = "", InheritVersion.ReleaseTime.ToString("yyyy'-'MM'-'dd"), DownloadInfo.ReleaseTime.Replace("/", "-")) & "T23:33:33+08:00"", + ""time"": """ & If(DownloadInfo.ReleaseTime = "", InheritInstance.ReleaseTime.ToString("yyyy'-'MM'-'dd"), DownloadInfo.ReleaseTime.Replace("/", "-")) & "T23:33:33+08:00"", + ""releaseTime"": """ & If(DownloadInfo.ReleaseTime = "", InheritInstance.ReleaseTime.ToString("yyyy'-'MM'-'dd"), DownloadInfo.ReleaseTime.Replace("/", "-")) & "T23:33:33+08:00"", ""type"": ""release"", ""libraries"": [ - {""name"": ""optifine:OptiFine:" & DownloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "") & """}, + {""name"": ""optifine:OptiFine:" & DownloadInfo.FileName.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "") & """}, {""name"": ""net.minecraft:launchwrapper:1.12""} ], ""mainClass"": ""net.minecraft.launchwrapper.Launch""," Task.Progress = 0.8 - If InheritVersion.IsOldJson Then + If InheritInstance.IsOldJson Then '输出旧版 Json 格式 Json += " ""minimumLauncherVersion"": 18, - ""minecraftArguments"": """ & InheritVersion.JsonObject("minecraftArguments").ToString & " --tweakClass optifine.OptiFineTweaker"" + ""minecraftArguments"": """ & InheritInstance.JsonObject("minecraftArguments").ToString & " --tweakClass optifine.OptiFineTweaker"" }" Else '输出新版 Json 格式 @@ -708,7 +702,7 @@ Retry: '下载支持库 If FixLibrary Then Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 OptiFine 支持库文件", - Sub(Task) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) + Sub(Task) Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) Loaders.Add(New LoaderDownload("下载 OptiFine 支持库文件", New List(Of NetFile)) With {.ProgressWeight = 4}) End If @@ -727,20 +721,20 @@ Retry: Dim BmclapiInherit As String = DownloadInfo.Inherit If BmclapiInherit = "1.8" OrElse BmclapiInherit = "1.9" Then BmclapiInherit &= ".0" '#4281 If DownloadInfo.IsPreview Then - Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/")) + Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U_" & DownloadInfo.DisplayName.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/")) Else - Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "")) + Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U/" & DownloadInfo.DisplayName.Replace(DownloadInfo.Inherit & " ", "")) End If '官方源 Dim PageData As String Try - PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, + PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.FileName, 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) + Log("[Download] OptiFine " & DownloadInfo.DisplayName & " 官方下载地址:" & Sources.Last) Catch ex As Exception - Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败") + Log(ex, "获取 OptiFine " & DownloadInfo.DisplayName & " 官方下载地址失败") End Try Task.Progress = 0.9 '构造文件请求 @@ -755,24 +749,26 @@ Retry: #Region "OptiFine 下载菜单" - Public Function OptiFineDownloadListItem(Entry As DlOptiFineListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem - '建立控件 - Dim NewItem As New MyListItem With { - .Title = Entry.NameDisplay, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, - .Info = If(Entry.IsPreview, "测试版", "正式版") & - If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime) & - If(Entry.RequiredForgeVersion Is Nothing, ",不兼容 Forge", If(Entry.RequiredForgeVersion = "", "", ",兼容 Forge " & Entry.RequiredForgeVersion)), - .Logo = PathImage & "Blocks/GrassPath.png" - } - AddHandler NewItem.Click, OnClick - '建立菜单 - If IsSaveOnly Then - NewItem.ContentHandler = AddressOf OptiFineSaveContMenuBuild - Else - NewItem.ContentHandler = AddressOf OptiFineContMenuBuild - End If - '结束 - Return NewItem + Public Function OptiFineDownloadListItem(Entry As DlOptiFineListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + '建立控件 + Dim NewItem As New MyListItem With { + .Title = Entry.DisplayName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, + .Info = If(Entry.IsPreview, "测试版", "正式版") & + If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime) & + If(Entry.RequiredForgeVersion Is Nothing, ",不兼容 Forge", If(Entry.RequiredForgeVersion = "", "", ",兼容 Forge " & Entry.RequiredForgeVersion)), + .Logo = PathImage & "Blocks/GrassPath.png" + } + AddHandler NewItem.Click, OnClick + '建立菜单 + If IsSaveOnly Then + NewItem.ContentHandler = AddressOf OptiFineSaveContMenuBuild + Else + NewItem.ContentHandler = AddressOf OptiFineContMenuBuild + End If + Return NewItem + End Function) With {.Height = 42} End Function Private Sub OptiFineSaveContMenuBuild(sender As Object, e As EventArgs) Dim BtnInfo As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonInfo, .ToolTip = "更新日志"} @@ -804,7 +800,7 @@ Retry: Else Version = sender.Parent.Parent.Tag End If - OpenWebsite("https://optifine.net/changelog?f=" & Version.NameFile) + OpenWebsite("https://optifine.net/changelog?f=" & Version.FileName) End Sub Public Sub OptiFineSave_Click(sender As Object, e As RoutedEventArgs) Dim Version As DlOptiFineListEntry @@ -826,8 +822,8 @@ Retry: Try Dim Id As String = DownloadInfo.Inherit Dim Target As String = PathTemp & "Download\" & Id & "-Liteloader.jar" - Dim VersionName As String = DownloadInfo.Inherit & "-LiteLoader" - Dim VersionFolder As String = PathMcFolder & "versions\" & VersionName & "\" + Dim InstanceName As String = DownloadInfo.Inherit & "-LiteLoader" + Dim VersionFolder As String = McFolderSelected & "versions\" & InstanceName & "\" '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() @@ -837,10 +833,10 @@ Retry: Next '已有版本检查 - If File.Exists(VersionFolder & VersionName & ".json") Then - If MyMsgBox("版本 " & VersionName & " 已存在,是否重新下载?" & vbCrLf & "这会覆盖版本的 json 和 jar 文件,但不会影响版本隔离的文件。", "版本已存在", "继续", "取消") = 1 Then - File.Delete(VersionFolder & VersionName & ".jar") - File.Delete(VersionFolder & VersionName & ".json") + If File.Exists(VersionFolder & InstanceName & ".json") Then + If MyMsgBox("版本 " & InstanceName & " 已存在,是否重新下载?" & vbCrLf & "这会覆盖版本的 json 和 jar 文件,但不会影响版本隔离的文件。", "版本已存在", "继续", "取消") = 1 Then + File.Delete(VersionFolder & InstanceName & ".jar") + File.Delete(VersionFolder & InstanceName & ".json") Else Return End If @@ -913,8 +909,8 @@ Retry: Private Function McDownloadLiteLoaderLoader(DownloadInfo As DlLiteLoaderListEntry, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase) '参数初始化 - McFolder = If(McFolder, PathMcFolder) - Dim IsCustomFolder As Boolean = McFolder <> PathMcFolder + McFolder = If(McFolder, McFolderSelected) + Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected Dim Id As String = DownloadInfo.Inherit Dim Target As String = PathTemp & "Download\" & Id & "-Liteloader.jar" Dim VersionName As String = DownloadInfo.Inherit & "-LiteLoader" @@ -938,8 +934,8 @@ Retry: '构造版本 Json Dim VersionJson As New JObject VersionJson.Add("id", VersionName) - VersionJson.Add("time", Date.ParseExact(DownloadInfo.ReleaseTime, "yyyy/MM/dd HH:mm", Globalization.CultureInfo.CurrentCulture)) - VersionJson.Add("releaseTime", Date.ParseExact(DownloadInfo.ReleaseTime, "yyyy/MM/dd HH:mm", Globalization.CultureInfo.CurrentCulture)) + VersionJson.Add("time", Date.ParseExact(DownloadInfo.ReleaseTime, "yyyy/MM/dd HH:mm", Globalization.CultureInfo.InvariantCulture)) + VersionJson.Add("releaseTime", Date.ParseExact(DownloadInfo.ReleaseTime, "yyyy/MM/dd HH:mm", Globalization.CultureInfo.InvariantCulture)) VersionJson.Add("type", "release") VersionJson.Add("arguments", GetJson("{""game"":[""--tweakClass"",""" & DownloadInfo.JsonToken("tweakClass").ToString & """]}")) VersionJson.Add("libraries", DownloadInfo.JsonToken("libraries")) @@ -957,7 +953,7 @@ Retry: '下载支持库 If FixLibrary Then Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 LiteLoader 支持库文件", - Sub(Task) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) + Sub(Task) Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) Loaders.Add(New LoaderDownload("下载 LiteLoader 支持库文件", New List(Of NetFile)) With {.ProgressWeight = 6}) End If @@ -968,22 +964,25 @@ Retry: #Region "LiteLoader 下载菜单" - Public Function LiteLoaderDownloadListItem(Entry As DlLiteLoaderListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem - '建立控件 - Dim NewItem As New MyListItem With { - .Title = Entry.Inherit, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, - .Info = If(Entry.IsPreview, "测试版", "稳定版") & If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime), - .Logo = PathImage & "Blocks/Egg.png" - } - AddHandler NewItem.Click, OnClick - '建立菜单 - If IsSaveOnly Then - NewItem.ContentHandler = AddressOf LiteLoaderSaveContMenuBuild - Else - NewItem.ContentHandler = AddressOf LiteLoaderContMenuBuild - End If - '结束 - Return NewItem + Public Function LiteLoaderDownloadListItem(Entry As DlLiteLoaderListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + '建立控件 + Dim NewItem As New MyListItem With { + .Title = Entry.Inherit, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, + .Info = If(Entry.IsPreview, "测试版", "稳定版") & If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime), + .Logo = PathImage & "Blocks/Egg.png" + } + AddHandler NewItem.Click, OnClick + '建立菜单 + If IsSaveOnly Then + NewItem.ContentHandler = AddressOf LiteLoaderSaveContMenuBuild + Else + NewItem.ContentHandler = AddressOf LiteLoaderContMenuBuild + End If + '结束 + Return NewItem + End Function) With {.Height = 42} End Function Private Sub LiteLoaderSaveContMenuBuild(sender As MyListItem, e As EventArgs) If sender.Tag.IsLegacy Then @@ -1056,8 +1055,8 @@ Retry: Dim Files As New List(Of NetFile) If Info.IsNeoForge Then 'NeoForge - Dim Neo As DlNeoForgeListEntry = Info - Dim Url As String = Neo.UrlBase & "-installer.jar" + Dim NeoForge As DlNeoForgeListEntry = Info + Dim Url As String = NeoForge.UrlBase & "-installer.jar" Files.Add(New NetFile({ Url.Replace("maven.neoforged.net/releases", "bmclapi2.bangbang93.com/maven"), Url }, Target, New FileChecker(MinSize:=64 * 1024))) @@ -1092,9 +1091,8 @@ Retry: SyncLock JavaLock Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 60)) If Java Is Nothing Then - If Not JavaDownloadConfirm("Java 8 或更高版本") Then Throw New Exception("由于未找到 Java,已取消安装。") '开始自动下载 - Dim JavaLoader = JavaFixLoaders(17) + Dim JavaLoader = GetJavaDownloadLoader() Try JavaLoader.Start(17, IsForceRestart:=True) Do While JavaLoader.State = LoadState.Loading AndAlso Not Task.IsAborted @@ -1116,7 +1114,7 @@ Retry: Else Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}" End If - If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments + If Java.MajorVersion >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments '开始启动 SyncLock InstallSyncLock Dim Info = New ProcessStartInfo With { @@ -1247,10 +1245,10 @@ Retry: ''' ''' 获取下载某个 Forgelike 版本的加载器列表。 ''' - Private Function McDownloadForgelikeLoader(IsNeoForge As Boolean, LoaderVersion As String, TargetVersion As String, Inherit As String, Optional Info As DlForgelikeEntry = Nothing, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional ClientFolder As String = Nothing) As List(Of LoaderBase) + Private Function McDownloadForgelikeLoader(IsNeoForge As Boolean, LoaderVersion As String, NewInstanceName As String, Inherit As String, Optional Info As DlForgelikeEntry = Nothing, Optional McFolder As String = Nothing, Optional ClientDownloadLoader As LoaderCombo(Of String) = Nothing, Optional ClientFolder As String = Nothing) As List(Of LoaderBase) '参数初始化 - McFolder = If(McFolder, PathMcFolder) + McFolder = If(McFolder, McFolderSelected) If IsNeoForge AndAlso Info Is Nothing Then '需要传入 API Name,但整合包版本可能不以 1.20.1- 开头,所以需要进行特别处理 If Inherit = "1.20.1" AndAlso Not LoaderVersion.StartsWithF("1.20.1-") Then @@ -1265,12 +1263,12 @@ Retry: LoaderVersion = LoaderVersion.AfterLast("-") End If Dim LoaderName As String = If(IsNeoForge, "NeoForge", "Forge") - Dim IsCustomFolder As Boolean = McFolder <> PathMcFolder + Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected Dim InstallerAddress As String = RequestTaskTempFolder() & "forge_installer.jar" - Dim VersionFolder As String = $"{McFolder}versions\{TargetVersion}\" + Dim VersionFolder As String = $"{McFolder}versions\{NewInstanceName}\" Dim DisplayName As String = $"{LoaderName} {Inherit} - {LoaderVersion}" Dim Loaders As New List(Of LoaderBase) - Dim LibVersionFolder As String = $"{PathMcFolder}versions\{TargetVersion}\" '作为 Lib 文件目标的版本文件夹 + Dim LibVersionFolder As String = $"{McFolderSelected}versions\{NewInstanceName}\" '作为 Lib 文件目标的版本文件夹 '获取 Forge 下载信息 If Info Is Nothing Then @@ -1282,7 +1280,7 @@ Retry: Task.Progress = 0.8 '查找对应版本 For Each ForgeVersion In ForgeLoader.Output - If VersionSortInteger(ForgeVersion.Version.ToString, LoaderVersion) = 0 Then + If CompareVersion(ForgeVersion.Version.ToString, LoaderVersion) = 0 Then Info = ForgeVersion Return End If @@ -1362,21 +1360,21 @@ Retry: Exit For End If Next - Task.Output = McLibFixFromLibToken(Libs, PathMcFolder) + Task.Output = McLibNetFilesFromTokens(Libs, McFolderSelected) Catch ex As Exception Throw New Exception($"获取{If(IsNeoForge, " Neo", "新版 ")}Forge 支持库列表失败", ex) Finally '释放文件 If Installer IsNot Nothing Then Installer.Dispose() End Try - End Sub) With {.ProgressWeight = 2}) + End Sub) With {.ProgressWeight = 1}) Loaders.Add(New LoaderDownload($"下载 {LoaderName} 支持库文件", New List(Of NetFile)) With {.ProgressWeight = 12}) Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)($"获取 {LoaderName} 支持库文件", Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) #Region "Forgelike 文件" If IsCustomFolder Then For Each LibFile As McLibToken In Libs - Dim RealPath As String = LibFile.LocalPath.Replace(PathMcFolder, McFolder) + Dim RealPath As String = LibFile.LocalPath.Replace(McFolderSelected, McFolder) If Not File.Exists(RealPath) Then Directory.CreateDirectory(IO.Path.GetDirectoryName(RealPath)) CopyFile(LibFile.LocalPath, RealPath) @@ -1458,8 +1456,8 @@ Retry: '如果没有新增文件夹,那么预测的文件夹名就是正确的 '如果只新增 1 个文件夹,那么拷贝 json 文件 Dim JsonFile As FileInfo = DeltaList(0).EnumerateFiles.First() - WriteFile(VersionFolder & TargetVersion & ".json", ReadFile(JsonFile.FullName)) - Log($"[Download] 已拷贝新增的版本 JSON 文件:{JsonFile.FullName} -> {VersionFolder}{TargetVersion}.json") + WriteFile(VersionFolder & NewInstanceName & ".json", ReadFile(JsonFile.FullName)) + Log($"[Download] 已拷贝新增的版本 JSON 文件:{JsonFile.FullName} -> {VersionFolder}{NewInstanceName}.json") ElseIf DeltaList.Count > 1 Then '新增了多个文件夹 Log($"[Download] 有多个疑似的新增版本,无法确定:{DeltaList.Select(Function(d) d.Name).Join(";")}") @@ -1498,8 +1496,8 @@ Retry: Log("[Download] 开始进行 Forge 安装,Legacy 方式 1:" & InstallerAddress) '建立 Json 文件 Dim JsonVersion As JObject = GetJson(ReadFile(Installer.GetEntry(Json("json").ToString.TrimStart("/")).Open)) - JsonVersion("id") = TargetVersion - WriteFile(VersionFolder & TargetVersion & ".json", JsonVersion.ToString) + JsonVersion("id") = NewInstanceName + WriteFile(VersionFolder & NewInstanceName & ".json", JsonVersion.ToString) Task.Progress = 0.6 '解压支持库文件 Installer.Dispose() @@ -1515,9 +1513,9 @@ Retry: WriteFile(JarAddress, Installer.GetEntry(Json("install")("filePath")).Open) Task.Progress = 0.9 '建立 Json 文件 - Json("versionInfo")("id") = TargetVersion + Json("versionInfo")("id") = NewInstanceName If Json("versionInfo")("inheritsFrom") Is Nothing Then CType(Json("versionInfo"), JObject).Add("inheritsFrom", Inherit) - WriteFile(VersionFolder & TargetVersion & ".json", Json("versionInfo").ToString) + WriteFile(VersionFolder & NewInstanceName & ".json", Json("versionInfo").ToString) End If Catch ex As Exception Throw New Exception("非新版方式安装 Forge 失败", ex) @@ -1549,7 +1547,7 @@ Retry: If Entries.Any Then FreshVersion = Entries(0) Else - Log("[System] 未找到可用的 Forge 版本", LogLevel.Debug) + Log("[System] 未找到可用的 Forge 版本") End If Dim RecommendedVersion As DlForgeVersionEntry = Nothing For Each Entry In Entries @@ -1559,35 +1557,38 @@ Retry: If FreshVersion IsNot Nothing AndAlso FreshVersion Is RecommendedVersion Then FreshVersion = Nothing '显示各个版本 If RecommendedVersion IsNot Nothing Then - Dim Recommended = ForgeDownloadListItem(RecommendedVersion, OnClick, IsSaveOnly) + Dim Recommended = ForgeDownloadListItem(RecommendedVersion, OnClick, IsSaveOnly).Init() Recommended.Info = "推荐版" & If(Recommended.Info = "", "", "," & Recommended.Info) Stack.Children.Add(Recommended) End If If FreshVersion IsNot Nothing Then - Dim Fresh = ForgeDownloadListItem(FreshVersion, OnClick, IsSaveOnly) + Dim Fresh = ForgeDownloadListItem(FreshVersion, OnClick, IsSaveOnly).Init() Fresh.Info = "最新版" & If(Fresh.Info = "", "", "," & Fresh.Info) Stack.Children.Add(Fresh) End If '添加间隔 Stack.Children.Add(New TextBlock With {.Text = "全部版本 (" & Entries.Count & ")", .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 13, 0, 4)}) End Sub - Public Function ForgeDownloadListItem(Entry As DlForgeVersionEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem - '建立控件 - Dim NewItem As New MyListItem With { - .Title = Entry.VersionName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, - .Info = {If(Entry.ReleaseTime = "", "", "发布于 " & Entry.ReleaseTime), If(ModeDebug, "种类:" & Entry.Category, "")}. - Where(Function(d) d <> "").Join(","), - .Logo = PathImage & "Blocks/Anvil.png" - } - AddHandler NewItem.Click, OnClick - '建立菜单 - If IsSaveOnly Then - NewItem.ContentHandler = AddressOf ForgeSaveContMenuBuild - Else - NewItem.ContentHandler = AddressOf ForgeContMenuBuild - End If - '结束 - Return NewItem + Public Function ForgeDownloadListItem(Entry As DlForgeVersionEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + '建立控件 + Dim NewItem As New MyListItem With { + .Title = Entry.VersionName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, + .Info = {If(Entry.ReleaseTime = "", "", "发布于 " & Entry.ReleaseTime), If(ModeDebug, "种类:" & Entry.Category, "")}. + Where(Function(d) d <> "").Join(","), + .Logo = PathImage & "Blocks/Anvil.png" + } + AddHandler NewItem.Click, OnClick + '建立菜单 + If IsSaveOnly Then + NewItem.ContentHandler = AddressOf ForgeSaveContMenuBuild + Else + NewItem.ContentHandler = AddressOf ForgeContMenuBuild + End If + '结束 + Return NewItem + End Function) With {.Height = 42} End Function Private Sub ForgeContMenuBuild(sender As MyListItem, e As EventArgs) Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"} @@ -1683,7 +1684,7 @@ Retry: Return Nothing End If Dim Json As JObject = GetJson(List) - If Json Is Nothing OrElse (Not If(McVersion, "null").Contains(".")) OrElse Not Json.ContainsKey(McVersion) Then Return Nothing + If Json Is Nothing OrElse (Not If(McVersion, "").Contains(".")) OrElse Not Json.ContainsKey(McVersion) Then Return Nothing Return If(Json(McVersion), "").ToString Catch ex As Exception Log(ex, "获取 Forge 推荐版本失败(" & If(McVersion, "null") & ")", LogLevel.Feedback) @@ -1715,34 +1716,37 @@ Retry: End If '显示各个版本 If FreshStableVersion IsNot Nothing Then - Dim Fresh = NeoForgeDownloadListItem(FreshStableVersion, OnClick, IsSaveOnly) + Dim Fresh = NeoForgeDownloadListItem(FreshStableVersion, OnClick, IsSaveOnly).Init() Fresh.Info = If(Fresh.Info = "", "最新稳定版", "最新" & Fresh.Info) Stack.Children.Add(Fresh) End If If FreshBetaVersion IsNot Nothing Then - Dim Fresh = NeoForgeDownloadListItem(FreshBetaVersion, OnClick, IsSaveOnly) + Dim Fresh = NeoForgeDownloadListItem(FreshBetaVersion, OnClick, IsSaveOnly).Init() Fresh.Info = If(Fresh.Info = "", "最新测试版", "最新" & Fresh.Info) Stack.Children.Add(Fresh) End If '添加间隔 Stack.Children.Add(New TextBlock With {.Text = "全部版本 (" & Entries.Count & ")", .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 13, 0, 4)}) End Sub - Public Function NeoForgeDownloadListItem(Info As DlNeoForgeListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyListItem - '建立控件 - Dim NewItem As New MyListItem With { - .Title = Info.VersionName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Info, - .Info = If(Info.IsBeta, "测试版", "稳定版"), - .Logo = PathImage & "Blocks/NeoForge.png" - } - AddHandler NewItem.Click, OnClick - '建立菜单 - If IsSaveOnly Then - NewItem.ContentHandler = AddressOf NeoForgeSaveContMenuBuild - Else - NewItem.ContentHandler = AddressOf NeoForgeContMenuBuild - End If - '结束 - Return NewItem + Public Function NeoForgeDownloadListItem(Info As DlNeoForgeListEntry, OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + '建立控件 + Dim NewItem As New MyListItem With { + .Title = Info.VersionName, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Info, + .Info = If(Info.IsBeta, "测试版", "稳定版"), + .Logo = PathImage & "Blocks/NeoForge.png" + } + AddHandler NewItem.Click, OnClick + '建立菜单 + If IsSaveOnly Then + NewItem.ContentHandler = AddressOf NeoForgeSaveContMenuBuild + Else + NewItem.ContentHandler = AddressOf NeoForgeContMenuBuild + End If + Return NewItem + End Function + ) With {.Height = 42} End Function Private Sub NeoForgeContMenuBuild(sender As MyListItem, e As EventArgs) Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"} @@ -1832,8 +1836,8 @@ Retry: Private Function McDownloadFabricLoader(FabricVersion As String, MinecraftName As String, Optional McFolder As String = Nothing, Optional FixLibrary As Boolean = True) As List(Of LoaderBase) '参数初始化 - McFolder = If(McFolder, PathMcFolder) - Dim IsCustomFolder As Boolean = McFolder <> PathMcFolder + McFolder = If(McFolder, McFolderSelected) + Dim IsCustomFolder As Boolean = McFolder <> McFolderSelected Dim Id As String = "fabric-loader-" & FabricVersion & "-" & MinecraftName Dim VersionFolder As String = McFolder & "versions\" & Id & "\" Dim Loaders As New List(Of LoaderBase) @@ -1858,7 +1862,7 @@ Retry: '下载支持库 If FixLibrary Then Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 Fabric 支持库文件", - Sub(Task) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) + Sub(Task) Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) Loaders.Add(New LoaderDownload("下载 Fabric 支持库文件", New List(Of NetFile)) With {.ProgressWeight = 8}) End If @@ -1869,38 +1873,41 @@ Retry: #Region "Fabric 下载菜单" - Public Function FabricDownloadListItem(Entry As JObject, OnClick As MyListItem.ClickEventHandler) As MyListItem - '建立控件 - Dim NewItem As New MyListItem With { - .Title = Entry("version").ToString.Replace("+build", ""), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, - .Info = If(Entry("stable").ToObject(Of Boolean), "稳定版", "测试版"), - .Logo = PathImage & "Blocks/Fabric.png" - } - AddHandler NewItem.Click, OnClick - '结束 - Return NewItem + Public Function FabricDownloadListItem(Entry As JObject, OnClick As MyListItem.ClickEventHandler) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + Dim NewItem As New MyListItem With { + .Title = Entry("version").ToString.Replace("+build", ""), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, + .Info = If(Entry("stable").ToObject(Of Boolean), "稳定版", "测试版"), + .Logo = PathImage & "Blocks/Fabric.png" + } + AddHandler NewItem.Click, OnClick + Return NewItem + End Function) With {.Height = 42} End Function - Public Function FabricApiDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem - '建立控件 - Dim NewItem As New MyListItem With { - .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").BeforeFirst("+").Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, - .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy'/'MM'/'dd HH':'mm"), - .Logo = PathImage & "Blocks/Fabric.png" - } - AddHandler NewItem.Click, OnClick - '结束 - Return NewItem + Public Function FabricApiDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + Dim NewItem As New MyListItem With { + .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").BeforeFirst("+").Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, + .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy'/'MM'/'dd HH':'mm"), + .Logo = PathImage & "Blocks/Fabric.png" + } + AddHandler NewItem.Click, OnClick + Return NewItem + End Function) With {.Height = 42} End Function - Public Function OptiFabricDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem - '建立控件 - Dim NewItem As New MyListItem With { - .Title = Entry.DisplayName.ToLower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, - .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy'/'MM'/'dd HH':'mm"), - .Logo = PathImage & "Blocks/OptiFabric.png" - } - AddHandler NewItem.Click, OnClick - '结束 - Return NewItem + Public Function OptiFabricDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyVirtualizingElement(Of MyListItem) + Return New MyVirtualizingElement(Of MyListItem)( + Function() + Dim NewItem As New MyListItem With { + .Title = Entry.DisplayName.ToLower.Replace("optifabric-", "").Replace(".jar", "").Trim.TrimStart("v"), .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry, + .Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy'/'MM'/'dd HH':'mm"), + .Logo = PathImage & "Blocks/OptiFabric.png" + } + AddHandler NewItem.Click, OnClick + Return NewItem + End Function) With {.Height = 42} End Function #End Region @@ -1915,11 +1922,11 @@ Retry: ''' ''' 必填。安装目标版本名称。 ''' - Public TargetVersionName As String + Public NewInstanceName As String ''' ''' 必填。安装目标文件夹。 ''' - Public TargetVersionFolder As String + Public VersionFolder As String ''' ''' 必填。欲下载的 Minecraft 的版本名。 @@ -2002,7 +2009,7 @@ Retry: Public Sub McInstallState(Loader As LoaderBase) Select Case Loader.State Case LoadState.Finished - WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存) + WriteIni(McFolderSelected & "PCL.ini", "InstanceCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存) Hint(Loader.Name & "成功!", HintType.Green) Case LoadState.Failed Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Red) @@ -2012,7 +2019,7 @@ Retry: Return '不重新加载版本列表 End Select McInstallFailedClearFolder(Loader) - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End Sub Public Sub McInstallFailedClearFolder(Loader) Try @@ -2038,10 +2045,10 @@ Retry: Try Dim SubLoaders = McInstallLoader(Request) If SubLoaders Is Nothing Then Return False - Dim Loader As New LoaderCombo(Of String)(Request.TargetVersionName & " 安装", SubLoaders) With {.OnStateChanged = AddressOf McInstallState} + Dim Loader As New LoaderCombo(Of String)(Request.NewInstanceName & " 安装", SubLoaders) With {.OnStateChanged = AddressOf McInstallState} '启动 - Loader.Start(Request.TargetVersionFolder) + Loader.Start(Request.VersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() FrmMain.BtnExtraDownload.Ribble() @@ -2063,21 +2070,21 @@ Retry: Dim TempMcFolder As String = RequestTaskTempFolder(Request.OptiFineEntry IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing) '获取参数 - Dim VersionFolder As String = PathMcFolder & "versions\" & Request.TargetVersionName & "\" + Dim VersionFolder As String = McFolderSelected & "versions\" & Request.NewInstanceName & "\" If Directory.Exists(TempMcFolder) Then DeleteDirectory(TempMcFolder) Dim OptiFineFolder As String = Nothing If Request.OptiFineVersion IsNot Nothing Then If Request.OptiFineVersion.Contains("_HD_U_") Then Request.OptiFineVersion = "HD_U_" & Request.OptiFineVersion.AfterLast("_HD_U_") '#735 Request.OptiFineEntry = New DlOptiFineListEntry With { - .NameDisplay = Request.MinecraftName & " " & Request.OptiFineVersion.Replace("HD_U_", "").Replace("_", "").Replace("pre", " pre"), + .DisplayName = Request.MinecraftName & " " & Request.OptiFineVersion.Replace("HD_U_", "").Replace("_", "").Replace("pre", " pre"), .Inherit = Request.MinecraftName, .IsPreview = Request.OptiFineVersion.ContainsF("pre", True), - .NameVersion = Request.MinecraftName & "-OptiFine_" & Request.OptiFineVersion, - .NameFile = If(Request.OptiFineVersion.ContainsF("pre", True), "preview_", "") & + .InstanceName = Request.MinecraftName & "-OptiFine_" & Request.OptiFineVersion, + .FileName = If(Request.OptiFineVersion.ContainsF("pre", True), "preview_", "") & "OptiFine_" & Request.MinecraftName & "_" & Request.OptiFineVersion & ".jar" } End If - If Request.OptiFineEntry IsNot Nothing Then OptiFineFolder = TempMcFolder & "versions\" & Request.OptiFineEntry.NameVersion + If Request.OptiFineEntry IsNot Nothing Then OptiFineFolder = TempMcFolder & "versions\" & Request.OptiFineEntry.InstanceName Dim ForgeFolder As String = Nothing If Request.ForgeEntry IsNot Nothing Then Request.ForgeVersion = If(Request.ForgeVersion, Request.ForgeEntry.VersionName) If Request.ForgeVersion IsNot Nothing Then ForgeFolder = TempMcFolder & "versions\forge-" & Request.ForgeVersion @@ -2107,8 +2114,8 @@ Retry: Log("[Download] 对应的原版版本:" & Request.MinecraftName) '重复版本检查 - If File.Exists($"{VersionFolder}{Request.TargetVersionName}.json") Then - Hint("版本 " & Request.TargetVersionName & " 已经存在!", HintType.Red) + If File.Exists($"{VersionFolder}{Request.NewInstanceName}.json") Then + Hint("版本 " & Request.NewInstanceName & " 已经存在!", HintType.Red) Throw New CancelledException End If @@ -2124,27 +2131,27 @@ Retry: LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsTempFolder)}) With {.ProgressWeight = 3, .Block = False}) End If '原版 - Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39, + Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.NewInstanceName)) With {.Show = False, .ProgressWeight = 39, .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing} LoaderList.Add(ClientLoader) 'OptiFine If Request.OptiFineEntry IsNot Nothing Then If OptiFineAsMod Then - LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.NameFile)) With {.Show = False, .ProgressWeight = 16, + LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.DisplayName, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.FileName)) With {.Show = False, .ProgressWeight = 16, .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) Else - LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 24, + LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.DisplayName, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.VersionFolder, False)) With {.Show = False, .ProgressWeight = 24, .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) End If End If 'Forge If Request.ForgeVersion IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgelikeLoader(False, Request.ForgeVersion, "forge-" & Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25, + LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgelikeLoader(False, Request.ForgeVersion, "forge-" & Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.VersionFolder)) With {.Show = False, .ProgressWeight = 25, .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing AndAlso Request.NeoForgeEntry Is Nothing}) End If 'NeoForge If Request.NeoForgeVersion IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & Request.NeoForgeVersion, McDownloadForgelikeLoader(True, Request.NeoForgeVersion, "neoforge-" & Request.NeoForgeVersion, Request.MinecraftName, Request.NeoForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25, + LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & Request.NeoForgeVersion, McDownloadForgelikeLoader(True, Request.NeoForgeVersion, "neoforge-" & Request.NeoForgeVersion, Request.MinecraftName, Request.NeoForgeEntry, TempMcFolder, ClientLoader, Request.VersionFolder)) With {.Show = False, .ProgressWeight = 25, .Block = Request.ForgeEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) End If 'LiteLoader @@ -2164,24 +2171,24 @@ Retry: MergeJson(VersionFolder, VersionFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder) Task.Progress = 0.2 '迁移文件 - If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries") + If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", McFolderSelected & "libraries") Task.Progress = 0.8 '创建 Mod 和资源包文件夹 - Dim ModsFolder = New McVersion(VersionFolder).PathIndie & "mods\" '版本隔离信息在此时被决定 + Dim ModsFolder = New McInstance(VersionFolder).PathIndie & "mods\" '版本隔离信息在此时被决定 If Directory.Exists(ModsTempFolder) Then CopyDirectory(ModsTempFolder, ModsFolder) ElseIf Modable Then Directory.CreateDirectory(ModsFolder) Log("[Download] 自动创建 Mod 文件夹:" & ModsFolder) End If - Dim ResourcepacksFolder = New McVersion(VersionFolder).PathIndie & "resourcepacks\" + Dim ResourcepacksFolder = New McInstance(VersionFolder).PathIndie & "resourcepacks\" Directory.CreateDirectory(ResourcepacksFolder) Log("[Download] 自动创建资源包文件夹:" & ResourcepacksFolder) End Sub) With {.ProgressWeight = 2, .Block = True}) '补全文件 If Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.BeforeFirst(".") >= 20) OrElse Request.NeoForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing Then Dim LoadersLib As New List(Of LoaderBase) - LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) + LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task) Task.Output = McLibNetFilesFromInstance(New McInstance(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) LoaderList.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}) End If diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb index f4c0737..6efabd1 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb @@ -14,9 +14,9 @@ DlNeoForgeListLoader.Start() '重载预览 - TextSelectName.ValidateRules = New ObjectModel.Collection(Of Validate) From {New ValidateFolderName(PathMcFolder & "versions")} + TextSelectName.ValidateRules = New ObjectModel.Collection(Of Validate) From {New ValidateFolderName(McFolderSelected & "versions")} TextSelectName.Validate() - SelectReload() + ReloadSelected() '非重复加载部分 If IsLoad Then Return @@ -71,10 +71,10 @@ Next '启动 Forge 加载 - If SelectedMinecraftId.StartsWith("1.") Then - Dim ForgeLoader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion " & SelectedMinecraftId, AddressOf DlForgeVersionMain) + If McVersion.IsFormatFit(VanillaName) Then + Dim ForgeLoader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion " & VanillaName, AddressOf DlForgeVersionMain) LoadForge.State = ForgeLoader - ForgeLoader.Start(SelectedMinecraftId) + ForgeLoader.Start(VanillaName) End If '启动 Fabric API、OptiFabric 加载 @@ -95,7 +95,7 @@ Fabric_Loaded() FabricApi_Loaded() OptiFabric_Loaded() - SelectReload() + ReloadSelected() PanMinecraft.Visibility = Visibility.Collapsed End Sub, After:=True), AaOpacity(PanSelect, 1 - PanSelect.Opacity, 70, 100), @@ -124,7 +124,7 @@ DisabledPageAnimControls.Add(BtnStart) BtnStart.Show = False - SelectClear() '清除已选择项 + ClearSelected() '清除已选择项 PanMinecraft.Visibility = Visibility.Visible PanSelect.IsHitTestVisible = False PanMinecraft.IsHitTestVisible = True @@ -145,9 +145,9 @@ }, "FrmDownloadInstall SelectPageSwitch") End Sub Public Sub MinecraftSelected(sender As MyListItem, e As MouseButtonEventArgs) - SelectedMinecraftId = sender.Title - SelectedMinecraftJsonUrl = sender.Tag("url").ToString - SelectedMinecraftIcon = sender.Logo + VanillaName = sender.Title + VanillaData = sender.Tag + VanillaIcon = sender.Logo EnterSelectPage() End Sub @@ -156,157 +156,31 @@ #Region "选择" 'Minecraft - Private SelectedMinecraftId As String - Private SelectedMinecraftJsonUrl As String - Private SelectedMinecraftIcon As String + Private VanillaName As String + Private VanillaData As JObject + Private VanillaIcon As String + Private ReadOnly Property VanillaDrop As Integer + Get + Return McVersion.VersionToDrop(VanillaName, True) + End Get + End Property - 'OptiFine + '附加组件 Private SelectedOptiFine As DlOptiFineListEntry = Nothing - Private Sub SetOptiFineInfoShow(IsShow As String) - If PanOptiFineInfo.Tag = IsShow Then Return - PanOptiFineInfo.Tag = IsShow - If IsShow = "True" Then - '显示信息栏 - AniStart({ - AaTranslateY(PanOptiFineInfo, -CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), - AaOpacity(PanOptiFineInfo, 1 - PanOptiFineInfo.Opacity, 80, 90) - }, "SetOptiFineInfoShow") - Else - '隐藏信息栏 - AniStart({ - AaTranslateY(PanOptiFineInfo, 6 - CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 120), - AaOpacity(PanOptiFineInfo, -PanOptiFineInfo.Opacity, 50) - }, "SetOptiFineInfoShow") - End If - End Sub - - 'LiteLoader Private SelectedLiteLoader As DlLiteLoaderListEntry = Nothing - Private Sub SetLiteLoaderInfoShow(IsShow As String) - If PanLiteLoaderInfo.Tag = IsShow Then Return - PanLiteLoaderInfo.Tag = IsShow - If IsShow = "True" Then - '显示信息栏 - AniStart({ - AaTranslateY(PanLiteLoaderInfo, -CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), - AaOpacity(PanLiteLoaderInfo, 1 - PanLiteLoaderInfo.Opacity, 80, 90) - }, "SetLiteLoaderInfoShow") - Else - '隐藏信息栏 - AniStart({ - AaTranslateY(PanLiteLoaderInfo, 6 - CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 120), - AaOpacity(PanLiteLoaderInfo, -PanLiteLoaderInfo.Opacity, 50) - }, "SetLiteLoaderInfoShow") - End If - End Sub - - 'Forge Private SelectedForge As DlForgeVersionEntry = Nothing - Private Sub SetForgeInfoShow(IsShow As String) - If PanForgeInfo.Tag = IsShow Then Return - PanForgeInfo.Tag = IsShow - If IsShow = "True" Then - '显示信息栏 - AniStart({ - AaTranslateY(PanForgeInfo, -CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), - AaOpacity(PanForgeInfo, 1 - PanForgeInfo.Opacity, 80, 90) - }, "SetForgeInfoShow") - Else - '隐藏信息栏 - AniStart({ - AaTranslateY(PanForgeInfo, 6 - CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 120), - AaOpacity(PanForgeInfo, -PanForgeInfo.Opacity, 50) - }, "SetForgeInfoShow") - End If - End Sub - - 'NeoForge Private SelectedNeoForge As DlNeoForgeListEntry = Nothing - Private Sub SetNeoForgeInfoShow(IsShow As String) - If PanNeoForgeInfo.Tag = IsShow Then Return - PanNeoForgeInfo.Tag = IsShow - If IsShow = "True" Then - '显示信息栏 - AniStart({ - AaTranslateY(PanNeoForgeInfo, -CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), - AaOpacity(PanNeoForgeInfo, 1 - PanNeoForgeInfo.Opacity, 80, 90) - }, "SetNeoForgeInfoShow") - Else - '隐藏信息栏 - AniStart({ - AaTranslateY(PanNeoForgeInfo, 6 - CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 120), - AaOpacity(PanNeoForgeInfo, -PanNeoForgeInfo.Opacity, 50) - }, "SetNeoForgeInfoShow") - End If - End Sub - - 'Fabric Private SelectedFabric As String = Nothing - Private Sub SetFabricInfoShow(IsShow As String) - If PanFabricInfo.Tag = IsShow Then Return - PanFabricInfo.Tag = IsShow - If IsShow = "True" Then - '显示信息栏 - AniStart({ - AaTranslateY(PanFabricInfo, -CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), - AaOpacity(PanFabricInfo, 1 - PanFabricInfo.Opacity, 80, 90) - }, "SetFabricInfoShow") - Else - '隐藏信息栏 - AniStart({ - AaTranslateY(PanFabricInfo, 6 - CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 120), - AaOpacity(PanFabricInfo, -PanFabricInfo.Opacity, 50) - }, "SetFabricInfoShow") - End If - End Sub - - 'FabricApi Private SelectedFabricApi As CompFile = Nothing - Private Sub SetFabricApiInfoShow(IsShow As String) - If PanFabricApiInfo.Tag = IsShow Then Return - PanFabricApiInfo.Tag = IsShow - If IsShow = "True" Then - '显示信息栏 - AniStart({ - AaTranslateY(PanFabricApiInfo, -CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), - AaOpacity(PanFabricApiInfo, 1 - PanFabricApiInfo.Opacity, 80, 90) - }, "SetFabricApiInfoShow") - Else - '隐藏信息栏 - AniStart({ - AaTranslateY(PanFabricApiInfo, 6 - CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 120), - AaOpacity(PanFabricApiInfo, -PanFabricApiInfo.Opacity, 50) - }, "SetFabricApiInfoShow") - End If - End Sub - - 'OptiFabric Private SelectedOptiFabric As CompFile = Nothing - Private Sub SetOptiFabricInfoShow(IsShow As String) - If PanOptiFabricInfo.Tag = IsShow Then Return - PanOptiFabricInfo.Tag = IsShow - If IsShow = "True" Then - '显示信息栏 - AniStart({ - AaTranslateY(PanOptiFabricInfo, -CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), - AaOpacity(PanOptiFabricInfo, 1 - PanOptiFabricInfo.Opacity, 80, 90) - }, "SetOptiFabricInfoShow") - Else - '隐藏信息栏 - AniStart({ - AaTranslateY(PanOptiFabricInfo, 6 - CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 120), - AaOpacity(PanOptiFabricInfo, -PanOptiFabricInfo.Opacity, 50) - }, "SetOptiFabricInfoShow") - End If - End Sub - Private IsReloading As Boolean = False '#3742 中,LoadOptiFineGetError 会初始化 LoadOptiFine,触发事件 LoadOptiFine.StateChanged,导致再次调用 SelectReload ''' ''' 重载已选择的项目的显示。 ''' - Private Sub SelectReload() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged - If SelectedMinecraftId Is Nothing OrElse IsReloading Then Return - IsReloading = True + Private Sub ReloadSelected() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged + Static Ongoing As Boolean = False '#3742 中,LoadOptiFineGetError 会初始化 LoadOptiFine,触发事件 LoadOptiFine.StateChanged,导致再次调用 SelectReload + If VanillaName Is Nothing OrElse Ongoing Then Return + Ongoing = True '主预览 SelectNameUpdate() ImgLogo.Source = GetSelectLogo() @@ -314,7 +188,7 @@ Dim OptiFineError As String = LoadOptiFineGetError() CardOptiFine.MainSwap.Visibility = If(OptiFineError Is Nothing, Visibility.Visible, Visibility.Collapsed) If OptiFineError IsNot Nothing Then CardOptiFine.IsSwapped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 - SetOptiFineInfoShow(CardOptiFine.IsSwapped) + SetPanelVisibility(PanOptiFineInfo, CardOptiFine.IsSwapped) If SelectedOptiFine Is Nothing Then BtnOptiFineClear.Visibility = Visibility.Collapsed ImgOptiFine.Visibility = Visibility.Collapsed @@ -323,18 +197,18 @@ Else BtnOptiFineClear.Visibility = Visibility.Visible ImgOptiFine.Visibility = Visibility.Visible - LabOptiFine.Text = SelectedOptiFine.NameDisplay.Replace(SelectedMinecraftId & " ", "") + LabOptiFine.Text = SelectedOptiFine.DisplayName.Replace(VanillaName & " ", "") LabOptiFine.Foreground = ColorGray1 End If 'LiteLoader - If Not SelectedMinecraftId.Contains("1.") OrElse Val(SelectedMinecraftId.Split(".")(1)) > 12 Then + If VanillaDrop >= 130 Then CardLiteLoader.Visibility = Visibility.Collapsed Else CardLiteLoader.Visibility = Visibility.Visible Dim LiteLoaderError As String = LoadLiteLoaderGetError() CardLiteLoader.MainSwap.Visibility = If(LiteLoaderError Is Nothing, Visibility.Visible, Visibility.Collapsed) If LiteLoaderError IsNot Nothing Then CardLiteLoader.IsSwapped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 - SetLiteLoaderInfoShow(CardLiteLoader.IsSwapped) + SetPanelVisibility(PanLiteLoaderInfo, CardLiteLoader.IsSwapped) If SelectedLiteLoader Is Nothing Then BtnLiteLoaderClear.Visibility = Visibility.Collapsed ImgLiteLoader.Visibility = Visibility.Collapsed @@ -348,47 +222,56 @@ End If End If 'Forge - Dim ForgeError As String = LoadForgeGetError() - CardForge.MainSwap.Visibility = If(ForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If ForgeError IsNot Nothing Then CardForge.IsSwapped = True - SetForgeInfoShow(CardForge.IsSwapped) - If SelectedForge Is Nothing Then - BtnForgeClear.Visibility = Visibility.Collapsed - ImgForge.Visibility = Visibility.Collapsed - LabForge.Text = If(ForgeError, "可以添加") - LabForge.Foreground = ColorGray4 + If Not McVersion.IsFormatFit(VanillaName) Then + CardForge.Visibility = Visibility.Collapsed Else - BtnForgeClear.Visibility = Visibility.Visible - ImgForge.Visibility = Visibility.Visible - LabForge.Text = SelectedForge.VersionName - LabForge.Foreground = ColorGray1 + CardForge.Visibility = Visibility.Visible + Dim ForgeError As String = LoadForgeGetError() + CardForge.MainSwap.Visibility = If(ForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) + If ForgeError IsNot Nothing Then CardForge.IsSwapped = True + SetPanelVisibility(PanForgeInfo, CardForge.IsSwapped) + If SelectedForge Is Nothing Then + BtnForgeClear.Visibility = Visibility.Collapsed + ImgForge.Visibility = Visibility.Collapsed + LabForge.Text = If(ForgeError, "可以添加") + LabForge.Foreground = ColorGray4 + Else + BtnForgeClear.Visibility = Visibility.Visible + ImgForge.Visibility = Visibility.Visible + LabForge.Text = SelectedForge.VersionName + LabForge.Foreground = ColorGray1 + End If End If 'NeoForge - CardNeoForge.Visibility = Visibility.Visible - Dim NeoForgeError As String = LoadNeoForgeGetError() - CardNeoForge.MainSwap.Visibility = If(NeoForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwapped = True - SetNeoForgeInfoShow(CardNeoForge.IsSwapped) - If SelectedNeoForge Is Nothing Then - BtnNeoForgeClear.Visibility = Visibility.Collapsed - ImgNeoForge.Visibility = Visibility.Collapsed - LabNeoForge.Text = If(NeoForgeError, "可以添加") - LabNeoForge.Foreground = ColorGray4 + If VanillaData("releaseTime").ToObject(Of Date) < New Date(2023, 6, 11) Then '匹配 1.20.1+ 与一些愚人节版本 + CardNeoForge.Visibility = Visibility.Collapsed Else - BtnNeoForgeClear.Visibility = Visibility.Visible - ImgNeoForge.Visibility = Visibility.Visible - LabNeoForge.Text = SelectedNeoForge.VersionName - LabNeoForge.Foreground = ColorGray1 + CardNeoForge.Visibility = Visibility.Visible + Dim NeoForgeError As String = LoadNeoForgeGetError() + CardNeoForge.MainSwap.Visibility = If(NeoForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) + If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwapped = True + SetPanelVisibility(PanNeoForgeInfo, CardNeoForge.IsSwapped) + If SelectedNeoForge Is Nothing Then + BtnNeoForgeClear.Visibility = Visibility.Collapsed + ImgNeoForge.Visibility = Visibility.Collapsed + LabNeoForge.Text = If(NeoForgeError, "可以添加") + LabNeoForge.Foreground = ColorGray4 + Else + BtnNeoForgeClear.Visibility = Visibility.Visible + ImgNeoForge.Visibility = Visibility.Visible + LabNeoForge.Text = SelectedNeoForge.VersionName + LabNeoForge.Foreground = ColorGray1 + End If End If 'Fabric - If SelectedMinecraftId.Contains("1.") AndAlso Val(SelectedMinecraftId.Split(".")(1)) <= 13 Then + If VanillaDrop <= 130 Then CardFabric.Visibility = Visibility.Collapsed Else CardFabric.Visibility = Visibility.Visible Dim FabricError As String = LoadFabricGetError() CardFabric.MainSwap.Visibility = If(FabricError Is Nothing, Visibility.Visible, Visibility.Collapsed) If FabricError IsNot Nothing Then CardFabric.IsSwapped = True - SetFabricInfoShow(CardFabric.IsSwapped) + SetPanelVisibility(PanFabricInfo, CardFabric.IsSwapped) If SelectedFabric Is Nothing Then BtnFabricClear.Visibility = Visibility.Collapsed ImgFabric.Visibility = Visibility.Collapsed @@ -409,7 +292,7 @@ Dim FabricApiError As String = LoadFabricApiGetError() CardFabricApi.MainSwap.Visibility = If(FabricApiError Is Nothing, Visibility.Visible, Visibility.Collapsed) If FabricApiError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardFabricApi.IsSwapped = True - SetFabricApiInfoShow(CardFabricApi.IsSwapped) + SetPanelVisibility(PanFabricApiInfo, CardFabricApi.IsSwapped) If SelectedFabricApi Is Nothing Then BtnFabricApiClear.Visibility = Visibility.Collapsed ImgFabricApi.Visibility = Visibility.Collapsed @@ -430,7 +313,7 @@ Dim OptiFabricError As String = LoadOptiFabricGetError() CardOptiFabric.MainSwap.Visibility = If(OptiFabricError Is Nothing, Visibility.Visible, Visibility.Collapsed) If OptiFabricError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardOptiFabric.IsSwapped = True - SetOptiFabricInfoShow(CardOptiFabric.IsSwapped) + SetPanelVisibility(PanOptiFabricInfo, CardOptiFabric.IsSwapped) If SelectedOptiFabric Is Nothing Then BtnOptiFabricClear.Visibility = Visibility.Collapsed ImgOptiFabric.Visibility = Visibility.Collapsed @@ -450,7 +333,7 @@ HintFabricAPI.Visibility = Visibility.Collapsed End If If SelectedFabric IsNot Nothing AndAlso SelectedOptiFine IsNot Nothing AndAlso SelectedOptiFabric Is Nothing Then - If SelectedMinecraftId.StartsWith("1.14") OrElse SelectedMinecraftId.StartsWith("1.15") Then + If VanillaDrop >= 140 AndAlso VanillaDrop <= 150 Then HintOptiFabric.Visibility = Visibility.Collapsed HintOptiFabricOld.Visibility = Visibility.Visible Else @@ -461,22 +344,22 @@ HintOptiFabric.Visibility = Visibility.Collapsed HintOptiFabricOld.Visibility = Visibility.Collapsed End If - If SelectedMinecraftId.Contains("1.") AndAlso Val(SelectedMinecraftId.Split(".")(1)) >= 16 AndAlso SelectedOptiFine IsNot Nothing AndAlso + If VanillaDrop >= 160 AndAlso SelectedOptiFine IsNot Nothing AndAlso (SelectedForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) Then HintModOptiFine.Visibility = Visibility.Visible Else HintModOptiFine.Visibility = Visibility.Collapsed End If '结束 - IsReloading = False + Ongoing = False End Sub ''' ''' 清空已选择的项目。 ''' - Private Sub SelectClear() - SelectedMinecraftId = Nothing - SelectedMinecraftJsonUrl = Nothing - SelectedMinecraftIcon = Nothing + Private Sub ClearSelected() + VanillaName = Nothing + VanillaData = Nothing + VanillaIcon = Nothing SelectedOptiFine = Nothing SelectedLiteLoader = Nothing SelectedForge = Nothing @@ -484,6 +367,23 @@ SelectedFabric = Nothing SelectedFabricApi = Nothing SelectedOptiFabric = Nothing + IsSelectNameEdited = False + End Sub + '信息栏动画 + Private Sub SetPanelVisibility(Panel As Grid, IsVisible As Boolean) + If Panel.Tag = IsVisible.ToString Then Return + Panel.Tag = IsVisible.ToString + If IsVisible Then + AniStart({ + AaTranslateY(Panel, -CType(Panel.RenderTransform, TranslateTransform).Y, 150, Ease:=New AniEaseOutFluent), + AaOpacity(Panel, 1 - Panel.Opacity, 60) + }, "PageDownloadInstall Visibility " & Panel.Name) + Else + AniStart({ + AaTranslateY(Panel, 6 - CType(Panel.RenderTransform, TranslateTransform).Y, 60), + AaOpacity(Panel, -Panel.Opacity, 60) + }, "PageDownloadInstall Visibility " & Panel.Name) + End If End Sub ''' @@ -501,7 +401,7 @@ ElseIf SelectedOptiFine IsNot Nothing Then Return "pack://application:,,,/images/Blocks/GrassPath.png" Else - Return SelectedMinecraftIcon + Return VanillaIcon End If End Function @@ -510,22 +410,12 @@ ''' 获取默认版本名。 ''' Private Function GetSelectName() As String - Dim Name As String = SelectedMinecraftId - If SelectedFabric IsNot Nothing Then - Name += "-Fabric " & SelectedFabric.Replace("+build", "") - End If - If SelectedForge IsNot Nothing Then - Name += "-Forge_" & SelectedForge.VersionName - End If - If SelectedNeoForge IsNot Nothing Then - Name += "-NeoForge_" & SelectedNeoForge.VersionName - End If - If SelectedLiteLoader IsNot Nothing Then - Name += "-LiteLoader" - End If - If SelectedOptiFine IsNot Nothing Then - Name += "-OptiFine_" & SelectedOptiFine.NameDisplay.Replace(SelectedMinecraftId & " ", "").Replace(" ", "_") - End If + Dim Name As String = VanillaName + If SelectedFabric IsNot Nothing Then Name += "-Fabric " & SelectedFabric.BeforeFirst("+") + If SelectedForge IsNot Nothing Then Name += "-Forge_" & SelectedForge.VersionName + If SelectedNeoForge IsNot Nothing Then Name += "-NeoForge_" & SelectedNeoForge.VersionName.BeforeFirst("+") + If SelectedLiteLoader IsNot Nothing Then Name += "-LiteLoader" + If SelectedOptiFine IsNot Nothing Then Name += "-OptiFine_" & SelectedOptiFine.DisplayName.Replace(VanillaName & " ", "").Replace(" ", "_") Return Name End Function Private IsSelectNameEdited As Boolean = False @@ -539,7 +429,7 @@ Private Sub TextSelectName_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextSelectName.TextChanged If IsSelectNameChanging Then Return IsSelectNameEdited = True - SelectReload() + ReloadSelected() End Sub Private Sub TextSelectName_ValidateChanged(sender As Object, e As EventArgs) Handles TextSelectName.ValidateChanged BtnStart.IsEnabled = TextSelectName.IsValidated @@ -634,12 +524,12 @@ PanMinecraft.Children.Add(NewCard) Next '自动选择版本 - If McVersionWaitingForSelect Is Nothing Then Exit Try - Log("[Download] 自动选择 MC 版本:" & McVersionWaitingForSelect) + If VersionWaitingSelect Is Nothing Then Exit Try + Log("[Download] 自动选择 MC 版本:" & VersionWaitingSelect) For Each Version As JObject In Versions - If Version("id").ToString <> McVersionWaitingForSelect Then Continue For + If Version("id").ToString <> VersionWaitingSelect Then Continue For Dim Item = McDownloadListItem(Version, Sub() - End Sub, False) + End Sub, False).Init() MinecraftSelected(Item, Nothing) Next Catch ex As Exception @@ -649,7 +539,7 @@ ''' ''' 当 MC 版本列表加载完时,立即自动选择的版本。用于外部调用。 ''' - Public Shared McVersionWaitingForSelect As String = Nothing + Public Shared VersionWaitingSelect As String = Nothing #End Region @@ -660,30 +550,30 @@ ''' Private Function LoadOptiFineGetError() As String If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容" - If LoadOptiFine Is Nothing OrElse LoadOptiFine.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……" - If LoadOptiFine.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadOptiFine.State, Object).Error.Message '检查 Forge 1.13 - 1.14.3:全部不兼容 If SelectedForge IsNot Nothing AndAlso - VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then + CompareVersion(VanillaName, "1.13") >= 0 AndAlso CompareVersion("1.14.3", VanillaName) >= 0 Then Return "与 Forge 不兼容" End If '检查 Fabric 1.20.5+:全部不兼容 If SelectedFabric IsNot Nothing AndAlso - VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then + CompareVersion(VanillaName, "1.20.4") > 0 Then Return "与 Fabric 不兼容" End If + '检查 Loader + If GetLoaderError(LoadOptiFine) IsNot Nothing Then Return GetLoaderError(LoadOptiFine) '检查 Forge 版本 Dim HasAny As Boolean = False Dim HasRequiredVersion As Boolean = False For Each OptiFineVersion As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value - If Not OptiFineVersion.NameDisplay.StartsWith(SelectedMinecraftId & " ") Then Continue For '不是同一个大版本 + If Not OptiFineVersion.DisplayName.StartsWith(VanillaName & " ") Then Continue For '不是同一个大版本 HasAny = True If SelectedForge Is Nothing Then Return Nothing '未选择 Forge If IsOptiFineSuitForForge(OptiFineVersion, SelectedForge) Then Return Nothing '该版本可用 If OptiFineVersion.RequiredForgeVersion IsNot Nothing Then HasRequiredVersion = True Next If Not HasAny Then - Return "不可用" + Return "无" ElseIf HasRequiredVersion Then Return "仅兼容特定版本的 Forge" Else @@ -697,7 +587,7 @@ If OptiFine.RequiredForgeVersion Is Nothing Then Return False '不兼容 Forge If String.IsNullOrWhiteSpace(OptiFine.RequiredForgeVersion) Then Return True '#4183 If OptiFine.RequiredForgeVersion.Contains(".") Then 'XX.X.XXX - Return VersionSortInteger(Forge.Version.ToString, OptiFine.RequiredForgeVersion) = 0 + Return CompareVersion(Forge.Version.ToString, OptiFine.RequiredForgeVersion) = 0 Else 'XXXX Return Forge.Version.Revision = OptiFine.RequiredForgeVersion End If @@ -719,15 +609,15 @@ Dim Versions As New List(Of DlOptiFineListEntry) For Each Version As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value If SelectedForge IsNot Nothing AndAlso Not IsOptiFineSuitForForge(Version, SelectedForge) Then Continue For - If Version.NameDisplay.StartsWith(SelectedMinecraftId & " ") Then Versions.Add(Version) + If Version.DisplayName.StartsWith(VanillaName & " ") Then Versions.Add(Version) Next If Not Versions.Any() Then Return '排序 - Versions = Versions.Sort( + Versions = Versions.SortByComparison( Function(Left As DlOptiFineListEntry, Right As DlOptiFineListEntry) As Boolean If Not Left.IsPreview AndAlso Right.IsPreview Then Return True If Left.IsPreview AndAlso Not Right.IsPreview Then Return False - Return VersionSortBoolean(Left.NameDisplay, Right.NameDisplay) + Return CompareVersionGE(Left.DisplayName, Right.DisplayName) End Function) '可视化 PanOptiFine.Children.Clear() @@ -747,7 +637,7 @@ Forge_Loaded() NeoForge_Loaded() CardOptiFine.IsSwapped = True - SelectReload() + ReloadSelected() End Sub Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp SelectedOptiFine = Nothing @@ -757,7 +647,7 @@ e.Handled = True Forge_Loaded() NeoForge_Loaded() - SelectReload() + ReloadSelected() End Sub #End Region @@ -768,13 +658,10 @@ ''' 获取 LiteLoader 的加载异常信息。若正常则返回 Nothing。 ''' Private Function LoadLiteLoaderGetError() As String - If Not SelectedMinecraftId.Contains("1.") OrElse Val(SelectedMinecraftId.Split(".")(1)) > 12 Then Return "不可用" - If LoadLiteLoader Is Nothing OrElse LoadLiteLoader.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……" - If LoadLiteLoader.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadLiteLoader.State, Object).Error.Message - For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value - If Version.Inherit = SelectedMinecraftId Then Return Nothing - Next - Return "不可用" + '检查 Loader + If GetLoaderError(LoadLiteLoader) IsNot Nothing Then Return GetLoaderError(LoadLiteLoader) + '检查版本 + Return If(DlLiteLoaderListLoader.Output.Value.Any(Function(v) v.Inherit = VanillaName), Nothing, "无") End Function '限制展开 @@ -791,7 +678,7 @@ '获取版本列表 Dim Versions As New List(Of DlLiteLoaderListEntry) For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value - If Version.Inherit = SelectedMinecraftId Then Versions.Add(Version) + If Version.Inherit = VanillaName Then Versions.Add(Version) Next If Not Versions.Any() Then Return '可视化 @@ -808,13 +695,13 @@ Private Sub LiteLoader_Selected(sender As MyListItem, e As EventArgs) SelectedLiteLoader = sender.Tag CardLiteLoader.IsSwapped = True - SelectReload() + ReloadSelected() End Sub Private Sub LiteLoader_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnLiteLoaderClear.MouseLeftButtonUp SelectedLiteLoader = Nothing CardLiteLoader.IsSwapped = True e.Handled = True - SelectReload() + ReloadSelected() End Sub #End Region @@ -825,36 +712,24 @@ ''' 获取 Forge 的加载异常信息。若正常则返回 Nothing。 ''' Private Function LoadForgeGetError() As String - If Not SelectedMinecraftId.StartsWith("1.") Then Return "不可用" - If Not LoadForge.State.IsLoader Then Return "加载中……" + If CompareVersionGE("1.5.1", VanillaName) AndAlso CompareVersionGE(VanillaName, "1.1") Then Return "无" + '检查 Loader + If GetLoaderError(LoadForge) IsNot Nothing Then Return GetLoaderError(LoadForge) Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State - If SelectedMinecraftId <> Loader.Input Then Return "加载中……" - If Loader.State = LoadState.Loading Then Return "加载中……" - If Loader.State = LoadState.Failed Then - Dim ErrorMessage As String = Loader.Error.Message - If ErrorMessage.Contains("不可用") Then - Return "不可用" - Else - Return "获取版本列表失败:" & ErrorMessage - End If - End If - If Loader.State <> LoadState.Finished Then Return "获取版本列表失败:未知错误,状态为 " & GetStringFromEnum(Loader.State) - Dim NotSuitForOptiFine As Boolean = False + If VanillaName <> Loader.Input Then Return "获取中……" + '检查版本 For Each Version In Loader.Output If Version.Category = "universal" OrElse Version.Category = "client" Then Continue For '跳过无法自动安装的版本 If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容" If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容" If SelectedOptiFine IsNot Nothing AndAlso - VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then + CompareVersionGE(VanillaName, "1.13") AndAlso CompareVersionGE("1.14.3", VanillaName) Then Return "与 OptiFine 不兼容" '1.13 ~ 1.14.3 OptiFine 检查 End If - If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, Version) Then - NotSuitForOptiFine = True '与 OptiFine 不兼容 - Continue For - End If + If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, Version) Then Continue For Return Nothing Next - Return If(NotSuitForOptiFine, "与 OptiFine 不兼容", "该版本不支持自动安装") + Return "与 OptiFine 不兼容" End Function '限制展开 @@ -869,7 +744,7 @@ Try If Not LoadForge.State.IsLoader Then Return Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State - If SelectedMinecraftId <> Loader.Input Then Return + If VanillaName <> Loader.Input Then Return If Loader.State <> LoadState.Finished Then Return '获取要显示的版本 Dim Versions = Loader.Output.ToList '复制数组,以免 Output 在实例化后变空 @@ -880,7 +755,7 @@ If v.Category = "universal" OrElse v.Category = "client" Then Return False '跳过无法自动安装的版本 If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, v) Then Return False Return True - End Function).OrderByDescending(Function(v) v.Version).ToList() + End Function).OrderByDescending(Function(v) v).ToList() ForgeDownloadListItemPreload(PanForge, Versions, AddressOf Forge_Selected, False) For Each Version In Versions PanForge.Children.Add(ForgeDownloadListItem(Version, AddressOf Forge_Selected, False)) @@ -896,14 +771,14 @@ CardForge.IsSwapped = True If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, SelectedForge) Then SelectedOptiFine = Nothing OptiFine_Loaded() - SelectReload() + ReloadSelected() End Sub Private Sub Forge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnForgeClear.MouseLeftButtonUp SelectedForge = Nothing CardForge.IsSwapped = True e.Handled = True OptiFine_Loaded() - SelectReload() + ReloadSelected() End Sub #End Region @@ -917,13 +792,10 @@ If SelectedOptiFine IsNot Nothing Then Return "与 OptiFine 不兼容" If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容" If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容" - If LoadNeoForge Is Nothing OrElse LoadNeoForge.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……" - If LoadNeoForge.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadNeoForge.State, Object).Error.Message - If DlNeoForgeListLoader.Output.Value.Any(Function(v) v.Inherit = SelectedMinecraftId) Then - Return Nothing - Else - Return "不可用" - End If + '检查 Loader + If GetLoaderError(LoadNeoForge) IsNot Nothing Then Return GetLoaderError(LoadNeoForge) + '检查版本 + Return If(DlNeoForgeListLoader.Output.Value.Any(Function(v) v.Inherit = VanillaName), Nothing, "无") End Function '限制展开 @@ -938,7 +810,7 @@ Try '获取版本列表 If DlNeoForgeListLoader.State <> LoadState.Finished Then Return - Dim Versions = DlNeoForgeListLoader.Output.Value.Where(Function(v) v.Inherit = SelectedMinecraftId).ToList + Dim Versions = DlNeoForgeListLoader.Output.Value.Where(Function(v) v.Inherit = VanillaName).ToList If Not Versions.Any() Then Return '可视化 PanNeoForge.Children.Clear() @@ -956,14 +828,14 @@ SelectedNeoForge = sender.Tag CardNeoForge.IsSwapped = True OptiFine_Loaded() - SelectReload() + ReloadSelected() End Sub Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp SelectedNeoForge = Nothing CardNeoForge.IsSwapped = True e.Handled = True OptiFine_Loaded() - SelectReload() + ReloadSelected() End Sub #End Region @@ -974,21 +846,19 @@ ''' 获取 Fabric 的加载异常信息。若正常则返回 Nothing。 ''' Private Function LoadFabricGetError() As String - If LoadFabric Is Nothing OrElse LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……" - If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabric.State, Object).Error.Message '检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容 - If SelectedOptiFine IsNot Nothing AndAlso - VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then - Return "与 OptiFine 不兼容" - End If + If SelectedOptiFine IsNot Nothing AndAlso CompareVersionGE(VanillaName, "1.20.5") Then Return "与 OptiFine 不兼容" + '检查 Loader + If GetLoaderError(LoadFabric) IsNot Nothing Then Return GetLoaderError(LoadFabric) + '检查版本 For Each Version As JObject In DlFabricListLoader.Output.Value("game") - If Version("version").ToString = SelectedMinecraftId.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") Then + If Version("version").ToString = VanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") Then If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容" If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容" Return Nothing End If Next - Return "不可用" + Return "无" End Function '限制展开 @@ -1021,7 +891,7 @@ FabricApi_Loaded() OptiFabric_Loaded() CardFabric.IsSwapped = True - SelectReload() + ReloadSelected() End Sub Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp SelectedFabric = Nothing @@ -1031,7 +901,7 @@ AutoSelectedOptiFabric = False CardFabric.IsSwapped = True e.Handled = True - SelectReload() + ReloadSelected() End Sub #End Region @@ -1039,21 +909,22 @@ #Region "Fabric API 列表" ''' - ''' 从显示名判断该 API 是否与某版本适配。 + ''' 判断某 Fabric API 是否适配当前选择的原版版本。 ''' - Public Shared Function IsSuitableFabricApi(DisplayName As String, MinecraftVersion As String) As Boolean + Public Function IsFabricApiCompatible(FabricApi As CompFile) As Boolean + Dim FabricApiName = FabricApi.DisplayName Try - If DisplayName Is Nothing OrElse MinecraftVersion Is Nothing Then Return False - DisplayName = DisplayName.ToLower : MinecraftVersion = MinecraftVersion.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").ToLower - If DisplayName.StartsWith("[" & MinecraftVersion & "]") Then Return True - If Not DisplayName.Contains("/") OrElse Not DisplayName.Contains("]") Then Return False + If FabricApiName Is Nothing OrElse VanillaName Is Nothing Then Return False + FabricApiName = FabricApiName.ToLower : VanillaName = VanillaName.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3").ToLower + If FabricApiName.StartsWith("[" & VanillaName & "]") Then Return True + If Not FabricApiName.Contains("/") OrElse Not FabricApiName.Contains("]") Then Return False '直接的判断(例如 1.18.1/22w03a) - For Each Part As String In DisplayName.BeforeFirst("]").TrimStart("[").Split("/") - If Part = MinecraftVersion Then Return True + For Each Part As String In FabricApiName.BeforeFirst("]").TrimStart("[").Split("/") + If Part = VanillaName Then Return True Next '将版本名分割语素(例如 1.16.4/5) - Dim Lefts = RegexSearch(DisplayName.BeforeFirst("]"), "[a-z/]+|[0-9/]+") - Dim Rights = RegexSearch(MinecraftVersion.BeforeFirst("]"), "[a-z/]+|[0-9/]+") + Dim Lefts = RegexSearch(FabricApiName.BeforeFirst("]"), "[a-z/]+|[0-9/]+") + Dim Rights = RegexSearch(VanillaName.BeforeFirst("]"), "[a-z/]+|[0-9/]+") '对每段进行判断 Dim i As Integer = 0 While True @@ -1072,7 +943,7 @@ End While Return True Catch ex As Exception - Log(ex, "判断 Fabric API 版本适配性出错(" & DisplayName & ", " & MinecraftVersion & ")") + Log(ex, "判断 Fabric API 版本适配性出错(" & FabricApiName & ", " & VanillaName & ")") Return False End Try End Function @@ -1081,18 +952,15 @@ ''' 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。 ''' Private Function LoadFabricApiGetError() As String - If LoadFabricApi Is Nothing OrElse LoadFabricApi.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……" - If LoadFabricApi.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabricApi.State, Object).Error.Message - If DlFabricApiLoader.Output Is Nothing Then - If SelectedFabric Is Nothing Then Return "需要安装 Fabric" - Return "加载中……" + '检查 Loader + If GetLoaderError(LoadFabricApi) IsNot Nothing Then Return GetLoaderError(LoadFabricApi) + If DlFabricApiLoader.Output Is Nothing Then Return If(SelectedFabric Is Nothing, "需要安装 Fabric", "获取中……") + '检查版本 + If DlFabricApiLoader.Output.Any(Function(f) IsFabricApiCompatible(f)) Then + Return If(SelectedFabric Is Nothing, "需要安装 Fabric", Nothing) + Else + Return "无" End If - For Each Version In DlFabricApiLoader.Output - If Not IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then Continue For - If SelectedFabric Is Nothing Then Return "需要安装 Fabric" - Return Nothing - Next - Return "不可用" End Function '限制展开 @@ -1107,14 +975,14 @@ Private Sub FabricApi_Loaded() Handles LoadFabricApi.StateChanged Try If DlFabricApiLoader.State <> LoadState.Finished Then Return - If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing Then Return + If VanillaName Is Nothing OrElse SelectedFabric Is Nothing Then Return '获取版本列表 Dim Versions As New List(Of CompFile) For Each Version In DlFabricApiLoader.Output - If IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then + If IsFabricApiCompatible(Version) Then If Not Version.DisplayName.StartsWith("[") Then Log("[Download] 已特判修改 Fabric API 显示名:" & Version.DisplayName, LogLevel.Debug) - Version.DisplayName = "[" & SelectedMinecraftId & "] " & Version.DisplayName + Version.DisplayName = "[" & VanillaName & "] " & Version.DisplayName End If Versions.Add(Version) End If @@ -1124,14 +992,15 @@ '可视化 PanFabricApi.Children.Clear() For Each Version In Versions - If Not IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then Continue For + If Not IsFabricApiCompatible(Version) Then Continue For PanFabricApi.Children.Add(FabricApiDownloadListItem(Version, AddressOf FabricApi_Selected)) Next '自动选择 Fabric API If Not AutoSelectedFabricApi Then AutoSelectedFabricApi = True - Log($"[Download] 已自动选择 Fabric API:{CType(PanFabricApi.Children(0), MyListItem).Title}") - FabricApi_Selected(PanFabricApi.Children(0), Nothing) + Dim Item As MyListItem = MyVirtualizingElement.TryInit(PanFabricApi.Children(0)) + Log($"[Download] 已自动选择 Fabric API:{Item.Title}") + FabricApi_Selected(Item, Nothing) End If Catch ex As Exception Log(ex, "可视化 Fabric API 安装版本列表出错", LogLevel.Feedback) @@ -1142,13 +1011,13 @@ Private Sub FabricApi_Selected(sender As MyListItem, e As EventArgs) SelectedFabricApi = sender.Tag CardFabricApi.IsSwapped = True - SelectReload() + ReloadSelected() End Sub Private Sub FabricApi_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricApiClear.MouseLeftButtonUp SelectedFabricApi = Nothing CardFabricApi.IsSwapped = True e.Handled = True - SelectReload() + ReloadSelected() End Sub #End Region @@ -1156,14 +1025,14 @@ #Region "OptiFabric 列表" ''' - ''' 从显示名判断该 Mod 是否与某版本适配。 + ''' 判断某 OptiFabric 是否适配当前选择的原版版本。 ''' - Private Function IsSuitableOptiFabric(ModFile As CompFile, MinecraftVersion As String) As Boolean + Private Function IsOptiFabricCompatible(ModFile As CompFile) As Boolean Try - If MinecraftVersion Is Nothing Then Return False - Return ModFile.GameVersions.Contains(MinecraftVersion) + If VanillaName Is Nothing Then Return False + Return ModFile.GameVersions.Contains(VanillaName) Catch ex As Exception - Log(ex, "判断 OptiFabric 版本适配性出错(" & MinecraftVersion & ")") + Log(ex, "判断 OptiFabric 版本适配性出错(" & VanillaName & ")") Return False End Try End Function @@ -1173,23 +1042,24 @@ ''' 获取 OptiFabric 的加载异常信息。若正常则返回 Nothing。 ''' Private Function LoadOptiFabricGetError() As String - If SelectedMinecraftId.StartsWith("1.14") OrElse SelectedMinecraftId.StartsWith("1.15") Then Return "不兼容老版本 Fabric,请手动下载 OptiFabric Origins" - If LoadOptiFabric Is Nothing OrElse LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……" - If LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadOptiFabric.State, Object).Error.Message + If VanillaDrop >= 140 AndAlso VanillaDrop <= 150 Then Return "不兼容老版本 Fabric,请手动下载 OptiFabric Origins" + '检查 Loader + If GetLoaderError(LoadOptiFabric) IsNot Nothing Then Return GetLoaderError(LoadOptiFabric) + '检查版本 If DlOptiFabricLoader.Output Is Nothing Then If SelectedFabric Is Nothing AndAlso SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine 与 Fabric" If SelectedFabric Is Nothing Then Return "需要安装 Fabric" If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine" - Return "加载中……" + Return "获取中……" End If For Each Version In DlOptiFabricLoader.Output - If Not IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Continue For '2135# + If Not IsOptiFabricCompatible(Version) Then Continue For '2135# If SelectedFabric Is Nothing AndAlso SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine 与 Fabric" If SelectedFabric Is Nothing Then Return "需要安装 Fabric" If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine" Return Nothing '通过检查 Next - Return "不可用" + Return "无" End Function '限制展开 @@ -1203,11 +1073,11 @@ Private Sub OptiFabric_Loaded() Handles LoadOptiFabric.StateChanged Try If DlOptiFabricLoader.State <> LoadState.Finished Then Return - If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing OrElse SelectedOptiFine Is Nothing Then Return + If VanillaName Is Nothing OrElse SelectedFabric Is Nothing OrElse SelectedOptiFine Is Nothing Then Return '获取版本列表 Dim Versions As New List(Of CompFile) For Each Version In DlOptiFabricLoader.Output - If IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Versions.Add(Version) + If IsOptiFabricCompatible(Version) Then Versions.Add(Version) Next If Not Versions.Any() Then Return '排序 @@ -1215,16 +1085,15 @@ '可视化 PanOptiFabric.Children.Clear() For Each Version In Versions - If Not IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Continue For + If Not IsOptiFabricCompatible(Version) Then Continue For PanOptiFabric.Children.Add(OptiFabricDownloadListItem(Version, AddressOf OptiFabric_Selected)) Next '自动选择 OptiFabric - If Not AutoSelectedOptiFabric AndAlso - Not (SelectedMinecraftId.StartsWith("1.14") OrElse SelectedMinecraftId.StartsWith("1.15")) Then '1.14~15 不自动选择 - AutoSelectedOptiFabric = True - Log($"[Download] 已自动选择 OptiFabric:{CType(PanOptiFabric.Children(0), MyListItem).Title}") - OptiFabric_Selected(PanOptiFabric.Children(0), Nothing) - End If + If AutoSelectedOptiFabric OrElse VanillaDrop >= 140 AndAlso VanillaDrop <= 150 Then Return '1.14~15 不自动选择 + AutoSelectedOptiFabric = True + Dim Item As MyListItem = MyVirtualizingElement.TryInit(PanOptiFabric.Children(0)) + Log($"[Download] 已自动选择 OptiFabric:{Item.Title}") + OptiFabric_Selected(Item, Nothing) Catch ex As Exception Log(ex, "可视化 OptiFabric 安装版本列表出错", LogLevel.Feedback) End Try @@ -1234,13 +1103,13 @@ Private Sub OptiFabric_Selected(sender As MyListItem, e As EventArgs) SelectedOptiFabric = sender.Tag CardOptiFabric.IsSwapped = True - SelectReload() + ReloadSelected() End Sub Private Sub OptiFabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFabricClear.MouseLeftButtonUp SelectedOptiFabric = Nothing CardOptiFabric.IsSwapped = True e.Handled = True - SelectReload() + ReloadSelected() End Sub #End Region @@ -1261,12 +1130,12 @@ End If End If '提交安装申请 - Dim VersionName As String = TextSelectName.Text + Dim InstanceName As String = TextSelectName.Text Dim Request As New McInstallRequest With { - .TargetVersionName = VersionName, - .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", - .MinecraftJson = SelectedMinecraftJsonUrl, - .MinecraftName = SelectedMinecraftId, + .NewInstanceName = InstanceName, + .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\", + .MinecraftJson = VanillaData("url").ToString, + .MinecraftName = VanillaName, .OptiFineEntry = SelectedOptiFine, .ForgeEntry = SelectedForge, .NeoForgeEntry = SelectedNeoForge, @@ -1282,4 +1151,20 @@ #End Region + Private Function GetLoaderError(Loader As MyLoading) As String + If Loader Is Nothing Then Return "获取中……" + If Not Loader.State.IsLoader Then Return "获取中……" + Select Case Loader.State.LoadingState + Case MyLoading.MyLoadingState.Run + Return "获取中……" + Case MyLoading.MyLoadingState.Error + Dim Message As String = CType(Loader.State, LoaderBase).Error.Message + Return If(Message = "无", "无", "获取失败:" & Message) + Case MyLoading.MyLoadingState.Unloaded + Return "未知错误,状态为 Unloaded" + Case Else + Return Nothing + End Select + End Function + End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb index 3ddece3..7c02f25 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, ItemMod.Check, ItemPack.Check, ItemResourcePack.Check, ItemShader.Check, ItemDataPack.Check + Private Sub PageCheck(sender As FrameworkElement, 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)) diff --git a/Plain Craft Launcher 2/Pages/PageVersion/MyLocalModItem.xaml b/Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml similarity index 100% rename from Plain Craft Launcher 2/Pages/PageVersion/MyLocalModItem.xaml rename to Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml diff --git a/Plain Craft Launcher 2/Pages/PageVersion/MyLocalModItem.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.vb similarity index 75% rename from Plain Craft Launcher 2/Pages/PageVersion/MyLocalModItem.xaml.vb rename to Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.vb index 4c9632c..12a448c 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/MyLocalModItem.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/MyLocalModItem.xaml.vb @@ -5,16 +5,6 @@ Public Class MyLocalModItem #Region "基础属性" Public Uuid As Integer = GetUuid() - 'Logo - Public Property Logo As String - Get - Return PathLogo.Source - End Get - Set(value As String) - PathLogo.Source = value - End Set - End Property - '标题 Private _Title As String Public Property Title As String @@ -28,9 +18,6 @@ Public Class MyLocalModItem LabTitle.TextDecorations = Nothing Case McMod.McModState.Disabled LabTitle.TextDecorations = TextDecorations.Strikethrough - Case McMod.McModState.Unavailable - LabTitle.TextDecorations = TextDecorations.Strikethrough - value &= " [错误]" End Select If LabTitle.Text = value Then Return LabTitle.Text = value @@ -63,17 +50,17 @@ Public Class MyLocalModItem 'Tag Public WriteOnly Property Tags As List(Of String) - Set(value As List(Of String)) + Set(Tags As List(Of String)) PanTags.Children.Clear() - PanTags.Visibility = If(value.Any(), Visibility.Visible, Visibility.Collapsed) - For Each TagText In value - Dim NewTag = GetObjectFromXML( - " - - ") - PanTags.Children.Add(NewTag) + PanTags.Visibility = If(Tags.Any(), Visibility.Visible, Visibility.Collapsed) + For Each TagText In Tags + Dim BorderTag As New Border With { + .Background = New MyColor("#0A000000"), .Padding = New Thickness(3, 1, 3, 1), .CornerRadius = New CornerRadius(3), + .Margin = New Thickness(0, 0, 3, 0), .SnapsToDevicePixels = True, .UseLayoutRounding = False} + Dim LabTag As New TextBlock With { + .Text = TagText, .Foreground = New MyColor("#B0B0B0"), .FontSize = 11} + BorderTag.Child = LabTag + PanTags.Children.Add(BorderTag) Next End Set End Property @@ -123,7 +110,7 @@ Public Class MyLocalModItem End Get Set(value As Boolean) _Swiping = value - FrmVersionMod.CardSelect.IsHitTestVisible = Not value + FrmInstanceMod.CardSelect.IsHitTestVisible = Not value End Set End Property Private Shared SwipToState As Boolean '被滑动到的目标应将 Checked 改为此值 @@ -313,11 +300,11 @@ Public Class MyLocalModItem #End Region Private Function GetUpdateCompareDescription() As String - Dim CurrentName = Entry.CompFile.FileName.Replace(".jar", "") - Dim NewestName = Entry.UpdateFile.FileName.Replace(".jar", "") + Dim CurrentName = If(Entry.CompFile.Version, Entry.CompFile.DisplayName).Replace(".jar", "") + Dim NewestName = If(Entry.UpdateFile.Version, Entry.UpdateFile.DisplayName).Replace(".jar", "") '简化名称对比 - Dim CurrentSegs = CurrentName.Split("-"c).ToList() - Dim NewestSegs = NewestName.Split("-"c).ToList() + Dim CurrentSegs = CurrentName.Split("-_+ ".ToCharArray).ToList() + Dim NewestSegs = NewestName.Split("-_+ ".ToCharArray).ToList() Dim Shortened As Boolean = False For Each Seg In CurrentSegs.ToList() If Not NewestSegs.Contains(Seg) Then Continue For @@ -332,92 +319,103 @@ Public Class MyLocalModItem End If Return $"当前版本:{CurrentName}({GetTimeSpanString(Entry.CompFile.ReleaseDate - Date.Now, False)}){vbCrLf}最新版本:{NewestName}({GetTimeSpanString(Entry.UpdateFile.ReleaseDate - Date.Now, False)})" End Function - Public Sub Refresh() Handles Me.Loaded - RunInUi( - Sub() - '更新 - If Entry.CanUpdate Then - BtnUpdate.Visibility = Visibility.Visible - BtnUpdate.ToolTip = $"{GetUpdateCompareDescription()}{vbCrLf}点击以更新,右键查看更新日志。" + + '懒加载 + Public Sub New() + InitializeComponent() + OnFirstEnterScrollViewerViewport(AddressOf FirstEnterScrollViewerViewport) + End Sub + Private HasEnteredViewport As Boolean = False + Private Sub FirstEnterScrollViewerViewport() + HasEnteredViewport = True + Refresh() + End Sub + Public Sub Refresh() + If Not HasEnteredViewport Then Return '避免在未显示时因为 Comp 更新而加载内容 + '更新 + If Entry.CanUpdate Then + BtnUpdate.Visibility = Visibility.Visible + BtnUpdate.ToolTip = $"{GetUpdateCompareDescription()}{vbCrLf}点击以更新,右键查看更新日志。" + Else + BtnUpdate.Visibility = Visibility.Collapsed + End If + '标题与描述 + Dim DescFileName As String + If Entry.State = McMod.McModState.Fine OrElse Entry.State = McMod.McModState.Disabled Then + DescFileName = IO.Path.GetFileNameWithoutExtension(Entry.Path) + Else + DescFileName = GetFileNameFromPath(Entry.Path) + End If + Dim NewDescription As String + If Setup.Get("ToolModLocalNameStyle") = 1 Then + '标题显示文件名,详情显示译名 + '标题 + Title = DescFileName + SubTitle = "" + '描述 + If Entry.Comp Is Nothing Then + NewDescription = Entry.DisplayName Else - BtnUpdate.Visibility = Visibility.Collapsed + Dim Titles = Entry.Comp.GetControlTitle(False) + NewDescription = Titles.Key & Titles.Value End If - '标题与描述 - Dim DescFileName As String - If Entry.State = McMod.McModState.Fine OrElse Entry.State = McMod.McModState.Disabled Then - DescFileName = IO.Path.GetFileNameWithoutExtension(Entry.Path) + NewDescription = NewDescription.Replace(" | ", " / ") + If Entry.Version IsNot Nothing Then NewDescription &= $" ({Entry.Version})" + Else + '标题显示译名,详情显示文件名 + '标题 + If Entry.Comp Is Nothing Then + Title = Entry.DisplayName + SubTitle = If(Entry.Version Is Nothing, "", " | " & Entry.Version) Else - DescFileName = GetFileNameFromPath(Entry.Path) + Dim Titles = Entry.Comp.GetControlTitle(False) + Title = Titles.Key + SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", " | " & Entry.Version) End If - Dim NewDescription As String - If Setup.Get("ToolModLocalNameStyle") = 1 Then - '标题显示文件名,详情显示译名 - '标题 - Title = DescFileName - SubTitle = "" - '描述 - If Entry.Comp Is Nothing Then - NewDescription = Entry.Name - Else - Dim Titles = Entry.Comp.GetControlTitle(False) - NewDescription = Titles.Key & Titles.Value - End If - NewDescription = NewDescription.Replace(" | ", " / ") - If Entry.Version IsNot Nothing Then NewDescription &= $" ({Entry.Version})" - Else - '标题显示译名,详情显示文件名 - '标题 - If Entry.Comp Is Nothing Then - Title = Entry.Name - SubTitle = If(Entry.Version Is Nothing, "", " | " & Entry.Version) - Else - Dim Titles = Entry.Comp.GetControlTitle(False) - Title = Titles.Key - SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", " | " & Entry.Version) - End If - '描述 - NewDescription = DescFileName + '描述 + NewDescription = DescFileName + End If + If Entry.Comp IsNot Nothing Then + NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "") + ElseIf Entry.Description IsNot Nothing Then + NewDescription += ": " & Entry.Description.Replace(vbCr, "").Replace(vbLf, "") + End If + Description = NewDescription + If Checked Then + LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5")) + Else + LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush1", "ColorBrushGray4")) + End If + '主 Logo + If Entry.Comp IsNot Nothing Then + Entry.Comp.ApplyLogoToMyImage(PathLogo) + Else + PathLogo.Source = PathImage & "Icons/NoIcon.png" + End If + '图标右下角的 Logo + If Entry.State = McMod.McModState.Fine Then + If ImgState IsNot Nothing Then + Children.Remove(ImgState) + ImgState = Nothing End If - If Entry.Comp IsNot Nothing Then - NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "") - ElseIf Entry.Description IsNot Nothing Then - NewDescription += ": " & Entry.Description.Replace(vbCr, "").Replace(vbLf, "") - ElseIf Not Entry.IsFileAvailable Then - NewDescription += ": " & "存在错误,无法获取信息" + Else + If ImgState Is Nothing Then + ImgState = New Image With { + .Width = 20, .Height = 20, .Margin = New Thickness(0, 0, -5, -3), .IsHitTestVisible = False, + .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Bottom + } + RenderOptions.SetBitmapScalingMode(ImgState, BitmapScalingMode.HighQuality) + SetColumn(ImgState, 1) : SetRow(ImgState, 1) : SetRowSpan(ImgState, 2) + Children.Add(ImgState) + ' End If - Description = NewDescription - If Checked Then - LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5")) - Else - LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush1", "ColorBrushGray4")) - End If - '主 Logo - Logo = If(Entry.Comp Is Nothing, PathImage & "Icons/NoIcon.png", Entry.Comp.GetControlLogo()) - '图标右下角的 Logo - If Entry.State = McMod.McModState.Fine Then - If ImgState IsNot Nothing Then - Children.Remove(ImgState) - ImgState = Nothing - End If - Else - If ImgState Is Nothing Then - ImgState = New Image With { - .Width = 20, .Height = 20, .Margin = New Thickness(0, 0, -5, -3), .IsHitTestVisible = False, - .HorizontalAlignment = HorizontalAlignment.Right, .VerticalAlignment = VerticalAlignment.Bottom - } - RenderOptions.SetBitmapScalingMode(ImgState, BitmapScalingMode.HighQuality) - SetColumn(ImgState, 1) : SetRow(ImgState, 1) : SetRowSpan(ImgState, 2) - Children.Add(ImgState) - ' - End If - ImgState.Source = New MyBitmap(PathImage & $"Icons/{Entry.State}.png") - End If - '标签 - If Entry.Comp IsNot Nothing Then Tags = Entry.Comp.Tags - End Sub) + ImgState.Source = New MyBitmap(PathImage & $"Icons/{Entry.State}.png") + End If + '标签 + If Entry.Comp IsNot Nothing Then Tags = Entry.Comp.Tags End Sub Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp, Me.Changed @@ -488,9 +486,9 @@ Public Class MyLocalModItem '触发更新 Private Sub BtnUpdate_Click(sender As Object, e As EventArgs) Handles BtnUpdate.Click - Select Case MyMsgBox($"是否要更新 {Entry.Name}?{vbCrLf}{vbCrLf}{GetUpdateCompareDescription()}", "Mod 更新确认", "更新", "查看更新日志", "取消") + Select Case MyMsgBox($"是否要更新 {Entry.DisplayName}?{vbCrLf}{vbCrLf}{GetUpdateCompareDescription()}", "Mod 更新确认", "更新", "查看更新日志", "取消") Case 1 '更新 - FrmVersionMod.UpdateMods({Entry}) + FrmInstanceMod.UpdateMods({Entry}) Case 2 '查看更新日志 ShowUpdateLog() Case 3 '取消 diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml similarity index 97% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml index 4eee204..ae32c51 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:System="clr-namespace:System;assembly=mscorlib" - mc:Ignorable="d" x:Class="PageVersionExport" + mc:Ignorable="d" x:Class="PageInstanceExport" PanScroll="PanBack"> @@ -249,8 +249,8 @@ Theme="Yellow" Margin="0,2,0,8" Visibility="{Binding Checked, ElementName=CheckAdvancedInclude, Converter={StaticResource BooleanToVisibilityConverter}}" /> - + diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml.vb similarity index 93% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml.vb index 0ad29e3..3c34917 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceExport.xaml.vb @@ -15,28 +15,28 @@ Public Class ExportOption Public Property RequireModLoaderOrOptiFine As Boolean = False End Class -Public Class PageVersionExport +Public Class PageInstanceExport Implements IRefreshable - Private CurrentVersion As String = "" - Private Sub PageVersionExport_Loaded() Handles Me.Loaded + Private CurrentInstance As String = "" + Private Sub PageInstanceExport_Loaded() Handles Me.Loaded AniControlEnabled += 1 - If CurrentVersion <> PageVersionLeft.Version.Path Then RefreshAll() '切换到了另一个版本,重置页面 + If CurrentInstance <> PageInstanceLeft.Instance.PathVersion Then RefreshAll() '切换到了另一个版本,重置页面 CustomEventService.SetEventData(BtnAdvancedHelp, If(VersionBranchName = "Release", "指南/整合包制作 - Public.json", "指南/整合包制作 - Snapshot.json")) AniControlEnabled -= 1 End Sub Public Sub RefreshAll() Implements IRefreshable.Refresh Log($"[Export] 刷新导出页面") - HintOptiFine.Visibility = If(PageVersionLeft.Version.Version.HasOptiFine, Visibility.Visible, Visibility.Collapsed) - CurrentVersion = PageVersionLeft.Version.Path + HintOptiFine.Visibility = If(PageInstanceLeft.Instance.Version.HasOptiFine, Visibility.Visible, Visibility.Collapsed) + CurrentInstance = PageInstanceLeft.Instance.PathVersion TextExportName.Text = "" - TextExportName.HintText = PageVersionLeft.Version.Name + TextExportName.HintText = PageInstanceLeft.Instance.Name TextExportVersion.Text = "" TextExportVersion.HintText = "1.0.0" CheckAdvancedInclude.Checked = False CheckAdvancedModrinth.Checked = False - GetExportOption(CheckOptionsBasic).Description = PageVersionLeft.Version.GetVersionDescription() + GetExportOption(CheckOptionsBasic).Description = PageInstanceLeft.Instance.VersionDisplayName() ResetConfigOverrides() ReloadAllSubOptions() RefreshAllOptionsUI() @@ -58,7 +58,7 @@ Public Class PageVersionExport Private Sub ReloadSubOptions(Panel As StackPanel, AcceptCompressedFile As Boolean, AcceptFolder As Boolean, ParamArray Folders As String()) Panel.Children.Clear() For Each Folder In Folders - Dim TargetFolder As New DirectoryInfo(PageVersionLeft.Version.PathIndie & Folder) + Dim TargetFolder As New DirectoryInfo(PageInstanceLeft.Instance.PathIndie & Folder) If Not TargetFolder.Exists() Then Continue For '查找文件夹下的对应项 If AcceptCompressedFile Then @@ -100,7 +100,7 @@ Public Class PageVersionExport Return False End Try End Function - Dim PathInfo As New DirectoryInfo(PageVersionLeft.Version.PathIndie) + Dim PathInfo As New DirectoryInfo(PageInstanceLeft.Instance.PathIndie) AllEntries.AddRange(PathInfo.EnumerateFiles().Select(Function(f) f.Name)) For Each SubFolder In PathInfo.EnumerateDirectories().Where(IsValidDirectory) AllEntries.Add($"{SubFolder.Name}\") @@ -112,9 +112,9 @@ Public Class PageVersionExport Dim IsVisible = Function(TargetOption As ExportOption) As Boolean '检查需要 OptiFine 或 Mod 加载器 - If TargetOption.RequireOptiFine AndAlso Not PageVersionLeft.Version.Version.HasOptiFine Then Return False - If TargetOption.RequireModLoader AndAlso Not PageVersionLeft.Version.Modable Then Return False - If TargetOption.RequireModLoaderOrOptiFine AndAlso Not PageVersionLeft.Version.Version.HasOptiFine AndAlso Not PageVersionLeft.Version.Modable Then Return False + If TargetOption.RequireOptiFine AndAlso Not PageInstanceLeft.Instance.Version.HasOptiFine Then Return False + If TargetOption.RequireModLoader AndAlso Not PageInstanceLeft.Instance.Modable Then Return False + If TargetOption.RequireModLoaderOrOptiFine AndAlso Not PageInstanceLeft.Instance.Version.HasOptiFine AndAlso Not PageInstanceLeft.Instance.Modable Then Return False '粗略检查是否可能有符合规则的文件/文件夹 Return StandardizeLines(If(TargetOption.Rules, TargetOption.ShowRules).Split("|"c), True).Any( Function(Rule As String) @@ -130,9 +130,9 @@ Public Class PageVersionExport Rule = Rule.Trim("*?".ToCharArray) If Rule.Split({"\"c}, StringSplitOptions.RemoveEmptyEntries).Count >= 3 Then If Rule.EndsWithF("\") Then - Return IsValidDirectory(New DirectoryInfo(PageVersionLeft.Version.PathIndie & Rule)) '文件夹有效 + Return IsValidDirectory(New DirectoryInfo(PageInstanceLeft.Instance.PathIndie & Rule)) '文件夹有效 Else - Return File.Exists(PageVersionLeft.Version.PathIndie & Rule) '文件有效 + Return File.Exists(PageInstanceLeft.Instance.PathIndie & Rule) '文件有效 End If Else Return False @@ -324,9 +324,9 @@ Public Class PageVersionExport ConfigLines.Add("# 二次分发可能违反使用协议,请尽量不要公开发布包含资源文件的整合包!") ConfigLines.Add("DontCheckHostedAssets:" & CheckAdvancedInclude.Checked) ConfigLines.Add("") - ConfigLines.Add("# 如果你想要打包上传到 Modrinth,启用此项会生成完全符合 Modrinth 要求的整合包文件。") - ConfigLines.Add("# 由于 Modrinth 要求,只能从 CurseForge 下载的资源将无法联网下载,会被直接放入整合包中。") - ConfigLines.Add("# 此选项与 IncludeLauncher、IncludeLauncherCustom、DontCheckHostedAssets 冲突。") + ConfigLines.Add("# 若资源未在 Modrinth 上托管,则将其原始文件直接放入整合包中。") + ConfigLines.Add("# 如果你想将整合包上传到 Modrinth,这能帮助你分辨哪些文件未在 Modrinth 上托管。") + ConfigLines.Add("# 此选项与 DontCheckHostedAssets 冲突。") ConfigLines.Add("ModrinthUploadMode:" & CheckAdvancedModrinth.Checked) ConfigLines.Add("") ConfigLines.Add("# 导出的文件的存放位置。") @@ -435,7 +435,7 @@ Public Class PageVersionExport '缓存所需参数 Dim CacheFolder = RequestTaskTempFolder() Dim OverridesFolder = CacheFolder & "modpack\overrides\" - Dim McVersion = PageVersionLeft.Version + Dim McVersion = PageInstanceLeft.Instance Dim PathIndie As String = McVersion.PathIndie Dim CheckHostedAssets As Boolean = Not CheckAdvancedInclude.Checked Dim ModrinthUploadMode As Boolean = CheckAdvancedModrinth.Checked @@ -530,7 +530,7 @@ Public Class PageVersionExport Next Loader.Progress = 0.97 '复制 PCL 版本设置 - CopyDirectory(McVersion.Path & "PCL\", OverridesFolder & "PCL\") + CopyDirectory(McVersion.PathVersion & "PCL\", OverridesFolder & "PCL\") WriteIni(OverridesFolder & "PCL\Setup.ini", "IsStar", False) #If BETA Then '复制 PCL 本体 @@ -589,7 +589,7 @@ Public Class PageVersionExport RunInNewThread( Sub() Try - If ModrinthUploadMode Then Return 'Modrinth 上传模式下,不能从 CurseForge 获取信息 + If ModrinthUploadMode Then Return 'Modrinth 上传模式下,不从 CurseForge 获取信息 Dim CurseForgeHashes = Loader.Input.Select(Function(m) m.CurseForgeHash) Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", HttpMethod.Post, $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches") @@ -648,17 +648,17 @@ Public Class PageVersionExport Files.Add(New JObject From { {"path", ModFile.Path.AfterFirst(OverridesFolder).Replace("\", "/")}, {"hashes", New JObject From {{"sha1", ModFile.ModrinthHash}, {"sha512", GetFileSHA512(ModFile.Path)}}}, - {"downloads", New JArray(Pair.Value.OrderByDescending(Function(u) u.Contains("modrinth.com")))}, + {"downloads", New JArray(Pair.Value.OrderBy(Function(u) u.Contains("modrinth.com")))}, '不优先选择 Modrinth {"fileSize", New FileInfo(ModFile.Path).Length} }) File.Delete(ModFile.Path) Next Loader.Progress = 0.2 '导出最终 JSON 文件 - Dim Dependencies As New JObject From {{"minecraft", McVersion.Version.McName}} - If McVersion.Version.HasForge Then Dependencies.Add("forge", McVersion.Version.ForgeVersion) - If McVersion.Version.HasFabric Then Dependencies.Add("fabric-loader", McVersion.Version.FabricVersion) - If McVersion.Version.HasNeoForge Then Dependencies.Add("neoforge", McVersion.Version.NeoForgeVersion) + Dim Dependencies As New JObject From {{"minecraft", McVersion.Version.VanillaName}} + If McVersion.Version.HasForge Then Dependencies.Add("forge", McVersion.Version.Forge) + If McVersion.Version.HasFabric Then Dependencies.Add("fabric-loader", McVersion.Version.Fabric) + If McVersion.Version.HasNeoForge Then Dependencies.Add("neoforge", McVersion.Version.NeoForge) Dim ResultJson As New JObject From { {"game", "minecraft"}, {"formatVersion", 1}, @@ -711,12 +711,6 @@ Public Class PageVersionExport End If End Sub - '勾选 Modrinth 上传模式时,禁止打包 PCL - Private Sub CheckAdvancedModrinth_Change(sender As Object, user As Boolean) Handles CheckAdvancedModrinth.Change - If CheckAdvancedModrinth.Checked Then CheckOptionsPcl.Checked = False - CheckOptionsPcl.IsEnabled = Not CheckAdvancedModrinth.Checked - End Sub - '勾选打包资源文件时,禁止开启 Modrinth 上传模式 Private Sub CheckAdvancedInclude_Change(sender As Object, user As Boolean) Handles CheckAdvancedInclude.Change If CheckAdvancedInclude.Checked Then CheckAdvancedModrinth.Checked = False diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml similarity index 99% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml index c2abb78..54daa0f 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml @@ -1,4 +1,4 @@ - diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml.vb similarity index 68% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml.vb index b400936..e429db2 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceLeft.xaml.vb @@ -1,13 +1,13 @@ -Public Class PageVersionLeft +Public Class PageInstanceLeft Implements IRefreshable ''' ''' 当前显示设置的 MC 版本。 ''' - Public Shared Version As McVersion = Nothing + Public Shared Instance As McInstance = Nothing Public Sub RefreshModDisabled() Handles Me.Loaded - If Version IsNot Nothing AndAlso Version.Modable Then + If Instance IsNot Nothing AndAlso Instance.Modable Then ItemMod.Visibility = Visibility.Visible ItemModDisabled.Visibility = Visibility.Collapsed Else @@ -26,7 +26,7 @@ ''' ''' 勾选事件改变页面。 ''' - Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemOverall.Check, ItemMod.Check, ItemModDisabled.Check, ItemSetup.Check, ItemExport.Check + Private Sub PageCheck(sender As FrameworkElement, e As RouteEventArgs) Handles ItemOverall.Check, ItemMod.Check, ItemModDisabled.Check, ItemSetup.Check, ItemExport.Check '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0 '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False) If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag)) @@ -35,21 +35,21 @@ Public Function PageGet(Optional ID As FormMain.PageSubType = -1) If ID = -1 Then ID = PageID Select Case ID - Case FormMain.PageSubType.VersionOverall - If FrmVersionOverall Is Nothing Then FrmVersionOverall = New PageVersionOverall - Return FrmVersionOverall - Case FormMain.PageSubType.VersionMod - If FrmVersionMod Is Nothing Then FrmVersionMod = New PageVersionMod - Return FrmVersionMod - Case FormMain.PageSubType.VersionModDisabled - If FrmVersionModDisabled Is Nothing Then FrmVersionModDisabled = New PageVersionModDisabled - Return FrmVersionModDisabled - Case FormMain.PageSubType.VersionSetup - If IsNothing(FrmVersionSetup) Then FrmVersionSetup = New PageVersionSetup - Return FrmVersionSetup - Case FormMain.PageSubType.VersionExport - If FrmVersionExport Is Nothing Then FrmVersionExport = New PageVersionExport - Return FrmVersionExport + Case FormMain.PageSubType.InstanceOverall + If FrmInstanceOverall Is Nothing Then FrmInstanceOverall = New PageInstanceOverall + Return FrmInstanceOverall + Case FormMain.PageSubType.InstanceMod + If FrmInstanceMod Is Nothing Then FrmInstanceMod = New PageInstanceMod + Return FrmInstanceMod + Case FormMain.PageSubType.InstanceModDisabled + If FrmInstanceModDisabled Is Nothing Then FrmInstanceModDisabled = New PageInstanceModDisabled + Return FrmInstanceModDisabled + Case FormMain.PageSubType.InstanceSetup + If IsNothing(FrmInstanceSetup) Then FrmInstanceSetup = New PageInstanceSetup + Return FrmInstanceSetup + Case FormMain.PageSubType.InstanceExport + If FrmInstanceExport Is Nothing Then FrmInstanceExport = New PageInstanceExport + Return FrmInstanceExport Case Else Throw New Exception("未知的版本设置子页面种类:" & ID) End Select @@ -99,19 +99,19 @@ End Sub Public Sub Refresh(SubType As FormMain.PageSubType) Select Case SubType - Case FormMain.PageSubType.VersionMod - PageVersionMod.Refresh() + Case FormMain.PageSubType.InstanceMod + PageInstanceMod.Refresh() ItemMod.Checked = True - Case FormMain.PageSubType.VersionExport - If FrmVersionExport IsNot Nothing Then FrmVersionExport.RefreshAll() + Case FormMain.PageSubType.InstanceExport + If FrmInstanceExport IsNot Nothing Then FrmInstanceExport.RefreshAll() ItemExport.Checked = True End Select End Sub Public Sub Reset(sender As Object, e As EventArgs) If MyMsgBox("是否要初始化该版本的版本独立设置?该操作不可撤销。", "初始化确认",, "取消", IsWarn:=True) = 1 Then - If IsNothing(FrmVersionSetup) Then FrmVersionSetup = New PageVersionSetup - FrmVersionSetup.Reset() + If IsNothing(FrmInstanceSetup) Then FrmInstanceSetup = New PageInstanceSetup + FrmInstanceSetup.Reset() ItemSetup.Checked = True End If End Sub diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml similarity index 94% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml index 120a17c..f1b7f3e 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" x:Class="PageVersionMod" + mc:Ignorable="d" x:Class="PageInstanceMod" PanScroll="PanBack"> @@ -22,7 +22,6 @@ - @@ -31,7 +30,6 @@ - diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml.vb similarity index 87% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml.vb index a844d23..012d267 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceMod.xaml.vb @@ -1,4 +1,4 @@ -Public Class PageVersionMod +Public Class PageInstanceMod Implements IRefreshable #Region "初始化" @@ -23,28 +23,22 @@ Btn.LabText.Margin = New Thickness(-2, 0, 8, 0) Next -#If DEBUG Then - BtnManageCheck.Visibility = Visibility.Visible -#End If - End Sub ''' ''' 刷新 Mod 列表。 ''' Public Sub ReloadModList(Optional ForceReload As Boolean = False) - If LoaderRun(If(ForceReload, LoaderFolderRunType.ForceRun, LoaderFolderRunType.RunOnUpdated)) Then - Log("[System] 已刷新 Mod 列表") - Filter = FilterType.All - PanBack.ScrollToHome() - SearchBox.Text = "" - End If + If Not LoaderRun(If(ForceReload, LoaderFolderRunType.ForceRun, LoaderFolderRunType.RunOnUpdated)) Then Return + Log("[System] 已刷新 Mod 列表") + Filter = FilterType.All + PanBack.ScrollToHome() + SearchBox.Text = "" End Sub '强制刷新 Private Sub RefreshSelf() Implements IRefreshable.Refresh Refresh() End Sub Public Shared Sub Refresh() - '强制刷新 Try CompProjectCache.Clear() CompFilesCache.Clear() @@ -53,8 +47,8 @@ Catch ex As Exception Log(ex, "强制刷新时清理本地 Mod 信息缓存失败") End Try - If FrmVersionMod IsNot Nothing Then FrmVersionMod.ReloadModList(True) '无需 Else,还没加载刷个鬼的新 - FrmVersionLeft.ItemMod.Checked = True + If FrmInstanceMod IsNot Nothing Then FrmInstanceMod.ReloadModList(True) '无需 Else,还没加载刷个鬼的新 + FrmInstanceLeft.ItemMod.Checked = True Hint("正在刷新……", Log:=False) End Sub @@ -67,7 +61,7 @@ End If End Sub Public Function LoaderRun(Type As LoaderFolderRunType) As Boolean - Return LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", Type) + Return LoaderFolderRun(McModLoader, PageInstanceLeft.Instance.PathIndie & "mods\", Type) End Function #End Region @@ -109,8 +103,7 @@ AniControlEnabled += 1 Dim NewItem As New MyLocalModItem With {.SnapsToDevicePixels = True, .Entry = Entry, .ButtonHandler = AddressOf McModContent, .Checked = SelectedMods.Contains(Entry.RawFileName)} - AddHandler Entry.OnCompUpdate, AddressOf NewItem.Refresh - NewItem.Refresh() + AddHandler Entry.OnUpdate, Sub() RunInUi(AddressOf NewItem.Refresh) AniControlEnabled -= 1 Return NewItem End Function @@ -144,11 +137,7 @@ ToolTipService.SetVerticalOffset(BtnED, 30) ToolTipService.SetHorizontalOffset(BtnED, 2) AddHandler BtnED.Click, AddressOf ED_Click - If sender.Entry.State = McMod.McModState.Unavailable Then - sender.Buttons = {BtnCont, BtnOpen, BtnDelete} - Else - sender.Buttons = {BtnCont, BtnOpen, BtnED, BtnDelete} - End If + sender.Buttons = {BtnCont, BtnOpen, BtnED, BtnDelete} End Sub ''' @@ -188,13 +177,11 @@ Dim EnabledCount As Integer = 0 Dim DisabledCount As Integer = 0 Dim UpdateCount As Integer = 0 - Dim UnavalialeCount As Integer = 0 For Each ModItem In If(IsSearching, SearchResult, If(McModLoader.Output, New List(Of McMod))) AnyCount += 1 If ModItem.CanUpdate Then UpdateCount += 1 If ModItem.State.Equals(McMod.McModState.Fine) Then EnabledCount += 1 If ModItem.State.Equals(McMod.McModState.Disabled) Then DisabledCount += 1 - If ModItem.State.Equals(McMod.McModState.Unavailable) Then UnavalialeCount += 1 Next '显示 BtnFilterAll.Text = If(IsSearching, "搜索结果", "全部") & $" ({AnyCount})" @@ -204,8 +191,6 @@ BtnFilterEnabled.Visibility = If(Filter = FilterType.Enabled OrElse (EnabledCount > 0 AndAlso EnabledCount < AnyCount), Visibility.Visible, Visibility.Collapsed) BtnFilterDisabled.Text = $"禁用 ({DisabledCount})" BtnFilterDisabled.Visibility = If(Filter = FilterType.Disabled OrElse DisabledCount > 0, Visibility.Visible, Visibility.Collapsed) - BtnFilterError.Text = $"错误 ({UnavalialeCount})" - BtnFilterError.Visibility = If(Filter = FilterType.Unavailable OrElse UnavalialeCount > 0, Visibility.Visible, Visibility.Collapsed) '----------------- ' 底部栏 @@ -289,31 +274,13 @@ ''' Private Sub BtnManageOpen_Click(sender As Object, e As EventArgs) Handles BtnManageOpen.Click, BtnHintOpen.Click Try - Directory.CreateDirectory(PageVersionLeft.Version.PathIndie & "mods\") - OpenExplorer(PageVersionLeft.Version.PathIndie & "mods\") + Directory.CreateDirectory(PageInstanceLeft.Instance.PathIndie & "mods\") + OpenExplorer(PageInstanceLeft.Instance.PathIndie & "mods\") Catch ex As Exception Log(ex, "打开 Mods 文件夹失败", LogLevel.Msgbox) End Try End Sub -#If DEBUG Then - ''' - ''' 检查 Mod。 - ''' - Private Sub BtnManageCheck_Click(sender As Object, e As EventArgs) Handles BtnManageCheck.Click - Try - Dim Result = McModCheck(PageVersionLeft.Version, McModLoader.Output) - If Result.Any Then - MyMsgBox(Join(Result, vbCrLf & vbCrLf), "Mod 检查结果") - Else - Hint("Mod 检查完成,未发现任何问题!", HintType.Green) - End If - Catch ex As Exception - Log(ex, "进行 Mod 检查时出错", LogLevel.Feedback) - End Try - End Sub -#End If - ''' ''' 全选。 ''' @@ -344,14 +311,14 @@ Return True End If '获取并检查目标版本 - Dim TargetVersion As McVersion = McVersionCurrent - If FrmMain.PageCurrent = FormMain.PageType.VersionSetup Then TargetVersion = PageVersionLeft.Version - If FrmMain.PageCurrent = FormMain.PageType.VersionSelect OrElse TargetVersion Is Nothing OrElse Not TargetVersion.Modable Then + Dim Instance As McInstance = McInstanceSelected + If FrmMain.PageCurrent = FormMain.PageType.InstanceSetup Then Instance = PageInstanceLeft.Instance + If FrmMain.PageCurrent = FormMain.PageType.InstanceSelect OrElse Instance Is Nothing OrElse Not Instance.Modable Then '正在选择版本,或当前版本不能安装 Mod Hint("若要安装 Mod,请先选择一个可以安装 Mod 的版本!") - ElseIf Not (FrmMain.PageCurrent = FormMain.PageType.VersionSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.VersionMod) Then + ElseIf Not (FrmMain.PageCurrent = FormMain.PageType.InstanceSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.InstanceMod) Then '未处于 Mod 管理页面 - If MyMsgBox($"是否要将这{If(FilePathList.Count = 1, "个", "些")}文件作为 Mod 安装到 {TargetVersion.Name}?", "Mod 安装确认", "确定", "取消") = 1 Then GoTo Install + If MyMsgBox($"是否要将这{If(FilePathList.Count = 1, "个", "些")}文件作为 Mod 安装到 {Instance.Name}?", "Mod 安装确认", "确定", "取消") = 1 Then GoTo Install Else '处于 Mod 管理页面 Install: @@ -359,7 +326,7 @@ Install: For Each ModFile In FilePathList Dim NewFileName = GetFileNameFromPath(ModFile).Replace(".disabled", "").Replace(".old", "") If Not NewFileName.Contains(".") Then NewFileName += ".jar" '#4227 - CopyFile(ModFile, TargetVersion.PathIndie & "mods\" & NewFileName) + CopyFile(ModFile, Instance.PathIndie & "mods\" & NewFileName) Next If FilePathList.Count = 1 Then Hint($"已安装 {GetFileNameFromPath(FilePathList.First).Replace(".disabled", "").Replace(".old", "")}!", HintType.Green) @@ -367,8 +334,8 @@ Install: Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Green) End If '刷新列表 - If FrmMain.PageCurrent = FormMain.PageType.VersionSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.VersionMod Then - LoaderFolderRun(McModLoader, TargetVersion.PathIndie & "mods\", LoaderFolderRunType.ForceRun) + If FrmMain.PageCurrent = FormMain.PageType.InstanceSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.InstanceMod Then + LoaderFolderRun(McModLoader, Instance.PathIndie & "mods\", LoaderFolderRunType.ForceRun) End If Catch ex As Exception Log(ex, "复制 Mod 文件失败", LogLevel.Msgbox) @@ -382,7 +349,7 @@ Install: ''' Private Sub BtnManageDownload_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageDownload.Click, BtnHintDownload.Click '预设置当前版本和搜索框内容 - PageComp.TargetVersion = PageVersionLeft.Version + PageComp.TargetInstance = PageInstanceLeft.Instance PageComp.TargetName = SearchBox.Text FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadMod) End Sub @@ -455,10 +422,8 @@ Install: BtnFilterEnabled.Checked = True Case FilterType.Disabled BtnFilterDisabled.Checked = True - Case FilterType.CanUpdate - BtnFilterCanUpdate.Checked = True Case Else - BtnFilterError.Checked = True + BtnFilterCanUpdate.Checked = True End Select RefreshUI() End Set @@ -468,7 +433,6 @@ Install: Enabled = 1 Disabled = 2 CanUpdate = 3 - Unavailable = 4 End Enum ''' @@ -484,15 +448,13 @@ Install: Return CheckingMod.State = McMod.McModState.Disabled Case FilterType.CanUpdate Return CheckingMod.CanUpdate - Case FilterType.Unavailable - Return CheckingMod.State = McMod.McModState.Unavailable Case Else Return False End Select End Function '点击筛选项触发的改变 - Private Sub ChangeFilter(sender As MyRadioButton, raiseByMouse As Boolean) Handles BtnFilterAll.Check, BtnFilterCanUpdate.Check, BtnFilterDisabled.Check, BtnFilterEnabled.Check, BtnFilterError.Check + Private Sub ChangeFilter(sender As MyRadioButton, raiseByMouse As Boolean) Handles BtnFilterAll.Check, BtnFilterCanUpdate.Check, BtnFilterDisabled.Check, BtnFilterEnabled.Check Filter = sender.Tag RefreshUI() End Sub @@ -586,7 +548,7 @@ Install: ''' ''' 记录正在进行 Mod 更新的 mods 文件夹路径。 ''' - Public Shared UpdatingVersions As New List(Of String) + Public Shared UpdatingInstanceModFolders As New List(Of String) Public Sub UpdateMods(ModList As IEnumerable(Of McMod)) '更新前警告 If Not Setup.Get("HintUpdateMod") OrElse ModList.Count >= 15 Then @@ -665,8 +627,8 @@ Install: End Try End Sub)) '结束处理 - Dim Loader As New LoaderCombo(Of IEnumerable(Of McMod))("Mod 更新:" & PageVersionLeft.Version.Name, InstallLoaders) - Dim PathMods As String = PageVersionLeft.Version.PathIndie & "mods\" + Dim Loader As New LoaderCombo(Of IEnumerable(Of McMod))("Mod 更新:" & PageInstanceLeft.Instance.Name, InstallLoaders) + Dim PathMods As String = PageInstanceLeft.Instance.PathIndie & "mods\" Loader.OnStateChanged = Sub() '结果提示 @@ -688,7 +650,7 @@ Install: Return End Select Log($"[Mod] 已从正在进行 Mod 更新的文件夹列表移除:{PathMods}") - UpdatingVersions.Remove(PathMods) + UpdatingInstanceModFolders.Remove(PathMods) '清理缓存 RunInNewThread( Sub() @@ -703,7 +665,7 @@ Install: End Sub '启动加载器 Log($"[Mod] 开始更新 {ModList.Count} 个 Mod:{PathMods}") - UpdatingVersions.Add(PathMods) + UpdatingInstanceModFolders.Add(PathMods) Loader.Start() LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() @@ -807,41 +769,27 @@ Install: Dim ModEntry As McMod = CType(If(TypeOf sender Is MyIconButton, sender.Tag, sender), MyLocalModItem).Entry '加载失败信息 - If ModEntry.State = McMod.McModState.Unavailable Then - MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & ModEntry.FileUnavailableReason.GetDetail(), "Mod 读取失败") - Return - End If If ModEntry.Comp IsNot Nothing Then '跳转到 Mod 下载页面 FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail, - .Additional = {ModEntry.Comp, New List(Of String), PageVersionLeft.Version.Version.McName, - If(PageVersionLeft.Version.Version.HasForge, CompModLoaderType.Forge, - If(PageVersionLeft.Version.Version.HasNeoForge, CompModLoaderType.NeoForge, - If(PageVersionLeft.Version.Version.HasFabric, CompModLoaderType.Fabric, CompModLoaderType.Any))), + .Additional = {ModEntry.Comp, New List(Of String), PageInstanceLeft.Instance.Version.VanillaName, + If(PageInstanceLeft.Instance.Version.HasForge, CompModLoaderType.Forge, + If(PageInstanceLeft.Instance.Version.HasNeoForge, CompModLoaderType.NeoForge, + If(PageInstanceLeft.Instance.Version.HasFabric, CompModLoaderType.Fabric, CompModLoaderType.Any))), CompType.Mod}}) Else '获取信息 Dim ContentLines As New List(Of String) If ModEntry.Description IsNot Nothing Then ContentLines.Add(ModEntry.Description & vbCrLf) - If ModEntry.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntry.Authors) ContentLines.Add("文件:" & ModEntry.FileName & "(" & GetString(New FileInfo(ModEntry.Path).Length) & ")") If ModEntry.Version IsNot Nothing Then ContentLines.Add("版本:" & ModEntry.Version) Dim DebugInfo As New List(Of String) - If ModEntry.ModId IsNot Nothing Then - DebugInfo.Add("Mod ID:" & ModEntry.ModId) - End If - If ModEntry.Dependencies.Any Then - DebugInfo.Add("依赖于:") - For Each Dep In ModEntry.Dependencies - DebugInfo.Add(" - " & Dep.Key & If(Dep.Value Is Nothing, "", ",版本:" & Dep.Value)) - Next - End If If DebugInfo.Any Then ContentLines.Add("") ContentLines.AddRange(DebugInfo) End If '获取用于搜索的 Mod 名称 - Dim ModOriginalName As String = ModEntry.Name.Replace(" ", "+") + Dim ModOriginalName As String = ModEntry.DisplayName.Replace(" ", "+") Dim ModSearchName As String = ModOriginalName.Substring(0, 1) For i = 1 To ModOriginalName.Count - 1 Dim IsLastLower As Boolean = ModOriginalName(i - 1).ToString.ToLower.Equals(ModOriginalName(i - 1).ToString) @@ -853,18 +801,9 @@ Install: ModSearchName += ModOriginalName(i) Next ModSearchName = ModSearchName.Replace("++", "+").Replace("pti+Fine", "ptiFine") - '显示 - If ModEntry.Url Is Nothing Then - If MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "百科搜索", "返回") = 1 Then - OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0") - End If - Else - Select Case MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "打开官网", "百科搜索", "返回") - Case 1 - OpenWebsite(ModEntry.Url) - Case 2 - OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0") - End Select + '百科搜索链接 + If MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.DisplayName, "百科搜索", "返回") = 1 Then + OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0") End If End If Catch ex As Exception @@ -907,7 +846,7 @@ Install: Dim QueryList As New List(Of SearchEntry(Of McMod)) For Each Entry As McMod In McModLoader.Output Dim SearchSource As New List(Of KeyValuePair(Of String, Double)) - SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Name, 1)) + SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.DisplayName, 1)) SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.FileName, 1)) If Entry.Version IsNot Nothing Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Version, 0.2)) @@ -916,7 +855,7 @@ Install: SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Description, 0.4)) End If If Entry.Comp IsNot Nothing Then - If Entry.Comp.RawName <> Entry.Name Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.RawName, 1)) + If Entry.Comp.RawName <> Entry.DisplayName Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.RawName, 1)) If Entry.Comp.TranslatedName <> Entry.Comp.RawName Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.TranslatedName, 1)) If Entry.Comp.Description <> Entry.Description Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.Description, 0.4)) SearchSource.Add(New KeyValuePair(Of String, Double)(String.Join("", Entry.Comp.Tags), 0.2)) diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionModDisabled.xaml b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceModDisabled.xaml similarity index 97% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionModDisabled.xaml rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceModDisabled.xaml index 1daef41..9274c83 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionModDisabled.xaml +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceModDisabled.xaml @@ -1,4 +1,4 @@ - @@ -25,7 +25,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.vb similarity index 60% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.vb index 3035e9f..6676829 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceOverall.xaml.vb @@ -1,4 +1,4 @@ -Public Class PageVersionOverall +Public Class PageInstanceOverall Private IsLoad As Boolean = False Private Sub PageSetupLaunch_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded @@ -7,7 +7,6 @@ PanBack.ScrollToHome() '更新设置 - ItemDisplayLogoCustom.Tag = "PCL\Logo.png" Reload() '非重复加载部分 @@ -17,7 +16,7 @@ End Sub - Public ItemVersion As MyListItem + Public InstanceHeaderItem As MyListItem ''' ''' 确保当前页面上的信息已正确显示。 ''' @@ -25,19 +24,19 @@ AniControlEnabled += 1 '刷新设置项目 - ComboDisplayType.SelectedIndex = ReadIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "DisplayType", McVersionCardType.Auto) - BtnDisplayStar.Text = If(PageVersionLeft.Version.IsStar, "从收藏夹中移除", "加入收藏夹") - BtnFolderMods.Visibility = If(PageVersionLeft.Version.Modable, Visibility.Visible, Visibility.Collapsed) + ComboDisplayType.SelectedIndex = ReadIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "DisplayType", McInstanceCardType.Auto) + BtnDisplayStar.Text = If(PageInstanceLeft.Instance.IsStar, "从收藏夹中移除", "加入收藏夹") + BtnFolderMods.Visibility = If(PageInstanceLeft.Instance.Modable, Visibility.Visible, Visibility.Collapsed) '刷新版本显示 PanDisplayItem.Children.Clear() - ItemVersion = PageVersionLeft.Version.ToListItem - ItemVersion.IsHitTestVisible = False - PanDisplayItem.Children.Add(ItemVersion) + InstanceHeaderItem = PageInstanceLeft.Instance.ToListItem().Init() + InstanceHeaderItem.IsHitTestVisible = False + PanDisplayItem.Children.Add(InstanceHeaderItem) FrmMain.PageNameRefresh() '刷新版本图标 ComboDisplayLogo.SelectedIndex = 0 - Dim Logo As String = ReadIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "Logo", "") - Dim LogoCustom As Boolean = ReadIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "LogoCustom", "False") + Dim Logo As String = ReadIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "Logo", "") + Dim LogoCustom As Boolean = ReadIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "LogoCustom", "False") If LogoCustom Then For Each Selection As MyComboBoxItem In ComboDisplayLogo.Items If Selection.Tag = Logo OrElse (Selection.Tag = "PCL\Logo.png" AndAlso Logo.EndsWith("PCL\Logo.png")) Then @@ -59,14 +58,14 @@ '改为不隐藏 Try '若设置分类为可安装 Mod,则显示正常的 Mod 管理页面 - WriteIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "DisplayType", ComboDisplayType.SelectedIndex) - PageVersionLeft.Version.DisplayType = ReadIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "DisplayType", McVersionCardType.Auto) - FrmVersionLeft.RefreshModDisabled() + WriteIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "DisplayType", ComboDisplayType.SelectedIndex) + PageInstanceLeft.Instance.DisplayType = ReadIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "DisplayType", McInstanceCardType.Auto) + FrmInstanceLeft.RefreshModDisabled() - WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '要求刷新缓存 - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + WriteIni(McFolderSelected & "PCL.ini", "InstanceCache", "") '要求刷新缓存 + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") Catch ex As Exception - Log(ex, "修改版本分类失败(" & PageVersionLeft.Version.Name & ")", LogLevel.Feedback) + Log(ex, "修改版本分类失败(" & PageInstanceLeft.Instance.Name & ")", LogLevel.Feedback) End Try Reload() '更新 “打开 Mod 文件夹” 按钮 Else @@ -79,11 +78,11 @@ End If Setup.Set("HintHide", True) End If - WriteIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "DisplayType", McVersionCardType.Hidden) - WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '要求刷新缓存 - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + WriteIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "DisplayType", McInstanceCardType.Hidden) + WriteIni(McFolderSelected & "PCL.ini", "InstanceCache", "") '要求刷新缓存 + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") Catch ex As Exception - Log(ex, "隐藏版本 " & PageVersionLeft.Version.Name & " 失败", LogLevel.Feedback) + Log(ex, "隐藏版本 " & PageInstanceLeft.Instance.Name & " 失败", LogLevel.Feedback) End Try End If End Sub @@ -91,14 +90,14 @@ '更改描述 Private Sub BtnDisplayDesc_Click(sender As Object, e As EventArgs) Handles BtnDisplayDesc.Click Try - Dim OldInfo As String = ReadIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "CustomInfo") + Dim OldInfo As String = ReadIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "CustomInfo") Dim NewInfo As String = MyMsgBoxInput("更改描述", "修改版本的描述文本,留空则使用 PCL 的默认描述。", OldInfo, New ObjectModel.Collection(Of Validate), "默认描述") - If NewInfo IsNot Nothing AndAlso OldInfo <> NewInfo Then WriteIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "CustomInfo", NewInfo) - PageVersionLeft.Version = New McVersion(PageVersionLeft.Version.Name).Load() + If NewInfo IsNot Nothing AndAlso OldInfo <> NewInfo Then WriteIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "CustomInfo", NewInfo) + PageInstanceLeft.Instance = New McInstance(PageInstanceLeft.Instance.Name).Load() Reload() - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") Catch ex As Exception - Log(ex, "版本 " & PageVersionLeft.Version.Name & " 描述更改失败", LogLevel.Msgbox) + Log(ex, "版本 " & PageInstanceLeft.Instance.Name & " 描述更改失败", LogLevel.Msgbox) End Try End Sub @@ -106,32 +105,32 @@ Private Sub BtnDisplayRename_Click(sender As Object, e As EventArgs) Handles BtnDisplayRename.Click Try '确认输入的新名称 - Dim OldName As String = PageVersionLeft.Version.Name - Dim OldPath As String = PageVersionLeft.Version.Path + Dim OldName As String = PageInstanceLeft.Instance.Name + Dim OldPath As String = PageInstanceLeft.Instance.PathVersion '修改此部分的同时修改快速安装的版本名检测* Dim NewName As String = MyMsgBoxInput("重命名版本", "", OldName, New ObjectModel.Collection(Of Validate) From { - New ValidateFolderName(PathMcFolder & "versions", IgnoreCase:=False), New ValidateExceptSame(OldName & "_temp", "不能使用该名称!")}) + New ValidateFolderName(McFolderSelected & "versions", IgnoreCase:=False), New ValidateExceptSame(OldName & "_temp", "不能使用该名称!")}) If String.IsNullOrWhiteSpace(NewName) Then Return - Dim NewPath As String = PathMcFolder & "versions\" & NewName & "\" + Dim NewPath As String = McFolderSelected & "versions\" & NewName & "\" '获取临时中间名,以防止仅修改大小写的重命名失败 Dim TempName As String = NewName & "_temp" - Dim TempPath As String = PathMcFolder & "versions\" & TempName & "\" + Dim TempPath As String = McFolderSelected & "versions\" & TempName & "\" Dim OnlyChangedCase As Boolean = NewName.ToLower = OldName.ToLower '重新读取版本 JSON 信息,避免 JsonObject 中已被合并的项被重新存储 Dim JsonObject As JObject - Dim OldJsonPath As String = PageVersionLeft.Version.GetJsonPath() + Dim OldJsonPath As String = PageInstanceLeft.Instance.GetJsonPath() Try JsonObject = GetJson(ReadFile(OldJsonPath)) Catch ex As Exception Log(ex, "在重命名读取 json 时失败") - JsonObject = PageVersionLeft.Version.JsonObject + JsonObject = PageInstanceLeft.Instance.JsonObject End Try '重命名主文件夹 My.Computer.FileSystem.RenameDirectory(OldPath, TempName) My.Computer.FileSystem.RenameDirectory(TempPath, NewName) '清理 ini 缓存 - IniClearCache(PageVersionLeft.Version.PathIndie & "options.txt") - IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini") + IniClearCache(PageInstanceLeft.Instance.PathIndie & "options.txt") + IniClearCache(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini") '重命名 jar 文件与 natives 文件夹 '不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443) If Directory.Exists($"{NewPath}{OldName}-natives") Then @@ -157,8 +156,8 @@ WriteFile(NewPath & "PCL\Setup.ini", ReadFile(NewPath & "PCL\Setup.ini").Replace(OldPath, NewPath)) End If '更改已选中的版本 - If ReadIni(PathMcFolder & "PCL.ini", "Version") = OldName Then - WriteIni(PathMcFolder & "PCL.ini", "Version", NewName) + If ReadIni(McFolderSelected & "PCL.ini", "Version") = OldName Then + WriteIni(McFolderSelected & "PCL.ini", "Version", NewName) End If '更新版本 Json Try @@ -170,10 +169,10 @@ End Try '刷新与提示 Hint("重命名成功!", HintType.Green) - PageVersionLeft.Version = New McVersion(NewName).Load() - If Not IsNothing(McVersionCurrent) AndAlso McVersionCurrent.Equals(PageVersionLeft.Version) Then WriteIni(PathMcFolder & "PCL.ini", "Version", NewName) + PageInstanceLeft.Instance = New McInstance(NewName).Load() + If Not IsNothing(McInstanceSelected) AndAlso McInstanceSelected.Equals(PageInstanceLeft.Instance) Then WriteIni(McFolderSelected & "PCL.ini", "Version", NewName) Reload() - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") Catch ex As Exception Log(ex, "重命名版本失败", LogLevel.Msgbox) End Try @@ -190,38 +189,38 @@ Reload() '还原选项 Return End If - CopyFile(FileName, PageVersionLeft.Version.Path & "PCL\Logo.png") + CopyFile(FileName, PageInstanceLeft.Instance.PathVersion & "PCL\Logo.png") Else - File.Delete(PageVersionLeft.Version.Path & "PCL\Logo.png") + File.Delete(PageInstanceLeft.Instance.PathVersion & "PCL\Logo.png") End If Catch ex As Exception - Log(ex, "更改自定义版本图标失败(" & PageVersionLeft.Version.Name & ")", LogLevel.Feedback) + Log(ex, "更改自定义版本图标失败(" & PageInstanceLeft.Instance.Name & ")", LogLevel.Feedback) End Try '进行更改 Try Dim NewLogo As String = ComboDisplayLogo.SelectedItem.Tag - WriteIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "Logo", NewLogo) - WriteIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "LogoCustom", Not NewLogo = "") + WriteIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "Logo", NewLogo) + WriteIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "LogoCustom", Not NewLogo = "") '刷新显示 - WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '要求刷新缓存 - PageVersionLeft.Version = New McVersion(PageVersionLeft.Version.Name).Load() + WriteIni(McFolderSelected & "PCL.ini", "InstanceCache", "") '要求刷新缓存 + PageInstanceLeft.Instance = New McInstance(PageInstanceLeft.Instance.Name).Load() Reload() - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") Catch ex As Exception - Log(ex, "更改版本图标失败(" & PageVersionLeft.Version.Name & ")", LogLevel.Feedback) + Log(ex, "更改版本图标失败(" & PageInstanceLeft.Instance.Name & ")", LogLevel.Feedback) End Try End Sub '收藏夹 Private Sub BtnDisplayStar_Click(sender As Object, e As EventArgs) Handles BtnDisplayStar.Click Try - WriteIni(PageVersionLeft.Version.Path & "PCL\Setup.ini", "IsStar", Not PageVersionLeft.Version.IsStar) - PageVersionLeft.Version = New McVersion(PageVersionLeft.Version.Name).Load() + WriteIni(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini", "IsStar", Not PageInstanceLeft.Instance.IsStar) + PageInstanceLeft.Instance = New McInstance(PageInstanceLeft.Instance.Name).Load() Reload() - McVersionListForceRefresh = True - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + McInstanceListForceRefresh = True + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") Catch ex As Exception - Log(ex, "版本 " & PageVersionLeft.Version.Name & " 收藏状态更改失败", LogLevel.Msgbox) + Log(ex, "版本 " & PageInstanceLeft.Instance.Name & " 收藏状态更改失败", LogLevel.Msgbox) End Try End Sub @@ -231,22 +230,22 @@ '版本文件夹 Private Sub BtnFolderVersion_Click() Handles BtnFolderVersion.Click - OpenVersionFolder(PageVersionLeft.Version) + OpenInstanceFolder(PageInstanceLeft.Instance) End Sub - Public Shared Sub OpenVersionFolder(Version As McVersion) - OpenExplorer(Version.Path) + Public Shared Sub OpenInstanceFolder(Instance As McInstance) + OpenExplorer(Instance.PathVersion) End Sub '存档文件夹 Private Sub BtnFolderSaves_Click() Handles BtnFolderSaves.Click - Dim FolderPath As String = PageVersionLeft.Version.PathIndie & "saves\" + Dim FolderPath As String = PageInstanceLeft.Instance.PathIndie & "saves\" Directory.CreateDirectory(FolderPath) OpenExplorer(FolderPath) End Sub 'Mod 文件夹 Private Sub BtnFolderMods_Click() Handles BtnFolderMods.Click - Dim FolderPath As String = PageVersionLeft.Version.PathIndie & "mods\" + Dim FolderPath As String = PageInstanceLeft.Instance.PathIndie & "mods\" Directory.CreateDirectory(FolderPath) OpenExplorer(FolderPath) End Sub @@ -259,7 +258,7 @@ Private Sub BtnManageScript_Click() Handles BtnManageScript.Click Try '弹窗要求指定脚本的保存位置 - Dim SavePath As String = SelectSaveFile("选择脚本保存位置", "启动 " & PageVersionLeft.Version.Name & ".bat", "批处理文件(*.bat)|*.bat") + Dim SavePath As String = SelectSaveFile("选择脚本保存位置", "启动 " & PageInstanceLeft.Instance.Name & ".bat", "批处理文件(*.bat)|*.bat") If SavePath = "" Then Return '检查中断(等玩家选完弹窗指不定任务就结束了呢……) If McLaunchLoader.State = LoadState.Loading Then @@ -267,7 +266,7 @@ Return End If '生成脚本 - If McLaunchStart(New McLaunchOptions With {.SaveBatch = SavePath, .Version = PageVersionLeft.Version}) Then + If McLaunchStart(New McLaunchOptions With {.SaveBatch = SavePath, .Instance = PageInstanceLeft.Instance}) Then If Setup.Get("LoginType") = McLoginType.Legacy Then Hint("正在导出启动脚本……") Else @@ -275,7 +274,7 @@ End If End If Catch ex As Exception - Log(ex, "导出启动脚本失败(" & PageVersionLeft.Version.Name & ")", LogLevel.Msgbox) + Log(ex, "导出启动脚本失败(" & PageInstanceLeft.Instance.Name & ")", LogLevel.Msgbox) End Try End Sub @@ -283,18 +282,18 @@ Private Sub BtnManageCheck_Click(sender As Object, e As EventArgs) Handles BtnManageCheck.Click Try '忽略文件检查提示 - If ShouldIgnoreFileCheck(PageVersionLeft.Version) Then + If ShouldIgnoreFileCheck(PageInstanceLeft.Instance) Then Hint("请先关闭 [版本设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!", HintType.Blue) Return End If '重复任务检查 For Each OngoingLoader In LoaderTaskbar - If OngoingLoader.Name <> PageVersionLeft.Version.Name & " 文件补全" Then Continue For + If OngoingLoader.Name <> PageInstanceLeft.Instance.Name & " 文件补全" Then Continue For Hint("正在处理中,请稍候!", HintType.Red) Return Next '启动 - Dim Loader As New LoaderCombo(Of String)(PageVersionLeft.Version.Name & " 文件补全", DlClientFix(PageVersionLeft.Version, True, AssetsIndexExistsBehaviour.AlwaysDownload)) + Dim Loader As New LoaderCombo(Of String)(PageInstanceLeft.Instance.Name & " 文件补全", DlClientFix(PageInstanceLeft.Instance, True, AssetsIndexExistsBehaviour.AlwaysDownload)) Loader.OnStateChanged = Sub() Select Case Loader.State @@ -306,12 +305,12 @@ Hint(Loader.Name & "已取消!", HintType.Blue) End Select End Sub - Loader.Start(PageVersionLeft.Version.Name) + Loader.Start(PageInstanceLeft.Instance.Name) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() FrmMain.BtnExtraDownload.Ribble() Catch ex As Exception - Log(ex, "尝试补全文件失败(" & PageVersionLeft.Version.Name & ")", LogLevel.Msgbox) + Log(ex, "尝试补全文件失败(" & PageInstanceLeft.Instance.Name & ")", LogLevel.Msgbox) End Try End Sub @@ -320,29 +319,28 @@ Private Sub BtnManageDelete_Click(sender As Object, e As EventArgs) Handles BtnManageDelete.Click Try Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown - Dim IsHintIndie As Boolean = PageVersionLeft.Version.State <> McVersionState.Error AndAlso PageVersionLeft.Version.PathIndie <> PathMcFolder - Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {PageVersionLeft.Version.Name} 吗?" & + Dim IsHintIndie As Boolean = PageInstanceLeft.Instance.State <> McInstanceState.Error AndAlso PageInstanceLeft.Instance.PathIndie <> McFolderSelected + Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {PageInstanceLeft.Instance.Name} 吗?" & If(IsHintIndie, vbCrLf & "由于该版本开启了版本隔离,删除版本时该版本对应的存档、资源包、Mod 等文件也将被一并删除!", ""), "版本删除确认", , "取消",, IsHintIndie OrElse IsShiftPressed) Case 1 - IniClearCache(PageVersionLeft.Version.PathIndie & "options.txt") - IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini") + IniClearCache(PageInstanceLeft.Instance.PathIndie & "options.txt") + IniClearCache(PageInstanceLeft.Instance.PathVersion & "PCL\Setup.ini") If IsShiftPressed Then - DeleteDirectory(PageVersionLeft.Version.Path) - Hint("版本 " & PageVersionLeft.Version.Name & " 已永久删除!", HintType.Green) + DeleteDirectory(PageInstanceLeft.Instance.PathVersion) Else - FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) - Hint("版本 " & PageVersionLeft.Version.Name & " 已删除到回收站!", HintType.Green) + FileIO.FileSystem.DeleteDirectory(PageInstanceLeft.Instance.PathVersion, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) End If + Hint("已删除版本 " & PageInstanceLeft.Instance.Name & "!", HintType.Green) Case 2 Return End Select - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") FrmMain.PageBack() Catch ex As OperationCanceledException - Log(ex, "删除版本 " & PageVersionLeft.Version.Name & " 被主动取消") + Log(ex, "删除版本 " & PageInstanceLeft.Instance.Name & " 被主动取消") Catch ex As Exception - Log(ex, "删除版本 " & PageVersionLeft.Version.Name & " 失败", LogLevel.Msgbox) + Log(ex, "删除版本 " & PageInstanceLeft.Instance.Name & " 失败", LogLevel.Msgbox) End Try End Sub diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml similarity index 99% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml index 7c1e11d..0be84ec 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:System="clr-namespace:System;assembly=mscorlib" - mc:Ignorable="d" x:Class="PageVersionSetup" + mc:Ignorable="d" x:Class="PageInstanceSetup" PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.vb similarity index 82% rename from Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb rename to Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.vb index b22e277..95c67f0 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageInstance/PageInstanceSetup.xaml.vb @@ -1,4 +1,4 @@ -Public Class PageVersionSetup +Public Class PageInstanceSetup Private Shadows IsLoaded As Boolean = False @@ -27,41 +27,41 @@ Try '启动参数 - TextArgumentTitle.Text = Setup.Get("VersionArgumentTitle", Version:=PageVersionLeft.Version) - TextArgumentInfo.Text = Setup.Get("VersionArgumentInfo", Version:=PageVersionLeft.Version) - Dim _unused = PageVersionLeft.Version.PathIndie '触发自动判定 - ComboArgumentIndieV2.SelectedIndex = If(Setup.Get("VersionArgumentIndieV2", Version:=PageVersionLeft.Version), 0, 1) + TextArgumentTitle.Text = Setup.Get("VersionArgumentTitle", Instance:=PageInstanceLeft.Instance) + TextArgumentInfo.Text = Setup.Get("VersionArgumentInfo", Instance:=PageInstanceLeft.Instance) + Dim _unused = PageInstanceLeft.Instance.PathIndie '触发自动判定 + ComboArgumentIndieV2.SelectedIndex = If(Setup.Get("VersionArgumentIndieV2", Instance:=PageInstanceLeft.Instance), 0, 1) RefreshJavaComboBox() '游戏内存 - CType(FindName("RadioRamType" & Setup.Load("VersionRamType", Version:=PageVersionLeft.Version)), MyRadioBox).Checked = True - SliderRamCustom.Value = Setup.Get("VersionRamCustom", Version:=PageVersionLeft.Version) - ComboRamOptimize.SelectedIndex = Setup.Get("VersionRamOptimize", Version:=PageVersionLeft.Version) + CType(FindName("RadioRamType" & Setup.Load("VersionRamType", Instance:=PageInstanceLeft.Instance)), MyRadioBox).Checked = True + SliderRamCustom.Value = Setup.Get("VersionRamCustom", Instance:=PageInstanceLeft.Instance) + ComboRamOptimize.SelectedIndex = Setup.Get("VersionRamOptimize", Instance:=PageInstanceLeft.Instance) '服务器 - TextServerEnter.Text = Setup.Get("VersionServerEnter", Version:=PageVersionLeft.Version) - ComboServerLogin.SelectedIndex = Setup.Get("VersionServerLogin", Version:=PageVersionLeft.Version) + TextServerEnter.Text = Setup.Get("VersionServerEnter", Instance:=PageInstanceLeft.Instance) + ComboServerLogin.SelectedIndex = Setup.Get("VersionServerLogin", Instance:=PageInstanceLeft.Instance) ComboServerLoginLast = ComboServerLogin.SelectedIndex ServerLogin(ComboServerLogin.SelectedIndex) - TextServerNide.Text = Setup.Get("VersionServerNide", Version:=PageVersionLeft.Version) - TextServerAuthServer.Text = Setup.Get("VersionServerAuthServer", Version:=PageVersionLeft.Version) - TextServerAuthName.Text = Setup.Get("VersionServerAuthName", Version:=PageVersionLeft.Version) - TextServerAuthRegister.Text = Setup.Get("VersionServerAuthRegister", Version:=PageVersionLeft.Version) + TextServerNide.Text = Setup.Get("VersionServerNide", Instance:=PageInstanceLeft.Instance) + TextServerAuthServer.Text = Setup.Get("VersionServerAuthServer", Instance:=PageInstanceLeft.Instance) + TextServerAuthName.Text = Setup.Get("VersionServerAuthName", Instance:=PageInstanceLeft.Instance) + TextServerAuthRegister.Text = Setup.Get("VersionServerAuthRegister", Instance:=PageInstanceLeft.Instance) '高级设置 - TextAdvanceJvm.Text = Setup.Get("VersionAdvanceJvm", Version:=PageVersionLeft.Version) - TextAdvanceGame.Text = Setup.Get("VersionAdvanceGame", Version:=PageVersionLeft.Version) - TextAdvanceRun.Text = Setup.Get("VersionAdvanceRun", Version:=PageVersionLeft.Version) - CheckAdvanceRunWait.Checked = Setup.Get("VersionAdvanceRunWait", Version:=PageVersionLeft.Version) - If Setup.Get("VersionAdvanceAssets", Version:=PageVersionLeft.Version) = 2 Then + TextAdvanceJvm.Text = Setup.Get("VersionAdvanceJvm", Instance:=PageInstanceLeft.Instance) + TextAdvanceGame.Text = Setup.Get("VersionAdvanceGame", Instance:=PageInstanceLeft.Instance) + TextAdvanceRun.Text = Setup.Get("VersionAdvanceRun", Instance:=PageInstanceLeft.Instance) + CheckAdvanceRunWait.Checked = Setup.Get("VersionAdvanceRunWait", Instance:=PageInstanceLeft.Instance) + If Setup.Get("VersionAdvanceAssets", Instance:=PageInstanceLeft.Instance) = 2 Then Log("[Setup] 已迁移老版本的关闭文件校验设置") - Setup.Reset("VersionAdvanceAssets", Version:=PageVersionLeft.Version) - Setup.Set("VersionAdvanceAssetsV2", True, Version:=PageVersionLeft.Version) + Setup.Reset("VersionAdvanceAssets", Instance:=PageInstanceLeft.Instance) + Setup.Set("VersionAdvanceAssetsV2", True, Instance:=PageInstanceLeft.Instance) End If - CheckAdvanceAssetsV2.Checked = Setup.Get("VersionAdvanceAssetsV2", Version:=PageVersionLeft.Version) - CheckAdvanceJava.Checked = Setup.Get("VersionAdvanceJava", Version:=PageVersionLeft.Version) - CheckAdvanceDisableJLW.Checked = Setup.Get("VersionAdvanceDisableJLW", Version:=PageVersionLeft.Version) - CheckAdvanceDisableModUpdate.Checked = Setup.Get("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version) + CheckAdvanceAssetsV2.Checked = Setup.Get("VersionAdvanceAssetsV2", Instance:=PageInstanceLeft.Instance) + CheckAdvanceJava.Checked = Setup.Get("VersionAdvanceJava", Instance:=PageInstanceLeft.Instance) + CheckAdvanceDisableJLW.Checked = Setup.Get("VersionAdvanceDisableJLW", Instance:=PageInstanceLeft.Instance) + CheckAdvanceDisableModUpdate.Checked = Setup.Get("VersionAdvanceDisableModUpdate", Instance:=PageInstanceLeft.Instance) Catch ex As Exception Log(ex, "重载版本独立设置时出错", LogLevel.Feedback) @@ -72,29 +72,29 @@ Public Sub Reset() Try - Setup.Reset("VersionServerEnter", Version:=PageVersionLeft.Version) - Setup.Reset("VersionServerLogin", Version:=PageVersionLeft.Version) - Setup.Reset("VersionServerNide", Version:=PageVersionLeft.Version) - Setup.Reset("VersionServerAuthServer", Version:=PageVersionLeft.Version) - Setup.Reset("VersionServerAuthRegister", Version:=PageVersionLeft.Version) - Setup.Reset("VersionServerAuthName", Version:=PageVersionLeft.Version) - Setup.Reset("VersionArgumentTitle", Version:=PageVersionLeft.Version) - Setup.Reset("VersionArgumentInfo", Version:=PageVersionLeft.Version) - Setup.Reset("VersionArgumentIndieV2", Version:=PageVersionLeft.Version) - Setup.Reset("VersionRamType", Version:=PageVersionLeft.Version) - Setup.Reset("VersionRamCustom", Version:=PageVersionLeft.Version) - Setup.Reset("VersionRamOptimize", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceJvm", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceGame", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceAssets", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceAssetsV2", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceJava", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceRun", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceRunWait", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceDisableJLW", Version:=PageVersionLeft.Version) - Setup.Reset("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version) + Setup.Reset("VersionServerEnter", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionServerLogin", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionServerNide", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionServerAuthServer", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionServerAuthRegister", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionServerAuthName", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionArgumentTitle", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionArgumentInfo", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionArgumentIndieV2", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionRamType", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionRamCustom", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionRamOptimize", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceJvm", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceGame", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceAssets", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceAssetsV2", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceJava", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceRun", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceRunWait", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceDisableJLW", Instance:=PageInstanceLeft.Instance) + Setup.Reset("VersionAdvanceDisableModUpdate", Instance:=PageInstanceLeft.Instance) - Setup.Reset("VersionArgumentJavaSelect", Version:=PageVersionLeft.Version) + Setup.Reset("VersionArgumentJavaSelect", Instance:=PageInstanceLeft.Instance) JavaSearchLoader.Start(IsForceRestart:=True) Log("[Setup] 已初始化版本独立设置") @@ -108,27 +108,27 @@ '将控件改变路由到设置改变 Private Shared Sub RadioBoxChange(sender As MyRadioBox, e As Object) Handles RadioRamType0.Check, RadioRamType1.Check, RadioRamType2.Check - If AniControlEnabled = 0 Then Setup.Set(sender.Tag.ToString.Split("/")(0), Val(sender.Tag.ToString.Split("/")(1)), Version:=PageVersionLeft.Version) + If AniControlEnabled = 0 Then Setup.Set(sender.Tag.ToString.Split("/")(0), Val(sender.Tag.ToString.Split("/")(1)), Instance:=PageInstanceLeft.Instance) End Sub Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextServerEnter.ValidatedTextChanged, TextArgumentInfo.ValidatedTextChanged, TextAdvanceGame.ValidatedTextChanged, TextAdvanceJvm.ValidatedTextChanged, TextServerNide.ValidatedTextChanged, TextServerAuthName.ValidatedTextChanged, TextServerAuthRegister.ValidatedTextChanged, TextServerAuthServer.ValidatedTextChanged, TextArgumentTitle.ValidatedTextChanged, TextAdvanceRun.ValidatedTextChanged If AniControlEnabled = 0 Then '#3194,不能删减 / 'Dim HandledText As String = sender.Text 'If sender.Tag = "VersionServerAuthServer" OrElse sender.Tag = "VersionServerAuthRegister" Then HandledText = HandledText.TrimEnd("/") - Setup.Set(sender.Tag, sender.Text, Version:=PageVersionLeft.Version) + Setup.Set(sender.Tag, sender.Text, Instance:=PageInstanceLeft.Instance) End If End Sub Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderRamCustom.Change - If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value, Version:=PageVersionLeft.Version) + If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value, Instance:=PageInstanceLeft.Instance) End Sub Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboRamOptimize.SelectionChanged - If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex, Version:=PageVersionLeft.Version) + If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex, Instance:=PageInstanceLeft.Instance) End Sub Private Shared Sub CheckBoxLikeComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndieV2.SelectionChanged - If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex = 0, Version:=PageVersionLeft.Version) + If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex = 0, Instance:=PageInstanceLeft.Instance) End Sub Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckAdvanceAssetsV2.Change, CheckAdvanceJava.Change, CheckAdvanceDisableJLW.Change, CheckAdvanceDisableModUpdate.Change - If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked, Version:=PageVersionLeft.Version) + If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked, Instance:=PageInstanceLeft.Instance) End Sub #Region "游戏内存" @@ -142,9 +142,9 @@ ''' 刷新 UI 上的 RAM 显示。 ''' Public Sub RefreshRam(ShowAnim As Boolean) - If LabRamGame Is Nothing OrElse LabRamUsed Is Nothing OrElse FrmMain.PageCurrent <> FormMain.PageType.VersionSetup OrElse FrmVersionLeft.PageID <> FormMain.PageSubType.VersionSetup Then Return + If LabRamGame Is Nothing OrElse LabRamUsed Is Nothing OrElse FrmMain.PageCurrent <> FormMain.PageType.InstanceSetup OrElse FrmInstanceLeft.PageID <> FormMain.PageSubType.InstanceSetup Then Return '获取内存情况 - Dim RamGame As Double = Math.Round(GetRam(PageVersionLeft.Version), 5) + Dim RamGame As Double = Math.Round(GetRam(PageInstanceLeft.Instance), 5) Dim RamTotal As Double = Math.Round(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024 / 1024, 1) Dim RamAvailable As Double = Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024, 1) Dim RamGameActual As Double = Math.Round(Math.Min(RamGame, RamAvailable), 5) @@ -165,7 +165,7 @@ If(RamGame <> RamGameActual, " (可用 " & If(RamGameActual = Math.Floor(RamGameActual), RamGameActual & ".0", RamGameActual) & " GB)", "") LabRamUsed.Text = If(RamUsed = Math.Floor(RamUsed), RamUsed & ".0", RamUsed) & " GB" LabRamTotal.Text = " / " & If(RamTotal = Math.Floor(RamTotal), RamTotal & ".0", RamTotal) & " GB" - LabRamWarn.Visibility = If(RamGame = 1 AndAlso Not JavaIs64Bit(PageVersionLeft.Version) AndAlso Not Is32BitSystem AndAlso JavaList.Any, Visibility.Visible, Visibility.Collapsed) + LabRamWarn.Visibility = If(RamGame = 1 AndAlso Not JavaIs64Bit(PageInstanceLeft.Instance) AndAlso Not Is32BitSystem AndAlso JavaList.Any, Visibility.Visible, Visibility.Collapsed) If ShowAnim Then '宽度动画 AniStart({ @@ -269,10 +269,10 @@ ''' ''' 获取当前设置的 RAM 值。单位为 GB。 ''' - Public Shared Function GetRam(Version As McVersion, Optional Is32BitJava As Boolean? = Nothing) As Double + Public Shared Function GetRam(Instance As McInstance, Optional Is32BitJava As Boolean? = Nothing) As Double '跟随全局设置 - If Setup.Get("VersionRamType", Version:=Version) = 2 Then - Return PageSetupLaunch.GetRam(Version, True, Is32BitJava) + If Setup.Get("VersionRamType", Instance:=Instance) = 2 Then + Return PageSetupLaunch.GetRam(Instance, True, Is32BitJava) End If '------------------------------------------ @@ -281,7 +281,7 @@ '使用当前版本的设置 Dim RamGive As Double - If Setup.Get("VersionRamType", Version:=Version) = 0 Then + If Setup.Get("VersionRamType", Instance:=Instance) = 0 Then '自动配置 Dim RamAvailable As Double = Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024 * 10) / 10 '确定需求的内存值 @@ -289,16 +289,16 @@ Dim RamTarget1 As Double '估计能勉强带动了的内存 Dim RamTarget2 As Double '估计没啥问题了的内存 Dim RamTarget3 As Double '安装过多附加组件需要的内存 - If Version IsNot Nothing AndAlso Not Version.IsLoaded Then Version.Load() - If Version IsNot Nothing AndAlso Version.Modable Then + If Instance IsNot Nothing AndAlso Not Instance.IsLoaded Then Instance.Load() + If Instance IsNot Nothing AndAlso Instance.Modable Then '可安装 Mod 的版本 - Dim ModDir As New DirectoryInfo(Version.PathIndie & "mods\") + Dim ModDir As New DirectoryInfo(Instance.PathIndie & "mods\") Dim ModCount As Integer = If(ModDir.Exists, ModDir.GetFiles.Length, 0) RamMininum = 0.5 + ModCount / 150 RamTarget1 = 1.5 + ModCount / 90 RamTarget2 = 2.7 + ModCount / 50 RamTarget3 = 4.5 + ModCount / 25 - ElseIf Version IsNot Nothing AndAlso Version.Version.HasOptiFine Then + ElseIf Instance IsNot Nothing AndAlso Instance.Version.HasOptiFine Then 'OptiFine 版本 RamMininum = 0.5 RamTarget1 = 1.5 @@ -337,7 +337,7 @@ PreFin: RamGive = Math.Round(Math.Max(RamGive, RamMininum), 1) Else '手动配置 - Dim Value As Integer = Setup.Get("VersionRamCustom", Version:=Version) + Dim Value As Integer = Setup.Get("VersionRamCustom", Instance:=Instance) If Value <= 12 Then RamGive = Value * 0.1 + 0.3 ElseIf Value <= 25 Then @@ -349,7 +349,7 @@ PreFin: End If End If '若使用 32 位 Java,则限制为 1G - If If(Is32BitJava, Not JavaIs64Bit(PageVersionLeft.Version)) Then RamGive = Math.Min(1, RamGive) + If If(Is32BitJava, Not JavaIs64Bit(PageInstanceLeft.Instance)) Then RamGive = Math.Min(1, RamGive) Return RamGive End Function @@ -423,9 +423,9 @@ PreFin: ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "自动选择合适的 Java", .Tag = "自动选择"}) '更新列表 Dim SelectedItem As MyComboBoxItem = Nothing - Dim SelectedBySetup As String = Setup.Get("VersionArgumentJavaSelect", Version:=PageVersionLeft.Version) + Dim SelectedBySetup As String = Setup.Get("VersionArgumentJavaSelect", Instance:=PageInstanceLeft.Instance) Try - For Each Java In JavaList.Clone().OrderByDescending(Function(v) v.VersionCode) + For Each Java In JavaList.Clone().OrderByDescending(Function(v) v.MajorVersion) Dim ListItem = New MyComboBoxItem With {.Content = Java.ToString, .ToolTip = Java.PathFolder, .Tag = Java} ToolTipService.SetHorizontalOffset(ListItem, 400) ComboArgumentJava.Items.Add(ListItem) @@ -434,7 +434,7 @@ PreFin: If JavaEntry.FromJson(GetJson(SelectedBySetup)).PathFolder = Java.PathFolder Then SelectedItem = ListItem Next Catch ex As Exception - Setup.Set("VersionArgumentJavaSelect", "使用全局设置", Version:=PageVersionLeft.Version) + Setup.Set("VersionArgumentJavaSelect", "使用全局设置", Instance:=PageInstanceLeft.Instance) Log(ex, "更新版本设置 Java 下拉框失败", LogLevel.Feedback) End Try '更新选择项 @@ -469,15 +469,15 @@ PreFin: Dim SelectedJava = ComboArgumentJava.SelectedItem.Tag If "使用全局设置".Equals(SelectedJava) Then '选择 “自动” - Setup.Set("VersionArgumentJavaSelect", "使用全局设置", Version:=PageVersionLeft.Version) + Setup.Set("VersionArgumentJavaSelect", "使用全局设置", Instance:=PageInstanceLeft.Instance) Log("[Java] 修改版本 Java 选择设置:跟随全局设置") ElseIf "自动选择".Equals(SelectedJava) Then '选择 “自动” - Setup.Set("VersionArgumentJavaSelect", "", Version:=PageVersionLeft.Version) + Setup.Set("VersionArgumentJavaSelect", "", Instance:=PageInstanceLeft.Instance) Log("[Java] 修改版本 Java 选择设置:自动选择") Else '选择指定项 - Setup.Set("VersionArgumentJavaSelect", CType(SelectedJava.ToJson(), JObject).ToString(Newtonsoft.Json.Formatting.None), Version:=PageVersionLeft.Version) + Setup.Set("VersionArgumentJavaSelect", CType(SelectedJava.ToJson(), JObject).ToString(Newtonsoft.Json.Formatting.None), Instance:=PageInstanceLeft.Instance) Log("[Java] 修改版本 Java 选择设置:" & SelectedJava.ToString) End If RefreshRam(True) diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb index ec28fe9..8347be7 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb @@ -178,7 +178,7 @@ Try '更新缓存 WriteIni(PathTemp & "Cache\Skin\IndexMs.ini", Setup.Get("CacheMsV2Uuid"), SkinAddress) - Log(String.Format("[Skin] 已写入皮肤地址缓存 {0} -> {1}", Setup.Get("CacheMsV2Uuid"), SkinAddress)) + Log($"[Skin] 已写入皮肤地址缓存 {Setup.Get("CacheMsV2Uuid")} -> {SkinAddress}") '刷新控件 For Each SkinLoader In {PageLaunchLeft.SkinMs, PageLaunchLeft.SkinLegacy} SkinLoader.WaitForExit(IsForceRestart:=True) @@ -244,13 +244,13 @@ Retry: {"Cherry Blossom", "樱花披风"}, {"15th Anniversary", "15 周年纪念披风"}, {"Purple Heart", "紫色心形披风"}, {"Follower's", "追随者披风"}, {"MCC 15th Year", "MCC 15 周年披风"}, {"Minecraft Experience", "村民救援披风"}, {"Mojang Office", "Mojang 办公室披风"}, {"Home", "家园披风"}, {"Menace", "入侵披风"}, {"Yearn", "渴望披风"}, - {"Common", "普通披风"}, {"Pan", "薄煎饼披风"}, {"Founder's", "创始人披风"}, {"Copper", "铜披风"} + {"Common", "普通披风"}, {"Pan", "薄煎饼披风"}, {"Founder's", "创始人披风"}, {"Copper", "铜披风"}, {"Zombie Horse", "僵尸马披风"} } Dim SelectionControl As New List(Of IMyRadio) From {New MyRadioBox With {.Text = "无披风"}} For Each Cape In SkinData("capes") Dim CapeName As String = Cape("alias").ToString If CapeNames.ContainsKey(CapeName) Then CapeName = CapeNames(CapeName) - SelectionControl.Add(New MyRadioBox With {.Text = CapeName, .Checked = (Cape("state") = "ACTIVE")}) + SelectionControl.Add(New MyRadioBox With {.Text = CapeName, .Checked = Cape("state")?.ToString = "ACTIVE"}) Next SelId = MyMsgBoxSelect(SelectionControl, "选择披风", "确定", "取消") Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb index a1d5b7e..3348d63 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb @@ -14,7 +14,7 @@ AniControlEnabled += 1 '开始按钮 - AddHandler McVersionListLoader.LoadingStateChanged, AddressOf RefreshButtonsUI + AddHandler McInstanceListLoader.LoadingStateChanged, AddressOf RefreshButtonsUI AddHandler McFolderListLoader.LoadingStateChanged, AddressOf RefreshButtonsUI RefreshButtonsUI() @@ -37,18 +37,18 @@ McFolderListLoader.WaitForExit() End If '确认 Minecraft 文件夹存在 - PathMcFolder = Setup.Get("LaunchFolderSelect").ToString.Replace("$", Path) - If PathMcFolder = "" OrElse Not Directory.Exists(PathMcFolder) Then + McFolderSelected = Setup.Get("LaunchFolderSelect").ToString.Replace("$", Path) + If McFolderSelected = "" OrElse Not Directory.Exists(McFolderSelected) Then '无效的文件夹 - If PathMcFolder = "" Then + If McFolderSelected = "" Then Log("[Launch] 没有已储存的 Minecraft 文件夹") Else - Log("[Launch] Minecraft 文件夹无效,该文件夹已不存在:" & PathMcFolder, LogLevel.Debug) + Log("[Launch] Minecraft 文件夹无效,该文件夹已不存在:" & McFolderSelected, LogLevel.Debug) End If McFolderListLoader.WaitForExit(IsForceRestart:=True) - Setup.Set("LaunchFolderSelect", McFolderList(0).Path.Replace(Path, "$")) + Setup.Set("LaunchFolderSelect", McFolderList(0).Location.Replace(Path, "$")) End If - Log("[Launch] Minecraft 文件夹:" & PathMcFolder) + Log("[Launch] Minecraft 文件夹:" & McFolderSelected) If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(500, 3000)) '自动整合包安装 If PackInstallPath IsNot Nothing Then @@ -68,24 +68,24 @@ End If '确认 Minecraft 版本存在 Dim Selection As String = Setup.Get("LaunchVersionSelect") - Dim Version As McVersion = If(Selection = "", Nothing, New McVersion(Selection)) - If Version Is Nothing OrElse Not Version.Path.StartsWithF(PathMcFolder) OrElse Not Version.Check() Then + Dim Instance As McInstance = If(Selection = "", Nothing, New McInstance(Selection)) + If Instance Is Nothing OrElse Not Instance.PathVersion.StartsWithF(McFolderSelected) OrElse Not Instance.Check() Then '无效的版本 - Log("[Launch] 当前选择的 Minecraft 版本无效:" & If(Version Is Nothing, "null", Version.Path), If(IsNothing(Version), LogLevel.Normal, LogLevel.Debug)) - If Not McVersionListLoader.State = LoadState.Finished Then LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True) - If Not McVersionList.Any() OrElse McVersionList.First.Value(0).Logo.Contains("RedstoneBlock") Then - Version = Nothing + Log("[Launch] 当前选择的 Minecraft 版本无效:" & If(Instance Is Nothing, "null", Instance.PathVersion), If(IsNothing(Instance), LogLevel.Normal, LogLevel.Debug)) + If Not McInstanceListLoader.State = LoadState.Finished Then LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True) + If Not McInstanceList.Any() OrElse McInstanceList.First.Value(0).Logo.Contains("RedstoneBlock") Then + Instance = Nothing Setup.Set("LaunchVersionSelect", "") Log("[Launch] 无可用 Minecraft 版本") Else - Version = McVersionList.First.Value(0) - Setup.Set("LaunchVersionSelect", Version.Name) - Log("[Launch] 自动选择 Minecraft 版本:" & Version.Path) + Instance = McInstanceList.First.Value(0) + Setup.Set("LaunchVersionSelect", Instance.Name) + Log("[Launch] 自动选择 Minecraft 版本:" & Instance.PathVersion) End If End If RunInUi( Sub() - McVersionCurrent = Version '绕这一圈是为了避免 McVersionCheck 触发第二次版本改变 + McInstanceSelected = Instance '绕这一圈是为了避免 McVersionCheck 触发第二次版本改变 IsLoadFinished = True RefreshButtonsUI() RefreshPage(False, False) '有可能选择的版本变化了,需要重新刷新 @@ -119,7 +119,7 @@ LabLaunchingMethod.Text = "Authlib-Injector" End Select '初始化页面 - LabLaunchingName.Text = McVersionCurrent.Name + LabLaunchingName.Text = McInstanceSelected.Name LabLaunchingStage.Text = "初始化" LabLaunchingTitle.Text = If(CurrentLaunchOptions?.SaveBatch Is Nothing, "正在启动游戏", "正在导出启动脚本") LabLaunchingProgress.Text = "0.00 %" @@ -278,9 +278,9 @@ '获取页面的可用种类并回写缓存 Dim Type As PageType Dim LoginPageType As Integer - If McVersionCurrent IsNot Nothing Then - LoginPageType = Setup.Get("VersionServerLogin", Version:=McVersionCurrent) - '缓存当前版本的页面种类,下一次打开 McVersionCurrent 为空时才能加载出正确的页面 + If McInstanceSelected IsNot Nothing Then + LoginPageType = Setup.Get("VersionServerLogin", Instance:=McInstanceSelected) + '缓存当前版本的页面种类,下一次打开 McInstanceSelected 为空时才能加载出正确的页面 Setup.Set("LoginPageType", LoginPageType) Else LoginPageType = Setup.Get("LoginPageType") @@ -344,7 +344,7 @@ UnknownType: PanType.Visibility = Visibility.Collapsed PanTypeOne.Visibility = Visibility.Visible PathTypeOne.Data = (New GeometryConverter).ConvertFromString(Logo.IconButtonCard) - LabTypeOne.Text = If(McVersionCurrent Is Nothing, Setup.Get("CacheAuthServerName"), Setup.Get("VersionServerAuthName", Version:=McVersionCurrent)) + LabTypeOne.Text = If(McInstanceSelected Is Nothing, Setup.Get("CacheAuthServerName"), Setup.Get("VersionServerAuthName", Instance:=McInstanceSelected)) If LabTypeOne.Text = "" Then LabTypeOne.Text = "第三方登录" Case Else Log("[Control] 未知的登录页面:" & LoginPageType, LogLevel.Hint) @@ -591,7 +591,7 @@ Finish: '版本选择按钮 Private Sub BtnVersion_Click(sender As Object, e As EventArgs) Handles BtnVersion.Click If McLaunchLoader.State = LoadState.Loading Then Return - FrmMain.PageChange(FormMain.PageType.VersionSelect) + FrmMain.PageChange(FormMain.PageType.InstanceSelect) End Sub '启动按钮 Public Sub LaunchButtonClick() Handles BtnLaunch.Click @@ -615,15 +615,15 @@ Finish: End If End Sub Private BtnLaunchState As Integer = 0 - Private BtnLaunchVersion As McVersion = Nothing + Private BtnLaunchInstance As McInstance = Nothing Public Sub RefreshButtonsUI() Handles BtnLaunch.Loaded If Not BtnLaunch.IsLoaded Then Return '获取当前状态 Dim CurrentState As Integer - If (Not IsLoadFinished) OrElse McVersionListLoader.State = LoadState.Loading OrElse McFolderListLoader.State = LoadState.Loading Then + If (Not IsLoadFinished) OrElse McInstanceListLoader.State = LoadState.Loading OrElse McFolderListLoader.State = LoadState.Loading Then CurrentState = 0 Else - If McVersionCurrent Is Nothing Then + If McInstanceSelected Is Nothing Then If Setup.Get("UiHiddenPageDownload") AndAlso Not PageSetupUI.HiddenForceShow Then CurrentState = 1 Else @@ -635,8 +635,8 @@ Finish: End If '更新状态 If CurrentState = BtnLaunchState AndAlso - If(McVersionCurrent Is Nothing, "", McVersionCurrent.Path) = If(BtnLaunchVersion Is Nothing, "", BtnLaunchVersion.Path) Then GoTo ExitRefresh - BtnLaunchVersion = McVersionCurrent + If(McInstanceSelected Is Nothing, "", McInstanceSelected.PathVersion) = If(BtnLaunchInstance Is Nothing, "", BtnLaunchInstance.PathVersion) Then GoTo ExitRefresh + BtnLaunchInstance = McInstanceSelected BtnLaunchState = CurrentState Select Case CurrentState Case 0 @@ -661,11 +661,11 @@ Finish: FrmLaunchLeft.BtnVersion.IsEnabled = True FrmLaunchLeft.BtnMore.Visibility = Visibility.Collapsed Case 3 - Log("[Minecraft] 启动按钮:Minecraft 版本:" & McVersionCurrent.Path) + Log("[Minecraft] 启动按钮:Minecraft 版本:" & McInstanceSelected.PathVersion) FrmLaunchLeft.BtnLaunch.Text = "启动游戏" FrmLaunchLeft.BtnVersion.IsEnabled = True FrmLaunchLeft.BtnLaunch.IsEnabled = True - FrmLaunchLeft.LabVersion.Text = McVersionCurrent.Name + FrmLaunchLeft.LabVersion.Text = McInstanceSelected.Name 'FrmLaunchLeft.BtnMore.Visibility = Visibility.Visible '由功能隐藏设置修改 End Select ExitRefresh: @@ -694,12 +694,12 @@ ExitRefresh: '版本设置按钮 Private Sub BtnMore_Click(sender As Object, e As EventArgs) Handles BtnMore.Click If McLaunchLoader.State = LoadState.Loading Then Return - McVersionCurrent.Load() - PageVersionLeft.Version = McVersionCurrent - FrmMain.PageChange(FormMain.PageType.VersionSetup, 0) + McInstanceSelected.Load() + PageInstanceLeft.Instance = McInstanceSelected + FrmMain.PageChange(FormMain.PageType.InstanceSetup, 0) End Sub ''' - ''' 每 0.2s 执行一次,刷新启动的数据 UI 显示。 + ''' 每 0.1s 执行一次,刷新启动的数据 UI 显示。 ''' Public Sub LaunchingRefresh() Try @@ -719,20 +719,27 @@ ExitRefresh: Log(ex, "获取是否启动完成失败,可能是由于启动状态改变导致集合已修改") Return End Try + LabLaunchingTitle.Text = If(IsLaunched OrElse McLaunchLoaderReal.State = LoadState.Finished, + "已启动游戏", + If(CurrentLaunchOptions.SaveBatch Is Nothing, "正在启动游戏", "正在导出启动脚本")) If AniIsRun("Launch State Page") Then IsLaunched = False '等待页面切换动画完成 - '计算应显示的进度 + '更新进度 Dim ActualProgress = McLaunchLoaderReal.Progress - If ActualProgress >= ShowProgress Then ShowProgress += (ActualProgress - ShowProgress) * 0.2 + 0.005 '向实际进度靠一点 + If ActualProgress >= ShowProgress Then ShowProgress += (ActualProgress - ShowProgress) * 0.1 + 0.0025 '向实际进度靠一点 If ActualProgress <= ShowProgress Then ShowProgress = ActualProgress '原来或处理后变得比实际进度高,直接回退 If IsLaunched Then ShowProgress = 1 '如果已经完成了,就不卖关子了 - '文本 - LabLaunchingTitle.Text = If(IsLaunched, "已启动游戏", If(CurrentLaunchOptions.SaveBatch Is Nothing, "正在启动游戏", "正在导出启动脚本")) - LabLaunchingProgress.Text = StrFillNum(ShowProgress * 100, 2) & " %" + LabLaunchingProgress.Text = (ShowProgress * 100).ToString("0.00") & " %" + '更新下载速度 Dim HasLaunchDownloader As Boolean = False Try - For Each Loader In NetManager.Tasks - If Loader.RealParent IsNot Nothing AndAlso Loader.RealParent.Name = "Minecraft 启动" AndAlso Loader.State = LoadState.Loading Then HasLaunchDownloader = True - Next + If NetManager.Speed = 0 AndAlso LabLaunchingDownload.Visibility = Visibility.Collapsed Then + '可能只是在检查文件,没有实际下载 + HasLaunchDownloader = False + Else + For Each Loader In NetManager.Tasks + If Loader.RealParent IsNot Nothing AndAlso Loader.RealParent.Name = "Minecraft 启动" AndAlso Loader.State = LoadState.Loading Then HasLaunchDownloader = True + Next + End If Catch ex As Exception Log(ex, "获取 Minecraft 启动下载器失败,可能是因为启动被取消") HasLaunchDownloader = False @@ -740,41 +747,33 @@ ExitRefresh: LabLaunchingDownload.Text = GetString(NetManager.Speed) & "/s" '进度改变动画 Dim AnimList As New List(Of AniData) From { - AaGridLengthWidth(ProgressLaunchingFinished, ShowProgress - ProgressLaunchingFinished.Width.Value, 260,, New AniEaseOutFluent), - AaGridLengthWidth(ProgressLaunchingUnfinished, 1 - ShowProgress - ProgressLaunchingUnfinished.Width.Value, 260,, New AniEaseOutFluent) + AaGridLengthWidth(ProgressLaunchingFinished, ShowProgress - ProgressLaunchingFinished.Width.Value, 130,, New AniEaseOutFluent), + AaGridLengthWidth(ProgressLaunchingUnfinished, 1 - ShowProgress - ProgressLaunchingUnfinished.Width.Value, 130,, New AniEaseOutFluent) } - Dim IsDownloadStateChanged As Boolean = HasLaunchDownloader = (LabLaunchingDownload.Visibility = Visibility.Collapsed) - If IsDownloadStateChanged Then + If HasLaunchDownloader = (LabLaunchingDownload.Visibility = Visibility.Collapsed) Then 'IsDownloadStateChanged LabLaunchingDownload.Visibility = Visibility.Visible LabLaunchingDownloadLeft.Visibility = Visibility.Visible AnimList.AddRange({ - AaOpacity(LabLaunchingDownload, If(HasLaunchDownloader, 1, 0) - LabLaunchingDownload.Opacity, 100), - AaOpacity(LabLaunchingDownloadLeft, If(HasLaunchDownloader, 0.5, 0) - LabLaunchingDownloadLeft.Opacity, 100), - AaCode(Sub() - If Not HasLaunchDownloader Then - LabLaunchingDownload.Visibility = Visibility.Collapsed - LabLaunchingDownloadLeft.Visibility = Visibility.Collapsed - End If - End Sub, 110) - }) + AaOpacity(LabLaunchingDownload, If(HasLaunchDownloader, 1, 0) - LabLaunchingDownload.Opacity, 90), + AaOpacity(LabLaunchingDownloadLeft, If(HasLaunchDownloader, 0.5, 0) - LabLaunchingDownloadLeft.Opacity, 90), + AaCode( + Sub() + If Not HasLaunchDownloader Then + LabLaunchingDownload.Visibility = Visibility.Collapsed + LabLaunchingDownloadLeft.Visibility = Visibility.Collapsed + End If + End Sub, 110) + }) End If - Dim IsProgressStateChanged As Boolean = (Not IsLaunched) = (LabLaunchingProgress.Visibility = Visibility.Collapsed) - If IsProgressStateChanged Then + If (Not IsLaunched) = (LabLaunchingProgress.Visibility = Visibility.Collapsed) Then 'IsProgressStateChanged LabLaunchingProgress.Visibility = Visibility.Visible LabLaunchingProgressLeft.Visibility = Visibility.Visible - If IsLaunched Then - 'IsWidthAnimating = True - PanLaunchingHint.Visibility = Visibility.Visible - 'AniStop("Launching Info Width") - 'PanLaunchingInfo.Width = 260 - 'ActualUsedWidth = 260 - 'IsWidthAnimating = False - End If + If IsLaunched Then PanLaunchingHint.Visibility = Visibility.Visible AnimList.AddRange({ - AaOpacity(LabLaunchingProgress, If(Not IsLaunched, 1, 0) - LabLaunchingProgress.Opacity, 100), - AaOpacity(LabLaunchingProgressLeft, If(Not IsLaunched, 0.5, 0) - LabLaunchingProgressLeft.Opacity, 100), - AaOpacity(PanLaunchingHint, If(IsLaunched, 1, 0) - PanLaunchingHint.Opacity, 100) - }) + AaOpacity(LabLaunchingProgress, If(Not IsLaunched, 1, 0) - LabLaunchingProgress.Opacity, 90), + AaOpacity(LabLaunchingProgressLeft, If(Not IsLaunched, 0.5, 0) - LabLaunchingProgressLeft.Opacity, 90), + AaOpacity(PanLaunchingHint, If(IsLaunched, 1, 0) - PanLaunchingHint.Opacity, 90) + }) End If AniStart(AnimList, "Launching Progress") Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb index 84efb45..7cb2e4f 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb @@ -107,6 +107,12 @@ Public Class PageLaunchRight Case 13 Log("[Page] 主页预设:PCL 更新摘要") Url = "https://raw.gitcode.com/ENC_Euphony/PCL-AI-Summary-HomePage/raw/master/Custom.xaml" + Case 14 + Log("[Page] 主页预设:今日新闻热点") + Url = "https://pcl.wyc-w.top/index.xaml" + Case 15 + Log("[Page] 主页预设:Minecraft 芝士站") + Url = "https://www.xxag.top/mkss" End Select End Select '联网下载 diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuth.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuth.xaml.vb index e9ae125..843ac2d 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuth.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuth.xaml.vb @@ -27,7 +27,7 @@ ''' 获取当前页面的登录信息。 ''' Public Shared Function GetLoginData() As McLoginServer - Dim Server As String = If(IsNothing(McVersionCurrent), Setup.Get("CacheAuthServerServer"), Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent)) & "/authserver" + Dim Server As String = If(IsNothing(McInstanceSelected), Setup.Get("CacheAuthServerServer"), Setup.Get("VersionServerAuthServer", Instance:=McInstanceSelected)) & "/authserver" If FrmLoginAuth Is Nothing Then Return New McLoginServer(McLoginType.Auth) With {.Token = "Auth", .BaseUrl = Server, .UserName = "", .Password = "", .Description = "Authlib-Injector", .Type = McLoginType.Auth} Else @@ -71,15 +71,15 @@ End Sub Private Sub Btn_Click(sender As Object, e As EventArgs) Handles BtnLink.Click If BtnLink.Content = "注册账号" Then - OpenWebsite(If(McVersionCurrent IsNot Nothing, Setup.Get("VersionServerAuthRegister", Version:=McVersionCurrent), Setup.Get("CacheAuthServerRegister"))) + OpenWebsite(If(McInstanceSelected IsNot Nothing, Setup.Get("VersionServerAuthRegister", Instance:=McInstanceSelected), Setup.Get("CacheAuthServerRegister"))) Else - Dim Website As String = If(McVersionCurrent IsNot Nothing, Setup.Get("VersionServerAuthRegister", Version:=McVersionCurrent), Setup.Get("CacheAuthServerRegister")) + Dim Website As String = If(McInstanceSelected IsNot Nothing, Setup.Get("VersionServerAuthRegister", Instance:=McInstanceSelected), Setup.Get("CacheAuthServerRegister")) OpenWebsite(Website.Replace("/auth/register", "/auth/forgot")) End If End Sub '切换注册按钮可见性 Private Sub ReloadRegisterButton() Handles Me.Loaded - Dim Address As String = If(McVersionCurrent IsNot Nothing, Setup.Get("VersionServerAuthRegister", Version:=McVersionCurrent), Setup.Get("CacheAuthServerRegister")) + Dim Address As String = If(McInstanceSelected IsNot Nothing, Setup.Get("VersionServerAuthRegister", Instance:=McInstanceSelected), Setup.Get("CacheAuthServerRegister")) BtnLink.Visibility = If(String.IsNullOrEmpty(New ValidateHttp().Validate(Address)), Visibility.Visible, Visibility.Collapsed) End Sub diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb index a339893..216c9ad 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb @@ -21,7 +21,7 @@ ''' 获取当前页面的登录信息。 ''' Public Shared Function GetLoginData() As McLoginServer - Dim Server As String = If(IsNothing(McVersionCurrent), Setup.Get("CacheAuthServerServer"), Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent)) & "/authserver" + Dim Server As String = If(IsNothing(McInstanceSelected), Setup.Get("CacheAuthServerServer"), Setup.Get("VersionServerAuthServer", Instance:=McInstanceSelected)) & "/authserver" Return New McLoginServer(McLoginType.Auth) With {.Token = "Auth", .BaseUrl = Server, .UserName = Setup.Get("CacheAuthUsername"), .Password = Setup.Get("CacheAuthPass"), .Description = "Authlib-Injector", .Type = McLoginType.Auth} End Function @@ -78,7 +78,7 @@ End Sub Private Sub Skin_Click(sender As Object, e As MouseButtonEventArgs) Handles Skin.Click - Dim Address As String = If(McVersionCurrent IsNot Nothing, Setup.Get("VersionServerAuthRegister", Version:=McVersionCurrent), Setup.Get("CacheAuthServerRegister")) + Dim Address As String = If(McInstanceSelected IsNot Nothing, Setup.Get("VersionServerAuthRegister", Instance:=McInstanceSelected), Setup.Get("CacheAuthServerRegister")) If String.IsNullOrEmpty(New ValidateHttp().Validate(Address)) Then OpenWebsite(Address.Replace("/auth/register", "/user/closet")) End Sub diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb index 780d8d6..652330e 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb @@ -44,8 +44,8 @@ Public Class PageLoginLegacy Public Shared Function IsVaild(LoginData As McLoginLegacy) As String If LoginData.UserName.Trim = "" Then Return "玩家名不能为空!" If LoginData.UserName.Contains("""") Then Return "玩家名不能包含英文引号!" - If McVersionCurrent IsNot Nothing AndAlso - (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 20, 3)) AndAlso + If McInstanceSelected IsNot Nothing AndAlso + McInstanceSelected.Version.Vaild AndAlso McInstanceSelected.Version.Vanilla >= New Version(20, 0, 3) AndAlso LoginData.UserName.Trim.Length > 16 Then Return "自 1.20.3 起,玩家名至多只能包含 16 个字符!" End If diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNide.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNide.xaml.vb index 3b47833..1fda558 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNide.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNide.xaml.vb @@ -27,7 +27,7 @@ ''' 获取当前页面的登录信息。 ''' Public Shared Function GetLoginData() As McLoginServer - Dim Server As String = If(IsNothing(McVersionCurrent), Setup.Get("CacheNideServer"), Setup.Get("VersionServerNide", Version:=McVersionCurrent)) + Dim Server As String = If(IsNothing(McInstanceSelected), Setup.Get("CacheNideServer"), Setup.Get("VersionServerNide", Instance:=McInstanceSelected)) If FrmLoginNide Is Nothing Then Return New McLoginServer(McLoginType.Nide) With {.Token = "Nide", .UserName = "", .Password = "", .Description = "统一通行证", .Type = McLoginType.Nide, .BaseUrl = "https://auth.mc-user.com:233/" & Server & "/authserver"} Else @@ -71,7 +71,7 @@ End Sub Private Sub Btn_Click(sender As Object, e As EventArgs) Handles BtnLink.Click If BtnLink.Content = "注册账号" Then - OpenWebsite("https://login.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=McVersionCurrent) & "/register") + OpenWebsite("https://login.mc-user.com:233/" & Setup.Get("VersionServerNide", Instance:=McInstanceSelected) & "/register") Else OpenWebsite("https://login.mc-user.com:233/account/login") End If diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNideSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNideSkin.xaml.vb index 84fa773..5baeb15 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNideSkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNideSkin.xaml.vb @@ -21,7 +21,7 @@ ''' 获取当前页面的登录信息。 ''' Public Shared Function GetLoginData() As McLoginServer - Dim Server As String = If(IsNothing(McVersionCurrent), Setup.Get("CacheNideServer"), Setup.Get("VersionServerNide", Version:=McVersionCurrent)) + Dim Server As String = If(IsNothing(McInstanceSelected), Setup.Get("CacheNideServer"), Setup.Get("VersionServerNide", Instance:=McInstanceSelected)) Return New McLoginServer(McLoginType.Nide) With {.Token = "Nide", .UserName = Setup.Get("CacheNideUsername"), .Password = Setup.Get("CacheNidePass"), .Description = "统一通行证", .Type = McLoginType.Nide, .BaseUrl = "https://auth.mc-user.com:233/" & Server & "/authserver"} End Function @@ -54,7 +54,7 @@ End Sub Private Sub Skin_Click(sender As Object, e As MouseButtonEventArgs) Handles Skin.Click - OpenWebsite("https://login.mc-user.com:233/" & If(IsNothing(McVersionCurrent), Setup.Get("CacheNideServer"), Setup.Get("VersionServerNide", Version:=McVersionCurrent)) & "/skin") + OpenWebsite("https://login.mc-user.com:233/" & If(IsNothing(McInstanceSelected), Setup.Get("CacheNideServer"), Setup.Get("VersionServerNide", Instance:=McInstanceSelected)) & "/skin") End Sub End Class diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml index 6e5496f..018ef4d 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml @@ -202,6 +202,7 @@ + diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml.vb index 643d06b..8a7b1e4 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkMain.xaml.vb @@ -430,7 +430,7 @@ Public Class PageLinkMain If ServerConfig Is Nothing Then Throw New Exception("无法从服务器获取配置") '检查是否已禁用联机功能 Dim DisableReason = ServerConfig("Link")?("DisableReason2")?.ToString - If Not String.IsNullOrEmpty(DisableReason) Then Throw New Exception("$" & DisableReason) + If Not String.IsNullOrEmpty(DisableReason) Then Throw New Exception("$" & DisableReason) 'TODO: 可能不会显示错误原因? If CType(ServerConfig("Link"), JObject).ContainsKey("MinVersionCode") AndAlso VersionCode < ServerConfig("Link")("MinVersionCode").ToObject(Of Integer) Then Throw New Exception("$你的 PCL 版本太老了,请在更新 PCL 之后再联机!") @@ -523,7 +523,7 @@ Public Class PageLinkMain Dim ListenersPort As Integer = FreePorts(2) '它会占用【当前、当前 +1、当前 +2】共三个端口 '获取启动参数 Dim Arguments As String = ServerConfig("Link")("Argument") - Arguments += $" --network-name={NetworkName} --network-secret={NetworkSecret} --listeners {ListenersPort} --rpc-portal {RPCPort}" + Arguments += $" --network-name={NetworkName} --network-secret={NetworkSecret} --listeners {ListenersPort} --rpc-portal {RPCPort} --private-mode true" Dim HostName = If(IsServerSide, "Server-", "Client-") & RadixConvert(Math.Abs(Identify.GetHashCode), 10, 36) If IsServerSide Then Arguments += $" -i 10.114.114.114 --hostname={HostName} --tcp-whitelist={ServerPort} --udp-whitelist={ServerPort}" @@ -539,7 +539,6 @@ Public Class PageLinkMain For Each Peer As String In RawPeers Arguments += $" -p=""{Peer}""" Next - Arguments += " --private-mode true" '老好人模式现在莫得用:If Not Setup.Get("LinkShareMode") Then If Setup.Get("LinkLatencyMode") = 1 Then Arguments += " --latency-first" '启动进程 ProcessStart(Arguments) diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml index 8b75d08..2d7edce 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml @@ -114,567 +114,2521 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb index 5ecaa33..1389273 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb @@ -49,7 +49,7 @@ ''' ''' 勾选事件改变页面。 ''' - Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemAbout.Check, ItemHelp.Check, ItemTest.Check + Private Sub PageCheck(sender As FrameworkElement, e As RouteEventArgs) Handles ItemAbout.Check, ItemHelp.Check, ItemTest.Check '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0 '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False) If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag)) diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb index 4469cc2..2493b65 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb @@ -34,7 +34,7 @@ '添加控件 Dim ContMenu As ContextMenu = Nothing Select Case Folder.Type - Case McFolderType.Original + Case McFolder.Types.Original ContMenu = GetObjectFromXML( @@ -43,7 +43,7 @@ ) - Case McFolderType.RenamedOriginal + Case McFolder.Types.RenamedOriginal ContMenu = GetObjectFromXML( @@ -53,7 +53,7 @@ ) - Case McFolderType.Custom + Case McFolder.Types.Custom ContMenu = GetObjectFromXML( @@ -64,15 +64,15 @@ ) End Select - If (Folder.Type = McFolderType.Original OrElse Folder.Type = McFolderType.RenamedOriginal) AndAlso Folder.Path = Path & ".minecraft\" AndAlso McFolderList.Count = 1 Then CType(ContMenu.FindName("Delete"), MyMenuItem).Header = "清空" + If (Folder.Type = McFolder.Types.Original OrElse Folder.Type = McFolder.Types.RenamedOriginal) AndAlso Folder.Location = Path & ".minecraft\" AndAlso McFolderList.Count = 1 Then CType(ContMenu.FindName("Delete"), MyMenuItem).Header = "清空" '注册事件 - If Not Folder.Type = McFolderType.Original Then CType(ContMenu.FindName("Remove"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Remove_Click)) + If Not Folder.Type = McFolder.Types.Original Then CType(ContMenu.FindName("Remove"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Remove_Click)) CType(ContMenu.FindName("Open"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Open_Click)) CType(ContMenu.FindName("Delete"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Delete_Click)) CType(ContMenu.FindName("Rename"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Rename_Click)) CType(ContMenu.FindName("Refresh"), MyMenuItem).AddHandler(MyMenuItem.ClickEvent, New RoutedEventHandler(AddressOf FrmSelectLeft.Refresh_Click)) '构建框架与图表按钮 - Dim NewItem As New MyListItem With {.IsScaleAnimationEnabled = False, .Type = MyListItem.CheckType.RadioBox, .MinPaddingRight = 30, .Title = Folder.Name, .Info = Folder.Path, .Height = 40, .ContextMenu = ContMenu, .Tag = Folder} + Dim NewItem As New MyListItem With {.IsScaleAnimationEnabled = False, .Type = MyListItem.CheckType.RadioBox, .MinPaddingRight = 30, .Title = Folder.Name, .Info = Folder.Location, .Height = 40, .ContextMenu = ContMenu, .Tag = Folder} AddHandler NewItem.Changed, AddressOf FrmSelectLeft.Folder_Change Dim NewIconButton As New MyIconButton With {.Logo = Logo.IconButtonSetup, .LogoScale = 1.1} AddHandler NewIconButton.Click, Sub(sender, e) @@ -81,7 +81,7 @@ End Sub NewItem.Buttons = {NewIconButton} FrmSelectLeft.PanList.Children.Add(NewItem) - Log("[Minecraft] 有效的 Minecraft 文件夹:" & Folder.Name & " > " & Folder.Path) + Log("[Minecraft] 有效的 Minecraft 文件夹:" & Folder.Name & " > " & Folder.Location) Next '标题文本 @@ -125,7 +125,7 @@ '确认勾选状态 For i = 0 To McFolderList.Count - 1 - If McFolderList(i).Path = PathMcFolder Then + If McFolderList(i).Location = McFolderSelected Then CType(FrmSelectLeft.PanList.Children(i + 1), MyListItem).Checked = True '去掉第一个标题 Return End If @@ -133,14 +133,14 @@ If Not McFolderList.Any() Then Throw New ArgumentNullException("没有可用的 Minecraft 文件夹") Else - Setup.Set("LaunchFolderSelect", McFolderList(0).Path.Replace(Path, "$")) + Setup.Set("LaunchFolderSelect", McFolderList(0).Location.Replace(Path, "$")) CType(FrmSelectLeft.PanList.Children(1), MyListItem).Checked = True End If Catch ex As Exception Log(ex, "构建 Minecraft 文件夹列表 UI 出错", LogLevel.Feedback) Finally - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.RunOnUpdated, MaxDepth:=1, ExtraPath:="versions\") '刷新版本列表 + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.RunOnUpdated, MaxDepth:=1, ExtraPath:="versions\") '刷新版本列表 End Try End Sub Private McFolderListLast As List(Of McFolder) @@ -232,20 +232,20 @@ Dim ModFolder As New DirectoryInfo(FolderPath & "mods\") If Not (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Count >= 3) Then Return '2. 版本数较少,可能为整合包 - Dim VersionFolder As New DirectoryInfo(FolderPath & "versions\") - If Not (VersionFolder.Exists AndAlso VersionFolder.EnumerateDirectories.Count <= 3) Then Return + Dim VersionFolderInfo As New DirectoryInfo(FolderPath & "versions\") + If Not (VersionFolderInfo.Exists AndAlso VersionFolderInfo.EnumerateDirectories.Count <= 3) Then Return '3. 能够找到可安装 Mod 的版本 - For Each VersionPath In VersionFolder.EnumerateDirectories - Dim Version As New McVersion(VersionPath.FullName) - Version.Load() - If Not Version.Modable Then Continue For + For Each VersionFolder In VersionFolderInfo.EnumerateDirectories + Dim Instance As New McInstance(VersionFolder.FullName) + Instance.Load() + If Not Instance.Modable Then Continue For '4. 该版本的隔离文件夹下不存在 mods - Dim ModIndieFolder As New DirectoryInfo(Version.Path & "mods\") + Dim ModIndieFolder As New DirectoryInfo(Instance.PathVersion & "mods\") If ModIndieFolder.Exists AndAlso ModIndieFolder.EnumerateFiles.Any Then Return '满足以上全部条件则视为根目录整合包 - Setup.Set("VersionArgumentIndie", 2, Version:=Version) - Setup.Set("VersionArgumentIndieV2", False, Version:=Version) - Log("[Setup] 已自动关闭单版本隔离:" & Version.Name, LogLevel.Debug) + Setup.Set("VersionArgumentIndie", 2, Instance:=Instance) + Setup.Set("VersionArgumentIndieV2", False, Instance:=Instance) + Log("[Setup] 已自动关闭单版本隔离:" & Instance.Name, LogLevel.Debug) Next Catch ex As Exception Log(ex, "向文件夹列表中添加新文件夹失败", LogLevel.Feedback) @@ -276,14 +276,14 @@ Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag '若为 “移除”,则提醒是否删除 PCL 的配置文件 - If Folder.Type = McFolderType.Custom Then + If Folder.Type = McFolder.Types.Custom Then Select Case MyMsgBox("是否需要清理 PCL 在该文件夹中的配置文件?" & vbCrLf & "这包括各个版本的独立设置(如自定义图标、第三方登录配置)等,对游戏本身没有影响。", "配置文件清理", "删除", "保留", "取消") Case 1 '删除配置文件 - If File.Exists(Folder.Path & "PCL.ini") Then File.Delete(Folder.Path & "PCL.ini") - If Directory.Exists(Folder.Path & "versions\") Then - For Each Version In New DirectoryInfo(Folder.Path & "versions\").EnumerateDirectories - If Directory.Exists(Version.FullName & "\PCL\") Then Directory.Delete(Version.FullName & "\PCL\", True) + If File.Exists(Folder.Location & "PCL.ini") Then File.Delete(Folder.Location & "PCL.ini") + If Directory.Exists(Folder.Location & "versions\") Then + For Each InstanceInfo In New DirectoryInfo(Folder.Location & "versions\").EnumerateDirectories + If Directory.Exists(InstanceInfo.FullName & "\PCL\") Then Directory.Delete(InstanceInfo.FullName & "\PCL\", True) Next End If Case 2 @@ -299,7 +299,7 @@ Dim Name As String = "" For i = 0 To Folders.Count - 1 If Folders(i) = "" Then Exit For - If Folders(i).ToString.EndsWith(Folder.Path) Then + If Folders(i).ToString.EndsWith(Folder.Location) Then Name = Folders(i).ToString.BeforeFirst(">") Folders.RemoveAt(i) Exit For @@ -307,7 +307,7 @@ Next '保存 Setup.Set("LaunchFolders", If(Not Folders.Any(), "", Join(Folders.ToArray, "|"))) - Hint(If(Folder.Type = McFolderType.Custom, "文件夹 " & Name & " 已从列表中移除!", "文件夹名称已复原!"), HintType.Green) + Hint(If(Folder.Type = McFolder.Types.Custom, "文件夹 " & Name & " 已从列表中移除!", "文件夹名称已复原!"), HintType.Green) McFolderListLoader.Start(IsForceRestart:=True) Catch ex As Exception @@ -316,15 +316,15 @@ End Sub 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 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 + Dim DeleteText As String = If((Folder.Type = McFolder.Types.Original OrElse Folder.Type = McFolder.Types.RenamedOriginal) AndAlso Folder.Location = Path & ".minecraft\" AndAlso McFolderList.Count = 1, "清空", "删除") + If MyMsgBox("你确定要" & DeleteText & "这个文件夹吗?" & vbCrLf & "目标文件夹:" & Folder.Location & vbCrLf & vbCrLf & "这会导致该文件夹中的所有存档与其他文件永久丢失,且不可恢复!", "删除警告", "取消", "确认", "取消") <> 2 Then Return + If MyMsgBox("如果你在该文件夹中存放了除 MC 以外的其他文件,这些文件也会被一同删除!" & vbCrLf & "继续删除会导致该文件夹中的所有文件永久丢失,请在仔细确认后再继续!" & vbCrLf & "目标文件夹:" & Folder.Location & vbCrLf & vbCrLf & "这是最后一次警告!", "删除警告", "确认" & DeleteText, "取消", IsWarn:=True) <> 1 Then Return '移出列表 - If Folder.Type = McFolderType.Custom Then + If Folder.Type = McFolder.Types.Custom Then Dim Folders As New List(Of String)(Setup.Get("LaunchFolders").ToString.Split("|")) For i = 0 To Folders.Count - 1 If Folders(i) = "" Then Exit For - If Folders(i).ToString.EndsWith(Folder.Path) Then + If Folders(i).ToString.EndsWith(Folder.Location) Then 'Name = Folders(i).ToString.Before(">") Folders.RemoveAt(i) Exit For @@ -337,8 +337,8 @@ '删除文件夹 Try Hint("正在" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Blue) - DeleteDirectory(Folder.Path) - If DeleteText = "清空" Then Directory.CreateDirectory(Folder.Path) + DeleteDirectory(Folder.Location) + If DeleteText = "清空" Then Directory.CreateDirectory(Folder.Location) Hint("已" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Green) Catch ex As Exception Log(ex, DeleteText & "文件夹 " & Folder.Name & " 失败", LogLevel.Hint) @@ -353,14 +353,14 @@ End Sub Public Sub Refresh_Click(sender As Object, e As RoutedEventArgs) Dim Data As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag - RefreshCurrent(Data.Path) + RefreshCurrent(Data.Location) End Sub Public Sub RefreshCurrent() Implements IRefreshable.Refresh - RefreshCurrent(PathMcFolder) + RefreshCurrent(McFolderSelected) End Sub Public Shared Sub RefreshCurrent(Folder As String) - WriteIni(Folder & "PCL.ini", "VersionCache", "") '删除缓存以强制要求下一次加载时更新列表 - If Folder = PathMcFolder Then LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + WriteIni(Folder & "PCL.ini", "InstanceCache", "") '删除缓存以强制要求下一次加载时更新列表 + If Folder = McFolderSelected Then LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End Sub Public Sub Rename_Click(sender As Object, e As RoutedEventArgs) Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag @@ -376,19 +376,19 @@ For i = 0 To Folders.Count - 1 Dim FolderCurrent As String = Folders(i) If FolderCurrent = "" Then Continue For - If FolderCurrent.Split(">")(1) = Folder.Path Then + If FolderCurrent.Split(">")(1) = Folder.Location Then IsAdded = True If FolderCurrent.Split(">")(0) = NewName Then '名称未修改 Return Else - Folders(i) = NewName & ">" & Folder.Path + Folders(i) = NewName & ">" & Folder.Location End If Exit For End If Next '如果没有添加过,则添加进去(因为修改了默认项的名称) - If Not IsAdded Then Folders.Add(NewName & ">" & Folder.Path) + If Not IsAdded Then Folders.Add(NewName & ">" & Folder.Location) Hint("文件夹名称已更新为 " & NewName & " !", HintType.Green) '保存 Setup.Set("LaunchFolders", Join(Folders.ToArray, "|")) @@ -408,9 +408,9 @@ Return End If '更换 - Setup.Set("LaunchFolderSelect", CType(sender.Tag, McFolder).Path.Replace(Path, "$")) + Setup.Set("LaunchFolderSelect", CType(sender.Tag, McFolder).Location.Replace(Path, "$")) McFolderListLoader.Start(IsForceRestart:=True) - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.RunOnUpdated, MaxDepth:=1, ExtraPath:="versions\") '刷新版本列表 + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.RunOnUpdated, MaxDepth:=1, ExtraPath:="versions\") '刷新版本列表 End Sub End Class diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb index 16bd2f8..8b9e264 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb @@ -2,15 +2,15 @@ '窗口基础 Private Sub PageSelectRight_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.RunOnUpdated, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.RunOnUpdated, MaxDepth:=1, ExtraPath:="versions\") PanBack.ScrollToHome() End Sub Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanAllBack, Nothing, McVersionListLoader, AddressOf McVersionListUI, AutoRun:=False) + PageLoaderInit(Load, PanLoad, PanAllBack, Nothing, McInstanceListLoader, AddressOf McInstanceListUI, AutoRun:=False) End Sub Private Sub Load_Click(sender As Object, e As MouseButtonEventArgs) Handles Load.Click - If McVersionListLoader.State = LoadState.Failed Then - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + If McInstanceListLoader.State = LoadState.Failed Then + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End If End Sub @@ -22,30 +22,29 @@ #Region "结果 UI 化" - Private Sub McVersionListUI(Loader As LoaderTask(Of String, Integer)) + Private Sub McInstanceListUI(Loader As LoaderTask(Of String, Integer)) Try - Dim Path As String = Loader.Input '加载 UI PanMain.Children.Clear() - For Each Card As KeyValuePair(Of McVersionCardType, List(Of McVersion)) In McVersionList.ToArray + For Each Card As KeyValuePair(Of McInstanceCardType, List(Of McInstance)) In McInstanceList.ToArray '确认是否为隐藏版本显示状态 - If Card.Key = McVersionCardType.Hidden Xor ShowHidden Then Continue For + If Card.Key = McInstanceCardType.Hidden Xor ShowHidden Then Continue For #Region "确认卡片名称" Dim CardName As String = "" Select Case Card.Key - Case McVersionCardType.OriginalLike + Case McInstanceCardType.OriginalLike CardName = "常规版本" - Case McVersionCardType.API + Case McInstanceCardType.API Dim IsForgeExists As Boolean = False Dim IsNeoForgeExists As Boolean = False Dim IsFabricExists As Boolean = False Dim IsLiteExists As Boolean = False - For Each Version As McVersion In Card.Value - If Version.Version.HasFabric Then IsFabricExists = True - If Version.Version.HasLiteLoader Then IsLiteExists = True - If Version.Version.HasForge Then IsForgeExists = True - If Version.Version.HasNeoForge Then IsNeoForgeExists = True + For Each Instance As McInstance In Card.Value + If Instance.Version.HasFabric Then IsFabricExists = True + If Instance.Version.HasLiteLoader Then IsLiteExists = True + If Instance.Version.HasForge Then IsForgeExists = True + If Instance.Version.HasNeoForge Then IsNeoForgeExists = True Next If If(IsLiteExists, 1, 0) + If(IsForgeExists, 1, 0) + If(IsFabricExists, 1, 0) + If(IsNeoForgeExists, 1, 0) > 1 Then CardName = "可安装 Mod" @@ -58,15 +57,15 @@ Else CardName = "Fabric 版本" End If - Case McVersionCardType.Error + Case McInstanceCardType.Error CardName = "错误的版本" - Case McVersionCardType.Hidden + Case McInstanceCardType.Hidden CardName = "隐藏的版本" - Case McVersionCardType.Rubbish + Case McInstanceCardType.Rubbish CardName = "不常用版本" - Case McVersionCardType.Star + Case McInstanceCardType.Star CardName = "收藏夹" - Case McVersionCardType.Fool + Case McInstanceCardType.Fool CardName = "愚人节版本" Case Else Throw New ArgumentException("未知的卡片种类(" & Card.Key & ")") @@ -80,7 +79,7 @@ NewCard.SwapControl = NewStack PanMain.Children.Add(NewCard) '确定卡片是否展开 - If Card.Key = McVersionCardType.Rubbish OrElse Card.Key = McVersionCardType.Error OrElse Card.Key = McVersionCardType.Fool Then + If Card.Key = McInstanceCardType.Rubbish OrElse Card.Key = McInstanceCardType.Error OrElse Card.Key = McInstanceCardType.Fool Then NewCard.IsSwapped = True Else MyCard.StackInstall(NewStack, 0, CardTitle) @@ -114,13 +113,13 @@ Log(ex, "将版本列表转换显示时失败", LogLevel.Feedback) End Try End Sub - Public Shared Sub McVersionListContent(sender As MyListItem, e As EventArgs) - Dim Version As McVersion = sender.Tag + Public Shared Sub McInstanceListContent(sender As MyListItem, e As EventArgs) + Dim Instance As McInstance = sender.Tag '注册点击事件 AddHandler sender.Click, AddressOf Item_Click '图标按钮 Dim BtnStar As New MyIconButton - If Version.IsStar Then + If Instance.IsStar Then BtnStar.ToolTip = "取消收藏" ToolTipService.SetPlacement(BtnStar, Primitives.PlacementMode.Center) ToolTipService.SetVerticalOffset(BtnStar, 30) @@ -136,17 +135,17 @@ BtnStar.Logo = Logo.IconButtonLikeLine End If AddHandler BtnStar.Click, Sub() - WriteIni(Version.Path & "PCL\Setup.ini", "IsStar", Not Version.IsStar) - McVersionListForceRefresh = True - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + WriteIni(Instance.PathVersion & "PCL\Setup.ini", "IsStar", Not Instance.IsStar) + McInstanceListForceRefresh = True + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End Sub Dim BtnDel As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonDelete} BtnDel.ToolTip = "删除" ToolTipService.SetPlacement(BtnDel, Primitives.PlacementMode.Center) ToolTipService.SetVerticalOffset(BtnDel, 30) ToolTipService.SetHorizontalOffset(BtnDel, 2) - AddHandler BtnDel.Click, Sub() DeleteVersion(sender, Version) - If Version.State <> McVersionState.Error Then + AddHandler BtnDel.Click, Sub() DeleteInstance(sender, Instance) + If Instance.State <> McInstanceState.Error Then Dim BtnCont As New MyIconButton With {.LogoScale = 1.1, .Logo = Logo.IconButtonSetup} BtnCont.ToolTip = "设置" ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center) @@ -154,13 +153,13 @@ ToolTipService.SetHorizontalOffset(BtnCont, 2) AddHandler BtnCont.Click, Sub() - PageVersionLeft.Version = Version - FrmMain.PageChange(FormMain.PageType.VersionSetup, 0) + PageInstanceLeft.Instance = Instance + FrmMain.PageChange(FormMain.PageType.InstanceSetup, 0) End Sub AddHandler sender.MouseRightButtonUp, Sub() - PageVersionLeft.Version = Version - FrmMain.PageChange(FormMain.PageType.VersionSetup, 0) + PageInstanceLeft.Instance = Instance + FrmMain.PageChange(FormMain.PageType.InstanceSetup, 0) End Sub sender.Buttons = {BtnStar, BtnDel, BtnCont} Else @@ -169,8 +168,8 @@ ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center) ToolTipService.SetVerticalOffset(BtnCont, 30) ToolTipService.SetHorizontalOffset(BtnCont, 2) - AddHandler BtnCont.Click, Sub() PageVersionOverall.OpenVersionFolder(Version) - AddHandler sender.MouseRightButtonUp, Sub() PageVersionOverall.OpenVersionFolder(Version) + AddHandler BtnCont.Click, Sub() PageInstanceOverall.OpenInstanceFolder(Instance) + AddHandler sender.MouseRightButtonUp, Sub() PageInstanceOverall.OpenInstanceFolder(Instance) sender.Buttons = {BtnStar, BtnDel, BtnCont} End If End Sub @@ -181,15 +180,15 @@ '点击选项 Public Shared Sub Item_Click(sender As MyListItem, e As EventArgs) - Dim Version As McVersion = sender.Tag - If New McVersion(Version.Path).Check Then + Dim Instance As McInstance = sender.Tag + If New McInstance(Instance.PathVersion).Check Then '正常版本 - McVersionCurrent = Version - Setup.Set("LaunchVersionSelect", McVersionCurrent.Name) + McInstanceSelected = Instance + Setup.Set("LaunchVersionSelect", McInstanceSelected.Name) FrmMain.PageBack() Else '错误版本 - PageVersionOverall.OpenVersionFolder(Version) + PageInstanceOverall.OpenInstanceFolder(Instance) End If End Sub @@ -197,29 +196,28 @@ FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall) End Sub - '修改此代码时,同时修改 PageVersionOverall 中的代码 - Public Shared Sub DeleteVersion(Item As MyListItem, Version As McVersion) + '修改此代码时,同时修改 PageInstanceOverall 中的代码 + Public Shared Sub DeleteInstance(Item As MyListItem, Instance As McInstance) Try Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown - Dim IsHintIndie As Boolean = Version.State <> McVersionState.Error AndAlso Version.PathIndie <> PathMcFolder - Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {Version.Name} 吗?" & + Dim IsHintIndie As Boolean = Instance.State <> McInstanceState.Error AndAlso Instance.PathIndie <> McFolderSelected + Select Case MyMsgBox($"你确定要{If(IsShiftPressed, "永久", "")}删除版本 {Instance.Name} 吗?" & If(IsHintIndie, vbCrLf & "由于该版本开启了版本隔离,删除版本时该版本对应的存档、资源包、Mod 等文件也将被一并删除!", ""), "版本删除确认", , "取消",, True) Case 1 - IniClearCache(Version.PathIndie & "options.txt") - IniClearCache(Version.Path & "PCL\Setup.ini") + IniClearCache(Instance.PathIndie & "options.txt") + IniClearCache(Instance.PathVersion & "PCL\Setup.ini") If IsShiftPressed Then - DeleteDirectory(Version.Path) - Hint("版本 " & Version.Name & " 已永久删除!", HintType.Green) + DeleteDirectory(Instance.PathVersion) Else - FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) - Hint("版本 " & Version.Name & " 已删除!", HintType.Green) + FileIO.FileSystem.DeleteDirectory(Instance.PathVersion, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) End If + Hint("版本 " & Instance.Name & " 已删除!", HintType.Green) Case 2 Return End Select '从 UI 中移除 - If Version.DisplayType = McVersionCardType.Hidden OrElse Not Version.IsStar Then + If Instance.DisplayType = McInstanceCardType.Hidden OrElse Not Instance.IsStar Then '仅出现在当前卡片 Dim Parent As StackPanel = Item.Parent If Parent.Children.Count > 2 Then '当前的项目与一个占位符 @@ -227,23 +225,23 @@ Dim Card As MyCard = Parent.Parent Card.Title = Card.Title.Replace(Parent.Children.Count - 1, Parent.Children.Count - 2) '有一个占位符 Parent.Children.Remove(Item) - If McVersionCurrent IsNot Nothing AndAlso Version.Path = McVersionCurrent.Path Then + If McInstanceSelected IsNot Nothing AndAlso Instance.PathVersion = McInstanceSelected.PathVersion Then '删除当前版本就更改选择 - McVersionCurrent = CType(Parent.Children(0), MyListItem).Tag + McInstanceSelected = MyVirtualizingElement.TryInit(Parent.Children(0)).Tag End If - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.UpdateOnly, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.UpdateOnly, MaxDepth:=1, ExtraPath:="versions\") Else '删除后没剩了 - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End If Else '同时出现在当前卡片与收藏夹 - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End If Catch ex As OperationCanceledException - Log(ex, "删除版本 " & Version.Name & " 被主动取消") + Log(ex, "删除版本 " & Instance.Name & " 被主动取消") Catch ex As Exception - Log(ex, "删除版本 " & Version.Name & " 失败", LogLevel.Msgbox) + Log(ex, "删除版本 " & Instance.Name & " 失败", LogLevel.Msgbox) End Try End Sub diff --git a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb index a4f29ab..0377692 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb @@ -1,9 +1,6 @@ Public Class ModSetup + Private Const VersionSetup As Integer = 1 - ''' - ''' 设置的更新号。 - ''' - Public Const VersionSetup As Integer = 1 ''' ''' 设置列表。 ''' @@ -43,6 +40,7 @@ {"SystemSystemUpdate", New SetupEntry(0)}, {"SystemSystemActivity", New SetupEntry(0)}, {"SystemSystemTelemetry", New SetupEntry(True, Source:=SetupSource.Registry)}, + {"CacheDrops", New SetupEntry("", Source:=SetupSource.Registry)}, {"CacheConfig", New SetupEntry(0, Source:=SetupSource.Registry)}, {"CacheExportConfig", New SetupEntry("", Source:=SetupSource.Registry)}, {"CacheSavedPageUrl", New SetupEntry("", Source:=SetupSource.Registry)}, @@ -112,7 +110,6 @@ {"LaunchRamType", New SetupEntry(0)}, {"LaunchRamCustom", New SetupEntry(15)}, {"LinkLastAutoJoinInviteCode", New SetupEntry("", Source:=SetupSource.Registry)}, - {"LinkShareMode", New SetupEntry(True, Source:=SetupSource.Registry)}, {"LinkLatencyMode", New SetupEntry(0, Source:=SetupSource.Registry)}, {"LinkCustomPeer", New SetupEntry("")}, {"LinkEasyTierVersion", New SetupEntry(-1, Source:=SetupSource.Registry)}, @@ -174,29 +171,29 @@ {"UiHiddenOtherVote", New SetupEntry(False)}, {"UiHiddenOtherAbout", New SetupEntry(False)}, {"UiHiddenOtherTest", New SetupEntry(False)}, - {"VersionAdvanceJvm", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionAdvanceGame", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionAdvanceAssets", New SetupEntry(0, Source:=SetupSource.Version)}, - {"VersionAdvanceAssetsV2", New SetupEntry(False, Source:=SetupSource.Version)}, - {"VersionAdvanceJava", New SetupEntry(False, Source:=SetupSource.Version)}, - {"VersionAdvanceRun", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionAdvanceRunWait", New SetupEntry(True, Source:=SetupSource.Version)}, - {"VersionAdvanceDisableJLW", New SetupEntry(False, Source:=SetupSource.Version)}, - {"VersionAdvanceDisableModUpdate", New SetupEntry(False, Source:=SetupSource.Version)}, - {"VersionRamType", New SetupEntry(2, Source:=SetupSource.Version)}, - {"VersionRamCustom", New SetupEntry(15, Source:=SetupSource.Version)}, - {"VersionRamOptimize", New SetupEntry(0, Source:=SetupSource.Version)}, - {"VersionArgumentTitle", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionArgumentInfo", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionArgumentIndie", New SetupEntry(-1, Source:=SetupSource.Version)}, - {"VersionArgumentIndieV2", New SetupEntry(False, Source:=SetupSource.Version)}, - {"VersionArgumentJavaSelect", New SetupEntry("使用全局设置", Source:=SetupSource.Version)}, - {"VersionServerEnter", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionServerLogin", New SetupEntry(0, Source:=SetupSource.Version)}, - {"VersionServerNide", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionServerAuthRegister", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionServerAuthName", New SetupEntry("", Source:=SetupSource.Version)}, - {"VersionServerAuthServer", New SetupEntry("", Source:=SetupSource.Version)} + {"VersionAdvanceJvm", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionAdvanceGame", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionAdvanceAssets", New SetupEntry(0, Source:=SetupSource.Instance)}, + {"VersionAdvanceAssetsV2", New SetupEntry(False, Source:=SetupSource.Instance)}, + {"VersionAdvanceJava", New SetupEntry(False, Source:=SetupSource.Instance)}, + {"VersionAdvanceRun", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionAdvanceRunWait", New SetupEntry(True, Source:=SetupSource.Instance)}, + {"VersionAdvanceDisableJLW", New SetupEntry(False, Source:=SetupSource.Instance)}, + {"VersionAdvanceDisableModUpdate", New SetupEntry(False, Source:=SetupSource.Instance)}, + {"VersionRamType", New SetupEntry(2, Source:=SetupSource.Instance)}, + {"VersionRamCustom", New SetupEntry(15, Source:=SetupSource.Instance)}, + {"VersionRamOptimize", New SetupEntry(0, Source:=SetupSource.Instance)}, + {"VersionArgumentTitle", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionArgumentInfo", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionArgumentIndie", New SetupEntry(-1, Source:=SetupSource.Instance)}, + {"VersionArgumentIndieV2", New SetupEntry(False, Source:=SetupSource.Instance)}, + {"VersionArgumentJavaSelect", New SetupEntry("使用全局设置", Source:=SetupSource.Instance)}, + {"VersionServerEnter", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionServerLogin", New SetupEntry(0, Source:=SetupSource.Instance)}, + {"VersionServerNide", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionServerAuthRegister", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionServerAuthName", New SetupEntry("", Source:=SetupSource.Instance)}, + {"VersionServerAuthServer", New SetupEntry("", Source:=SetupSource.Instance)} } #Region "基础" @@ -204,7 +201,7 @@ Private Enum SetupSource Normal Registry - Version + Instance End Enum Private Class SetupEntry @@ -239,18 +236,18 @@ ''' ''' 改变某个设置项的值。 ''' - Public Sub [Set](Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing) - [Set](Key, Value, SetupDict(Key), ForceReload, Version) + Public Sub [Set](Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Instance As McInstance = Nothing) + [Set](Key, Value, SetupDict(Key), ForceReload, Instance) End Sub ''' ''' 写入某个未经加密的设置项。 ''' 若该设置项经过了加密,则会抛出异常。 ''' - Public Sub SetSafe(Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing) + Public Sub SetSafe(Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Instance As McInstance = Nothing) If SetupDict(Key).Encoded Then Throw New InvalidOperationException("禁止写入加密设置项:" & Key) - [Set](Key, Version, ForceReload, Version) + [Set](Key, Instance, ForceReload, Instance) End Sub - Private Sub [Set](Key As String, Value As Object, E As SetupEntry, ForceReload As Boolean, Version As McVersion) + Private Sub [Set](Key As String, Value As Object, E As SetupEntry, ForceReload As Boolean, Instance As McInstance) Try Value = CTypeDynamic(Value, E.Type) @@ -259,7 +256,7 @@ If E.Value = Value AndAlso Not ForceReload Then Return Else '如果未应用,则直接更改并应用 - If E.Source <> SetupSource.Version Then E.State = 2 + If E.Source <> SetupSource.Instance Then E.State = 2 End If '设置新值 E.Value = Value @@ -277,9 +274,9 @@ WriteIni("Setup", Key, Value) Case SetupSource.Registry WriteReg(Key, Value) - Case SetupSource.Version - If Version Is Nothing Then Throw New Exception($"更改版本设置 {Key} 时未提供目标版本") - WriteIni(Version.Path & "PCL\Setup.ini", Key, Value) + Case SetupSource.Instance + If Instance Is Nothing Then Throw New Exception($"更改版本设置 {Key} 时未提供目标版本") + WriteIni(Instance.PathVersion & "PCL\Setup.ini", Key, Value) End Select '应用 '例如 VersionServerLogin 要求在设置之后再引发事件 @@ -294,15 +291,15 @@ ''' ''' 应用某个设置项的值。 ''' - Public Function Load(Key As String, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing) - Return Load(Key, SetupDict(Key), ForceReload, Version) + Public Function Load(Key As String, Optional ForceReload As Boolean = False, Optional Instance As McInstance = Nothing) + Return Load(Key, SetupDict(Key), ForceReload, Instance) End Function - Private Function Load(Key As String, E As SetupEntry, ForceReload As Boolean, Version As McVersion) + Private Function Load(Key As String, E As SetupEntry, ForceReload As Boolean, Instance As McInstance) '如果已经应用过,则什么也不干 If E.State = 2 AndAlso Not ForceReload Then Return E.Value '读取,应用并设置状态 - Read(Key, E, Version) - If E.Source <> SetupSource.Version Then E.State = 2 + Read(Key, E, Instance) + If E.Source <> SetupSource.Instance Then E.State = 2 Dim Method As Reflection.MethodInfo = GetType(ModSetup).GetMethod(Key) If Method IsNot Nothing Then Method.Invoke(Me, {E.Value}) Return E.Value @@ -311,19 +308,19 @@ ''' ''' 获取某个设置项的值。 ''' - Public Function [Get](Key As String, Optional Version As McVersion = Nothing) + Public Function [Get](Key As String, Optional Instance As McInstance = Nothing) If Not SetupDict.ContainsKey(Key) Then Throw New KeyNotFoundException("未找到设置项:" & Key) - Return [Get](Key, SetupDict(Key), Version) + Return [Get](Key, SetupDict(Key), Instance) End Function ''' ''' 获取某个未经加密的设置项的值。 ''' 若该设置项经过了加密,则会抛出异常。 ''' - Public Function GetSafe(Key As String, Optional Version As McVersion = Nothing) + Public Function GetSafe(Key As String, Optional Instance As McInstance = Nothing) If SetupDict(Key).Encoded Then Throw New InvalidOperationException("禁止读取加密设置项:" & Key) - Return [Get](Key, Version) + Return [Get](Key, Instance) End Function - Private Function [Get](Key As String, E As SetupEntry, Version As McVersion) + Private Function [Get](Key As String, E As SetupEntry, Instance As McInstance) '获取强制值 Dim Force As String = ForceValue(Key) If Force IsNot Nothing Then @@ -332,8 +329,8 @@ End If '如果尚未读取过,则读取 If E.State = 0 Then - Read(Key, E, Version) - If E.Source <> SetupSource.Version Then E.State = 1 + Read(Key, E, Instance) + If E.Source <> SetupSource.Instance Then E.State = 1 End If '返回现在的值 Return E.Value @@ -342,17 +339,17 @@ ''' ''' 初始化某个设置项的值。 ''' - Public Sub Reset(Key As String, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing) + Public Sub Reset(Key As String, Optional ForceReload As Boolean = False, Optional Instance As McInstance = Nothing) Dim E As SetupEntry = SetupDict(Key) - [Set](Key, E.DefaultValue, E, ForceReload, Version) + [Set](Key, E.DefaultValue, E, ForceReload, Instance) Select Case SetupDict(Key).Source Case SetupSource.Normal DeleteIniKey("Setup", Key) Case SetupSource.Registry DeleteReg(Key) - Case Else 'SetupSource.Version - If Version Is Nothing Then Throw New Exception($"重置版本设置 {Key} 时未提供目标版本") - DeleteIniKey(Version.Path & "PCL\Setup.ini", Key) + Case Else 'SetupSource.Instance + If Instance Is Nothing Then Throw New Exception($"重置版本设置 {Key} 时未提供目标版本") + DeleteIniKey(Instance.PathVersion & "PCL\Setup.ini", Key) End Select End Sub ''' @@ -364,22 +361,22 @@ ''' ''' 某个设置项是否从未被设置过。 ''' - Public Function IsUnset(Key As String, Optional Version As McVersion = Nothing) As Boolean + Public Function IsUnset(Key As String, Optional Instance As McInstance = Nothing) As Boolean Select Case SetupDict(Key).Source Case SetupSource.Normal Return Not HasIniKey("Setup", Key) Case SetupSource.Registry Return Not HasReg(Key) - Case Else 'SetupSource.Version - If Version Is Nothing Then Throw New Exception($"判断版本设置 {Key} 是否存在时未提供目标版本") - Return Not HasIniKey(Version.Path & "PCL\Setup.ini", Key) + Case Else 'SetupSource.Instance + If Instance Is Nothing Then Throw New Exception($"判断版本设置 {Key} 是否存在时未提供目标版本") + Return Not HasIniKey(Instance.PathVersion & "PCL\Setup.ini", Key) End Select End Function ''' ''' 读取设置。 ''' - Private Sub Read(Key As String, ByRef E As SetupEntry, Version As McVersion) + Private Sub Read(Key As String, ByRef E As SetupEntry, Instance As McInstance) Try If Not E.State = 0 Then Return Dim SourceValue As String = Nothing '先用 String 储存,避免类型转换 @@ -388,11 +385,11 @@ SourceValue = ReadIni("Setup", Key, E.DefaultValueEncoded) Case SetupSource.Registry SourceValue = ReadReg(Key, E.DefaultValueEncoded) - Case SetupSource.Version - If Version Is Nothing Then + Case SetupSource.Instance + If Instance Is Nothing Then Throw New Exception("读取版本设置 " & Key & " 时未提供目标版本") Else - SourceValue = ReadIni(Version.Path & "PCL\Setup.ini", Key, E.DefaultValueEncoded) + SourceValue = ReadIni(Instance.PathVersion & "PCL\Setup.ini", Key, E.DefaultValueEncoded) End If End Select If E.Encoded Then @@ -420,6 +417,8 @@ #If BETA Then If Key = "UiLauncherTheme" Then Return "0" #End If + If Key = "UiHiddenPageLink" Then Return True + If Key = "UiHiddenSetupLink" Then Return True Return Nothing End Function @@ -430,11 +429,11 @@ '切换选择 Public Sub LaunchVersionSelect(Value As String) Log("[Setup] 当前选择的 Minecraft 版本:" & Value) - WriteIni(PathMcFolder & "PCL.ini", "Version", If(IsNothing(McVersionCurrent), "", McVersionCurrent.Name)) + WriteIni(McFolderSelected & "PCL.ini", "Version", If(IsNothing(McInstanceSelected), "", McInstanceSelected.Name)) End Sub Public Sub LaunchFolderSelect(Value As String) Log("[Setup] 当前选择的 Minecraft 文件夹:" & Value.ToString.Replace("$", Path)) - PathMcFolder = Value.ToString.Replace("$", Path) + McFolderSelected = Value.ToString.Replace("$", Path) End Sub '游戏内存 @@ -762,18 +761,18 @@ '游戏内存 Public Sub VersionRamType(Type As Integer) - If FrmVersionSetup Is Nothing Then Return - FrmVersionSetup.RamType(Type) + If FrmInstanceSetup Is Nothing Then Return + FrmInstanceSetup.RamType(Type) End Sub '服务器 Public Sub VersionServerLogin(Type As Integer) - If FrmVersionSetup Is Nothing Then Return + If FrmInstanceSetup Is Nothing Then Return '为第三方登录清空缓存以更新描述 - WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") - If PageVersionLeft.Version Is Nothing Then Return - PageVersionLeft.Version = New McVersion(PageVersionLeft.Version.Name).Load() - LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") + WriteIni(McFolderSelected & "PCL.ini", "InstanceCache", "") + If PageInstanceLeft.Instance Is Nothing Then Return + PageInstanceLeft.Instance = New McInstance(PageInstanceLeft.Instance.Name).Load() + LoaderFolderRun(McInstanceListLoader, McFolderSelected, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End Sub #End Region diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index 9345f80..96dc62c 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -252,8 +252,8 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb index cf5f028..91532fc 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb @@ -7,7 +7,7 @@ '重复加载部分 PanBack.ScrollToHome() RefreshRam(False) - If McVersionCurrent Is Nothing Then + If McInstanceSelected Is Nothing Then BtnSwitch.Visibility = Visibility.Collapsed Else BtnSwitch.Visibility = Visibility.Visible @@ -197,7 +197,7 @@ Public Sub RefreshRam(ShowAnim As Boolean) If LabRamGame Is Nothing OrElse LabRamUsed Is Nothing OrElse FrmMain.PageCurrent <> FormMain.PageType.Setup OrElse FrmSetupLeft.PageID <> FormMain.PageSubType.SetupLaunch Then Return '获取内存情况 - Dim RamGame As Double = Math.Round(GetRam(McVersionCurrent, False), 5) + Dim RamGame As Double = Math.Round(GetRam(McInstanceSelected, False), 5) Dim RamTotal As Double = Math.Round(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024 / 1024, 1) Dim RamAvailable As Double = Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024, 1) Dim RamGameActual As Double = Math.Round(Math.Min(RamGame, RamAvailable), 5) @@ -324,10 +324,10 @@ ''' ''' 获取当前设置的 RAM 值。单位为 GB。 ''' - Public Shared Function GetRam(Version As McVersion, UseVersionJavaSetup As Boolean, Optional Is32BitJava As Boolean? = Nothing) As Double + Public Shared Function GetRam(Instance As McInstance, UseVersionJavaSetup As Boolean, Optional Is32BitJava As Boolean? = Nothing) As Double '------------------------------------------ - ' 修改下方代码时需要一并修改 PageVersionSetup + ' 修改下方代码时需要一并修改 PageInstanceSetup '------------------------------------------ Dim RamGive As Double @@ -339,16 +339,16 @@ Dim RamTarget1 As Double '估计能勉强带动了的内存 Dim RamTarget2 As Double '估计没啥问题了的内存 Dim RamTarget3 As Double '放一百万个材质和 Mod 和光影需要的内存 - If Version IsNot Nothing AndAlso Not Version.IsLoaded Then Version.Load() - If Version IsNot Nothing AndAlso Version.Modable Then + If Instance IsNot Nothing AndAlso Not Instance.IsLoaded Then Instance.Load() + If Instance IsNot Nothing AndAlso Instance.Modable Then '可安装 Mod 的版本 - Dim ModDir As New DirectoryInfo(Version.PathIndie & "mods\") + Dim ModDir As New DirectoryInfo(Instance.PathIndie & "mods\") Dim ModCount As Integer = If(ModDir.Exists, ModDir.GetFiles.Length, 0) RamMininum = 0.5 + ModCount / 150 RamTarget1 = 1.5 + ModCount / 90 RamTarget2 = 2.7 + ModCount / 50 RamTarget3 = 4.5 + ModCount / 25 - ElseIf Version IsNot Nothing AndAlso Version.Version.HasOptiFine Then + ElseIf Instance IsNot Nothing AndAlso Instance.Version.HasOptiFine Then 'OptiFine 版本 RamMininum = 0.5 RamTarget1 = 1.5 @@ -399,7 +399,7 @@ PreFin: End If End If '若使用 32 位 Java,则限制为 1G - If If(Is32BitJava, Not JavaIs64Bit(If(UseVersionJavaSetup, Version, Nothing))) Then RamGive = Math.Min(1, RamGive) + If If(Is32BitJava, Not JavaIs64Bit(If(UseVersionJavaSetup, Instance, Nothing))) Then RamGive = Math.Min(1, RamGive) Return RamGive End Function @@ -417,7 +417,7 @@ PreFin: Dim SelectedItem As MyComboBoxItem = Nothing Dim SelectedBySetup As String = Setup.Get("LaunchArgumentJavaSelect") Try - For Each Java In JavaList.Clone().OrderByDescending(Function(v) v.VersionCode) + For Each Java In JavaList.Clone().OrderByDescending(Function(v) v.MajorVersion) Dim ListItem = New MyComboBoxItem With {.Content = Java.ToString, .ToolTip = Java.PathFolder, .Tag = Java} ToolTipService.SetHorizontalOffset(ListItem, 400) ComboArgumentJava.Items.Add(ListItem) @@ -580,9 +580,9 @@ PreFin: '切换到版本独立设置 Private Sub BtnSwitch_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSwitch.Click - McVersionCurrent.Load() - PageVersionLeft.Version = McVersionCurrent - FrmMain.PageChange(FormMain.PageType.VersionSetup, FormMain.PageSubType.VersionSetup) + McInstanceSelected.Load() + PageInstanceLeft.Instance = McInstanceSelected + FrmMain.PageChange(FormMain.PageType.InstanceSetup, FormMain.PageSubType.InstanceSetup) End Sub '去除参数中的回车 diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml index fdd2289..de4ee1a 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml @@ -39,8 +39,6 @@ - - + + + @@ -281,12 +283,12 @@ - + - + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb index 3d42789..ff4a56f 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb @@ -523,7 +523,7 @@ Refresh: '顶部栏未被全部隐藏 FrmMain.PanTitleSelect.Visibility = Visibility.Visible FrmMain.BtnTitleSelect1.Visibility = If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenPageDownload"), Visibility.Collapsed, Visibility.Visible) - FrmMain.BtnTitleSelect2.Visibility = If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenPageLink"), Visibility.Collapsed, Visibility.Visible) + FrmMain.BtnTitleSelect2.Visibility = Visibility.Collapsed 'If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenPageLink"), Visibility.Collapsed, Visibility.Visible) FrmMain.BtnTitleSelect3.Visibility = If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenPageSetup"), Visibility.Collapsed, Visibility.Visible) FrmMain.BtnTitleSelect4.Visibility = If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenPageOther"), Visibility.Collapsed, Visibility.Visible) End If @@ -536,13 +536,13 @@ Refresh: If FrmSetupLeft IsNot Nothing Then FrmSetupLeft.ItemLaunch.Visibility = If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenSetupLaunch"), Visibility.Collapsed, Visibility.Visible) FrmSetupLeft.ItemUI.Visibility = If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenSetupUi"), Visibility.Collapsed, Visibility.Visible) - FrmSetupLeft.ItemLink.Visibility = If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenSetupLink"), Visibility.Collapsed, Visibility.Visible) + FrmSetupLeft.ItemLink.Visibility = Visibility.Collapsed 'If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenSetupLink"), Visibility.Collapsed, Visibility.Visible) FrmSetupLeft.ItemSystem.Visibility = If(Not HiddenForceShow AndAlso Setup.Get("UiHiddenSetupSystem"), Visibility.Collapsed, Visibility.Visible) '隐藏左边选择卡 Dim AvaliableCount As Integer = 0 If Not Setup.Get("UiHiddenSetupLaunch") Then AvaliableCount += 1 If Not Setup.Get("UiHiddenSetupUi") Then AvaliableCount += 1 - If Not Setup.Get("UiHiddenSetupLink") Then AvaliableCount += 1 + 'If Not Setup.Get("UiHiddenSetupLink") Then AvaliableCount += 1 If Not Setup.Get("UiHiddenSetupSystem") Then AvaliableCount += 1 FrmSetupLeft.PanItem.Visibility = If(AvaliableCount < 2 AndAlso Not HiddenForceShow, Visibility.Collapsed, Visibility.Visible) End If @@ -574,9 +574,9 @@ Refresh: FrmMain.BtnTitleSelect4.Text = "更多" End If '各个页面的入口 - If FrmMain.PageCurrent = FormMain.PageType.VersionSelect Then FrmSelectRight.BtnEmptyDownload_Loaded() + If FrmMain.PageCurrent = FormMain.PageType.InstanceSelect Then FrmSelectRight.BtnEmptyDownload_Loaded() If FrmMain.PageCurrent = FormMain.PageType.Launch Then FrmLaunchLeft.RefreshButtonsUI() - If FrmMain.PageCurrent = FormMain.PageType.VersionSetup AndAlso FrmVersionModDisabled IsNot Nothing Then FrmVersionModDisabled.BtnDownload_Loaded() + If FrmMain.PageCurrent = FormMain.PageType.InstanceSetup AndAlso FrmInstanceModDisabled IsNot Nothing Then FrmInstanceModDisabled.BtnDownload_Loaded() '备注 If FrmSetupUI IsNot Nothing Then FrmSetupUI.CardSwitch.Title = If(HiddenForceShow, "功能隐藏(已暂时关闭,按 F12 以重新启用)", "功能隐藏") Catch ex As Exception diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj index d77c95b..1169d14 100644 --- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj +++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj @@ -162,6 +162,7 @@ False + Resources\NAudio.dll False @@ -189,6 +190,10 @@ + + False + Resources\System.ValueTuple.dll + @@ -207,6 +212,7 @@ + @@ -222,12 +228,13 @@ + - + MyLocalModItem.xaml @@ -312,23 +319,23 @@ PageOtherHelpDetail.xaml - - PageVersionExport.xaml + + PageInstanceExport.xaml - - PageVersionLeft.xaml + + PageInstanceLeft.xaml - - PageVersionMod.xaml + + PageInstanceMod.xaml - - PageVersionModDisabled.xaml + + PageInstanceModDisabled.xaml - - PageVersionOverall.xaml + + PageInstanceOverall.xaml - - PageVersionSetup.xaml + + PageInstanceSetup.xaml @@ -497,7 +504,7 @@ Code - + MSBuild:Compile Designer @@ -574,7 +581,7 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile @@ -654,23 +661,23 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile @@ -801,28 +808,6 @@ - - - False - - - - - Exclude - True - Assembly - - - False - - - - - Exclude - True - Assembly - - @@ -936,5 +921,8 @@ + + + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip index 3188d53..a6b9e81 100644 Binary files a/Plain Craft Launcher 2/Resources/Help.zip and b/Plain Craft Launcher 2/Resources/Help.zip differ diff --git a/Plain Craft Launcher 2/Resources/ModData.txt b/Plain Craft Launcher 2/Resources/ModData.txt index f40150f..7a5c5e5 100644 --- a/Plain Craft Launcher 2/Resources/ModData.txt +++ b/Plain Craft Launcher 2/Resources/ModData.txt @@ -7,7 +7,7 @@ railcraft@railcraft-reborn|铁路*¨railcraft-reborn|铁路 (Railcraft) @ee2|等价交换2 (Equivalent Exchange 2) - +forgotten-nature|被遗忘的大自然* touhou-item-mod|辉夜姬的五难题 (五つの難題MOD+ ~ Touhou Items) secretroomsmod|密室 (SecretRoomsMod) the-camping-mod|野营* @@ -16,7 +16,7 @@ powerconverters-portablejims-fork|能源转换 (PowerConverters) modular-forcefield-system|模块化力场系统* -minechem-recompile|化学 (MineChem) - 非官方重编译版¨73373|化学 (MineChem) - 已失效 +minechem-recompile|化学 (MineChem) - 非官方重编译版¨minechem-archive|化学 (MineChem)¨73373|化学 (MineChem) - 已失效 iron-chests@|更多箱子*¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)¨iron-chests-fabric@|更多箱子 (Iron Chests) balkons-weaponmod|更多武器 (Balkon's WeaponMod) advanced-machines|高级机械 (AdvancedMachines) - Chocohead版¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版 @@ -130,7 +130,7 @@ modular-powersuits@modularpowersuits|模块化动力套装* @redlogic|红石逻辑 (RedLogic) - +powercraft-2|能量工厂 (Power Craft) obsidiplates|黑曜石压力板 (ObsidiPlates) @infinitubes|终极管道 (InfiniTubes) flatsigns|平置牌子 (FlatSigns) @@ -142,7 +142,7 @@ cubebots|立方体机器人 (CubeBots) truss-mod|桁架* mutant-creatures-mod|突变生物 (Mutant Creatures) -logistics-pipes|物流管道* +logistics-pipes@|物流管道* magic-bees|魔法蜜蜂* @@ -197,7 +197,7 @@ galacticraft-legacy@|星系 (GalactiCraft) journeymap@|旅行地图 (JourneyMap) - +equivalent-exchange-classic|等价交换经典版* underground-biomes|科学地质*¨undergroundbiomesconstructs|科学地质 (Underground Biomes) fpsplus-lagfixes|FPS优化 (FPS Plus) ars-magica-2|魔法艺术2* @@ -238,7 +238,7 @@ thaumic-exploration|神秘探险* deconstruction-table|解构工作台* -kcs-growable-ores|可生长矿石 (Kc's Growable Ores) +kcs-growable-ores@|可生长矿石 (Kc's Growable Ores) @@ -291,7 +291,7 @@ enviromine|更多生存要素 (EnviroMine) the-erebus|混沌之地* emerald-mod|绿宝石 Mod* -lucky-block-official|幸运方块 (Lucky Block) +lucky-block|幸运方块* dynamic-lights|动态光源* yarr-cute-mob-models-remake|怪物娘化 (Yarr Cute Mob Models - Remake) monster-hunter-frontier-craft|怪物猎人边境工艺* @@ -479,7 +479,7 @@ hardcore-questing-mode@hqm|困难任务* unidict@|矿辞统一 (UniDict) thermal-dynamics@|热动力学* zoology|动物学* - +plant-mega-pack-reupload|植物世界 (Plant Mega Pack) thaumic-revelations|神秘启示录* warp-book|传送书* @@ -523,7 +523,7 @@ levels|装备等级* keeping-inventory orespawn|矿石菌种 (OreSpawn) reliquary-reincarnations@|圣遗物 (Reliquary Reincarnations (Reliquary) / Xeno's Reliquary)¨reliquary|圣遗物 (Reliquary Reincarnations* / Xeno's Reliquary) -mo-bends|更多弯曲 (Mo' Bends) +mo-bends@mobends|更多弯曲 (Mo' Bends) quark@|夸克* official-divinerpg@divinerpg|神圣RPG (DivineRPG) @@ -625,15 +625,15 @@ ichunutil@ intangible forge-multipart-cbe@cb-multipart¨forge-multi-part -the-titans-mod|泰坦生物 (The Titans) +the-titans-mod|泰坦生物 (The Titans)¨the-titans-legacy|泰坦生物 (The Titans) - 旧 enchiridion stimmedcow-nomorerecipeconflict|合成冲突消除 (NoMoreRecipeConflict) tinker-i-o|工匠接口 (Tinker I/O) woot|生物工厂* thermal-expansion|热力膨胀* -realistic-terrain-generation|真实地形生成*¨realistic-terrain-generation-plus|真实地形生成 (Realistic Terrain Generation) - RTG原作者继续开发的分支 -mob-rebirth|怪物重生* +realistic-terrain-generation|真实地形生成*¨realistic-terrain-generation-plus|真实地形生成 (Realistic Terrain Generation) - RTG 原作者继续开发的分支 +mob-rebirth@|怪物重生* diseasecraft|疾病 (Disease Craft) zombie-infection|僵尸感染* astral-sorcery|星辉魔法* @@ -679,7 +679,7 @@ pressure-pipes|压力管道* onlinepictureframe|在线相框显示 (OnlinePictureFrame) super-circuit-maker|超级电路* switch-bow -minecolonies@|模拟殖民地* +minecolonies@|模拟殖民地 (MineColonies) mcjtylib@ mcmultipart @@ -730,7 +730,7 @@ blocklings|方块酱* - +magia-naturalis@|自然魔法* sbm-wooden-shears|木剪刀 (Wooden Shears) schools-of-magic|魔法学院 (Schools of Magic) @@ -882,7 +882,7 @@ mobdrip customskinloader@|万用皮肤补丁 (CustomSkinLoader) saltymod|盐 (SaltyMod) -immersive-tech|沉浸科技 (Immersive Technology) - 已停更¨mct-immersive-technology|沉浸科技 (Immersive Technology) - 非官方版 +immersive-tech|沉浸科技 (Immersive Technology) - 已停更¨mct-immersive-technology@|沉浸科技 (Immersive Technology) funky-locomotion compatlayer@|版本兼容 (CompatLayer) @@ -897,7 +897,7 @@ tardis-mod|时间和空间的相对维度 (TARDIS MOD) another-one-bites-the-dust-berry-bushes gokistats|天赋技能树 (GokiStats) -tropicraft|热带世界* +tropicraft@|热带世界* sumsang|亖圼* @@ -1009,8 +1009,8 @@ thaumic-jei@ tinkers-steelworks|匠魂炼钢厂 (Tinkers' Steelworks)¨tinkers-steelworks-fork|匠魂炼钢厂 (Tinkers' Steelworks) unicode-font-extension|Unicode 字体扩展* placeable-items|可放置物品* -compendium|工匠防御 (Tinkers' Compendium (Tinkers' Defense)) -stargate-network|格雷的星门 (Greg's SG Craft) +compendium|工匠防御 (Compendium / Tinkers' Defense) +sg-craft|格雷的星门 (Greg's SG Craft)¨stargate-network|格雷的星门 (Greg's SG Craft) damage-indicators-mod|伤害显示 (Damage Indicators) torohealth-damage-indicators|ToroHealth 伤害显示 (ToroHealth Damage Indicators) omni-ocular @@ -1026,7 +1026,7 @@ plants chinjufumod|镇守府 (ChinjufuMod +JapaneseBlock) track-api@ immersive-railroading@|沉浸铁路* - +foodcraft-reloaded|食物工艺重置版 (FoodCraft Reloaded) custom-main-menu|自定义主菜单* wildycraft @@ -1197,7 +1197,7 @@ chiseled-me|超级变变变* progressive-automation-early-miner|进阶自动化 (Progressive Automation) matmos|真实环境音效 (MAtmos) ye-olde-tanks|水箱* -improving-minecraft|改善的世界* +improving-minecraft|改善的世界*¨improving-minecraft-fork|改善的世界 (Improving Minecraft) - 他人的非官方重上传 wawla@|Wawla高亮显示 (What Are We Looking At) lightarea|点亮区域 (LightArea) @replaymod|录像回放 (Replay Mod) @@ -1263,7 +1263,7 @@ rift crop-eating-animals|动物自动喂食 (Crop-Eating Animals) refraction|折射* -librarianlib|图书馆 (LibrarianLib) +librarianlib@|图书馆 (LibrarianLib) creeper-confetti@|烟花苦力怕* funnels|液体漏斗* advent-of-ascension-nevermine@adventofascension|虚无世界 (Nevermine) @@ -1698,7 +1698,7 @@ huajiage-infinite-galaxy|滑稽纪元II:无尽星河 (Huaji Age II:Infinite Gala cqrepoured@chocolate-quest-repoured|寻找巧克力重铸版 (Chocolate Quest Repoured) fabricproxy@|Fabric 群组支持 (FabricProxy) ex-sartagine|锅具* -xaeros-minimap|Xaero的小地图 (Xaero's Minimap) - 默认版(功能完整)¨xaeros-minimap-fair-play-edition|Xaero的小地图 (Xaero's Minimap) - 公平竞争版(功能部分缺失)¨@xaeros-minimap|Xaero的小地图 (Xaero's Minimap) - 完整版¨@xaeros-minimap-fair|Xaero的小地图 (Xaero's Minimap) - 公平竞争版 +xaeros-minimap|Xaero的小地图 (Xaero's Minimap) - 默认版(功能完整)¨xaeros-minimap-fair-play-edition|Xaero的小地图 (Xaero's Minimap) - 公平竞争版(功能部分缺失,已停更)¨@xaeros-minimap|Xaero的小地图 (Xaero's Minimap) - 完整版¨@xaeros-minimap-fair|Xaero的小地图 (Xaero's Minimap) - 公平竞争版(已停更) everlastingabilities-potioncore|永恒能力-药水核心 (EverlastingAbilities-PotionCore) optifabric the-emerald-haven|翡翠天堂* @@ -1707,7 +1707,7 @@ simple-grinder@simplegrinder|简易研磨机* redstone-gauges-and-switches|红石测量与开关 (Gauges and Switches) apotheosis|神化* lighting-wand@|照明魔杖 🌟 (Lighting Wand 🌟)¨lighting-wand-fabric|照明魔杖 🌟 (Lighting Wand 🌟) -terrarium|地球* +terrarium@|地球* climatic-biomes howling-moon|狼人* hostile-worlds-invasions|敌对世界-入侵 (Hostile Worlds - Invasions) @@ -1765,10 +1765,10 @@ less-lag|较少的滞后* goodbye-grass|没有草丛 (Grassta la Vista) phosphor@|磷*¨phosphor-forge|磷 (Phosphor) clay-bucket|粘土桶* -extra-anvils|更多铁砧/更多砧 (Extra anvils) +extra-anvils|更多铁砧 / 更多砧* glassential|精致玻璃*¨glassential-fabric|精致玻璃 (Glassential) tinkers-addons@|匠魂拓展 (Tinkers' Addons) -barrels-drums-storage-more|桶,圆桶,存储&更多 (Barrels, Drums, Storage & More) +barrels-drums-storage-more|桶,圆桶,存储 & 更多 (Barrels, Drums, Storage & More) wings|翅膀* up-and-down-and-all-around|颠倒世界* lost-souls@|失落的灵魂* @@ -1978,7 +1978,7 @@ kottle customdrones|自定义无人机 (CustomDrones) call-to-battle-2-authentic-world-war-2-experience|战争召唤2 (Call To Battle 2)¨call-to-battle-the-wwii-mod|战争召唤2 (Call To Battle 2) - 远古 some-like-it-dry -yungs-better-caves|YUNG 的洞穴优化 (YUNG's Better Caves)¨yungs-better-caves-fabric|YUNG 的洞穴优化 (YUNG's Better Caves) +yungs-better-caves@|YUNG 的洞穴优化 (YUNG's Better Caves)¨yungs-better-caves-fabric|YUNG 的洞穴优化 (YUNG's Better Caves) resynth|神农科技* flatworld|开山棒 (Flat World) pymtech|皮姆科技 (PymTech) @@ -2114,7 +2114,7 @@ emerging-technology-hydroponics|新兴技术 (Emerging Technology) twilightopia-phantasy|暮托邦之梦 (Twilightopia) engineers-decor|工程师的装饰 (Engineer's Decor) engineers-tools|工程师的工具 (Engineer's Tools) -wesleys-roguelike-dungeons|Wesley的冒险地牢 (Wesley's Roguelike Dungeons) +wesleys-roguelike-dungeons@|Wesley的冒险地牢 (Wesley's Roguelike Dungeons) xp-tome@|经验之书* varied-commodities tinkers-things|匠魂扩增 (Tinkers' Things) @@ -2244,7 +2244,7 @@ more-chickens|更多鸡扩展* eoa-fa|魔戒:第一纪元 (Eras of Arda: The First Age Legacy submod) the-rc-mod rubybyimxiaoanag|红宝石 (Ruby) -jojo-hamon-era@|JoJo-波纹时代 (JoJo-Hamon Era) +jojo-hamon-era@|JoJo: 波纹时代 (JoJo: Hamon Era) OpenSecurity|开放式安全* the-legend-era|传说纪元* @@ -2320,7 +2320,7 @@ lightning-bolt-mod|雷电攻击* spartan-and-fire|斯巴达之冰与火 (Spartan and Fire) beacons-for-all@¨beacons-for-all-fabric -mxts-tutu-mod|混沌凋灵 (Chaos Wither) + more-flowers|更多功能花* buzzier-bees@ @@ -2524,7 +2524,7 @@ recipes-for-all|配方书全解锁 (Recipes for All) gregicality the-lord-of-the-rings-mod-renewed|魔戒:复兴 (The Lord of the Rings Mod: Renewed) modelloader -animal-crops|动物作物* +animal-crops@|动物作物* terrafirmacraftneiplugin furenikusroads@furenikus-roads|Fureniku的路 (Fureniku's Roads) patchouli-quests|帕秋莉任务手册* @@ -2594,7 +2594,7 @@ gulliver-reborn rationcraft default-options@¨default-options-fabric ender-mail@|末影邮递* -more-overlays +more-overlays|更多叠加层* stefinus-3d-guns-mod|3D枪械 (New Stefinus Guns)¨stefinus-3d-guns-mod-s3dgm|3D枪械 (New Stefinus Guns) moderncraft-core @@ -2697,7 +2697,7 @@ coffee-time drink-mod|饮料模组* more-zombie-villagers@|更多僵尸村民*¨more-zombie-villagers-fabric|更多僵尸村民 (More Zombie Villagers) naturally-charged-creepers@|自然生成闪电苦力怕*¨naturally-charged-creepers-fabric|自然生成闪电苦力怕 (Naturally Charged Creepers) -skinport +skinport|皮肤移植 (SkinPort) dynamic-surroundings-soundcontrol|动态环境:音效控制 (Dynamic Surroundings: SoundControl) chop-down-updated @@ -2960,7 +2960,7 @@ durability-viewer|耐久显示器* ground-item-highlighting|地面物品高光显示* -real-survivor|幸存者 (RealSurvivor) +real-survivor@realsurvivor|幸存者 (RealSurvivor) armor-toughness-bar|盔甲韧性显示* block-meter time-to-live @@ -3002,7 +3002,7 @@ embellishcraft@|装饰工艺 (EmbellishCraft) open-loader@|开放式加载* deep-mob-learning-refabricated@|深度怪物学习:重制版 (Deep Mob Learning: Refabricated) ftb-ultimine-forge|连锁破坏 (FTB Ultimine)¨ftb-ultimine-fabric|连锁破坏 (FTB Ultimine) -server-translations +server-translation-api¨server-translations image2map@ corpse@|遗体* ring-of-the-miner-fabric|矿工戒指 (Ring of the Miner)¨ring-of-the-miner-forge|矿工戒指 (Ring of the Miner) @@ -3055,7 +3055,7 @@ falling-tree@fallingtree|伐树 (FallingTree) untranslated-items|物品名称双语同屏* particle-culling@|粒子渲染机制优化* aggressive-chickens -entity-culling|实体渲染机制优化* +entity-culling@|实体渲染机制优化* hammerandvil|锻造锤与锤锻台 (HammerAndVil) simply-backpacks|简易背包* @@ -3102,7 +3102,7 @@ mob-amputation|怪物截肢* packguard wtfs-texturegeneratorlib more-totems-of-undying|更多的不死图腾* -dungeon-crawl +dungeon-crawl@dungeoncrawl tesslocator|超立方转运器* iblis-headshots|爆头击杀* bee-barker @@ -3133,10 +3133,10 @@ toomanydanyores glass-pipe|玻璃管道* origins-classes@|起源:职业 (Origins: Classes) plonk@|世界内物品放置* -baritone-simply¨baritone-1-18-2|Baritone - 已失效 +baritone-simply potionsmaster|魔药大师 (Potions Master) tinkered-hegemony@|匠器主导* -better-impaling|更好的穿刺附魔* +better-impaling@|更好的穿刺附魔* caliper botanical-machinery@|植物机械* demagnetize|消磁* @@ -3168,7 +3168,7 @@ fabric-language-scala fluid-physics-forge|流体物理 (Fluid Physics)¨fluid-physics|流体物理* zettai-grimoires runorama|全景截图背景* -item-stages|物品阶段* +item-stages@|物品阶段* rocksalt cazfps-the-dead-sea|死亡之海 (CAZfps The Dead Sea) pouch-of-unknown|未知之袋 (Pouch of Unknown) @@ -3208,7 +3208,7 @@ libx@ atmospheric@|悠然一派* monospace-font|等宽字体 (Monospace font) -mana-liquidizer|魔力液化器* +mana-liquidizer@|魔力液化器* neapolitan@|那不勒斯风味* clear-despawn@cleardespawn¨clear-despawn-fabric tinkers-modifier-modifier|匠魂强化修改 (Tinkers' Modifier Modifier) @@ -3298,7 +3298,7 @@ charging-gadgets|充电设备* integration-foregoing|工业先锋集成附属* enderfuge|末影熔炉* calm-down-zombie-guy -universal-mod-core@ +universal-mod-core@|通用模组核心* suspended-server|挂起服务器* starlight-forge@|星光 (Starlight)¨starlight@|星光*¨starlight-neoforge@|星光 (Starlight) research-table|研究台 🔬 (Research Table 🔬) @@ -3392,7 +3392,7 @@ optifabric-1-8-9 gt6-ore-helper|GT6 矿石助手 (GT6 Ore Helper) applied-integrations|应用集成* -inventory-hud-forge|物品栏HUD+ (Inventory HUD+) +inventory-hud-forge@inventoryhudplus|物品栏HUD+ (Inventory HUD+) langsplit@ legacy-fabric-api@ @@ -3482,7 +3482,7 @@ savage-and-ravage@|残暴与掠夺 (Savage & Ravage) jade@|玉 🔍 (Jade 🔍) somnia@|梦醒时分 (Somnia Awoken) industrial-reborn@ -mo-structures-forge|更多自然生成结构 (Mo' Structures)¨mo-structures|更多自然生成结构 (Mo' Structures) +mo-structures-forge@mo-structures|更多自然生成结构 (Mo' Structures)¨mo-structures|更多自然生成结构 (Mo' Structures) wallpapercraft-a-fresh-roll|墙纸创造 (Wallpapercraft - A Fresh Roll) @@ -3530,7 +3530,7 @@ bedrockify@bedrockIfy rftools-power@|RF工具:能量 (RFTools Power) pipez@ -coins-je +coins-je@ beenfo@|蜂巢信息* chest-building-blocks @@ -3559,7 +3559,7 @@ pointless-tech-collective revampedwolf@ bedrock-b-gone@ -elemental-creepers@|元素爬行者*¨elemental-creepers-og|元素爬行者 (Elemental Creepers) +elemental-creepers-og@elemental-creepers|元素爬行者 (Elemental Creepers)¨elemental-creepers|元素爬行者* gymcraft infernal-expansion@|地狱扩展* @@ -3580,7 +3580,7 @@ xnicex@ krate chickensshed@|鸡棚重置版 (ChickensShed) chickenshed -expanded-storage|扩展存储* - 已失效¨expanded-storage-fabric@expanded-storage|扩展存储 (Expanded Storage)¨expanded-storage-forge|扩展存储 (Expanded Storage) +expanded-storage@|扩展存储*¨expanded-storage-fabric|扩展存储 (Expanded Storage)¨expanded-storage-forge|扩展存储 (Expanded Storage) project-mmo@ abnormals-delight@ jojos-bizarre-survival @@ -3616,7 +3616,7 @@ iguana-in-a-blanket@iguana|真实调整 (Iguana in a Blanket) anthill-inside|蚂蚁物流* nei-integration uncrafting-grinder|分解磨床* -emendatus-enigmatica +emendatus-enigmatica@emendatusenigmatica @@ -3646,7 +3646,7 @@ improved-mobs@|怪物增强*¨improved-mobs-fabric|怪物增强 (Improved Mobs) limitless|无上限附魔* vpn-blocker|禁用VPN (VPN Blocker) premium-wood@ -tenshilib¨tenshilib-fabric +tenshilib@¨tenshilib-fabric randomportals|随意传送门 (RandomPortals) configured|配置界面*¨configured-fabric|配置界面 (Configured) @@ -3789,7 +3789,7 @@ mojangdark|暗色加载 (MojangDark) astromine-technologies|天体矿工:科技 (Astromine: Technologies) astromine-transportations|天体矿工:运输 (Astromine: Transportaions) azuredooms-chunk-loader -creeper-spores|苦力怕孢子* +creeper-spores@|苦力怕孢子* more-dragon-eggs|更多的龙蛋* more-red@|更多红石* @@ -3810,7 +3810,7 @@ sweetmagic|甜蜜与魔法 (SweetMagic) astromine-discoveries|天体矿工:探索 (Astromine: Discoveries) azuredooms-angel-ring@|AzureDoom的天使指环 (AzureDooms Angel Ring) togglelib|列车手柄库 (ToggleLib) -regrowth|再生* +regrowth@|再生* mobs-main-menu chaos-awakens@|混沌觉醒* @@ -3946,7 +3946,7 @@ wireless-industry spice-of-life-tonio-edition|生活调味料:托尼欧版 (Spice of Life: Tonio Editon) husk-spawn@|尸壳生成微调*¨husk-spawn-fabric|尸壳生成微调 (Husk Spawn) artisan-tools-1-16|工匠工具 (Artisan Tools) -yttr|Yttr - 已失效¨@yttr +yttr@ wisla mixin-0-7-0-8-compatibility@mixincompat|兼容Mixin0.7-0.8 (Mixin 0.7-0.8 Compatibility) farplanetwo|远平面2 (Far Plane Two) @@ -3957,7 +3957,7 @@ forge-ae2-additional-opportunity@ae2ao¨ae2ao¨legacy-ae2-additional-opportunity iconexporter@icon-exporter curious-shulker-boxes@shulker-box-slot|潜影盒插槽/潜影盒槽位 (Shulker Box Slot)¨curious-shulker-boxes-fabric|潜影盒插槽/潜影盒槽位 (Shulker Box Slot) xenoclus-2 -darker-depths|深岩之下* +darker-depths@|深岩之下* terracraft-journey solar-cooker@|太阳灶* @@ -4028,7 +4028,7 @@ fish-traps|鱼栅* combustive-fishing@ fabric-survival-island@survival-island sonicraft@|索尼克工艺 (SoniCraft) -islands +islands@ origins-forge@|起源非官方Forge版 (Origins (Forge)) magic-feather alet@a-little-extra-tiles @@ -4127,7 +4127,7 @@ moarrecipes|更多的配方 (MoarRecipes) luminium better-nether-map-port@better-nether-map -iguanatweaks-reborn|生存重构/蜥蜴微调重生 (Survival Reimagined / IguanaTweaks Reborn) +iguanatweaks-reborn@|生存重构/蜥蜴微调重生 (Survival Reimagined / IguanaTweaks Reborn) enter-the-void|进入虚空* tensura-mod-that-time-i-got-reincarnated-as-a-slime@tensura-mod|关于我转生变成史莱姆这档事 (Tensura Mod -That time I got reincarnated as a Slime) @@ -4508,7 +4508,7 @@ sync-fabric@|克隆(Fabric) (Sync (Fabric)) tale-of-kingdoms-a-new-conquest@|王国:新征程 (Tale of Kingdoms: A new Conquest) schwarz@|黑* -megane@¨megane-forge@ +megane-forge@¨megane@ crusade@ vending-machine|售货机* modernxl @@ -4627,7 +4627,7 @@ ide-better-command-block-forge-editor|更好的命令方块 (Better Command Bloc breakfree@|自由破坏 (Break Free) tinkers-mechworks-fork extended-nether-backport -borderless|无边框全屏 (Borderless Window) +borderless@borderless-window|无边框全屏 (Borderless Window) lifted-restrictions-structure-block|更好的结构方块 (Better Structure Block) entity-model-json|实体模型 JSON (Entity Model JSON) death-coords|死亡坐标显示* @@ -4635,7 +4635,7 @@ death-coords|死亡坐标显示* never-enough-currency|货币多多* never-enough-currency-lite|货币多多:精简版* create-chunkloading -name-pain@ +name-pain@namepain wireless-chargers@|无线充电器* bouncier-beds@|弹力床*¨bouncier-beds-fabric|弹力床 (Bouncier Beds) hexlands-ii|六边形地貌 2 (HexLands II) @@ -4654,7 +4654,7 @@ peculiars|独特风味* draggable-resource-packs@draggable-lists|可拖动列表/可拖动资源包 (Draggable Lists/Draggable Resource Packs) fastchest|快速箱子渲染 (FastChest) - 已不再更新¨@fastchest|快速箱子渲染 (FastChest) - 在此处更新 yuushya-townscape@|方块小镇* -architects-palette-fabric@|建筑师的调色板 (Architect's Palette)¨architects-palette@architects-palette#all-versions|建筑师的调色板 (Architect's Palette) +architects-palette-fabric@|建筑师的调色板 (Architect's Palette)¨architects-palette@|建筑师的调色板 (Architect's Palette) whisperwoods@|低语之森* dimensional-dungeons@|维度地牢* bosses-of-mass-destruction@|祸乱鬼魅 (Bosses of Mass Destruction) @@ -4872,7 +4872,7 @@ create-confectionery@|机械动力:甜食* -rotten-piglins-mod +rotten-piglins-mod|腐朽猪灵 (Rotten Piglins) ex-nihilo-random|无中生有:随想 (Ex Nihilo: Random) @@ -4933,7 +4933,7 @@ forge-snad|子沙1.14+ (Snad)¨fabric-snad|子沙1.14+ (Snad) opentoall net-music@|网络音乐机* -torchbowmod|火把弓 (TorchBowMod) +torchbowmod@|火把弓 (TorchBowMod) esencia weirdmobs@ydms-weirdmobs enchantment-level-language-patch@enchlevel-langpatch|附魔等级语言补丁 (EnchLevel-LangPatch) @@ -4951,7 +4951,7 @@ mooshroomcraft|哞菇工艺* unearthed|出土*¨unearthed-fabric|出土 (Unearthed) terrestria@¨terrestria-reforged simplex-terrain-generation|Simplex地形生成器* -enderscape@ +enderscape@|末影之景* goblin-traders-fabric@|哥布林商人Fabric版 (Goblin Traders (Fabric)) lakeside@|湖畔* chenziqius-originality|CHENZIQIU的创意 (CHENZIQIU's Originality) @@ -5103,7 +5103,7 @@ scape-and-run-parasites-addon-by-nocube vanilla-degus|八齿鼠* ksyxis@ metallurgy-4-reforged|冶金4:重铸 (Metallurgy 4: Reforged) -harder-natural-healing +harder-natural-healing@ extra-bit-manipulation spartan-weaponry-twilight-forest|斯巴达的武器:暮色森林 (Spartan Weaponry: Twilight Forest) iris-reforged @@ -5225,7 +5225,7 @@ farsight¨farsight-fabric atlantis@|亚特兰蒂斯* gaia-dimension -pwem +pwem@ akisephilas-bosses @@ -5344,7 +5344,7 @@ gregtech-ce-unofficial@|格雷科技社区版:非官方版 (GregTech CE: Unoff eggtab-fabric|独立刷怪蛋列表 (Egg Tab)¨eggtab-forge|独立刷怪蛋列表 (Egg Tab) always-a-wither-skull@|必得凋灵骷髅头颅 (Always a Wither Skull)¨always-a-wither-skull-fabric|必得凋灵骷髅头颅 (Always a Wither Skull) anvil-restoration@|铁砧修复*¨anvil-restoration-fabric|铁砧修复 (Anvil Restoration) -hide-hands-fabric@hide-hands|隐藏主副手 (Hide Hands)¨hide-hands|隐藏主副手* +hide-hands@|隐藏主副手*¨hide-hands-fabric|隐藏主副手 (Hide Hands) better-beacon-placement@|更好的信标放置*¨better-beacon-placement-fabric|更好的信标放置 (Better Beacon Placement) dead-totems functional-storage@|功能性存储* @@ -5370,7 +5370,7 @@ additional-bars@additionalbars|更多的栏杆*¨additional-bars-fabric|更多 leap@ cameraoverhaul@ enchantment-id-extender@eide -creeper-overhaul@|苦力怕改革/苦力怕革新* +creeper-overhaul@|苦力怕改革 / 苦力怕革新* cool-elytra-roll@ camera-overhaul-forge blame¨blame-fabric @@ -5443,11 +5443,11 @@ advanced-fishing bettertabs@ classy-hats|时髦帽子* bedbreakbegone@ -bq-multiblock-structure-integration@|更好的任务-多方块与结构扩展 (Better Questing - Multiblock and Structure Integration) +bq-multiblock-structure-integration@|更好的任务 - 多方块与结构扩展 (Better Questing - Multiblock and Structure Integration) bqtweaker@ hypnos@ collision-damage@ -project-74246|毁灭地牢 (Doomlike Dungeons) +project-74246@doomlike-dungeons|毁灭地牢 (Doomlike Dungeons) dynamic-trees-defiled-lands|动态的树:污秽之地附属 (Dynamic Trees - Defiled Lands) xaero-map-addition@ lambdacontrols@|Lambd 的控制优化 (LambdaControls) @@ -5499,14 +5499,14 @@ macaws-paintings@|Macaw的画 (Macaw's Paintings) dyenamics multi-piston blood-magic@|血魔法* -resolutioncontrol@resolution-control-plus|分辨率控制 (Resolution Control)¨resolution-control|分辨率控制* +resolutioncontrol@resolution-control-plus|分辨率控制 (Resolution Control/ResolutionControl+)¨resolution-control|分辨率控制 (Resolution Control/ResolutionControl+) wdl|世界下载器 (World Downloader) domum-ornamentum methodicalmilking -save-and-load-inventories-fabric@save-and-load-inventories¨save-and-load-inventories +save-and-load-inventories@¨save-and-load-inventories-fabric forge-config-api-port-fabric@forge-config-api-port pams-cookables end-metals @@ -6098,7 +6098,7 @@ player-weight-mod|玩家重量* chunk-loaders@|区块加载器* tinkerstages @oneboxatatime -eccentric-tome@|怪奇宝典* +eccentric-tome-updated@eccentric-tome|怪奇宝典 (Eccentric Tome)¨eccentric-tome|怪奇宝典* arnicalib gregicality-multiblocks@ @@ -6178,7 +6178,7 @@ angel-ring|天使指环* debugify@ nomowanderer@ mc-modsthecompressed -cave-enhancements +cave-enhancements@ creative-apiary|创造蜂场* creative-crafter mcmod-search-reborn|MC百科资料搜索-重生 (MCMOD Search Reborn) @@ -6323,7 +6323,7 @@ emerald-craft-mod@emerald-craft|绿宝石工艺* roughly-enough-professions-rep@|REI工作方块 (Roughly Enough Professions) sophisticated-core@|精妙核心* edibles@¨edibles-fabric -reach-fix +reach-fix@ lootjs-forge@lootjs chat-sounds@chatsounds advancement-screenshot@¨advancement-screenshot-fabric @@ -6359,7 +6359,7 @@ incorporeal-3@|幻想多媒体3* oxygen|氧* -immersive-combat|沉浸式战斗* +immersive-combat@|沉浸式战斗* @essentialclient mystical-index@ combat-commons @@ -6418,7 +6418,7 @@ mystical-biomes forgero@ disenchantingforge¨disenchantingfabric redstone-pen@redstonepen|红石笔* -ae2things¨ae2-things-forge +ae2-things-forge@ae2things¨ae2things avaritia-endless|无尽贪婪·伪* dynamic-trees-jurassicraft|动态的树:侏罗纪时代附属 (Dynamic Trees - JurassiCraft) dynamic-trees-natures-aura|动态的树:自然灵气附属 (Dynamic Trees - Nature's Aura) @@ -6456,7 +6456,7 @@ fantasys-furniture@fantasy-furniture|Fantasy的家具 (Fantasy's Furniture) touhou-spell-card-additional|东方符卡附加包 (Touhou Spell Card Additional Pack) registrator apexcore@ -magiclib@|魔法类库 (MagicLib)¨magiclib-dev@|魔法类库 (MagicLib) +magiclib@|魔法类库 (MagicLib) - 正式版¨magiclib-dev|魔法类库 (MagicLib) - 开发版¨@magiclib-dev|魔法类库 (MagicLib) - 测试版 cull-less-leaves@|更好的树叶渲染优化* @ex-stellae|繁星之凝 (Ex Stellae) with-craft|手工艺 (With Crafts) @@ -6470,7 +6470,7 @@ dungeons-libraries @fabricated-forge raknetify@ -vmp-fabric@¨@vmp-forge +vmp-fabric@¨vmp-forge@ flotage@|漂浮物* dynamic-trees-for-hungteens-plants-vs-zombies-mod|动态的树:HungTeen的植物大战僵尸附属 (Dynamic Trees for PVZ) antidrop@|防丢 (AntiDrop) @@ -6604,7 +6604,7 @@ preventer@ starflight-innovation powah-rearchitected@powah @mouserate-framerate -@pickup-notifications +pickup-notifications@ @practical-barrels better-rocks horse-combat-controls @@ -6613,7 +6613,7 @@ simply-houses@ yungs-better-desert-temples@|YUNG的沙漠神殿优化 (YUNG's Better Desert Temples)¨yungs-better-desert-temples-fabric|YUNG的沙漠神殿优化 (YUNG's Better Desert Temples)¨yungs-better-desert-temples-neoforge|YUNG的沙漠神殿优化 (YUNG's Better Desert Temples) modern-dynamics@|现代动力学* doggo-mod@ -tan-huge-trees +tan-huge-trees@tans-huge-trees @wiangdtbisadtn|草我本来不想这么做的但是我这么做了啊啊啊啊啊啊啊啊啊啊啊啊啊啊 (Wait I Am Not Gonna Do This But I Somehow Accidentally Did This Nooooooooooooooo) yungs-better-witch-huts@|YUNG的沼泽小屋优化 (YUNG's Better Witch Huts)¨yungs-better-witch-huts-fabric|YUNG的沼泽小屋优化 (YUNG's Better Witch Huts)¨yungs-better-witch-huts-neoforge|YUNG的沼泽小屋优化 (YUNG's Better Witch Huts) panorama-creator|全景图创建器* @@ -6632,7 +6632,7 @@ nuclear-contraptions fire-crafting@firecrafting|烈焰配方 (FireCrafting) tooltiptweaks@|提示框微调 (Tooltip Tweaks) -more-babies +more-babies@ @save-my-bed data-reload@ winver@ @@ -6896,7 +6896,7 @@ the-graveyard-biomes-forge@nyctophobia¨the-graveyard-biomes-fabric cammies-minecart-tweaks@ renderlib -nothirium +nothirium@ advancedchatbox@AdvancedChatBox quick-pick advancements-debug@ @@ -6943,7 +6943,7 @@ extractinator@|提炼机* choicetheorems-overhauled-village@ct-overhaul-village blast@ no-indium@ -windsweptmod +windsweptmod@windswept immersive-paintings@|沉浸画框* melon-wireless-redstone@wireless-redstone automated-crafting@ @@ -6976,7 +6976,7 @@ dragon-fire-mod sandy-ores|沙子矿石* rainbowify@ be-quiet-please|请安静* -prehistoric-revival-a-prehistoric-fauna-add-on|史前复生 (Prehistoric Revival - A Prehistoric Fauna Add-On) - Issues提交处¨@prehistoric-revival-a-prehistoric-fauna-add-on|史前复生 (Prehistoric Revival - A Prehistoric Fauna Add-On) +prehistoric-revival-a-prehistoric-fauna-add-on@|史前复生 (Prehistoric Revival - A Prehistoric Fauna Add-On) had-enough-characters medieval-origins @@ -7036,7 +7036,7 @@ riding-utilities compact-help-command@¨compact-help-command-fabric miners-lung|矿工之肺 (Miner's Lung) -greenery|绿色植物🌿 (Greenery🌿) +greenery@|绿色植物🌿 (Greenery🌿) butchers-delight@|屠夫乐事 (Butcher's Delight) large-meals-an-add-on-for-farmers-delight explorers-delight@ @@ -7116,7 +7116,7 @@ lapis-stays-in-the-enchanting-table goodall freecam-by-zergatul -macaws-bridges-oh-the-biomes-youll-go|Macaw 的桥梁:你将去的生物群系附属 (Macaw's Bridges - Oh The Biomes You'll Go) - 已失效¨macaws-byg-bwg@|Macaw 的桥梁:你将去的生物群系附属 (Macaw's Bridges - Oh The Biomes You'll Go) +macaws-byg-bwg@|Macaw 的桥梁:你将去的生物群系附属 (Macaw's Bridges - Oh The Biomes You'll Go)¨macaws-bridges-oh-the-biomes-youll-go@|Macaw 的桥梁:你将去的生物群系附属 (Macaw's Bridges - Oh The Biomes You'll Go) - 原,已失效 macaws-fences-oh-the-biomes-youll-go@|Macaw的栅栏与墙:你将去的生物群系附属 (Macaw's Fences - Oh The Biomes You'll Go) alternate-current@ no-unused-chunks@ @@ -7172,7 +7172,7 @@ extrabotany2|额外植物学重置版 (ExtraBotany2) terramine@ aeinfinitybooster@ endertech-infinity|Endertech Infinity - 已失效¨@endertech-infinity -more-frogs-fabric@morefrogs|更多青蛙2 (More Frogs 2) +more-frogs-fabric@morefrogs|更多青蛙 (More Frogs) create-big-cannons@|机械动力:火炮* @@ -7194,7 +7194,7 @@ spice-of-life-sweet-potato-edition|生活调味料:红薯版 (Spice of Life: S @reality-c-c|真实城市建设模组 (Reality City Construction) sneak-through-berries@ winter-overhaul@|冬季改革* -offlineskins¨offlineskins-fabric +offlineskins|离线皮肤 (OfflineSkins)¨offlineskins-fabric|离线皮肤 (OfflineSkins) @xdi8aho-mod|灯纟火 (Xdi8Aho Mod) @@ -7223,7 +7223,7 @@ optigui@ background-music-selector@bgmselector|背景音乐选择器* dynamic-surrounding-resurrected -spells-shields +spells-shields@ player-companions@ technical-engineering-3|科能工程3* @@ -7235,7 +7235,7 @@ chatflow auto-third-person@|自动第三人称* flowery-structures@ chat-up -flowery-core +flowery-core@flowerycore level-hp sanguine-arsenal|幻梦:血染武装 (Eidolon's Sanguine Arsenal) automobility@|飞车奇匠* @@ -7314,15 +7314,15 @@ solar-energy restored-earth -pig-pen-cipher +pig-pen-cipher@ luckyblock-fabric@luckyblocks|幸运方块 (Lucky Blocks) @smeltingtouch things-fabric@things|杂七杂八的东西 (Things) exlines-snorkel-mod@snorkel -two-players-one-horse +two-players-one-horse@ headshot-forge left-hanging -stars_carrier|星光的载具 +stars_carrier|星光的载具 (stars_carrier) aetherworks|天华之作 (AetherWorks) slice-and-dice@ @@ -7334,7 +7334,7 @@ unlimited-dragon-eggs-fabric@unlimited-dragon-eggs moreplayermodels-plus@moreplayermodels+|更多玩家模型+ (MorePlayerModels+) @actioninventory -custom-void +custom-void|自定义虚空* create-jetpack@|机械动力:喷气背包* entity-outliner @sharpness6 @@ -7350,7 +7350,7 @@ bygone-nether|逝去的下界*¨bygone-nether-fabric|逝去的下界 (Bygone Net @clickopener kokoalinux-backport mechanical-clothesline|机械晾衣绳* -loading-progress-bar@ +loading-progress-bar@|加载进度条* intercontinental-ballistic-redux planefix quick-leaf-decay @@ -7389,7 +7389,7 @@ smartvision@ no-more-purple@ content-creator-integration¨content-creator-integration-fabric -defaultworldtype@ +defaultworldtype@|默认世界类型 (Default World Type) no-nether miniatures ad-astra@ @@ -7401,16 +7401,16 @@ ice-fire-katanas @cma|地毯-MCT拓展 (Carpet MCT Addition) brvsb@|更好的铷视频设置按钮 (Better Rubidium Video Settings Button) roughly-enough-trades@ -simple-quarry +simple-quarry@ magitek-mechs|Magitek Mechs - 已停用¨@magitek-mechs infectum nocubes-sea-dwellers show-me-your-skin@ invulnerable-pigs t88 -k-turrets¨k-turrets-fabric +k-turrets@¨k-turrets-fabric nevertooexpensive -max-enchant-x +max-enchant-x@ visible-toggle-sprint@ let-me-despawn@lmd glsl-panorama@ @@ -7424,7 +7424,7 @@ galacticborn-origin@ extra-recipe|更多配方* -custom-window-title@ +custom-window-title@|自定义窗口标题* crated-foods-farmers-delight damage-tilt @@ -7488,14 +7488,14 @@ playeranimator@ apathy-mod@apathy auudio-forge@auudio¨auudio-fabric -easy-shulker-boxes@ +easy-shulker-boxes@¨easy-shulker-boxes-fabric qu-enchantments@ ironchests@ piercing-paxels@piercingpaxels -icy-incitement@ +icy-incitement@icyincitement visible-librarian-trades -unlimited-chisel-works-tfc1 +unlimited-chisel-works-tfc1@ enchantedshulkers@|附魔潜影盒 (EnchantedShulkers) noits-simple-sorting colds-gauntlets @@ -7504,27 +7504,27 @@ wilder-wild@ paintings-with-dragons more-redstone-lamps|更多红石灯* placeable|可放置植物 (Placeable Plants) -hot-or-not-tfc-with-sticks +tfc-hot-or-not@ quick-elytra@ other-berries itank snowy-spirit@ -no-tutorial-toasts +no-tutorial-toasts@ experience-ores -anvil-player-heads-fabric@anvil-player-heads +anvil-player-heads-fabric@anvil-player-heads¨anvil-player-heads artisan-integrations seemyname biomes-o-plenty-tools bedrockcraft@ better-mobgriefing-gamerule frozen-fiend - +minefall gateways-to-eternity|永恒之门 (Gateways to Eternity) -gate-of-babylon-additions@|王之财宝附加 (Gate of Babylon Additions) +gate-of-babylon-additions|王之财宝附加 (Gate of Babylon Additions) - 已失效¨@gate-of-babylon-additions|王之财宝附加 (Gate of Babylon Additions) hephaestus-fabric@hephaestus|匠神 (Hephaestus) crafting-bench@ -morebeeinfo@ -bq-forestry|更好的任务-林业扩展 (Better Questing - Forestry) +morebeeinfo@|更多蜜蜂信息 (MoreBeeInfo) +bq-forestry|更好的任务 - 林业扩展 (Better Questing - Forestry) create-infinidrill @@ -7534,7 +7534,7 @@ attribute-tooltip-fix@ no-slow-down|不减速* crumbs@¨crumbs-fabric -italian-delights-add-on-for-farmers-delight +italians-delight immersivemc@|沉浸式MC (ImmersiveMC) mc-vr-api@ exordium@ @@ -7547,7 +7547,7 @@ moar-fps@moarfps flighthud meal-api@mealapi seed-drop -fishery-expansion|🎣 Fishery‎‎‎‏‏‎ ‎ - 已失效¨@fishery +fishery-expansion@fishery arrowcamfabric overpowered-mending@overpoweredmending @@ -7556,7 +7556,7 @@ instant-blocks@¨instant-blocks-fabric better-sprinting@|更好的疾跑* rpglevels snow-under-trees@ -elytra-physics@ +elytra-physics@|鞘翅物理* scout@ zenstages @@ -7573,15 +7573,15 @@ powered-food spirit@ elytra-bounce@ -no-report-button@nrb +no-report-button@nrb|无举报按钮* mana-wizardry aileron@ devworld@ -immersive-weapons-compatibility-bridge +immersive-weapons-compatibility-bridge@ gameidiots-craftable-wizardry-items craft-hacks -serene-seasons-harvestcraft2-compat +serene-seasons-harvestcraft2-compat|静谧四季 - 潘马斯农场 2:作物兼容 (Serene Seasons - Pam's HarvestCraft 2: Crops Compat) goat simple-autofish @@ -7590,7 +7590,7 @@ omo mushroom-additions project-the-void|项目 -虚空- (Project the Void) -ice-and-fire-rotn-edition +ice-and-fire-rotn-edition|冰火传说: RotN版 (Ice and Fire: RotN Edition) minescript@ no-capes@|禁用披风* apocalypsecraft @@ -7613,7 +7613,7 @@ haunted-harvest@ dungeonsenhanced@dungeons-enhanced create-crystal-clear@|机械动力:晶莹剔透 (Create: Crystal Clear) @create-crystal-clear-fabric -fix-experience-bug +fix-experience-bug@ @minecraft024|24点 (Minecraft024) @@ -7633,13 +7633,13 @@ soul-fired@soul-fire-d faithfulbackrooms@ ex-machinis@¨ex-machinis-divitiae-deorum@ goety@|诡厄巫法* -torch-hit +torch-hit@ we-got-runners @cabricality-utils dynamic-trees-croptopia medieval-music -gempire +gempire@ sbm-bone-torch linkmyitem @@ -7650,15 +7650,15 @@ resourceful-lib@ marg cozy@ -better-piglin-trade|更好的猪灵交易* +better-piglin-trade@|更好的猪灵交易* model-gap-fix@modelfix posture -little-ants|小蚂蚁* +little-ants@|小蚂蚁* hardcore-torches@|硬核火把*¨hardcore-torches-forge|硬核火把 (Hardcore Torches) -auto-torch +auto-torch@|自动火把* nightlights@|小夜灯 (Night Lights) -angry-mobs +angry-mobs@ onlylooking the-birdwatching-mod giant-ai @@ -7669,7 +7669,7 @@ rpgdifficulty@ tooltiprareness@|稀有度提示框 (TooltipRareness) @minimotd-reforged combat-roll@|战斗翻滚* -project-brazier +project-brazier@ over-enchanted@overenchanted creeper-overhaul-firework|烟花苦力怕改革* spartan-weaponry-bewitchment@|斯巴达的武器:巫师之路 (Spartan Weaponry - Bewitchment) @@ -7687,7 +7687,7 @@ wilder-wilds @ae2-unpowered better-respawn@|更好的重生* essentialgui@ -elementary-staffs +elementary-staffs@ dragon-fight-config infused-foods@ what-meme @@ -7707,7 +7707,7 @@ custom-cards|自定义卡牌* biome-cards|群系卡牌* bombinghud astral-dimension -followers-teleport-too-fabric@followers-teleport-too¨followers-teleport-too +followers-teleport-too@¨followers-teleport-too-fabric @citycraft|城市工艺 (City Craft) odysseymod libipn@ @@ -7746,18 +7746,18 @@ cave-generator|洞穴生成器* console-filter customized-dungeon-loot@ customquests -portal-cubed@ +portal-cubed|Portal Cubed / Thinking With Portatos - 无法¨@portal-cubed pehkui-resizer -@kaffees_dual_ride +kaffees-dual-ride@kaffees_dual_ride @sfreeze portable-hole@ betterteamchat@ -logic-chips +logic-chips|逻辑芯片* quit@ nears@ obsidianui@ -@walk-jog-run +walk-jog-run@ conveyor-belts-fabric|传送带 (Conveyor Belts) create-sifting@|机械动力:筛子* player-name-tweaks|角色名调整 (PlayerNameTweaks) @@ -7769,7 +7769,7 @@ modget@ no-leaf-decay@ mysteriumlib@ @inrealtime -furnace-recycle@¨furnace-recycle-fabric +furnace-recycle@|熔炉回收*¨furnace-recycle-fabric|熔炉回收 (Furnace Recycle) wild-lands@ @@ -7804,11 +7804,11 @@ swem@|星虫马术 (Star Worm Equestrian Mod) metal-barrels-legacy|金属储物桶:传承 (Metal Barrels: Legacy) inventoryfullalert@ lead-beyond|引向超越* -@dyeallthethings +dyeallthethings@ swampier-swamps@¨swampier-swamps-fabric weather-settings|更好的天气控制 (Weather settings) shutdowner -shield-mechanics|盾牌机制优化 (Shield mechanics) +shield-mechanics|盾牌机制优化 (Shield mechanics/Tactical Shield Overhaul) sun-and-moon-celestial-configuration@celestial-config chatsound advancedshader|光影前向兼容 (AdvancedShader) @@ -7818,7 +7818,7 @@ glued@ tinkers-craft|工匠创意 (Tinkers' Craft) tinker-leveling gtce-tj-edition|格雷科技社区版:TJ版 (GregTech Community Edition: TJ Edition) -upc +upc@ gregicality-tjfork @flagslib create-mechanical-extruder@ @@ -7849,14 +7849,14 @@ music-duration-reducer telepistons@ mariums-soulslike-weaponry@|Marium的魂类武器 (Marium's Soulslike Weaponry) lets-go-herping -modularui +modularui@ inventory-bogo-sorter@inventory-bogosorter gregtech-sieves|格雷矿石筛子 (GregTech Sieves) dynamic-trees-gregtech-ceu-rubber-tree|动态的树:格雷科技社区版:非官方版附属 (Dynamic Trees - GregTech CEu) fertility tcintegrations@ -underground-villages +underground-villages@ spook broglis-owls pluto@ @@ -7866,7 +7866,7 @@ reds-better-biomes-fabric-forge reds-more-structures rotn-tweaker cannot-build-over-lava-source-blocks -quark-rotn-edition +quark-rotn-edition|夸克:RotN 版 (Quark: RotN Edition) inmisaddon@ pineapple-delight@|凤梨乐事* woodworks@ @@ -7889,7 +7889,7 @@ mob-vote-2022-forge¨mob-vote-2022 curious-lands cpmsvcc@|自定义玩家模型的简单的语音聊天兼容 (Customizable Player Models Simple Voice Chat compat) pettable@ -create-enchantment-industry@|机械动力:附魔工业 (Create: Enchantment Industry)¨create-enchantment-industry-fabric@|机械动力:附魔工业 (Create: Enchantment Industry) +create-enchantment-industry@|机械动力:附魔工业 (Create: Enchantment Industry)¨cei-fabric-legacy@|机械动力:附魔工业 (Create: Enchantment Industry) enchanted-witchcraft@ wyml @@ -7935,7 +7935,7 @@ hudtweaks@ mod-button opt-carpet-addition@ trees-do-not-float@ -cosmos-library +cosmos-library@ groovyscript@ potion-ring-reforged@|药剂环重铸 (Potion Rings - REFORGED)¨potion-ring-reforged-fabric|药剂环重铸 (Potion Rings - REFORGED) @@ -7971,7 +7971,7 @@ the-space-age-mod|太空时代* touhou-origins@|起源:东方 Project 附加包* earth-mobs@earthmobs|地球生物* -simpleores-tin +simplemetals-tin just-enough-keys¨just-enough-keys-fabric why-am-i-on-fire@ bouncing-balls-api@|弹跳球 API (Bouncing Balls API) @@ -8122,7 +8122,7 @@ randomlib @kinventory pullup@|飞行警报* id-primeval@primeval -colorfulazaleas@colorful-azaleas +colorfulazaleas@colorful-azaleas|杜鹃缤纷 (Colorful Azaleas) pigeon-post@ hunted-api @@ -8167,7 +8167,7 @@ explorify variant-bookshelves-fabric@variant-bookshelves variant-tiered-shields-fabric¨@variant-shields|Variant Shields - 已失效 libertys-villagers@libertyvillagers|自由村民 (Liberty's Villagers) -server-translation-api + rough-mobs-2|粗暴的怪物2* barren-isles korgelin @@ -8203,7 +8203,7 @@ potato-food compassplus create-industry@create-tfmg|机械动力:工业长路 (Create: Industry/Create: The Factory Must Grow) simple-splash-screen@ -unusual-prehistory-forge +unusual-prehistory-forge@ biospheres|生物圈 (Biosphere) qkl@ @@ -8271,7 +8271,7 @@ simple-copper-pipes@¨simple-copper-pipes-forge advanced-nbt-tooltips@|高级NBT提示框 (Advanced NBT Tooltips) follow-me@ stabx-modern-guns@ -justhammers@just-hammers +justhammers@just-hammers|锤子合集 (JustHammers) reinforced-deepslate-crafting|强化深板岩的合成* jaopcacustom|JAOPCA自定义 (JAOPCA Custom) friendly-fire@ @@ -8449,7 +8449,7 @@ xp-storage@ xp-storage-trinkets@ carpet-layers¨@carpet-layers|Carpet Layers - 已失效 itemswapper@ -@caxton +caxton@ treasure2-wizardry-loot-pack laser-creeper-robot-dino-riders-from-space|来自外太空的激光爬行者机器恐龙骑士* paladins-furniture@|Paladin的家具 (Paladin's Furniture) @@ -8625,7 +8625,7 @@ betterlands koremods@ heart-of-ender@ heart-of-spartan@ -macaws-fences-biomes-o-plenty|Macaw 的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty)¨macaws-biomes-o-plenty@|Macaw的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty)¨@macaws-fences-biomes-o-plenty|Macaw 的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty) - 原,已失效 +macaws-fences-biomes-o-plenty|Macaw 的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty)¨@macaws-fences-biomes-o-plenty|Macaw 的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty) - 原,已失效 endemic@ lucys-sloths @gdtweaker @@ -8751,7 +8751,7 @@ aetherium-ashen-armor smoothmenu|平滑菜单 (Smooth Menu) @cabricality-mod unusual-fish@unusual-fish-mod|独特鱼类 (Unusual Fish Mod) -obscure-tooltips +obscure-tooltips@ otg-tutorial-world @@ -8799,7 +8799,7 @@ epicfight-auto-shouldersurfing zsrarity zenscroll zentraits -l2-archery|莱特兰-弓艺* +l2-archery@l2archery|莱特兰-弓艺* vanity-metallurgical minespaceex gregious-maximus @@ -8902,7 +8902,7 @@ leos-illagers modularmachinery-community-edition@|模块化机械:社区版 (Modular Machinery: Community Edition) wearable-backpacks-rlcraft-edition@|可穿戴背包-RLCraft版 (Wearable Backpacks: RLCraft Edition) spartan-fire-rlcraft-edition@spartan-and-fire-rlcraft-edition|斯巴达之冰与火-RLCraft版 (Spartan and Fire: RLCraft Edition) -frame-void-patch-mc-59363 +frame-void-patch-mc-59363@frame-void-patch-(mc-59363) archprp@|帕秋莉手册加载补丁跨平台版 (Patchouli Resource Patch - Arch/Architectured Patchouli Resource Patch) clear-skies-forge-port spell-power@ @@ -8982,7 +8982,7 @@ workings@ lucky-cases atmosfera@ feruchemy-allomancy|藏金术 (Feruchemy) -alive-combat|战斗生机* - 已不再使用¨@alive-combat|战斗生机 (Alive Combat) +alive-combat@|战斗生机* dialog|对话框* @@ -9046,7 +9046,7 @@ invmovecompats@ hesperus|燐* inventory-hotswap corail-backpack -chomper +chomper|食人花* flamelib better-planting@ @@ -9084,7 +9084,7 @@ coherent-villages terrains -@bugtorch +bugtorched-curseforged|BugTorch - 他人的非官方上传¨@bugtorch better-tridents@|更好的三叉戟* undead-unleashed@ missing-wilds@|遗落荒野*¨missing-wilds-forge|遗落荒野 (Missing Wilds) @@ -9173,7 +9173,7 @@ ftb-team-islands-forge market-crates|集市板条箱* industrial-renewal-port|工业复兴移植版* -tfcm|群峦合金非官方版 (TFC Metallum U) +tfcm@|群峦合金非官方版 (TFC Metallum U) tnt-time-mod drunken-mug cartoonish-horde-library@villainous-horde-manager @@ -9201,7 +9201,7 @@ mob-captains|怪物队长* @sound-track snowballing -winter-essentials +winter-essentials@winter_essentials embeddium-extras@|镁/铷附属:土豆版 (Magnesium/Rubidium Extra: Potato Editon*) @realistic-cruelty|真实残忍 (Realistic Cruelty) trapper-pelts@ @@ -9321,7 +9321,7 @@ rl-artifacts|奇异饰品-RLCraft版 (RLArtifacts) azurelib@ harder-monster-boats-finer-boat-control@harder-monster-boats spyglass-improvements@|望远镜改进* -vanilla-zoom@ +vanilla-zoom@|原版缩放* time-in-a-bottled improved-end@ raw-block-smelting @@ -9531,7 +9531,7 @@ grass-overhaul@ @purpurclient alkimicraft-part-i-project-oasis wild-caves-3|洞穴装饰3 (WildCaves 3) -modest-mining +modest-mining@ vanilla-compats vending-block|自动售货机 (Vending block)¨vending-block-1-5|自动售货机 (Vending block) @@ -9592,7 +9592,7 @@ choo-choo-craft eco-mobs classic-hostiles chalk-fabric-colorful-addon@chalk-colorful-addon - +@asielib food-bosses-reheated dreamland-biomes @bedrockjukebox|基岩版唱片机 (BedrockJukebox) @@ -9681,7 +9681,7 @@ blocks-booster@ tfc-cast-iron-grill@|群峦铸铁烤架 (TFC Cast Iron Grill) tfc_metalwork|群峦金属加工 (TFC Metalwork) - +@gan_delight|赣味乐事与家常菜 (Gan Delight Reborn) furniture-compatibility-for-macaw-tfc|Macaw的家具:群峦传说附属 (Furniture Compatibility for Macaw/TFC) @@ -9927,7 +9927,7 @@ ecospherical-expansion@ ecospherical-depths arrhys-simple-currency better-wandering-trader¨@better-wandering-traders|Prelaw's Better Wandering Trader - 已失效 -creative-wireless-transmitter|创造无线发送器* +interdimensional-wireless-transmitter@|创造无线发送器* cozy-home@ inventory-crafter @@ -10007,7 +10007,7 @@ badges-by-harmex@cobblebadges2 terrarian-arsenal zombie-veterinarian|僵尸科兽医* just-enough-sculk-sensor -hearth-and-home +hearth-and-home@ better-leveling|更好的升级* applied-mana|应用魔法* boundless-flux @@ -10053,7 +10053,7 @@ kawaii-dishes@ keep-my-hand@ no-lan-cheating@nolancheating -goat-horn-block-mod|可放置山羊角 (Placeable Goat Horns) +goat-horn-block-mod@placeable-goat-horns|可放置山羊角 (Placeable Goat Horns) metallics-arts|金属技艺* actions-of-stamina @@ -10262,7 +10262,7 @@ paladins-and-priests@ cataclysm-heaven-burner@|灾变:焚天神兵 (Cataclysm: Heaven Burner) echo-chest@|回响箱子* -new-nether-chest@|下界箱子 (Nether Chested) +nether-chested@|下界箱子* plenty-plates@ simple-sponge new-paper-doll@paper-doll @@ -10273,14 +10273,14 @@ dtm-integrations @hdskins bushier-flowers@ create-gravity -cataclysm-boss-bar@|灾变Boss血条显示* +cataclysm-boss-bar|灾变Boss血条显示*¨@cataclysm-boss-bar|灾变Boss血条显示 (Cataclysm Boss Bar) - 已失效 unusual-gifts building-blocks-overhaul-forge tick-info primal-stage@primalstage spontaneous-replace@|自然更替 (Spontaneous-Replace) -pixel-loader|像素加载器 (pixel loader) +pixel-loader|像素加载器2 (pixel loader2) kubejs-ars-nouveau@ cataclysmweaponery@cataclysm-weaponery twilight-paintings @@ -10306,7 +10306,7 @@ froglight-backport exquisito@|末域奇馔* species@ cles-battleitems|战斗物品 (Cle's BattleItems) -unu-parts-pack +unu-parts-pack@ spleaves@ modernworldcreation@modern-world-creation enchant-the-rainbow@ @@ -10349,7 +10349,7 @@ aether-expansion barrels-2012 @libzoomer tfc-sample-drill -tfc-rosia +tfc-tng-rosia¨tfc-rosia aether-additions custom-portal-api-forge@customportalapi|自定义传送门 API Forge 版 (Custom Portal API [Forge]) jaopca-extras @@ -10454,7 +10454,7 @@ varied-adventure kubejs-delight block-physics-overhaul searchlight-forge|探照灯 (Searchlight (& Wall Lights))¨searchlight|探照灯 (Searchlight (& Wall Lights)) -ancient-manuscripts@ +ancient-manuscripts@|远古手稿* gazebos@ scoreboard-get-time|记分板获取时间 (scoreboard get time) modern-keywizard|现代化按键精灵 (Modern KeyWizard) @@ -10722,7 +10722,7 @@ easy-enchanting@|轻松附魔* elitia cosmetic-wings deepspace|深空 (DeepSpace) -death-backup@ +death-backup@|死亡备份* bear-with-me create-diesel-generators@|机械动力:柴油动力 (Create: Diesel Generators) vintagefix@ @@ -10812,7 +10812,7 @@ more-armor-trims|更多盔甲纹饰* mc-mods-the-trims@allthetrims @bamboo-sign-editor|告示牌编辑器 (Bamboo Sign Editor) revival-orb@ -stackable-trims +stackable-trims@ keepcuriosinventory|饰品栏优化 (KeepCuriosInventory) megachicken@mega-chicken s33r-iris-azalea-mod @@ -10832,7 +10832,7 @@ benefits-of-reading|雕纹书架注魔/阅页读著 (Benefits of Reading) gothic-rpg@ arcadian-dream@|幻想之梦* -dungeon-and-taverns +dungeon-and-taverns|地下城与酒馆 (Dungeons and Taverns) elytra-trims-extensions@ fallingblocks fabric-musica @@ -10855,7 +10855,7 @@ armorskin@ simple-health-bar@ plenty-of-golems @hit_particles -@durability-viewer-updated +@durability-viewer-updated|耐久指示器更新版 (Durability Viewer Updated) puffish-skills@skills better-archeology@|更好的考古学* fake-people|假人* @@ -11062,7 +11062,7 @@ faster-crouching@ yeetusexperimentus@yeetus-experimentus xbob@|准星摇晃 (Crosshair Bobbing) zombie-horse-spawn@¨zombie-horse-spawn-fabric -tinkers-katanas +tinkers-katanas|匠魂武士刀 (Tinkers' Katanas) fbp yeetem-potions@ incapacitated@ @@ -11212,7 +11212,7 @@ easy-tweak@ netherless-quartz @maple-api -@reforged-fabric-api +reforged-fabric-api¨@reforged-fabric-api caffeinated@ buildcraft-rf gilded-ingot@ @@ -11413,7 +11413,7 @@ dangerousstonecutter@ normal-damage@ ftb-xmod-compat crabbers-delight@|蟹农乐事 (Crabber's Delight) -slot-cycler +slot-cycler@ resource-pack-overrides@ respawning-animals@|动物重生* arcane-lanterns@ @@ -11526,7 +11526,7 @@ vault-research the-chaser bandaging enchanted-charms@ -@nether-api +nether-api@ mother-silverfish mcretector@ @@ -11551,11 +11551,11 @@ eidolon-rebrewed@ fruity-random-teleport|紫颂果味随机传送* pipe-master-2000|管道专家 2000 (Pipe Master 2000 / Pipe Googles) command-macros@|命令宏* -@shwetro|上海地铁装饰组件 (The Shanghai Wetro Mod) +@shwetro|上海地铁装饰组件 (MTR Shanghai Wetro Addon) yuushya-modelling@|方块建模* tactical-aid@|战术医疗* @clear-enchanting -fishing-bobber-detector +fishing-bobber-detector@create-fishing-bobber-detector @unfix-mc-181190|村民折扣叠加 (Unfix MC-181190) create-balanced-flight brewin-and-chewin-remastered @@ -11664,7 +11664,7 @@ loot-stages@|战利品阶段* player-stages|玩家阶段* generation-stages|生成阶段* ars-oscura@|黑暗魔艺* -bobby-reforged +bobby-reforged|服务器区块缓存-重铸* shut-up-gl-error@ no-spider@ terracart-reloaded@ @@ -11710,7 +11710,7 @@ trenzalore -creo-lib@creo-api¨@creo-lib|Creo Lib - 已失效 +creo-lib¨@creo-lib|Creo Lib - 已失效 highlight@ lost-libraries @plant-pearl @@ -11721,7 +11721,7 @@ eastward-journeys@ omotions@ invisible-frames -ultimatebackportmod +ultimatebackportmod@ubm true-ending @@ -11754,12 +11754,12 @@ dangerous-oceans @featherfallflying @lidar anvil-never-too-expensive@ante -@crossbow|弩 (Crossbow) +crossbow-mod@crossbow|弩 (Crossbow) @place-item controlify@ @bedrodium - +xbbs-hot-air-balloon-fantasy@|XBB的热气球幻想 (XBB's hot air balloon fantasy) netherite-horse-armor-cj polydex@ plenty-of-armors@ @@ -11783,7 +11783,7 @@ totally-enough-pain@ custom-village|村民调教* animaticareforged shear-cows -fps-boost¨better-fps|FPS Boost - 已失效 +fps-boost afkfish@ @rend @@ -11794,7 +11794,7 @@ botany-ores@ @cauldron-fix|炼药锅修复 (Cauldron Fix) foods-plants-cooking-with-mindthemoods @customgui|自定义 GUI (Custom GUI) -keebszs-battletowers-fabric@keebszs-battletowers +keebszs-battletowers-fabric¨@keebszs-battletowers|Keebsz's Battle Towers - 已失效 as-a-creeper|×的和你爆了 (As a creeper) smarterwanderers @@ -11818,13 +11818,13 @@ kubejs-additions@ betterdays@ @seedy-place extradetails -@signed-paintings +signed-paintings@ inenchantment-reconstruction|负魔与改造 (Inenchantment & Reconstruction) nae2|Neeve的AE2:EL附加工具 (Neeve's AE2: Extended Life Additions) the-microworld vionta -hammer-animations +hammer-animations@ blueflame@ nocubes-craftable-music-discs @@ -11902,7 +11902,7 @@ more-creepers|更多苦力怕* cryptic-registry@ -level-text-fix +level-text-fix@ binnies-mods-patched@ cyanide@|氰化物*¨cyanide-fabric|氰化物 (Cyanide) @particlesenhanced @@ -11937,7 +11937,7 @@ valkyrie@ soaring-structures-2|Soaring Structures 2 - 完整版¨soaring-structures-2-clean-version-fabric|Soaring Structures 2 - 轻量版 prehistoric-nature-ordovician-dimension fadeless@ -create-tweaked-controllers@ +create-tweaked-controllers@|机械动力:高级遥控器 (Create: Tweaked Controllers) auto-elytra@ villager-names@villager-names-serilum @@ -11966,7 +11966,7 @@ omnom@ forest-x-reborn@ exlines-bark-carpets@bark-carpets exlines-more-carpets -ne|猫艺:附魔 (NekoEnchantment) +srsl@|星骸秘遗 (Star Remains Secret Legacy) unearthed-journey@|出土之旅* prehistoric-nature-devonian-dimension mwtis-stone-expansion@stone-expansion @@ -12082,7 +12082,7 @@ skibidi-toilet-stevebidop better-impaling-fabric vanadium-mod@vanadium|钒 (Vanadium) loginar-storage@ -@jewelry +jewelry@ elytra-drag@ night-vision-flash-be-gone @@ -12135,7 +12135,7 @@ infiniverse-utils coreextensions@|核心拓展 (CoreExtensions) @fast-items toad-terror -@modern-industrialization-sound-addon +modern-industrialization-sound-addon@ achievements-optimizer@ automessage@auto-message ftb-quests-optimizer@ @@ -12161,7 +12161,7 @@ euphoria-patches@ poke-ball|精灵球 (pokeballhv) aquamirae-mod-music@aquamirae-mod-extra-music deathmatch -@smooth-scroll|平滑滚动 (Smooth Scroll) +@smooth-scroll|平滑滚动 (Smooth Scrolling) toad-terror-battle-music @@ -12203,7 +12203,7 @@ akkamaddis-ashenwheat@ prehistoriclegacy redyed enchantmentdisabler@enchantment-disabler -miners-glasses +miners-glasses|矿工眼镜 (Miner's Glasses) relics-and-alchemy@ @greate advanced-world-selection @@ -12247,7 +12247,7 @@ jei-recipe-history@ @yeet macaws-paths-and-pavings@|Macaw的小径和路面 (Macaw's Paths and Pavings) chiseled-bookshelf-visualizer@ -@elytra-skins +elytra-skins@ mcv-biome-makeover-lieonlion@ more-chest-variants-lieonlion@ more-crafting-tables-mct@more-crafting-tables-lieonlion @@ -12255,7 +12255,7 @@ more-crafting-tables-mct@more-crafting-tables-lieonlion akkamaddis-classical-alchemy|Classical Alchemy - 新 @big-pony|彩虹大马 (Big Pony) qraftyfied -@theft +theft-mod@theft akkamaddis-golden-glitter@ @obsidianbucket enhanced-annihilation-planes @@ -12321,7 +12321,7 @@ terraria-calamity-mod|泰拉瑞亚灾厄 (Terraria Calamity) darkrtp-rtp-random-teleport@darkrtp cute-bears additional-fruits@ -bigger-better-end-cities@|更好的末地城* +bigger-better-end-cities@|更大更好的末地城* @some-peripherals reden@ sparse-structures@sparsestructures @@ -12371,11 +12371,11 @@ fizzle grimoire-api cristel-lib@ -more-sleeps@more_sleeps +more-sleeps@sleepquality o-coracao-da-selva creative-experience more-than-50 -underground-jungle@ +underground-jungle@underground-worlds forge-soul-like-armors-reworked meds-and-herbs@ soul-like-monsters-epic-fight-addon @@ -12388,7 +12388,7 @@ nohurtcam-plus@nohurtcam+ darker-souls @spooky-bats -swutms-extra-dungeons@ +swutms-extra-dungeons|SWUTM's Extra Dungeons - 已失效¨@swutms-extra-dungeons swutms-extra-desert@ @refill-chests @@ -12405,9 +12405,9 @@ mob-variants galvanization biome-grass-available@ pigment@ -@sunrise-sunset-audio +sunrise-sunset-audio@ halloween-nighmare -lookin-sharp@ +lookin-sharp@|锋芒毕露 (Lookin' sharp) halloween-luckyblocks spooky-halloween isle-of-berk-variant-loader@ @@ -12499,9 +12499,9 @@ tf2-stuff-reloaded|军团要塞2重制版 (TF2 Stuff - Reloaded) time-in-a-bottle-curio-support absolute-ultracraft@ more-layered-blocks@ -hybrid-aquatic@ +hybrid-aquatic@|混合水生* spectre-creep -resource-chickens +resource-chickens|资源鸡* storage-crate@|板条箱* oculus-particle-fix@ @@ -12588,7 +12588,7 @@ better-chunk-loading-forge-fabric mushroomified soul-like-boss-epic-fight-addon -moderately-enough-effect-descriptions-meed-a-jeed-addon@moderately-enough-effect-descriptions-(meed)-jeed-addon +moderately-enough-effect-descriptions-meed-a-jeed-addon¨@moderately-enough-effect-descriptions-meed|Moderately Enough Effect Descriptions - 新¨@moderately-enough-effect-descriptions-(meed)-jeed-addon|Moderately Enough Effect Descriptions - 已失效 alexs-caves@|Alex 的洞穴 (Alex's Caves) bow-spam-be-gone@ lmdcraft @@ -12732,7 +12732,7 @@ modular-item-api@¨truly-modular-arsenal@¨truly-modular-archery@¨truly-modular @actionhunger trafficcraft@ banilla-hammers@ -everyx-hotpot|一起火锅 (EveryXHotpot) - 他人代传¨@every-x-hotpot|一起火锅 (EveryXHotpot) +everyxhotpot@every-x-hotpot|一起火锅 (EveryXHotpot)¨everyx-hotpot|一起火锅 (EveryXHotpot) @hira-hira tierability@ soul-collector@ @@ -12746,12 +12746,12 @@ echo@ lanthanum@|镧* charge-thrower - +champions-ashes|王者遗灰 (Champion's Ashes) jade-addons-ic2classic@ic2cjadeaddon sit1@sit! monnef-core -robs-floating-islands +robs-floating-islands@ glasses@|眼镜* @@ -12850,7 +12850,7 @@ gcyr@ gregtechceu-modern@|格雷科技现代版 (GregTechCEu Modern) @intarsia|咒术器具 (Intarsia) instant-village-building -venus@ + nbt@ separated-leaves@separatedleaves @@ -12927,7 +12927,7 @@ create-creating-space@creating-space asmc-performance-patch mrcrayfishs-gun-mod-unofficial|MrCrayfish的枪:非官方版 (MrCrayfish's Gun Mod: Unofficial) @fish-tanks -4913730@ram-compat +ram-compat@ @wcsm|中国式学校 (Chinese School Mod) zombies-reworked-forge@zombies-reworked legendary-monsters@|传奇怪物* @@ -12954,7 +12954,7 @@ grassnotfloating|浮的古原草送别 (GrassNotFloating) thermite@ restriction -@collision-fix +collision-fix@ dynamic-name-tags@ @profundis @@ -12991,7 +12991,7 @@ recall-staffs enercell|能量电池* default-world-generator-ssp -homyatos-explosives +homyatos-explosives@ infchest@|无限箱子 (Inf Chest) @squashed unremovableeffects@unremovable-effects @@ -13034,7 +13034,7 @@ minetraps@|我的陷阱 (MineTraps) honey-sticky-piston ore-discovery-broadcast@ alchemists-garden -opolis-strainers +opolis-strainers@bbl-strainers book-eater@ rotom-phone-cobblemon@ @world-comment|随处随笔 (World Comment) @@ -13105,7 +13105,7 @@ biospherical-expansion@ create-connected@|机械动力:创意传动 (Create: Connected) @rgml-quilt hbms-nuclear-tech-hamster-reloaded -@noxesium +noxesium@ infinity-cave|无限洞穴* surface-lush-caves @@ -13117,7 +13117,7 @@ christmas-dweller create-curio-goggles@ @cobbleloots dontusecommand@|请别用指令 (DontUseCommand) -beans-backpacks@ +beans-backpacks@beans-backpacks-3¨@beans-backpacks randomjs@ piston-push@ @@ -13137,10 +13137,10 @@ metabolism@|新陈代谢* adv-js|Advancement JS - 更新较慢¨@advancementjs teleportcraft@ frights-delight@|惊悚乐事 (Fright's Delight) -travelers-compass@ +travelers-compass@|旅行者指南针 (Traveler's Compass) @artemis-thin-logs configjs@ -hardrock-tools-materials +hardrock-tools-materials@ animals-trap armor-damage-scaling-forge-fabric hornet @@ -13202,7 +13202,7 @@ cenozoic-nightmare-the-horror-of-prehistory quickbackupmulti@|多槽位备份 (QuickBackupMulti) slashblade-japanese-addon-pack-reforked|拔刀剑日系附属重置版 (SlashBlade Japanese Addon Pack Reforked) @create-fabric-fixes - +@command-block-sceptre|命令方块权杖 (Command Sceptre) crate-delight-forge@crate-delight|装箱乐事 (Crate Delight)¨crate-delight-fabric|装箱乐事 (Crate Delight) cultural-creators-forge-create-and-cultural@cultural-crators¨cultural-creators-fabric-create-and-cultural shuffle@ @@ -13215,8 +13215,8 @@ cerbons-api-forge@cerbons-api arcane-apprentices kirara nether-gold -create-interactive|Create: Interactive - 已放弃¨@interactive -this-tsunami +create-interactive@interactive|机械动力:交互学 (Create: Interactive) +this-tsunami@ blendium@ awakened-bosses survive-in-the-winter-frontier@misc_twf|冬境边域:生存法则 (Survive in the Winter Frontier) @@ -13271,7 +13271,7 @@ expanded-combat@ fancy-chests@ -glowing-ores-glore@glore +glore@ pi-privateisland|私人空岛 (PrivateIsland) i-didnt-mean-it-that-way@ finitewater@ @@ -13334,7 +13334,7 @@ vics-point-blank@ occultism-kubejs@ tetra-extras@ anilink -overworld-netherite-ore@ +overworld-netherite-ore¨@overworld-netherite-ore|Overworld Netherite Ore - 已失效 @rebindmykeys keybind-description-fix kappas-forgelin @@ -13415,7 +13415,7 @@ green-soul everyxdance@|一起跳舞! (EveryXDance) applied-flux|应用通量* -tics-delight@tinkers-delight|匠魂乐事 (Tinkers' Delight) +tics-delight@tinkers-delight|匠魂乐事 (Tinkers' Delight / TiC's Delight) unnamed-desert-structures@unnamed-desert bloomingnature@lets-do-bloomingnature|绽芳自然 (Let's Do: BloomingNature) waila-inhibitors@ @@ -13606,7 +13606,7 @@ anvilfixreborn@anvil-fix-reborn|铁砧修复重生 (Anvil Fix Reborn) apothic-curios hbm-ntm-lucky-blocks thaumtweaks -no-baby-zombies¨@no-baby-zombies|No Baby Zombies - 已失效 +no-baby-zombies@ combat-nouveau@ @hanging-on @@ -13698,7 +13698,7 @@ notenoughresources elenai-dodge xenon@xenon-forge|氙* riftstorm-rpg - +data-api better-babies morecreativetabs-forge-fabric@morecreativetabs @@ -13771,7 +13771,7 @@ lavender-api@lavender cheatutils-by-zergatul jump-sounds¨@jump-sounds|Jump Sounds - 已失效 -@kirin +kirin@ llamapalooza instant-drown acsguis @@ -13789,7 +13789,7 @@ dynamic-craft startinginventory wireless-speakers|无线扬声器* sagerfx@|SA特效 (SagerFX) - +@noteit jewelrycraft-2|珠宝工艺2* fallflyinglib enchantable-spike @@ -13843,7 +13843,7 @@ enchantment-reveal@ copycats@|机械动力:伪装方块+ (Create: Copycats+) the-great-alchemical-compendium@ -desperate-measures@ +desperate-measures¨@desperate-measures|Desperate Measures - 已失效 block-slabs-mod roost-re-hatched lifedrain @@ -14032,7 +14032,7 @@ natural-nether-portals hotbaaaar@|忄夬扌疌木兰 (HotBaaaar) ui@ui-lib @random-items -lucky-clover|Lucky Clover - 已失效¨@lucky-clover +lucky-clover|Lucky Clover - 已失效¨@lucky-clover! @better-portals|更好的传送门 (Better Portals) social-distance@|社交距离* gt@gtnn @@ -14042,7 +14042,7 @@ melee-ex-machina-meexma@melee-ex-machina arc@ cores@ -@citymod|我的城市:交通与生活 (CityMod:Traffic and life) +citymod-traffic-and-life@citymod|我的城市:交通与生活 (CityMod:Traffic and life) too-hard-to-dig@|隐岩砥凿 (Too Hard to Dig) simplecobblestonegenerator@simple-cobble-gen natural-baby-animals @@ -14054,7 +14054,7 @@ totem-of-immortality@ crystalline-ender-eye|晶化末影之眼* ars-caelum@ elytra-chestplates|鞘翅之甲* -variants-and-ventures@ +variants-and-ventures@|变种与冒险 (Variants & Ventures) horseshoes@|马掌* questlines@ endofherobrine@ @@ -14085,7 +14085,7 @@ multiscoreboard@ @cover-them-in-armor jerotes-village|Jerotes村庄-二轮世界 (Jerotes Village - Second Round World) @a-cold-day-in-the-nether -@translucencyfix +translucencyfix@ create-ponder@|机械动力:思索* mopeds @@ -14102,7 +14102,7 @@ dragon-finder respackopts@ fortuitous-feasts@ monazite@|独居石* -music-control|音乐控制* - 不再更新¨@music-control|音乐控制 (Music Control) +music-control@|音乐控制* stellarcore|星核 (StellarCore) ash-of-sin-custom-entity-attack-effect|罪业余烬:自定义实体攻击状态效果 (Ash Of Sin: Custom Entity Attack Effect) @@ -14174,7 +14174,7 @@ tfc-aged-alcohol tfc-improved-badlands@ @russian-metro-addon winter-massacre -ghostlys|幽灵 (Ghostly) +ghostlys@ghostly|幽灵 (Ghostly) tectonic-tweak forge-essentials-client@ @world-host @@ -14195,7 +14195,7 @@ descension toggledpickup|拾取控制 (Toggled Pickup) farmers-delight-refabricated@|农夫乐事:重织 (Farmer's Delight Refabricated) village-nullifier-terralith-mod -village-nullifier-mod + structure-credits@¨structure-credits-fabric-version i-wanna-travel simple-unbreakable-tools @@ -14265,7 +14265,7 @@ ducky@ducky! lost-idols simple-trashcan@ darwin-water-ambience@ -decorative-wooden-lattices +decorative-wooden-lattices@ apotheotic-additions lithosphere @@ -14294,7 +14294,7 @@ ash-of-sin-better-ai|罪业余烬:更好的AI (Ash Of Sin: Better AI) dimensional-cake-rebuilt@dimensional-cake-rebaked medieval-village-dimension create-big-contraptions@big-contraptions - +gtnhs-forgelin@ rose-gold-equipment@ pantheonsent@ @@ -14345,7 +14345,7 @@ runiclib@ dragon-mounts-more-dragons@|龙骑士附属:更多龙 (Dragon Mounts: More Dragons) -hey-wiki@|嘿Wiki* +hey-wiki@ turf@ ad-extendra@ad_extendra smava @@ -14378,7 +14378,7 @@ mo-enchantments soul-like-enchantment @blockbench-import-library -mrcrayfish-furniture-mod-legacy +mrcrayfish-furniture-mod-rebuilt btfixes vmfixes pillar-patched @@ -14426,7 +14426,7 @@ tfc-barrens@ chiselse-bits-for-tfc@chisels-bits-for-tfc tfc-fresh-water-everywhere@ tfc-more-swords -galleria¨@galleria|Galleria - 已失效 +galleria@ constructs-casting@ tinkers-innovation@tinkersinnovation|工匠创新 (Tinkers' Innovation) @@ -14436,7 +14436,7 @@ harvest-tweaks decay-2012 tfc-grooming-station terrafirmagregtech-ores -blaze-map +blaze-map@ epic-knights-japanese-armory tfcpyros-adjustedgen astikor-carts-tfc-florae @@ -14446,7 +14446,7 @@ the-slenderman-mod@the-slenderman flanchanmod cartography -laser-bridges-doors@wz31BboV +laser-bridges-doors@laser-bridges-and-doors lukis-grand-capitals@ fabric-create-netherless create-connected-fabric-experimental@create-connected-fabric @@ -14503,7 +14503,7 @@ souls-egg barely-enough-enchantments tool-kit@ @filament -lijms-armory@ +lijms-expanse@ tinker-delight@tinkers-construct-delight|匠魂乐事 (Tinkers Delight/Tinkers Construct Delight) barbeques-delight@|烧烤乐事 (Barbeque's Delight) coffeecraft-by-blockbrothers@coffeecraft @@ -14674,7 +14674,7 @@ the-fletching-table-mod@ customemoji|自定义表情 (CustomEmoji) sword-soaring|史诗战斗:御剑凌虚 (Epic Fight - Sword Soaring) bigger-ae2@ -scalar¨scalar-legacy|Scalar - Scala 2.11,一般使用此 +scalar¨scalar-legacy trials-chambers-backport@ blus-sign-pack-mts-iv alexs-mediumcore@ @@ -14706,7 +14706,7 @@ paniclecraft cpmpvc@|自定义玩家模型Plasmo语音兼容 (Customizable Player Models Plasmo Voice compat) ydms-gunblades-mod@ydms-gunblades warp-pads@warppads -marbleds-arsenal¨@marbledsarsenal|Marbled's Arsenal - 已失效 +marbleds-arsenal@ @inventory-tabs mob-spawning-fix|生物生成修复* lh-miracle-road|奇迹之路之魂系加点 (LH Miracle Road) @@ -14751,7 +14751,7 @@ astrocraft-mod@astrocraft neat-shaders-fix@|极简血量显示 - 光影修复版 (Neat - Shaders-Fix) worldtooltips-shadersfix@|掉落物信息显示 - 光影修复版 (WorldTooltips - ShadersFix) - +ars-repairing|新生魔艺:修复纤维强化* options-enforcer@ macaws-bridges-biome-o-plenty|Macaw 的桥梁:超多生物群系附属 (Macaw's Bridges - Biomes O' Plenty) - 已被更新版顶替¨@macaws-bridges-biome-o-plenty|Macaw 的桥梁:超多生物群系附属 (Macaw's Bridges - Biomes O' Plenty) renhider@ @@ -14812,7 +14812,7 @@ more-default-options treecapitator-updated|砍树更新版* @offhandlights|副手光源 (Offhand Lights) -keep-your-mouth-healthy|保持口腔健康* +dental-handbook@|牙科手册* prehistoric-nature-jurassic-dimension many-more-structures@¨many-more-structures-depricated@more-structures @@ -14841,7 +14841,7 @@ rolling-down-in-the-deep@ dwarven-realm|矮人诸国* renewed-eras-of-arda-addon|阿尔达纪元:复兴 (Eras of Arda: Renewed Addon (First and Second Age)) legendary-item@|魔戒传说物品* -lotr-middleearth-tweaks|中洲微调 (MiddleEarth Tweaks) +lotr-middleearth-tweaks@middleearth-tweaks|中洲微调 (MiddleEarth Tweaks) thaumcraft-integration@ reach-display@ gravitation-suite-patcher @@ -14849,7 +14849,7 @@ anti-rat-dungeon modchu_defaultmodelset create-storage@fxnt-create-storage avaritia-item|无尽特效物品* -kingdom-of-rin-regions +kingdom-of-rin-regions@regions durability-speed-legacy-backport inventory-on-death-mod berries-cherries-delight@berries-and-cherries @@ -14876,7 +14876,7 @@ life-is-precious|生命是宝贵的 (Life is precious) advanced-skills@|进阶技能* nuts-delight -vegetables-delight@ +vegetables-delight|Vegetables Delight - 已失效¨@vegetables-delight enderpack@ ingame-mod-configs|游戏内模组配置 (InGame Mod Configs) playertag|玩家标签 (PlayerTag) @@ -14902,7 +14902,7 @@ spawn-mod@ @create-tab-fix scape-and-run-origins@srp-origins origins-extra-keybinds@origins-extra-keybinds-fabric -epic-knights-addon +epic-knights-addon@ @arknights-skadi-orca-plushie|【明日方舟】斯卡蒂的虎鲸抱枕 ([Arknights] Skadi Orca Plushie) sona-survival-101|唢呐生存指南* @@ -14916,7 +14916,7 @@ frostbytes-improved-inventory@ mekanism-extras@mekanism_extra|通用机械:扩展* leaf-beds@ unidentified-enchantments@unidentified_enchantments -protection-pixel|机素防护*¨@protection-pixel|机素防护 (Protection Pixel) - 已失效 +protection-pixel|机素防护 (Create: Protection Pixel)¨@protection-pixel|机素防护 (Create: Protection Pixel) - 已失效 createdpcompat@ @@ -15005,7 +15005,7 @@ vtones heavy-inventories|物品负重* vs-addition@ gravity-torch@ -lifesteal-forge-fabric@ +lifesteal-forge-fabric@lifesteal-mian mores-by-its-infinite dragonite-gear lets-do-addon-fluids@ @@ -15073,7 +15073,7 @@ addurdisc@|自定义唱片 (AddurDisc/Add your disc) enigmatic-addons@|神秘遗物扩展* @bloodscythe -dungeons-delight@ +ddgourmetbattles@dungeons-delight-gourmet-battles stronger-potions@|更强的药水* @@ -15100,7 +15100,7 @@ command-macro-key@|命令宏键* realm-rpg-quests-rewards @roadworks unlucky-tnt@ -fastboot@ +fastboot¨@fastboot|FastBoot - 已失效 tardis-refined@|改良的TARDIS (TARDIS Refined) ocean-villager-trader@ @@ -15143,7 +15143,7 @@ ribbits@ raws-visual-keybinder - +synthetic-products|合成配方* scoreboard-tweaks@scoreboardtweaks rsinfinitewireless @@ -15242,7 +15242,7 @@ more-apples-hyj|更多苹果 (More Apples) fancy-enchantments@|奇异附魔* ash-of-sin-adventure-dimension|罪业余烬:冒险维度 (Ash Of Sin: Adventure Dimension) push-forward@|猪突猛进* -ymn@|你现在是我的了! (You're Mine Now!) +ymn|你现在是我的了! (You're Mine Now!) - 已失效¨@ymn|你现在是我的了! (You're Mine Now!) thermal-endergy@ mekanism-turrets-fences@mekanism-turrets nuka-cola-classic @@ -15263,7 +15263,7 @@ taxoceanarchitect nethers-flora microcosm -chinese-flying-island-tower +chinese-flying-island-tower@ @netherite-tools-for-crackers-witherstorm-mod better-maces jack-o-launcher @@ -15279,12 +15279,12 @@ camerapture@ simple-voice-radio@simple-radio adorned -fence-on-slab +fence-on-slab@fence_on_slab monochromatic-blocks@ - +archaicfixfix @inventive-inventory boxlib@ the-shattered-goddess-mokels-bossfight-saphyra @@ -15307,7 +15307,7 @@ smartercontraptionstorage@smarter-contraption-storage|智能动态结构存储 ( lexiconfig@ @advancementsfullscreen|进度全屏 (AdvancementsFullscreen) -luckys-wardrobe@ +luckys-wardrobe@luckyowls-wardrobe @forkkit more-affordable-than-ever-never-too-expensive@more-affordable-than-ever what-am-i @@ -15332,7 +15332,7 @@ yumo-compactmachines-por@|压缩空间:产量推演 (yumo compactmachines por) dynamic-trees-for-fruit-trees|动态的树:妙趣果园 🍊附属 (Dynamic Trees for Fruitful Fun 🍊) dynamic-trees-for-better-end|动态的树:更好的末地附属 (Dynamic Trees for BetterEnd) dynamic-trees-autumnity|动态的树:秋原附属 (Dynamic Trees - Autumnity) -dynamic-trees-for-bloomingnature|动态的树:BloomingNature附属 (Dynamic Trees for BloomingNature) +dynamic-trees-for-bloomingnature|动态的树:绽芳自然附属 (Dynamic Trees for BloomingNature) dynamic-trees-terralith@|动态的树:Terralith附属 (Dynamic Trees - Terralith) dynamic-trees-for-meadow|动态的树:青青草甸附属 (Dynamic Trees for Meadow) dynamic-trees-for-beachparty|动态的树:沙滩派对附属 (Dynamic Trees for Beachparty) @@ -15397,7 +15397,7 @@ dragon-enchants item-placer@ expandedworld monster-hunter-villager@ -jcraft-eyes-of-ender +jcraft-eyes-of-ender@jojo-eyes-of-ender @return-dirt-background @reset-keys-confirmation-screen @@ -15410,7 +15410,7 @@ unseaworthy@ @chromaanvils thermal-and-space@ create-curios-jetpack@ -epic-knights-antique-legacy +epic-knights-antique-legacy@ viking-samurai-knight-items barely-simple-staffs@barely-simple-staff @@ -15456,8 +15456,8 @@ more-lectern-variants@ @ase lush-cave-trader@ oceans-enhancements@ -xp-jellies -aromatic +xp-jellies@ +aromatic@ onekeyminer-nf@onekeyminer_nf|一键连锁(Neo)Forge (OneKeyMiner(Neo)Forge) retargetato@|约谊敌众 (ReTargetato) @@ -15465,7 +15465,7 @@ iceandfire-ce@|冰火传说社区版 (IceAndFire Community Edition) tomanos-vehicle-mod ancient-sands@ hs-bosses -chubby-mobs +chubby-mobs@ repair-gem|修复宝石* better-weaponry-compatible-with-better-combat@better-weaponry disable-front-perspective@ @@ -15509,7 +15509,7 @@ fallen-wizards-more-magic-series@ @whiffowisp mango-cats -tax-tree-giant +tax-tree-giant@tree-giant taxskyvillager compostable-rotten-flesh villager-in-a-bucket@ @@ -15533,7 +15533,7 @@ kasugalib@ encounter-mob-ai-tweaker|遭遇/长驱速弑 (Encounter) kubejs-mekanism-unofficial @mediaplayer|媒体播放器 (MediaPlayer) -bakery-villager-trader +bakery-villager-trader@ nebulus-trader-for-the-plains mokels-more-spiders @@ -15543,7 +15543,7 @@ skinchanger@ @halflifemenu sitting@sitting+ forgotten-east@ -brazil-legends +brazil-legends@ custom-item-despawn-duration@|自定义掉落物消失时间* your-items-to-new-worlds@ @@ -15573,7 +15573,7 @@ mounted-pearl-bring-your-mount-along-when-you biome-id-fixer immortal-coral@ immersive-guns@ - +guns-plusplus additional-rpg-jewelry@ mekanism-ad-astra-ores@ question-ducks-ambient-fireflies @@ -15652,7 +15652,7 @@ no-render@ runningshoes|跑鞋 (Running Shoes) fluid-void-fading@fluidvoidfading @movement-in-gui -glowing-eyes@glowing-eyes#all-versions +glowing-eyes@ youarch true-darkness-fabric@ esf-entity-sound-features@esf|实体声音特性 (Entity Sound Features) @@ -15661,7 +15661,7 @@ smooth-particles@ unloaded-activity@ @improperui -neverenoughanimation +neverenoughanimation@ reds-stagger-parry additional-placements@¨additional-placements-fabric@ akicaters-shelves@akicaters-shelf-mod @@ -15709,7 +15709,7 @@ phantom-flames darktitlebar@ cubes-without-borders@ block-parry-dodge -fargos-talismans|Fargo的护身符 (Fargo's Talismans) +fargos-talismans@|Fargo的护身符 (Fargo's Talismans) toostrongmonster simplystronger tacz-durability @@ -15746,7 +15746,7 @@ beddium@ create-thermal-compat create-lagless@ create-ironworks@ -@create-vibrant-vaults +@create-vibrant-vaults|机械动力:炫彩金库 (Create: Vibrant Vaults) steam-power kevs-library@ remove-purpleprison-ads @@ -15770,10 +15770,10 @@ villager-venture alekiships@ alekishipsbop@ antique-trading-ship@ -small-horse-stable +small-horse-stable@ shrek-the-ogre@shrek -the-anomaly +the-anomaly@the-anomaly-rephased desert-behemoths-sandworms@desert-behemoths-sandworms! vermintide-rising @@ -15825,7 +15825,7 @@ airport just-dandy@ @uk-roads my-totem-doll@my_totem_doll - +void-power-for-fabric|虚空动力 (Void Power) dis-enchanting-table@ @@ -15852,21 +15852,21 @@ nbt-tool dglab-x-minecraft¨@dglab-x-minecraft|Minecraft x DgLab - 已失效 -twintails|双马尾 (TwinTails)¨twin-tails|双马尾 (TwinTails) +twintails@|双马尾 (TwinTails)¨twin-tails|双马尾 (TwinTails) gt-wireless|格雷无线科技 (GT Wireless) forcejava21@|强制 Java21 (ForceJava21) blessings@|祝福* crafted-core@ call-from-the-depths|深渊召唤*¨@call-from-the-depths|深渊召唤 (Call From The Depths) - 落后 更新不及时 @clover-fabric|四叶草Fabric版 (Clover Fabric) -champions-unofficial@|冠军/强敌再续 (Champions-Unofficial) +champions-unofficial@|冠军/强敌再续* aetherworks-refracted@ eidolon-repraised@eidolonrepraised -create-northstar@ +create-northstar@|机械动力:北极星 (Create: Northstar) create-pattern-schematics@|机械动力:模式蓝图 (Create: Pattern Schematics) magic-merchant aether-villages@|天境村庄* -tricky-trials@tricky-trials-1.21 +tricky-trials|Tricky Trials 1.21 - 已失效¨@tricky-trials-1.21 the-boys-minecraft|黑袍:新世界 (The Boys : New world) @@ -15907,7 +15907,7 @@ reairdrop-supply@|空投供应:重生 (ReAirdrop Supply) @minequery|MC宝 (MineQuery)¨@minequery-legacy|MC宝 (MineQuery) @simply-create-model-mod|简单动力 (Simply Create Model) -mountains-poem@|峰峦辞 (Mountain's Poem) +mountains-poem@|青丘辞 (Mountain's Poem) @playtime-reborn|游戏时长计数器:重生 (Playtime Reborn) moon-between-medicine|在月亮之间:药学拓展 (Moon Between : medicine) @@ -15927,7 +15927,7 @@ tfc-artisanal @the-real-time|现实时间 dragonoptifix|DragonBlockC 与 OptiFine 的兼容修复 (DragonOptiFix) -effortless@ +effortless@|轻松建造 (Effortless Structure) @barriers-dont-block-rain-1.21 @nochathide @@ -16061,7 +16061,7 @@ iron-ladders@|更多梯子* crumbs-deepslate-button|Crumbs 的深板岩按钮 (Crumbs' Deepslate Button) cl0uds-paxels -unique-weaponry +unique-weaponry@ boss-keys @@ -16076,7 +16076,7 @@ achievements-mod-200@mysticrift-achievements saturation-plus mod-and-resource-pack-checker-abras-additions createstuffadditions-fix -warden-slayer-enchantment +warden-slayer-enchantment@wardenslayerenchantment mods-optimizer@ glowy-players-renewed@|玩家高亮:重置版* @@ -16107,11 +16107,11 @@ spider-moon@ @breed-parrots-with-seeds persistent-parrots@ @ultimate-autototem -anti-mob-cheese +anti-mob-cheese@anti-mob-farm bad-horse-fix@ vanilla-nei-fix -@fconfiglib +fconfiglib@ satin-free-wakes@ blue-archive-paintings|Sensei的珍藏* @@ -16123,7 +16123,7 @@ mana-unification@ immersive-energistics@ specialised-cells@ random-chunks@ -unique-commands +unique-commands@ flood@ @@ -16224,7 +16224,7 @@ parry-us@parry!|予以刀振/格挡! (Parry) frycooks-delight@|油炸乐事 (Frycook's Delight) advancedae@|高级AE (AdvancedAE) -pretty-pipes-fluids|更好的管道:流体 (Pretty Pipes: Fluids) +pretty-pipes-fluids@|更好的管道:流体 (Pretty Pipes: Fluids) webstone@ rotatable-blocks treasure-seas@|宝藏海* @@ -16325,7 +16325,7 @@ colourful-llamas create-halitosis-create-halitosis@create-halitosis create-unify@ create-more-sand-papers@create-more-sandpapers -sawmill-house +sawmill-house@ kaboom-moon hardcore-revive-mod@ @@ -16407,7 +16407,7 @@ through-the-lily-pads-gently@ decofirmacraft@ @dusty @beeg-smol -scaling-mobs +scaling-mobs@scaling-mob-difficulty multiline-mastery@ @confetti-stuff pirates-treasures @@ -16469,7 +16469,7 @@ water-balloon@ cataclysm-spellbooks@ call-of-drowner@|溺亡者之嚎 (Call of Drowner) -endofday|终焉之日 (Endofdays) +endofdays|终焉之日 (endofdays-re) fastfood-delight-unofficial|重启速食/快餐乐事非官方版 (FastFood Delight Unofficial) faster-fly-block-breaking|飞行速速破* @@ -16500,9 +16500,9 @@ battery-status-info@batterystatusinfo mineteam@mine-team @nocreativemode|禁用创造模式 (NoCreativeMode) @darktitlebar-forge -croaklib@ +croaklib@¨@croaklib-dep|CroakLib - 已失效 -nbt-copy +nbt-copy@ more-totem-effects bakeyourbread@bake-your-bread @@ -16515,7 +16515,7 @@ zombie-villager-control|催起死尸/僵尸村民控制* @too-many-shortcuts better-horse-movement tct-core@ -obscure-tooltips-fix + @notica @@ -16524,12 +16524,12 @@ zhonglv-12et@|钟吕十二律 (ZhongLv-12ET) just-another-random-spawn|遥缘调址/随机出生点* @kaimyentity-c|KAI我的实体-C (KAIMyEntity-C) -realistic-mobs@ +realistic-mobs|Realistic Mobs - 无法文件¨@realistic-mobs|Realistic Mobs - 已失效 fullthrottle-alchemist fullthrottle-nei creature-compendium@ -desert-update -ancient-elements +desert-update@desert_update +ancient-elements@ancient-elements-mod left-behind@ corrupted-witherskeleton-boss-mod @@ -16642,7 +16642,7 @@ shadow-x-netherite-scrap-from-piglin-brutes|猪灵蛮兵掉落下界合金碎片 better-modlist-neoforge@better-modlist sky-aesthetics@ -headshot-bonus|已无可惧/简单爆头* +headshot-bonus|已无可惧 / 简单爆头* enchantlevelbrek@enchantmentlevelbreak|附魔等级上限突破2 (EnchantmentLevelBreak) the-ruler@ biome-borders-revived-liteloader-only @@ -16650,7 +16650,7 @@ chunkborders|区块边界 (ChunkBorders) burnt@ goatman@ -armor-damage-limit|甲哇!/盔甲耐久损失限制* +armor-damage-limit@|甲哇!/盔甲耐久损失限制* knight-bodyguard-forge|骑士保镖 (Knight Bodyguard) deeper-depths unseens-nether-backport @@ -16708,7 +16708,7 @@ va-11-hall-a-drinks|Vall-11 Hall-A饮品 (Vall-11 Hall-A Drinks) bettergive-reborn@bettergivereborn @blur-server-address foodvariations@|生活调味料:经典版 (Spice of Life: Classic Edition) -pizza-delight +pizza-delight@pizzadelight simplefog@ @butter tnt-plus-mod|TNT Plus - 所有¨@tnt-plus-mod @@ -16744,7 +16744,7 @@ no-random-drop-offset|垂直落下/移除掉落随机偏移* croptopias-chocolaterie-unofficial|巧克力工坊:非官方版 (Croptopia's Chocolaterie Unofficial) epic-fight-wukong-moveset|史诗战斗:悟空 (Epic Fight - Wukong Moveset) -@petphrasex|口癖X (PetPhraseX) +petphrasex@|口癖X (PetPhraseX) dust-and-ash|尘与烬 (Dust and Ash) @particle-interactions @@ -16896,7 +16896,7 @@ aviator-dreams@ macaws-stairs@|Macaw的楼梯与露台 (Macaw's Stairs) cross-stitch-colours@ gaming-deco@ -@reactive-music|韵律回响 (Reactive Music) +reactive-music@|韵律回响* easel-does-it@ rbasamoyais-betsy-ross@betsy-ross @@ -16925,7 +16925,7 @@ project-lambda @pretty-hitboxes-create-addition|可配置的碰撞箱显示-机械动力功能附加 (Pretty Hitbox-Create Addition) watersource-reborn@|水源:重生 (WaterSource: Reborn) -locks-unofficial@|锁:非官方版 (Locks-Unofficial) +locks-unofficial|锁:非官方版 (Locks-Unofficial)¨@locks-unofficial|锁:非官方版 (Locks-Unofficial) - 已失效 epicfight-improve@epicfight_improve|史诗战斗:改进 (EpicFight:Improve) just-in-nether@ jinghui@|镜回 (JingHui) @@ -16972,7 +16972,7 @@ oceanic-delight@|海之乐事* the-player kelka-craftable-animals@craftableanimals super-hyper -wans-ancient-beasts@wab +wans-ancient-beasts@wab|Wan的远古异兽 (Wan's Ancient Beasts) invocations-spell-engine@invocations gearifiers@ recall-potion-neoforge@|回忆药水 (Recall Potion) @@ -17015,7 +17015,7 @@ sanity-prequel|理智:前传 (Sanity: Prequel) ayame-paperdoll@|Ayame 纸娃娃 (Ayame PaperDoll) @registry-helper-lib @carpet-permissions -passive-shield@ +passive-shield@|被动盾牌* trap-expansion roost-2-flying-higher @amber @@ -17180,10 +17180,10 @@ mahjongcraft@|麻将工艺 (MahjongCraft) formidulus@ ames-stranger-things herobrine-origins -milkable-blazes@ +milkable-blazes¨@milkable-blazes|Milkable Blazes - 已归档 @rotp-stone-legacy|RotP:暗之遗物 (Stone Legacy (Ripples of the Past addon)) furniture-beetle@ -mekanism-x-create-northstar@ +mekanism-x-create-northstar@|通用机械:北极星 (Mekanism x Create: Northstar) @createz create-smart-crafter@ create-fuel-and-water-information@create-fuel-water-information @@ -17270,8 +17270,8 @@ hexjs@ craftsense@ zenkorium-creatures -zhengelss-techguns-addon -more-artifacts +zhengelss-techguns-addon@zhengelss_techguns_addon +more-artifacts@ tfcgenviewer@ terra-math@ @@ -17316,7 +17316,7 @@ sodium-embeddium-options-mod-compat@sodium-options-mod-compat secureseed-reborn@ creative-eats mazerooms@ -friendly-chests +friendly-chests@ @chitinous_ties brewable-dragons-breath@|龙息可酿造 (Brewable Dragon's Breath) flerovium@|鈇* @@ -17434,7 +17434,7 @@ touchcontroller@|触摸控制器 (TouchController) let-me-click-and-send@ careful-cast-corrector-ccc -4691812@lagbgon-revived +lagbgon-revived@ project-steam extended-crafting-terminals-for-applied sweety-archeo@sweety-archaeology @@ -17552,7 +17552,7 @@ rehooked@ better-night-vision@betternightvision|更好的夜视* -manors-bounty|庄园馀事 (Manor's Bounty) +manors-bounty@|庄园馀话 (Manor's Bounty) @@ -17636,12 +17636,12 @@ programmed-circuit-card@ ae2-toogleable-view-cell@ae2-toggleable-viewcell|AE2可切换显示元件 (AE2 Toggleable ViewCell) @cobblemon-armors letmorefishlove@ -l_ender-s-delight +l_ender-s-delight@l_enders-cataclysm-delight ofaw-of-faith-and-war-1237|信仰与战争1237 (Of Faith and War 1237) lotmoresteves legendary-armory-zelda-mod@legendary-armory -hot-update-1-23-by-gt@hot-update-1.23-by-gt +hot-update-1-23-by-gt¨@hot-update-1.23-by-gt|Hot Update 1.23 by GT - 已失效 management-wanted@ perception@ fancy-vfx@ @@ -17731,7 +17731,7 @@ terra-entity@|泰拉生物* not-interested@ @new-soviet - +gt5r ragexs-nuclear-tech|Ragex的核科技 (Ragex's Nuclear Tech) civcraft create-springs-loaded @@ -17759,7 +17759,7 @@ goopreforged@ @crimes-against-the-jvm better-split-stack sdm-engine-core@sdm-core -sdm-gamestages@ +sdm-stages@sdm-gamestages huh-fabric@huh|哼? (Huh?) @@ -17821,7 +17821,7 @@ portable-wardrobes-easy-armor-swap -@eternalappetite|永恒食欲 (Eternal Appetite) +eternalappetite@|永恒食欲 (Eternal Appetite) platform@ wandering-cocoa@|流浪的可可豆* @@ -17869,7 +17869,7 @@ obsidian-equipment@ the-deep-void@(mortius)-the-deep-void -cataclysm-incinerators-try-hard|灾变:武器技参数配置/冒火的剑 (Cataclysm: Weapon Skill Config/ Incinerator's Try Hard) +cataclysm-incinerators-try-hard@incinerators-try-hard|灾变:武器技参数配置/冒火的剑 (Cataclysm: Weapon Skill Config/ Incinerator's Try Hard) scp-apollyon-scp-apollyon trickster@ pale-garden @@ -17877,11 +17877,11 @@ shulker-enchantments@ xenotech@ sewing-kit zephyr@zephyr-mod -create-radars@|机械动力:雷达 (Create: Radars) +create-radars@|机械动力:雷达 (Create: Radars) create-more-additions@ -minecart-turning-reforged@¨@minecart-turning +minecart-turning-reforged@minecart-turning¨@minecart-turning-reforged offlimits@ apotheosis-threat @string-duper-fix-remover @@ -17895,7 +17895,7 @@ lets-do-apple-wood@lets-do-addon-apple-wood ascended-quark@ enchanted-vertical-slabs@ @better-ladders -would@ +would@|木韵自然* gendustry-community-edition@|基因工业:社区版 (Gendustry: Community Edition) ae2fix@|AE2修复 (AE2Fix) @@ -17907,7 +17907,7 @@ betterenddelight@|更好的末地乐事 (BetterEndDelight) crate-delight-croptopia@|装箱乐事:作物盛景 (Crate Delight: Croptopia) foodspoiling@ natura-legacy - +pepsied-potted-farms @@ -17931,7 +17931,7 @@ evil-captcha@ - +fpsmatch@|通用射击游戏比赛框架 (FPSMatch) mythic-charms@|神话护符* @@ -17974,7 +17974,7 @@ fishing-dangers distinctpaintings@ @search-on-mcmod|MC百科搜索 (Search on MCMOD) infinite-lava-renewed -@better-fabric-console +@better-fabric-console|更好的 Fabric 控制台 (Better Fabric Console) mine-tooltips fastchest-reforged @better-caves-archaic|古早洞穴优化 (Better Caves Archaic) @@ -18005,7 +18005,7 @@ warmachinelib@|战争机器库 (WarMachineLib) crash-assistant@ -inventorytweak@ +inventorytweak@|库存调整 (InventoryTweak) @miss legendary-tabs|传说选项卡* sky-breaker-blue-skies-limit-remover-1-18-2|天穹突破_1.18.2 (Sky Breaker_1.18.2) @@ -18014,7 +18014,7 @@ sky-breaker-blue-skies-limit-remover-1-18-2|天穹突破_1.18.2 (Sky Breaker_1.1 @mxa-tools|MX的工具A (MXA Tools) - +cloudertinker|浮云工坊 (Clouder Tinker) xiyus-lucky-block|饩雨的幸运方块 (Xiyu's Lucky Block) @epicfight_oldsolar|史诗战斗:旧版solar (EpicFight_oldsolar) reskillable-reimagined @@ -18070,7 +18070,7 @@ simple-warp-commands|简单传送指令 (Simple warp commands) tatercart@ irons-spell-book-mob-attribute-addon epic-fightexsilium-gladius|EpicFight—放逐之刃 (Epic Fight — Exsilium Gladius) -@kamenriderweaponcraft|假面骑士武器大全集 (KamenRiderWeaponCraft) +kamenriderweaponcraft@|假面骑士武器大全集 (KamenRiderWeaponCraft) advancearmy@|先遣部队 (Advance Army) @blood-blade @@ -18169,7 +18169,7 @@ fancy-trinkets@|精美的小饰品* block-n-zapping@ cgs@ dynamic-trees-jadens -dynamic-trees-croptopia-new +dynamic-trees-croptopia-new@dynamic-trees-croptopia stacksonstacks more-of-all@ @@ -18183,8 +18183,8 @@ mowzies-mobs-respawner crafting-station-jei-edition@|合成站:JEI版 (Crafting Station: JEI Edition) more-waterlogging the-ancient-trinkets-index@ -straw-golem-rebaled-ported -haybale-1-20-1 +straw-golem-rebaled-ported@ +haybale-1-20-1@haybale-ported gtbcs-spelllib haybale@ @@ -18208,7 +18208,7 @@ more-hitboxes@|更多碰撞箱* shipwreck-fix|沉船修复/沉船卡顿优化* narcissus-farewell|水仙辞* -@mcmod-splashes-plusplus|百科闪烁标语++ (MCMod Splashes PlusPlus) +mcmod-splash-plus-plus|百科闪烁标语++ (MCMod Splashes PlusPlus) - 部分常用¨@mcmod-splashes-plusplus|百科闪烁标语++ (MCMod Splashes PlusPlus) - 全 random-enchants-2|随机附魔 2* sparkle-reforge@|火花:重铸 (Sparkle: Reforge) led-lighting @@ -18232,10 +18232,10 @@ keepthatinventoryopen@ falcons-single-person-sleep realisticsleep -avaritia-1-1x-unofficial|无尽贪婪1.1x非官方延续版 (Avaritia 1.1x Unofficial Extended Life) +avaritia-1-1x-unofficial|无尽贪婪 1.1x 非官方延续版 (Avaritia 1.1x Unofficial Extended Life) lightsaberx prime-punch-one-punch-mod -rpgmana@arch-rpgmana +rpgmana¨arch-rpgmana@ opposite-dimensions migamigos @cloudmusicroom|云音乐房间 / 服内一起听音乐与点歌 (CloudMusicRoom) @@ -18259,7 +18259,7 @@ fancy-lamps resizing-potion miku advanced-smelter@|荒古炼金炉 (Advanced Alchemical Furnace) - +extended-tinker|匠魂扩张* bedsheet@|床单 (BedSheet) @@ -18361,7 +18361,7 @@ scopic memory-leak-fix-unofficial-remastered @distance-traveled-calculation -@control-craft|控制学 (Control Craft) +valkyrien-skies-control-craft@control-craft|控制学 (Control Craft) reforbidden-magic@|禁忌魔法:重生 (ReForbidden Magic) moses-mod craftable-animals|可合成动物* @@ -18422,7 +18422,7 @@ ore-hammer@ exp-chest sophisticated-backpack-allthemodium-tier sophisticated-storage-allthemodium-tier -the-amazing-world-of-mobs-renovated +the-amazing-world-of-mobs-renovated@theamazingworldofmobs ash-of-sin-tempest|罪业余烬:特恩佩斯特 (Ash Of Sin: Tempest) @cauldronfix|铁锅工艺 (Cauldronfix) @@ -18476,7 +18476,7 @@ fake-ores crackedzombie error-404-not-found-horror-entity@error-404-not-found -apotheosis-totem +amelet-totem tbone @@ -18550,7 +18550,7 @@ the-god@ relics-rpg@ macaws-quark@ macaws-sajevius@ - +macaws-biomes-o-plenty@ @@ -18559,7 +18559,7 @@ fancy-spawn-eggs - +@no-more-serverconfig|不再有服务器配置 (NoMoreServerconfig) @queryinfo ftb-pause-menu-api @@ -18578,7 +18578,7 @@ liquipacks create-advanced-crafting create-stone-generators@ create-cold-sweat@|机械动力:冷汗 (Create: Cold Sweat) -another-unbreakable-enchantment +another-unbreakable-enchantment@ passive-piglins void-miners gwycraft-a-colored-blocks-mod @@ -18672,7 +18672,7 @@ experience-ore|经验矿石* passive-torch productive-metalworks better-leveling-respawn|更好的升级:重铸 (Better Leveling: Reforge) -drive-by-wire +drive-by-wire|线缆控制 (Drive-By-Wire) spirit-of-fight@|战魂 (Spirit of Fight) grim-bleak@grim-and-bleak carrymyboss @@ -18702,7 +18702,7 @@ infinibows tacz-labs@ @mineshock-cybernightmare|网络奇兵 (MineSHOCK: CyberNightmare) make-a-wish -archaeological_research@|考古研究 (Archaeological Research) +archaeological_research@|考古研究:探寻 (Archaeological Research:Exploration) forgotten-nunchakus create-big-cannons-advanced-technologies@|机械动力火炮:先进技术 (Create Big Cannons: Advanced Technologies) quality-food@ @@ -18827,7 +18827,7 @@ cyberspace@ displayed@|展示 (Display) engineered-compatibility@ -@createabysscatalysis|机械动力:深渊催化 (Create: AbyssCatalysis) +create-abyss-catalysis@createabysscatalysis|机械动力:深渊催化 (Create: AbyssCatalysis) @worlddownloaderlegacy|世界下载器旧版 Forge (WorldDownloaderLegacy) level-up-rpg-skills-more @@ -18858,7 +18858,7 @@ create-reforged-foundations@ better-experience@better-experience-of-confluence|更好的体验* thdilos-fox-origin@|ThDilos的狐狸起源 (ThDilos Fox Origin) -narutofix|火影修复 (NarutoFix) +narutofix@|火影修复 (NarutoFix) sbr-i-need-more-enchantment@slashblade-resharped-i-need-more-enchantment|重锋:施魔更哥 (SlashBlade Resharped: I need more Enchantment!!!) configurable-health|可配置的生命值* create-better-fps@createbetterfps @@ -18879,7 +18879,7 @@ moveui-forged@ harvest-level-config brewing-helper@ theatrical@ - +etlib|淬火煉刀通用库 (Emberthral) tacz-addon@taczaddon @heartcanister|心之容器 (Heart Canisters) @@ -18989,7 +18989,7 @@ illager-war-trireme hominid anvians-lib@ - +@creo-api amblekit@ nocubes-better-fletching-table @@ -19127,7 +19127,7 @@ growmeal-fabric@growmeal-(fabric) katharite-slimes@slimes-of-kathare botania-ceu -magic-from-the-east +magic-from-the-east@ blatapi@ @materials-lib cavelib@ @@ -19180,7 +19180,7 @@ lootbagsrenewal@lootbags|战利品袋重制版 (LootBagsRenewal) autoswaplowdurabilityelytra|自动鞘翅替换 (AutoSwapLowDurabilityElytra) -no-elytra-equip@no-elytra-mod +no-elytra-equip@elytras-disabled no-elytra title-changer@ clickthrough-plus@clickthrough+|穿牌开箱+ (ClickThrough Plus) @@ -19263,7 +19263,7 @@ hrtffix@|HRTF修复 (HRTF Fix) iammusicplayer-renewed@iam-music-player-renewed misc-tweaks@misctweaks ftb-filter-system -neonium@ +neonium¨@neonium|Neonium - 已失效 @super-fast-math @@ -19412,7 +19412,7 @@ selectivebounds@ @kotlin-for-forge-unofficial @circlemc nearbymobchecker@ -gputape@ +gputape¨@gputape|GPUBooster / GpuTape - 此处项目名更改为 GPUBooster @limitless-anvil|铁砧限制修复 (Limitless Anvil) carry-on-extend@|搬运:拓展* kaleidoscope-doll@|森罗物语:玩偶* @@ -19474,7 +19474,7 @@ tweakermoreforge@ vs2-cbc-armor-blocks create-integrated-farming@|机械动力:集成农业 (Create: Integrated Farming) ring-of-blink-forge|闪现戒指 (Ring of Blink) -golem-overhaul@ +golem-overhaul@|傀儡革新* voile@ title-tweaks@titletweaks ugoblock|动块 (UgoBlock) @@ -19544,7 +19544,7 @@ brush-mining-machine|刷矿机 (Ore Generator) set-effects|套装效果* tramways@ projecte-kubejs -actually-additions-v2-unofficial|实用拓展 V2 非官方版* +actually-additions-v2-unofficial|实用拓展 V2 非官方版 (Actually Additions V2 Coffee Edition) create-ender-link@ progressive-tweaks portable-blueprints@ @@ -19579,7 +19579,7 @@ maid-useful-tasks|女仆实用任务 (Maid Useful Task) @titlechanger-next|标题切换器 Next (TitleChanger Next) embers-underground-rooms@ -dontstarve4@|不要饿死4 (Don't Starve 4) +dontstarve4@|不要饿死 4 (Don't Starve 4) spark-core@|星火核心* nether-core-useful-nether-rock|下界核心 (Nether Core) slashblade-js|拔刀剑 JS (SlashBlade JS) @@ -19615,7 +19615,7 @@ poopsky@|空中厕所 (PoopSky) @solarus-classic create-unlimited@ -skeleton-ai-fix@ +skeleton-ai-fix@|骷髅 AI 修复 (Skeleton AI Fix) climate-rivers@ dungeonsdelight@dungeons_delight|地牢乐事 (Dungeons Delight) cleanroom-relauncher-unofficial@|Cleanroom 模组式启动器非官方版* @@ -19642,9 +19642,9 @@ sheep-variety@ @tetraclip|易验锻造 (TetraClip) @rozhud - +the-cosmic-spokespersons@|宇宙代言者* mc-mods-in-zero@all-in-zero|一切于零 (All In Zero) -@yunzhu-transit-extension|云竹交通拓展 (Yunzhu Transit Extension) +@yunzhu-transit-extension|云竹交通扩展 (Yunzhu Transit Extension) goodbye-dirt-screen@|澄清素视 / 去你的泥土屏幕* create-stock-bridge@ @@ -19680,7 +19680,7 @@ zettai-rpg@ md-units@md-unit-robots extreme-core dreamless-spells-and-spellbooks -gregtech-meowmelmuku|格雷科技非官方版-喵呜酱版 (GregTech-MeowmelMuku) +gregtech-meowmelmuku|格雷科技非官方版 - 喵呜酱版 (GregTech - MeowmelMuku) @namedloot @enchantment-decay create-jet-boots @@ -19725,7 +19725,7 @@ i-like-wood-oh-the-biomes-youll-go-plugin i-like-wood-immersive-engineering-plugin click2pick@ hudless@ -dungeons-perspective +dungeons-perspective@ miniutilities-flight-fix@ linked-chests@new-linked-chests create-direct-chute@direct-chute @@ -19813,9 +19813,9 @@ potion-packs armor-smelter carpet-sls-addition@sls-addition -@fsu|(Neo)Forge 常亮绊线特性恢复 (Forge String Unpatched - Powered Permanent String) +forge-string-unpatched-powered-permanent-string@fsu|(Neo)Forge 常亮绊线特性恢复 (Forge String Unpatched - Powered Permanent String) quests-kill-task-tweaks - +@lpctools immersive-messages-api@ immersive-damage-indicators@ easy-disenchanting@ @@ -19970,7 +19970,7 @@ backported-minecarts@ simple-incubator@ amethyst-stairs-slabs-and-walls @vessels - +ultimate-scaler@ legendary-weapons-byititor@legendaryweapons slender-reborn craft-vania-lite @@ -19985,7 +19985,7 @@ infinistack@|无限堆叠 (InfiniStack) powerful-dummy|强力假人* beyond-bosses@beyondbosses mini-utilities-y -concoction@concoction! +concoction@concoction!|田园调酿! (Concoction!) zombietactics2@ radiation-zone-reborn|辐射荒原:再行 (Radiation Zone: Reborn) @@ -19999,7 +19999,7 @@ shadered@ sweety-garden@ @twilight-forest-aurora-palace-regeared -changshengjue|长生诀 (ChangShengJue) +changshengjue@|长生诀 (ChangShengJue) medieval-buildings-nether-edition @ribbits-croaks @@ -20028,11 +20028,11 @@ tacz-tes-compat@ directionhud -raw-netherite-reforged +raw-netherite-reforged@ blast-travel-reborn@blasttravelreborn|旅行火炮·重生* shanxi-skeleton@shanxiskeleton|山西骷髅* @gunpowder-overhaul|火药改革 (Gunpowder Overhaul) -smoking-stuff-a-wizards-hobby +smoking-stuff-a-wizards-hobby@ @alloy-smelter @autocrafter-early time-in-a-bottle-charger@ @@ -20048,7 +20048,7 @@ threadtweak-reforged|ThreadTweak (Neo)Forge 移植版 (ThreadTweak Reforged) @environmental-creepers-backport com-bat touhou-little-maid-rpg-class-task - +unknown5473lib@ heat-and-climate-patch|热量与气候修复* @clientupdater @@ -20220,7 +20220,7 @@ lazy-ae2-tweaks coordinates-hud@coordinateshud just-enough-botania@ fumo-fumo -touhoupixelcanteen-a-farmers-delight-plugin@touhoupixelcanteen|东方像素食堂 (TouhouPixelCanteen) +touhoupixelcanteen-a-farmers-delight-plugin@touhoupixelcanteen|幻雀妖 (Gensokyo Izakaya) perfect-parity-the-garden-awakens-edition@ perfect-parity-spring-to-life-reforged hypertubes@hypertube|机械动力:超级管道 (Create: Hypertubes) @@ -20238,7 +20238,7 @@ identity-fix-morph@identity-fix stick-it@ aimod -srp-reset-mod|逃逸寄生体:重置 (SRP Reset) +epca|终末-归寄万物 (End-Parasitize and Convert All) legendary-relics|传说遗物* true-power@|真正的力量 (True POWER) @@ -20269,7 +20269,7 @@ cameraoverhaul-vintage@ advanced-hoppers -ars-meteorites +ars-meteorites|新生陨星* the-hard-herobrine-mod tektopia-addons @@ -20305,7 +20305,7 @@ cinchs-abandoned-houses@ curse-breaker@ hundred-years-warfare@|百年战争* biologica@ -oritech-things +oritech-things@ potato-vehicle-industries warfare-wings@ avaritias-delight@|无尽乐事 (Avaritia's Delight) @@ -20372,7 +20372,7 @@ initialdimensionreborn@ @oo-ee-a-e-a-fabric - +redstone-chans-enchantment-expansion|红石酱的附魔拓展 (Redstone-chan's Enchantment Expansion) the-plugin-mod@ cave-vision @el-addon-retrieve-to-slot @@ -20419,7 +20419,7 @@ 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) @@ -20478,11 +20478,11 @@ hide-hud-in-third-person-view@hide-hud-third-person automlg@auto_mlg elytraautopilot@elytra_autopilot simple-conveyor-belts@ -smidgeon-o-bliss-remastered +smidgeon-o-bliss-remastered@smidgeon-o-bliss xl-food-reborn|超多食物:重生 (XL Food Mod Reborn) create-spin-spawner vs-rotanator -create-refilling-boxes +create-refilling-boxes@ cbc-chemical-warfare|机械动力火炮:化学与生物战争 (CBC: Chemical Warfare) cbc-warium-projectiles|机械动力火炮:Warium炮弹 (CBC: Warium Projectiles) create-colored @@ -20496,17 +20496,17 @@ gildedarmors@gilded-armor|鎏金盔甲 (Gilded Armor) inventory-management-deluxe runningmapmusic|跑图音乐 (RunningMapMusic) lava-chicken-music-disc -porting-dead-libs +porting-dead-libs@ panoramica¨panoramica-fabric create-mt-integration|资源统合-机械动力配方附加* -@valkyrien-warpeace|瓦尔基里:战争与和平 (Valkyrien War and Peace) +valkyrien-skies-war-and-peace@valkyrien-warpeace|瓦尔基里:战争与和平 (Valkyrien War and Peace) sillys-auto-totem@ repairables-anvil@|可修复的铁砧* @throwable-bricks @now-time|准点报时 (Now Time) fluidjs¨@fluidjs|FluidJS - 审核中 hybridcompat|混合兼容 (HybridCompat) -@recipecooldown +@recipecooldown|配方冷却 (RecipeCooldown) counter-mod lotr-renewed-class-mod|魔戒种族 (LOTR Races) @@ -20540,7 +20540,7 @@ speedy-hopper-vanilla suggestions-fix-fabric no-ixapi@no.ixmodapi -epicfight-zylob|史诗战斗:刀光修改 (EpicFight Sword Light Modifications) +epicfight-zylob@epicfight-sword-light-modifications|史诗战斗:刀光修改 (EpicFight Sword Light Modifications) perfect-parity-pale-garden-awakens-neo expressive-advanced philia-amulet|友爱护符 (PhiLia Amulet) @@ -20563,7 +20563,7 @@ create-fantasizing-again@|机械动力:又在幻想了 (Create: Fantasizing Ag crafting-station-jei-edition-updated uncraft-everything@ @videoplayer -@net-music-play-list|网络音乐机:播放列表 (NetMusicList) +net-music-play-list@|网络音乐机:播放列表 (NetMusic: Play List) gregs-modern-construct @@ -20579,8 +20579,8 @@ simple-villager-follow@ tinkers-experience hedgehog-companion@ i-want-to-spin@|我要开转* -@kiss-fabric - +kiss@kiss-fabric +@mc-bds|北斗导航 (MC BDS) neo-the-titans@|新泰坦生物 (Neo Titans Mod) nuggets-repair-items@|矿粒修复物品* @@ -20592,7 +20592,7 @@ tlm-true-power@true-power-of-maid|车万女仆:真正的力量 (TLM: True POWE @splash-window - +neoenchant @showcase enhancedtooltips@ locate-fixer @@ -20644,7 +20644,7 @@ multicolored-redstone-lamps reliquified-twilight-forest@ witch-tower the-nightmare-000@ -meowmere +meowmere|彩虹猫之刃* @fallenflowers-server-economy|Fallenflower的服务器经济 (Fallenflower's Server Economy) deadout-medical-supplies|死亡突围医疗用品 (Deadout Medical) @thermal-shock @@ -20671,14 +20671,14 @@ immersive-furniture@|沉浸式家具* modernnetworking like1-8 spoduos-future-mineration -enderio-continuous +enderio-continuous|末影接口延续版 (EnderIO Continuous) enchantment-broker@ new-dawn enhanced-glass hospitals comfy-cozy -epicnpc|史诗战斗NPC (epicnpc) +epicnpc|史诗战斗NPC (EpicNpc) epic-fight-script-extension|史诗战斗:脚本扩展版* @undead-transformation @@ -20711,7 +20711,7 @@ fluidium|锍* peritia -inventory-sort|背包整理* +inventory-sort@|背包整理* corpse-kibou-no-hana|希望の花 (Corpse: Kibou no Hana) redyeable @natural-motion-blur @@ -20743,7 +20743,7 @@ tutes-cn-sweetinks-items|塔兹与甜墨的物品 (Tutes-cn & Sweetink's Items) i-am-steve@i..am-steve backported-stufff -tumbleweeds¨@tumbleweeds|Tumbleweeds - 审核中 +tumbleweeds@ mending-mirror|破镜重圆* natures-life @@ -20755,9 +20755,9 @@ yggdrasils isitwaxed -simple-hp-bar +simple-hp-bar@ login-hp-fix -shape-shifter-curse|幻形者诅咒* +shape-shifter-curse@|幻形者诅咒* tinkers-planner-reborn|匠魂蓝图:重燃 (Tinker's Planner Reborn)¨@tinkers-planner-reborn|匠魂蓝图:重燃 (Tinker's Planner Reborn) - 审核中 @@ -20790,7 +20790,7 @@ confluence-otherworld-gun-fix@ sbm-cardboardboxes -bedrock-age|基岩时代* +bedrock-age@|基岩时代* return-scepter|回程魔杖* @koniavacraft|娜拉工艺 (Koniavacraft) @@ -20858,13 +20858,13 @@ more-red-x-cc-tweaked-compat@ @speed-uuuuuuup|加加加加加加加速! (Speed Uuuuuuup) wither-reincarnated - +@biome-snapshot toros-auto-attack@ creeperfire|自定义烧货 / 苦力怕阳光下燃烧 (CreeperFire) fastspawner|刷怪笼渲染优化/产器速算 (FastSpawner) sophisticated-storage-plus|存取思深/精妙存储扩容* -whaleborne -uniful +whaleborne|鲸帆远航* +uniful@ newspaper-big-book-bibliocraft-legacy-backport bic-clipboard templates-2@ @@ -20876,7 +20876,7 @@ workstation-recipe-exporter@|可视化配方编写* simple-auto-attack hostile-to-neutral tracer -fishing-tick-fix +fishing-tick-fix@ fzmm@ pasteljei@|彩绘世界JEI (PastelJEI) e4mc-retro@ @@ -20914,9 +20914,9 @@ sanandreasps-manager-pack touhoulittlemaid-orihime@|车万女仆:织姬 (Touhou Little Maid: Orihime) more-mod-tetra|更多Tetra模组锻造材料* -cataclysm-dimensions|灾变:维度* +cataclysm-dimensions@cataclysm-dimension|灾变:维度* -misttaczskills@|TaCZ:圣化技能扩展 (MistCloud Tacz Skills Extension) +misttaczskills@|TaCZ:圣化技能扩展 (MistCloud Tacz Skills Extension) quality-railway@|品质铁路* custom-mob-attributes@ @@ -20927,10 +20927,10 @@ extra-carts fairy-factions-revived netherited@netherited-fireproof¨fabric-netherited creeperhost-presents-soul-shards@ -myths-n-legends@myths-and-legends -snackcraft|零食工艺 (SnackCraft) +myths-n-legends@myths-and-legends|神话与传说 (Myths & Legends) +@snackcraft|零食工艺 (SnackCraft) -copper-backport¨@copper-backport|Leaf's Copper Backport - 审核中 +copper-backport@ @shield-indicator|盾牌指示器 (Shield Indicator) a-bastion-remnant-treasure-reset|堡垒遗迹藏宝室重置 (Bastion Remnant Treasure Reset) @@ -20938,16 +20938,16 @@ a-bastion-remnant-hoglin-stable-reforged|堡垒遗迹疣猪兽棚重铸 (Bastion colonial-arcana-minecolonies-addon|殖民地魔法 (Colonial Arcana) fires-ender-expansion -thetitansneo@|泰坦生物Neo (The Titans Neo) +thetitansneo@|泰坦生物Neo (TheTitansNeo) 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-bugfix-schematic-patch@|机械动力:蓝图补丁 (Create Bugfix: Schematic Patch) -create-ornithopter-glider +create-ornithopter-glider@ create-train-parts@ @breakable-blocks flux-network-x-cc-tweaked @@ -20957,12 +20957,12 @@ closets-by-smplesco@ variety-chests-mod light-detector quality-hails - +ancient-structures-edo-japan@ tiger-shark@tigershark|海虎 (TigerShark) -better-end-cities-better-end@better-end-cities-for-betterend¨better-end-cities-vanilla@better-end-cities-base +better-end-cities-better-end@better-end-cities-for-betterend|更好的末地城 (Better End Cities)¨better-end-cities-vanilla@better-end-cities-base|更好的末地城 (Better End Cities) fallen-gems-affixes skill-tree blatium@ @@ -20996,12 +20996,12 @@ create-arm-made-barbeque|机械动力:臂力臂力烧烤 (Create : Arm-made Ba indestructible-blocks epic-fight-super-chef -epicfight-nightfall|史诗战斗:黄昏 (EpicFight-Nightfall) +epicfight-nightfall|史诗战斗:夜幕 (EpicFight-Nightfall) genesis-official featurecreep fabric-orm-jimmer@ -aces-spell-utils +aces-spell-utils@ targeteventbackport silk-kt@silk @@ -21017,7 +21017,7 @@ ashvehicle city-of-chambers tower-of-chambers@tower-of-chambers-neoforge - +ancient-structures-chinese@ eypipes shipping-bin hbm-nuclear-tech-mod-community-edition|HBM的核科技社区版 (HBM's Nuclear Tech Mod Community Edition) @@ -21028,7 +21028,7 @@ one-enough-item@ o123456789@ friendly-chests-no-fluid herios-floral-expansion@ -pinkcabbage-transparent-betterscreen|粉红大包菜的屏幕透明度优化 (PinkCabbage Transparent BetterScreen) +pinkcabbage-transparent-betterscreen@pinkcabbagetransparentbetterscreen|粉红大包菜的屏幕透明度优化 (PinkCabbage Transparent BetterScreen) wastify-returns sillys-crossbow-fix@ thaumpotion-tweaker@ @@ -21036,14 +21036,14 @@ novoatlas@ resource-pack-tweaks night-vision-decay-fix|夜视衰减修复* roid-tweaker -translatorpp@translatorppv +translatorpp@ 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 +mod-tabs +totem-bar@ visualores-meowmel savebabystriders bygone-nether-fixes@|逝去的下界修复* @@ -21064,7 +21064,7 @@ garden-cloche-backport bh-boss-hatred@boss-hatred|首领仇恨 (Boss Hatred) camol -bbb-unforged +bbb-unforged@ performance-monitoring-enhancer create-upsizing@ better-world-loading-forge@better-world-loading¨better-world-loading-fabric @@ -21073,12 +21073,12 @@ apocalypse-minimap-hud@apocalypseminimaphud|启示录:小地图Hud* advancementnomore|进度移除 (AdvancementNoMore) collisium dypsis -@chat-plus +chat-plus@ mcreatormemfix@ @atlas legacy-tweaks@legacytweaks easy-elevators@|简单电梯* -dakimakura-unofficial|抱枕-非官方 (Dakimakura-Unofficial) + davincing aeronautical-radio|航空无线电* hunter-vs-mice@|猎人鼠鼠游戏* @@ -21109,12 +21109,12 @@ betterexcavate|更好的挖掘机制 (BetterExcavate) optimization-of-campfire-smoke|营火烟雾优化 (Optimization of Campfire Smoke) armored-combat-enhancer -@animal-hats-yeah +animal-hats@animal-hats-yeah @raid-restore asgardandelion-for-botania trotting-wagons@ grindstonehoning -rainbowwrenchforjei-reforge|彩虹扳手:Forge版 (RainbowWrenchForJei:ReForge) +rainbowwrenchforjei-reforge|彩虹扳手:Forge版 (RainbowWrenchForJei: ReForge) better-locate-structure-command|更好的结构定位指令* att-miss|闪避攻击* energizexp@ @@ -21132,11 +21132,11 @@ familiarslib dmr@|龙骑士重制版 (Dragon Mounts Remastered) avaritia-expand|无尽贪婪 : 拓展 (Avaritia : Expand) - +polaristrading|北极星市场方块 (PolarisTrading) ecliptic-seasons-multimod-patch random-things-returns infinite-storage-cell -operation-eight-treasure-porridge|八宝粥行动* +delta-technology|八宝粥行动* elegant-spells@|雅致咒术* @seelescraft|希儿的工坊 (Seele's Craft) readyhomes|随时可住 (ReadyHomes) @@ -21159,11 +21159,11 @@ clayium-additions farmers-tfc|农夫群峦 (Farmers TFC) oaks-delight@ -pathfinding-edition-for-minecolonies|殖民地寻路大修* +pathfinding-edition-for-minecolonies@|殖民地寻路大修* clicksigns@ @salted-fish-craft|咸鱼 (Salted Fish) herios-helian-mod@ -@decorative-food|装饰性食物 (Decorative Food) +decorative-food@|装饰性食物* anvilcraft-alloyextension@|铁砧工艺:合金扩展 (Anvilcraft: Alloy Extension) arkifs-hoverboard-mod @@ -21194,7 +21194,7 @@ bbl-placers@ draconic-legacy-models|龙之进化旧版模型* furnaces -tinkersconstruct-classicsfluidvalues +gtconstruct rain-be-gone-ritual@ the-vegan-option|素食者的选择* gensokyo-delight-youkais-feasts@|幻想乡乐事 ~~ 妖怪们的飨宴 (Gensokyo Delight ~ Youkais' Feasts) @@ -21226,7 +21226,7 @@ unlimited-spawners@|无限刷怪笼* authenticate@|认证* unbinilium -veinbreaker +veinbreaker@ actions@ i-have-no-mouth-but-i-must-chat@ihavenomouthbutimustchat renderscale@ @@ -21259,7 +21259,7 @@ enchanter-fix@ dynamic-regen-enhancer guinea-pig-variety@ -@areas-hint|域名 (areas-hint) +@areas-hint|域名 (Areas Hint) yammo fps-display-doubler@ @@ -21300,7 +21300,7 @@ air-jump more-sniffer-flowers@|更多嗅探兽花卉* enchantability|附魔能力* -ender-lead|末影拴绳* +ender-lead@|末影拴绳* create-growableores-compat@creategrowableores-compat @@ -21309,7 +21309,7 @@ gears-n-kinetics@ valkyrien-sails@ moo-fluids-modern -yan-from-ff14@|FF14的羊 +yan-from-ff14@|FF14的羊 (Yan from FF14) tfc-cuisine@|群峦料理 (TFC Cuisine) @@ -21364,7 +21364,7 @@ move-subtitles@ modern-inline@ culler@ @playernoticeboard - +extendedae-plus@ @ae2uel-extra gems-realm@ @@ -21382,9 +21382,9 @@ byzantine-styles-pack-for-minecolonies aquaculture-tinkers@ cups -@blue-archive-halo|仿碧蓝档案光环 (Blue Archive Halo) - -ecological-replenishment-station|生态补给站* +blue-archive-halo@|仿碧蓝档案光环* +minebackup@|存档时光机 (MineBackup) +ecological-replenishment-station@|生态补给站* @advancedhostility|进阶仇恨 (Advanced Hostility) aggro-fix@ chest-curio-items@|箱子里的小饰品* @@ -21398,11 +21398,11 @@ ender-dragon-fix detailab-compat@|细节盔甲兼容 (Detail Armor Bar Compat) calypsos-mobs@|卡里普索的生物 (Calypso's Mobs) -dirtcraft-unoffical|泥土工艺-非官方版 (DirtCraft-Unofficial) - +dirtcraft-unoffical|泥土工艺 - 非官方版 (DirtCraft - Unofficial) +specialmaterials@|材料 (Materials) @onlytpnext|仅传送Next (OnlyTPNext) easy-survival-command|简单生存指令TP (EasySurvivalCommand) -@gensokyo-reverie-of-lost-dreams +@gensokyo-reverie-of-lost-dreams|梦隐的幻想乡 (Gensokyo Reverie of Lost Dream) reliquified-ars-nouveau@ requesting-interface@irequesting applied-experienced@ @@ -21417,15 +21417,15 @@ dream-in-a-manhunt @thesunrise -more-critters -oelib@ +more-critters@|更多异兽* +oelib¨@oelib|One Enough Lib - 仅0.0.1 itemblacklistfixed|物品黑名单修复版 (ItemBlackListFIXED) voxelmap-patch xaero-train-map@ traversable-leaves-neoforge¨traversable-leaves¨traversable-leaves-fabric @etched-extension -@vs-addition-continue +vs-addition-continue|VS Addition Continue - 更新慢¨@vs-addition-continue|VS Addition Continue - 更新较快 clavis@ luckyblock-lib @@ -21443,7 +21443,7 @@ fauna-and-orchestra@faunaandorchestra contact-love-rekindled|往来:旧情复燃 (Contact: LoveRekindled) autosow-by-neni-o@ piglin-join-wto|猪灵加入了WTO (Piglin Join WTO) -@keyboard-detector|键位检测器 (Keyboard Detector) +keyboard-detector@|键位检测器* potion-pill|药水药丸* inhrtburys-coin|Inhrtbury的金币 (Inhrtbury's Coin) @firesight @@ -21457,11 +21457,11 @@ create-programmable-pals create-ore-excavation-plus@ @createtreadmill|机械动力:跑步机 (Create:Treadmill) create-more-package-couriers@ -regius - +regius@ +dont-make-me-see-red|别让我红温* peyros-scythe|佩伊洛的镰刀 (Peyro's Scythe) @@ -21472,7 +21472,7 @@ giant-dead-oak-tree@ ascension-nether ascension-end @echovoids -@soulfulnether +soulful-nether@soulfulnether zefs-nether the-end-update-concept @ending-biomes @@ -21490,19 +21490,19 @@ invisibility-aggro-fix@ quick-skin@ countryball@minecraft-countryball-mod tinkers-construct-emergence -pointblank-jelly +pointblank-jelly@pb-jelly terrabnormals extra-mining-world herobrine011840s-nether-biomes my-end update-ender-a-reimagine-of-the-end @air-puffers -companions-mod@ +companions-mod@|同伴! (Companions!) @ai-chat|AI聊天 (AI Chat) apothic-attributes-extension|神化属性拓展* databank@ randomtp@ -@kium-optimizer + instantunify-reforged @blockfire morphoverlay @@ -21517,7 +21517,7 @@ shulkerrrt@srrt custom-icon-sets gregic-probe-ceu @antilootgame -@graphene|石墨烯 (Graphene) +graphene@|石墨烯性能优化* talents-fabric@talents @combat-maids @@ -21528,12 +21528,12 @@ no-stuck-healing pixelcompress matrinoxes-village-names redlink-wireless-redstone-control@ -@nekohashizuku-blade|雫拔刀剑 (shizukublade) +@nekohashizuku-blade|雫拔刀剑 (ShizukuBlade) xtones-reworked@ @tinkers-enchanting new-woods ennuis-bigger-inventories@ennuis_bigger_inventories -@soldisco +@soldisco|生活调味料:探索 (Spice of Life: Discovery) missing-cookery @farmers-tea-resteeped peruvians-delight@ @@ -21544,7 +21544,7 @@ krypton-fnp@ neoauth@ crystalix chisel-chipped-integration -refined-types +refined-types@ mystical-automation@ utility-vest little-big-redstone@ @@ -21560,7 +21560,7 @@ mc-mods-the-arcanist-gear epitaphs biologist|生物学家* wandering-trapper2|流浪猎人2* -skiing2 +skiing2|滑雪* backported-animal-variants@ explore-ruins-aether@ warpdrive-extras@ @@ -21583,12 +21583,12 @@ greekfantasy-unofficial musketeer-illager-port cataclysm-apotheosis-addon-neo iron-apples-iron-series@ - +@mangos-compat-delight tinkers-oreganized@ simple-basketballs@simple-basketball decorative-blocks-repurposed create-mechanical-botany@ - +@insecticide naked-gauntlet|焰眼掉装* only-one-item@ @@ -21628,9 +21628,9 @@ aquaacrobaticslegacy armored-arms@ armored-golems disable-fog@ -accrelatedrendering-refabricated@|加速渲染 重织版 (AccrelatedRendering-reFabricated) +accrelatedrendering-refabricated@|加速渲染 重织版 (AcceleratedRendering-reFabricated) minemenureforged|我的菜单 重制 (MineMenuReforged) -function-command-deduplicator|函数命令去重器* +function-command-deduplicator@|函数命令去重器* @hide-armour ftb-quests-freeze-fix@|FTB任务冻结修复 (FTB Quests Freeze Fix) playeranimatorapi@ @@ -21653,7 +21653,7 @@ audio-improvements@ simple-crop-harvester|简易农作物收割* darkness-curse|暗影诅咒·重生 (Darkness Curse • Reborn) -@scroll-whell-command|滚轮命令 (Scroll Wheel Command) +scroll-wheel-command@scroll-whell-command|滚轮命令* infoplus@ @bin-slot cobblemon-utils @@ -21667,7 +21667,7 @@ better-snifferty@|更好的嗅探* minibloom teatime@tea_time -garden-trails +garden-trails@ @gd656killicon|GD656击杀图标 (GD656Killicon) itemcleaner@|掉落物清洁者 (ItemCleaner) @@ -21680,9 +21680,9 @@ modern-beta-forge-release-plus pomkots-world@ black-dragon-armor|怪物猎人·世界:黑龙盔甲* kimetsu-no-yaiba-reload|鬼灭之刃 重制* -resonantisshelomoh|同调造物·所罗门 (Fabrica Resonantis:Shelomoh) +resonantisshelomoh|同调造物·所罗门 (Fabrica Resonantis: Shelomoh) withering-away-reborn -villager-extend-localbully +villager-extend-localbully|村民拓展:地头蛇 (Villager Extend - LocalBully) dancing-villagers natures-of-witchery tct-flashlight@tctflashlight @@ -21704,18 +21704,18 @@ solcarrot-fix|生活调味料:胡萝卜版 - 死吃修复* nanexs-cherry-biome digital-botania -crab-city-facilities|螃蟹的城建 (Crab city facilities) +crab-city-facilities@|螃蟹的城建 (Crab city facilities) bbl-casting@casting bbl-smart-crafting foolproof@ -realistic-terrain-generation-community +realistic-terrain-generation-community|真实地形生成:社区版* more-outlines@ phantomtweaks|移除幻翼 (RemovePhantoms) -simple-item-blacklist +simple-item-blacklist|简易物品黑名单* titlescreen-cleaner -manaitaplus-legacy|砧板传承 (ManaitaPlus Legacy) +manaitaplus-legacy@|砧板传承 (ManaitaPlus Legacy) blockoffensive@|方块攻势 (BlockOffensive) @@ -21723,29 +21723,29 @@ hugme-forge-edition|抱抱我!Forge 版* +tool-upadate@tool-update|工具附魔等级升级 (Tool Update) +@better-gregtech-and-applied-energistics|更好的格雷科技与应用能源 (Better GregTech And Applied Energistics) - - - -better-turtle-scute@|更好的海龟壳 (Better turtle scute) +tprt-sakuratinker-expand|挚爱 (myLove(sakuratnker expand)) +better-turtle-scute@|更好的海龟壳* floatable-pressure-plates@|可浮空压力板* more-rust@rust|锈 (Rust) -@cryofloric-arts|严寒插花术 (Cryofloric Arts) +cryofloric-arts@|严寒插花术* tacz-refabricated@|TaCZ:重织 (Timeless and Classics Zero: Refabricated) -@ashen-witch-broom|魔女的扫帚:重制版 (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) +create-xptool@cxptcreatexptool|机械动力:经验工具 (Create: XPTool) graveyard-death-mode -beautiful-campfires +beautiful-campfires@ kimetsu-no-yaiba-final-hope legend-ary@ tfc-hourglass|床起速时 / 梦该醒了 (TFC Hourglass)¨@tfc-hourglass|床起速时 / 梦该醒了 (TFC Hourglass) - 审核中 @@ -21756,7 +21756,7 @@ tfc-hourglass|床起速时 / 梦该醒了 (TFC Hourglass)¨@tfc-hourglass|床起 @alan-unified-ui @cntiertagger - +pick-up-the-hint|物品拾取提示 (Pick Up the Hint) jei-tetra eyes-in-the-shadows@ bandagez @@ -21784,8 +21784,8 @@ too-many-bows@ maid-beside|女仆伴行* -key|键* -vzlinglib +snkey|键 (Sn Key) +vzlinglib@ @signfinder|告示牌查找器 (Signfinder) @@ -21793,7 +21793,7 @@ vzlinglib @colorful-leaves-mod decorative-computers@|装饰性电脑 (DECORATIVE COMPUTERS) landk-furniture@ -derfl007s-road-mod +derfl007s-road-mod@derfl007s-road-mod-repaved medieval-paintings@ glymworld-deepslate @@ -21825,15 +21825,15 @@ more-stews-mod better-music-player berries-and-mobs applied-soul -packagedfluidcrafting|封包流体合成 (PackagedFluidCrafting) -packagingprovider|封包供应器 (PackagingProvider) +packagedfluidcrafting@|封包流体合成 (Packaged Fluid Crafting) +packagingprovider@|封包供应器 (Packaging Provider) mts-iv-recurring-air-war @explodee -swift-wheel|迅捷轮盘*¨@swift-wheel|迅捷轮盘 (Swift Wheel) - 审核中 - +swift-wheel@|迅捷轮盘* +@clienttimeandweathercontrolmod|客户端时间与天气控制 (ClientTimeAndWeatherControlMod) better-spectral-arrows polymorphic-tom@polymorphic-toms-simple-storag shoulder-surfing-ultimine@ @@ -21871,7 +21871,7 @@ golem-dungeons@|傀儡地牢* explosion-overhaul-a-new-level-of-destruction @skillz - +hadean-breathe@|幽焰之息* can-you-survive eternal-shield-bead|永御盾珠* slashnouveau @@ -21882,32 +21882,32 @@ simplebackhome|简单回家 (SimpleBackHome) @qismorecommands|齐的更多命令 (Qi's More Commands) polymorphic-travelers-backpack@ playernbt-quests-ftb-quests -interactive-block-prompt@ibp|可交互物品提示* +interactive-block-prompt@ibp|可交互方块提示* @timeshifts @weathershift chat-binds@chatbinds tridentperf-1-0-0 maid-s-bakeries|女仆的烘焙坊 (Maid's Bakeries) -elytraautocollect@|鞘翅自动搜集 (ElytraAutoCollect) +elytraautocollect|鞘翅自动搜集 (ElytraAutoCollect) @banitems|物品封禁 (Banitems) tt20-forged@tt20-reforged -default-server-properties|默认服务器属性* +default-server-properties@|默认服务器属性* f3-lite@ text-animator@ yet-another-furniture@ qarsans-insurgency -yum-delicious|Yum!美味! (yum! delicious!) +yum-delicious|Yum!美味! (Yum! Delicious!) lunarinispread@|丰盛晚宴 (Lunarini Spread) -rs-backpack +rs-backpack@ no-template-netherite@ @mobcapture -koifish +koifish@ @mcinspects @@ -21915,9 +21915,9 @@ koifish armor-hud-by-mcjunky33@ @can-i-sleep-now @zenxarch-damage-vignette -@teleportg +simple-teleport-commands@teleportg too-many-binds -too-many-binds-plus +too-many-binds-plus@toomanybinds astral-dragon middle-earth-additions-npc@|中洲拓展:NPC (Middle-Earth Additions: Npc) middle-earth-additions-factions@|中洲拓展:派系 (Middle-Earth Additions: Factions) @@ -21926,14 +21926,14 @@ brews-and-bevs@|酿造与饮料 (Brews and Bevs) @kimetsu-no-yaiba-x-epic-fight lrs-dynamic-dungeon -@buff-mobs +buff-mobs@ banner-claim@|旗帜圈地* @reformundyingtotem|不死图腾重制 (ReformUndyingTotem) -more-world-crafting|更多世界合成*¨@more-world-crafting|更多世界合成 (More World Crafting) - 审核中 +more-world-crafting@|更多世界合成* @simplelocks|简单的锁 (SimpleLocks) luminous-helmet -steel-armor-blocks@sab|机械动力:钢铁装甲方块 (Create: Steel armor blocks) +steel-armor-blocks@sab|机械动力:钢铁装甲方块 (Create: Steel Armor Blocks) create-armor-blocks@|机械动力:装甲方块 (Create: Armor Blocks) rolled-homogenous|轧压均质装甲* elemental-end-portal @@ -21947,14 +21947,1904 @@ rewithered@ better-lost-candle openblocks-reopened @rc-toys -fancy-toasts +fancy-toasts@ @trueuuid|正版离线共存 (TrueUUID) owapote-bc-exlaser truly-custom-horse-tack@ -@tacz-applied-ammo-box +tacz-applied-ammo-box@ azurum-miner@ thermal-recycling|热力回收* -revoted-wildfire@ \ No newline at end of file +revoted-wildfire@ +biome-makeover-altar-fix +techgunsmixins +open-persistence +hungerstrike-extended-support +@floras-delight +fintastic@|鳍妙至极* +auto-reload-crossbow@|弩自动装填* +aimassiste|辅助瞄准 (AimAssiste) +zombie-dinosaur@a-zombie-dinosaur +monster-expansion +monster-girl|怪物女孩* +@offlineauth|离线认证 (OfflineAuth) +claxels-classic-paxels +meowmeldrawers +@perrys-botany +warforge-factions-remaintained +lunar-moon-reborn +minefactorial@ +create-universal-ores@ +create-crystallized@ +chorus-expansion +@lootshare +clojure-lib +@stripcustomdata|扔掉"custom_data"! (StripCustomData) +wheres-my-brain +champions-alter@|冠军-重塑 (champions-alter) +slashblade-hf-blade@energyblade|拔刀剑:高频刀 (SlashBlade: HF Blade) +virtual-witch-arrangement|虚拟魔女排布法* +@player-move-detector +valkyrien-space-war@|瓦尔基里太空战争* +@blazerod-model-assimp +@bindfirework-hotbar +@new-soul +beautiful-potions@beautiful-potions-mod-edition +yukamis-sophisticated-backpack-tab@ +crouch-to-block@ + +juv@juvenile-preserve|永雏 (Juvenile Preserve) +hooklib@ +oasis2@ +blue-ice|蓝冰* +json-model-legacy@ +lidar-laser-mod +sleep-tighter +butterflies-extension +back-in-slime-honey +alans-basalt-generator +demis-better-stealth + + +@coords-hud|坐标 HUD (Coords HUD) + +@eternisstarrysky|星河传承 (Eternis Starry Sky) +@safe-respawn|安全重生 (Safe(ish) Respawn) +palmon|帕鲁梦* +@spectators-generate-loot|旁观者生成战利品 (Spectators Generate Loot) +death-reduces-life@deathpenaltymod|死亡减少生命值 (Death Reduces Life Mod) +maid-chunk-loader|车万女仆:区块加载* +@maidusehandcrank|女仆摇曲柄 (MaidUseHandCrank) +hard-pity@|大保底:凋灵骷髅/下界合金/三叉戟/龙蛋* +genetics-resequenced@ +moo-fluids +create-balanced-flight-forked +mc-modsthemodium-enderio-capacitor-additions +ironsarms +burst@ +chat-calculator@ + +unbreakable-leads@ +mortality-a-terramity-addon@ + + +chatjs@ +let-me-peek@peekmod +persistent-players +torchmaster-cobblemon-compat +nemos-backpacks@ +homy-tp@ +@shellphone +faded-conquest-ii + + +chosenlib@ + +render-blender|渲染搅拌机* +ve-fluidhandling +@client-export-helper|客户端数据导出 (Client Export Helper) + + +overgeared@ +rain-after-long-drough|久旱逢甘霖 (Rain after long drough) + +@clarityhud +spawn-egg-preview@|刷怪蛋预览* +vulkanmod-extra@ +vramo +@afar +@cmyk-mod +middle-key-ping@|中键标点 (MiddleKeyPing) +scape-and-run-randomizer +amnesia-core +@minecraft-sqlite-jdbc +sqlite-jdbc +forbidden-arcanus-js + +@sha1redemption +f-k-create-world@fcw|不要创建世界 (F××k Create World) +maidusehandcrank@maid-task-scrolling-helper|女仆任务滚动助手 (Maid Task Scrolling Helper) +rozutils@ +instantly-interact-internally@|随点随用* +@jsonem-mult|Json格式化实体模型·多加载器 (Json Entity Models Muit) +gadothaumy@ + +dimensional-teleporter|维度传送器* +avaritia-integration|无尽贪婪:集成 (Avaritia:Integration) +super-lead@superlead|超级拴绳 (SuperLead) +solo-leveling-mod +platanitos@ +@spiceoflifelatiao|生活调味料:辣条版 (Spice Of Life: Latiao) +spicy-species +thaumic-isorropia-kedition +thaumic-concilium-kedition +znet + +railcraft-diesel-locomotive-addon + + + + +alchemistry-redox@ + +extra-hostile-neural-networks +extended-crafting-expanded +packagedexexcrafting@ +scp-reborn|SCP:重生 (SCP: Reborn) +spiritual-flame-firearms@|灵焰炽铳* +a-weird-bow@|奇奇怪怪的弓* + +bacchanalian-mobs +toxony@|毒术* + +ncm-for-mc@neteasecloudmusic-for-minecraft|NCM播放器 (Ncm for Mc) +lavamelon|熔岩西瓜* +@bananaskin|香蕉皮 (BananaSkin) +worn-tear@|磨痕* +cool-rain-reforged +twilight-forest-cave-fix@|暮色森林洞穴修复* + +create-fluid@|机械动力:流体 (Create: Fluid) +create-fly@|机械动力飞越版* +sbm-wooden-buckets|木桶 ([SBM] Wooden Buckets) +sea-of-chests +flammable-tweaker +changed-armor-and-weapons +abominationsinfection +ly-target-dummy|目标假人 (Target Dummy) +unique-accessories@ +@plants-vs-zombieminecraft-warfare-pvz + +goners-armor-trims + +muffins-slime-golem@ +dontstarve-moss-edition@|不要饿死:Moss版 (Don't Starve: Moss Edition) +jerotes-delight|Jerotes乐事* +stellar-striker|星神之怒* +ptp@ +@the-mad-hatter +magic-bees-kedition +complicated-backpacks@complicatedbackpacks +retro-mario-worlds@ +@guard-villagers-1.21.1 +@mcmassok-guardvillagers-fork +villager-pagoda-house@ +@maid-function-extension|女仆!给我阎魔刀 (Maid Function Extension) +xfoxsword|狐狐圣剑 (XfoxSword) +soul-pact|灵魂契约* + +resolution-controll@resolution-control-plus-plus|分辨率控制++ (ResolutionControl++) +@stackable-shulkers-fix|潜影盒堆叠修复 (Stackable Shulkers Fix) +spectrum-rush@|色谱竞速* +libertys-villagers-revived +blue-skies-tweaks +drpgspawnfix +eat-yourself@ +kaiju-calamity +@hattened +ender-christmas-plus@ender-christmas+ +juniors-open-smash-bats@opensmashbats +choppers-delight@ +delighto-flight|云端之乐 (Delight o' Flight) +xbbs-roaring-knight@|XBB的呼啸骑士 (XBB's Roaring Knight) +suixingxiugai@|随性修改 (Suixing Xiugai) + + +avaritia-tweak|无尽贪婪:魔改 (Avaritia: Tweak) +gregtech-easy-core +polishedlib +minecraftapi +easy-gun-mod@easy-guns +lightroot@ +miscmfrcircuits +dartpatch +polar-bears-frozen-oceans-backport +uptodatemod +concrete-and-marble-mod-2 +kaminari-motor-works +block-highlightfx@block-hightlightfx +better-compass-hud@ +@mendmeter +smallstairs|小楼梯 (Small Stairs)¨small-stairs|小楼梯* +mana-crystals@ +abstract-rpg +more-pylons@ +thermal-parallel@|热力并行* +mcpatcher@ +spindles-tfc +tfc-more-items@ +dynamic-trees-tfc-seeds@ +farmer-enchants@ +tiny-tree + +xnet-zhcn-edition|XNet 汉化版 (XNet-zhCN edition) +mtr-yomi-utility-mod@|Yomi的MTR实用拓展 (MTR Yomi Utility Mod) + +laseas-wilderness-traders|Lasea的荒野商人 (Lasea's Wilderness Traders) + +mtr-moogs-temples-reimagined@ + +perennial-pastries@ +narrativecraft@ +mixinbooter2fermiumbooter@ +moogs-structure-lib@ +chickens-can-cast-fireball +bad-sound@ +heirlooms@ +warborn-capture-points +tacz-x-gunslightsaddon-addon@tacz-x-guns-lights-addon-addon +hunger-tweaks@ +it-takes-a-pillage-continuation@ +swansong@ + +croparium@|矿石种植工艺* +indigestion-tablet@indigestion-tablets|健胃消食片* +me-beam-former@|ME光束 (ME Beam Former) +create-fast-schematiccannon@create-fast-schematic-cannon|机械动力:更快的蓝图加农炮 (Create: Fast Schematic Cannon) + +trail-of-light +abominations-apocalypse +@pams-harvestcraft-2-nerf +pommel-held-item-models@ +echoes-of-battle +thaumic-wonders-unofficial|神秘奇境非官方版* +mystical-expansion +refraction-continuation|折射:延续* +expanded-arcanum +@tpatools|TPA工具集合 (TPATools) +hud-manager@|界面管理器 (HUD Manager) +simple-smithing-overhaul@ +@tinyfish-death-penalty|死亡惩罚 (Death Penalty) +@dine|动态NBT编辑器 (Dynamic In-Game NBT Editor) +@wecuifix +time-control@|时间控制* +embers-text-api@ +stageenchantment|阶段附魔 (StageEnchantment) +@black-sun +the-many-faces-of-enderman + +@eternal-sky-orbs +antiqueatlas-recurrentcomplex-compatability@ +global-shaderpack-properties|全局光影包配置 (Global Shaderpacks Properties) +strawberrylib@ +@ibukigourd +onekeyminer-chainable-visuals@|一键连锁:可视化 (OneKeyMiner Chainable Visuals) +precision-mining@ +better-capes-x@ +botanic-cheater +villagerxp@ +silk-suspicious-sand +trial-liberator@ +nycto@ + + +healing-bed +@cleardrops|清道夫 (ClearDrops) +@craftaudit|方块审计 (CraftAudit) +kiss-mod-unofficial@|Kiss Mod:非官方版* +rawinput@ +loot-beams-retro +am2pga +gtbcs-geomancy-plus + +@modmenu-babric + + +@hiirosakura +universalsummoning|通用召唤 (UniversalSummoning) + +@gambac +cad-editor +@unitweaks +@nyalib + +nei-mystcraft-plugin +jei-mystcraft-plugin +bubblesex +legendary-stages +tesseract-optimizations +@uwufied-beta +@uniwrench +useful-ribbits + + +power-advantage|能量优势* + +create-transmission + + + + +applied-pneumatics@|应用气动* +@bratwurst +neoa-ban|机器码封禁Neo版 (NeoABan) +trashpandas-ascension +@food-needed-to-respawn-at-bed +block-cannon|方块投射炮* +pocket-space-teleporter@|口袋空间传送器* +re-from-the-ground-up +@stacks-are-stacks +vc-vocacraft|乐音奇旅 (VocaCraft) - 审核中¨@vocacraft|乐音奇旅 (VocaCraft) +resource-fish + + + +@immibis-peripherals +horde-beacon|尸潮信标* + +extreme-blocks|极限方块* + +talecraft +new-crimson-revelations +makeae2better@ +@elemental-arrows-stationapi +@nyabags +@past-el-palettes +@blowing-bubbles +mobs-n-growths-origins +roadarchitect-encounters + +@mystcraft-ageless +thaumcraft-return-of-the-ancients@ + + + + +neocontinuity@ +continuity-unofficial-port-1-16@continuity-unofficial-port-1.16 +@safefarmland|耕地保护 (SafeFarmland) +@bone-meal-fern +ae2-cell-upgrade-compat +iris-shader-folder@ +rpcdrive@ + +mixintrace-resmithed@forge-1.20.1-1.0.0 +tetra-musket|火枪锻造* +tudigong-wayfinder|土地公 (TuDiGong) +mob-statues + +charset-cre +legacy-farms +thaumictweaker +covers1624-lib + +one-enough-block@ +@better-chat|更好的聊天框 (Better Chat) + +@renderlimiter|渲染抑制器 (RenderLimiter) +@cornucopia-reborn|丰饶之角:重生 (Cornucopia Reborn) +longwings@ +more-ae2-additions +congrega-mystica +@no-screenshot-message|无屏幕截图消息 (No Screenshot Message) +fast-industrial-platform|快速工业平台* +@rolling-gate-rzf-addition|人哉肥的卷帘门附加包 (Rolling Gate RZF Addition) +tensura-mortal-cultivation|转生:凡人修仙 (Tensura: Mortal Cultivation) +pickup-limits + + +armorycache|军械库 (Armory Cache) +gregtech-storage-drawers@ +@chiseled-mod + +@area_lib +unordinaryextras +awakened-craziness +villager-market-nbt-fix|村民市场 - NBT 修复版 (Villager Market - NBT Fix) +illager-expansions +@tropicraft-stationapi +bbl-shops +@dronebox +@volo-volare +@customanvilrenameable|自定义铁砧改名权 (CustomAnvilRenameable) + + +anvilcraft-extra-power|铁砧工艺:额外电力 (Anvilcraft:Extra Power) +create-prismatic-shine@|机械动力:流光溢彩 (Create: Prismatic Shine) +@dream-stone|梦之石 (Dream Stone) + +enhance|增幅 (Enhance Buffs) +@recipes-of-the-lost|失落配方 (Recipes of the Lost) +@huhobot-mod + +tacz-pack-upgrader@ + + +@ping-view + + +extravagant-delight@ +dodos-mobs +pillager-caravans@ +@worlds-whisper|世界之低语 (World's Whisper) + +create-soul|机械之魂* + + +quality-equipment-reforked +metalmith|铜铁新生* +@convenient-malum|更方便的灵灾 (Convenient Malum) +nautilus-spears-mounts-of-mayhem@|鹦鹉螺&矛 - 群骑纷争 (Nautilus & Spears - Mounts of Mayhem) +telepathic-maid@|电波女仆* +tombmanygraves-2-api +mytown + + +@linverticalslab|竖半砖 (Lin Vertical Slab) + +@pearlchunkloader|珍珠区块加载器 (PearlChunkLoader) +@lv-wings +@foxbox +@more_than_a_foxbox +@fox-plushies +ecliptic-seasons-oh-the-biomes-weve-gone +tacz-1-21-1@tacz-1.21.1 +tacz-special-forces@ +sculpture@|生物雕像* +aps-repowered +citizen-pot|市民锅* +linear-experience@lineaer-experience|线性经验* +@durability-viewer-continuation|耐久指示器:延续 (Durability Viewer Continuation) + +huaji-age-astral-regenesis@|滑稽纪元:轮回星启 (HUAJI Age: Astral Regenesis) +slashblade-fabric-addons@|拔刀剑:重织附属包 (SlashBlade Fabric Addons) + +proof-of-honor@|荣誉的证明 (Proof of Honor) +splendour-ablaze-epoch|棠煌纪元* +spellbooks-attributes-reforged|铁魔法属性:重铸 (Spellbooks Attributes:Reforged) + +snows-bosses-mechasent|Snow的Boss:机械遣使 (Snow's Bosses: Mechasent) +create-crafter-integration +create-schematicchecker@createschematicchecker|机械动力:蓝图校验 (Create: Schematic Checker) + +ae-terminal-view-cell-fix@terminal-view-cell-fix|AE2显示元件槽位修复 (AE Terminal View Cell Fix) +pearload|实体区块加载* +projectez +comfy-cozy-brickery@comfy-cozy-brickery-patch +spear-downport@|矛:向下移植版* +accurate-spears@|‌精准长矛‌* +soundreload +world-mod-cacher +orbital-railgun-by-mishkis|Orbital Railgun - 他人的非官方上传,已失效¨@orbital-railgun + + + +file-director-incremental-mod-downloading +paytheprice@ +@the-silicon-mod|硅 (Silicon) +@nitrogen_rbn|氮 (Nitrogen) + + +extended-itemview-eiv@eiv + + +projecte-teams-sync +sequence-pathways-lotm-mod@lotmmod +yung-structures-addon-for-loot-integrations +backpacks-for-dummies@ +@egafixer|附魔金苹果修复 (Enchant Golden Apple Fixer) +@fixtconstructpickaxe|匠魂挖掘等级修复 (FixTconstructPickaxe) +co-lib +omni-cells|OMNI 存储元件 (OMNI Cells)¨@ae2-omni-cells|OMNI 存储元件 (OMNI Cells) - 审核中 + + +sdm-gamestages-helper +mangos-multiblock-library + + +@a177-added-modified-enchantments|A177增修附魔 (A177 Added Modified Enchantments) +qa-enchants@|Qa 附魔* + +create-golems-galore@ +vmh +ore-stages-reborn@ +journey-map-stages-reborn@journeymapstages-reborn +the-one-prode-stage@ +sdm-mob-stages +passive-skill-tree-stage +sdm-loot-overhaul@ +@area-tools +lightbind|亮度绑定 (LightBind) + +timberreplant@ +hunting-dimension-reforged +better-angel-ring +tcompat-tinkers-construct-addon@tcompat +j-a-c-m-just-another-chisel +acuisine@assorted-cuisine +juniors-troll-tnt@trolltnt +compact-mekanism-machines +compact-mekanism-machines-plus@ +woot-revived@ + +croparium-the-twilight-forest@|矿石种植工艺:暮色森林 (Croparium: the Twilight Forest) +@leafs-crucible-ice-and-fire|Leaf的炼金炉 - 冰与火 (Leafs Crucible - Ice and Fire) + +starcatcher@|捕星者* +thermaloot +rs-insert-export-upgrade@ +void-miners-reforged +sanguine-neural-networks@ +chiseler@ + +@mod-menu-bta +xp-cost +shut-up-realms@ +narrator@narrator-legacy +mc-truststore@ +rlcraft-dregora-srp-arachnida-tweak +broken-heart-undershirt-statistic +stitchedsnow@ +@moderndeco + +create-henry@ +recipe-history-addon-for-jei@jei-recipe-history-addon-1.16.5 +@proto-hivemind-girl|新生心智 (Proto Hivemind Girl) +wooden-tools-are-dumb@ +laserio-uel +hamtastic-hamsters@hamtastic-hamsters! +gateways-to-eternity-more-gateways@ +cursed-earth-returns +not-enough-trials +orbital-railgun-reforged + + + +create-heat|机械热力 (Create: Heat) +@dream-enchantment|梦之咒 (Dream Enchantment) +@eatalways|一直吃 (EatAlways) + +rehurttime@|伤害免疫机制重做 (ReHurtTime) +naraka@ + +minechem-working-with-better-builders-wands +dungeons-mobs-port +obtainable-end@ +slideshowplus@ +classic-and-simple-status-bars-rebuild|经典且简易的状态栏:重制版 (Classic and simple status bars rebuild) +@the-last-crystal-cup +@crwui|新版创建世界界面 (CreateWorldUI) +@whiteout +mining-quakes@ +tpw@wenxingtpw +inspectability-forge@hi-inspectability +@orbital-railgun-sounds +beneathmixins +cutting-delight@ +locate-command-backport +the-copperier-age@ +@veinminer-client + +@vertigo +powercrystalscore +twilightsoul|暮色之魂 (TwilightSoul) + +endfield-industry@|终末地工业* +modclassifier|模组分类器 (ModClassifier) +lightningsuperpeashooter|电能机枪豌豆 (Lightning Super Peashooter) +display-delight-fabric@ + +@life-essential-oil|生命精油 (Life Essential Oil) +st-drive@stdrive +roadweaver@|阡陌交通 (RoadWeaver) +xemos-cluckshroom +@the-past-ages +take-back-the-night@ +winds-of-requiem@ +classjs@ +c3-blocks@ +@allium +create-netherless-ported@ +mechanical-healing-machine@ +@potionenchant|药水附魔 (PotionEnchant) +tongda-way|通达路 (TongDaWay) + +enigmatic-legacy-cursed-ring +elemental-synergies +simple-netherite-horse-armor@ +project-mmo-farmers-delight-compat@project-mmo-farmers-delight +darling-decorations@darling-decorations! +cute-n-sweet@cute-n-sweet! +inspectability@ +@crafttweakeradditions +hunting-dimension-remake +@lootify-(structures) +portable-storage@|随身仓库* +lootstories|遇言典著 (LootStories) +otherworld-core +simple-corpse +botania-mana-fluid +create-ae-generator +create-additional-logistics@|机械动力:物流附加 (Create: Additional Logistics) +issrf-range-fix-irons-spells-n-spellbooks|铁魔法溢出射程修复 (Range Fix - Iron's Spells 'n Spellbooks) +goety-delight@goetydelight|诡厄乐事* +advanced-little-garbage@advanced_little_garbage|高级小垃圾* +@tpa-tp +@miteequilibrium|MITE:宁静 (MITE: Equilibrium) +tetrawhetstone|Tetra磨刀石 (TetraWhetstone) +brain-stealer-dragon-addon|龙之生存:夺脑龙 (Brain Stealer Dragon - Addon) +official-epic-fight-x-parcool@official-epic-x-parcool +jump-efficiency|跳跳效率* +smooth-scrolling-reforged + +one-enough-fluid@ +jerksteve@ + +ydms-mobhealthbar-mod@ydms-mobhealthbar +otherworld-origins +dragon-mounts-patches@ +gregtech-modern-ore-generation-standalone@gtmogs +cape-banners +bftp@blast-from-the-past +compact-crafting-updated +appliedchisel|应用凿子 (AppliedChisel) +item-obliterator-despawn-fix +@borukva-food +dynamic-difficulty-mod +obituary-exchange +from-the-caves@from-the-cave +lumen-alveary-patch +configurable-tooltips +modlistmemory@|模组列表记忆 (Mod List Memory) +macu-lib@ + +better-end-crashed-ships-tweaks@betterend-crashed-ships-tweaks + +no-caves-mod@|无洞穴* +simply-desire-paths +happy-ghasts-boost@ +cbr-addon@|自定义大逃杀扩展 (CBR Addon) +compact-expansion +armor-quick-swap@|盔甲快速切换* + +mixing-and-cutting@mixing_and_cutting!|混合切制 (Mixing and Cutting!) +yumo-isk-stackstackstack|拼好组 (Yumo isk-Stackstackstack) + +flourfood|面食 (Flour Food) + +neo-ae2-toogleable-view-cell@|AE2可切换显示原件-Neo (Neo AE2 Toogleable View Cell) +mtt-more-tetra-tools|更多Tetra工具 (More Tetra Tools) + +compat-delight@ + + +thermal-processing +extramachinery-reforged +botanicalmachinery-reforged +armorplus-reforged + +@better_tooltips +@ironmanarmor +vs-genesis@ +element-endow|元素赋予* +resource-bar-api@ +goety-iron|诡厄铁魔法* +i-feel-bad@|恙 - 疫恙待诊* +@backoff + +mc-mods-creepypasta-in-one@all-creepypasta-in-one +disable-structures@|禁用结构* +windowlogging-renewed +leaderboards +@beacon-dragon-egg|信标龙蛋 (Beacon Dragon Egg) +maid-travel-camera|女仆旅行相机* +nieih@ + +origins-furs-updated@ +@tinkers-gears|匠械 (Tinkers' Gears) + +moreeverything +epic-fight-skill-tree@ +dtk-update@pale-lively-update +goety-tetra-fix|魔力剑聚晶Tetra兼容 (GoetyTetraFix) +otherworld-apotheosis +mana-attributes@ +azalea-growth-control@ +natural-waters@ +health-bars@new-health-bars +enchantment-insights@ +bloomcraft@ +bmc-pale-garden-patcher +@polluted-rain +modernfix-mvus@ +stutterfix-refursbished@stutterfix-refurbished +noisiumforked@ +golems-kill-creepers@golemskillcreepers +no-animal-tempt-delay@ + +pixelshot + +changeling +ancient-scribes +@kamenriderperfectionproject|假面骑士补完计划 (Kamen Rider Perfection Project) +goety-iron-link|法厄通* +superaim + +taczattributeadd@|Tacz 属性添加 (TaczAttributeAdd) +@itemtocoin|以物易币 (ItemToCoin) +pet-rocktm|宠物石 (Pet Rock™) +gunfire-overhaul-echoes-of-the-battlefield +stamina-attributes@ +clanging-howl +limbus-company-mod-lcm + +realistic-bird-behavior@|真实鸟类行为* +wing-kirin|翼麒麟* +mcheli-reforged-mch-r + + + +enigmatic-legacy-plus@enigmaticlegacy+|神秘遗物+ (Enigmatic Legacy+) + + +novacore@ +snowycrescentcore@|雪月核心 (Snowy Crescent Core) +hostile-neural-industrialization@ +clockwork@clockwork_mod +vista@vista_tv +kismet +sea-life@new-sea-life +armored-foes@ +vehicle-upgrade@ +party-creepers +mc-mods-the-heads@all-the-heads +fantastic-wings@ +village-masquerade +new-shroomcraft@shroomcraft +tinker-and-better-combat + +extralib@ +whats-your-build +where-did-i-die@ + +tooltips-txf@ +@allow-disconnect +target-locks|注视锁定 (Target Lock) +fleeing-animals@ +peacegiver|和平方块 (PeaceGiver) +better-books@better-enchantment-tooltips +@chest-on-a-ghast + +general-feedback@|通用意见反馈* +raid-counter@ +modern-startup-qol-reforked +loot-integrations-dungeon-crawl +vanilla-loot-addon-for-loot-integrations +loot-integrations-cataclysm +aquamirae-x-better-combat-compatibility +ice-and-fire-dragons-x-better-combat@ +eternal-currencies +comfortable-campfires@ +embers-floating-islands@ +farmers-cutting-oh-the-biomes-weve-gone +beds-dont-explode +enchantment-library-at-home +turtlearmor +wandering-collector-forge@ + +jaffactory +horses-can-swim@swimming-horses-forge +mob-population-control +blighted-beasts +@the-witchs-broom|魔女的扫帚-Fabric移植版 (Majo's Broom) + +attribute-sculptor|属性雕刻家* +notexturerotations@|无纹理旋转 (NoTextureRotations) +baked-enchants +@priority +combatproperties + +judgement-day +@origins-plus + + + +dynamic-rpg-resource-bars@dynamic-resource-bars +lwjgl3ify-colorful@lwjgl3ify-1.12 +@lib-39|叁玖库 (Lib39) + +@simple-paper-doll|简单的纸娃娃 (Simple Paper Doll) + +@destroying-minecraft + +@trophymod|奖杯模组 (Trophy Mod) +create-brassworks-missions@ +elysian-event +sticky-enchanting-lapis@ +hungry-pouches +antiquities@ + +pams-harvest-the-nether +certain-questing-additions@ +alexs-mobs-capsid-fix|Alex生物衣壳体修复 (Alex's Mobs Capsid Fix) +@parcoolskill|跑酷技能 (ParCoolSkill) +merchantjs@ +fetchjs@ +mc-mods-the-mafuyu@all-the-mafuyu|万象瑞雪 (All the Mafuyu) +goety-end-biomes +structure-spawn-area-fix@|结构生成区域修复* +aloof + +armory-rpg-series@ +moving-light-source +ftb-mighty-ender-chicken-rehatched +cyborg-robot-chicken-returns@ +dragon-wings@dragon-wings-09|龙翅膀* + +furnitures-o-plenty@ +windswept-delights@ +beltborne-lanterns-accessories-layer@ +beltborne-lanterns@ +mesophils-cities@ +mesophils-cities-classic +villagers-reborn@ +@simple-player-logger +mint-lib@ +better-fishtanks@|更好的鱼缸* +@roman-minimap +accelerated-recoiling|加速碰撞* +thank-your-eggs|谢个蛋/鸡谢动力* +star-rail-apocalyptic-shadow|星穹铁道:末日幻影 (Star Rail: Apocalyptic Shadow) +unusual-adventures +gui-scaler@auto-gui-scaler +ae2-id-sorter +@danganronpa|魔女论破 (DespairBook) +maids-return@|女仆归心 (Maid's Return) +boosted-brightness-remastered@ +helpfulcampfires@ +geophilic-backport-vanilla-backport-compact +nexus-layer +zipline@ +fathomless-crimson +@hexconnect +phase-journey +lucky-pot +@enchantment-controller|附魔控制器 (Enchantment Controller) +auroraslanterns@ +pocket-repose@ +fishing-plus@fishing+ +chaoscrafts-devices@ +obsessed@ +arsenal-rpg-series@ +mod-change-log|模组变更记录* +@entity-glow|实体发光 (Entity Glow (Backport)) +sophisticated-sorter@|精妙整理* +abi-forge¨abi-fabric +display-edit +arthys-elemental-engine-aee-forge +bobby-legacy@ +ambience-mini +arthys-ebonite-ae-forge +enigmatic-arcana + +optifine-cit-enchantment-patch@optifine-cit-patch +controller-mod@controllermod|控制器支持 (Controller Support Mod) +new-enchantment-switch@enchantment-switch +ftgu-from-the-ground-up@from-the-ground-up +options-profiles@ +forgematicaprinter@ +better-biome-blend-continued@ +furnace-xp-storage +recased@ + +elemenics|元质学* +pale-bloom +zip-fetcher@ +create-kinetic-illuminator@ +@tagban + +trade-tweaks@|交易优化* + + +bryszirake +no-worldgen-5-you@ +no-worldgen-5-you-continued@ +spiral-tower-villages@ +care-to-share@ +@hamstermod|仓鼠模组 (Hamster Mod) +@statictnt|静态TNT (StaticTNT) +floating-damage-numbers +voice-messages|语音消息* + + +@garden-farming|园林耕作 (Garden Farming) +lets-do-tfc-farm-charm|群峦农艺 ([Let's Do] TFC Farm Charm) +ride-casually@|随意骑乘* + +night-repair@|夜视修复* + + + + + + +originjs|起源JS (OriginJs) + +ancient-nature-a-prehistoric-experience +mte-more-tetra-effects|更多Tetra词条 (More Tetra Effects) +@vertical-bobbing +extraquests@ +crashexploitfixer@|崩溃漏洞修复 (CrashExploitFixer) +sticky-it +you-have-treasure|你有宝! (You Have Treasure!) - 审核中¨@you-have-treasure|你有宝! (You Have Treasure!) +nether-portal-configurator +lavasweeper@|熔岩清道夫 (LavaSweeper) +@more-people +simply-improved-terrain|简单地形改进*¨simply-improved-terrain-fabric|简单地形改进 (Simply Improved Terrain) + +faewulfs-lib@faewulfslib +mce2-lib +bop-legacy-reborn-croparium@|超多生物群系往昔重生 & 矿石种植工艺 (Biomes O' Plenty Legacy Reborn & Croparium) +lotr-reworked-new-beginning@|魔戒:重置 (LOTR Reworked) +kelp-fertilizer@|海带肥料* +crystalwing|水晶之翼 (CrystalWing) +@label-my-items +lightoverlay@ +rcw@ + + +obituary +worn-horseshoes +antiqueatlasautomarker +connectorlib@ +milkable-creepers +@villager-clothes +monsters-girls-witchy-stuff +grimoire-of-gaia-spells|盖亚魔法 (Grimoire of Gaia Spells) +psi-oddities|Psi-奇思妙想* +@schempaste +mining-progress-hud|挖掘进度 HUD (Mining Progress HUD) +creativetabs@creativetabsplus +create-missiles@ + + +nuclear-bomb-bay@ +lightning-rod-backport +complicated-bees@ +werdens-illagers|沃尔顿的刌民 (Werden's Illager +) +thaumic-attempts +horseshoes-forge +replication-matter-overflow-tank +spaceploitation + + +pathfinder-api@ +no-resource-pack-warnings-forge@ +dbc-expansion|DBC扩展 (DBC-Expansion) +cutt-promix@cutt_promix|砧板乐事 (Pro Assistant Cutting Board) +dirty-bowls-delight|脏碗乐事* +corpsetweaker@corpse-tweaker|遗体调整 (CorpseTweaker) +convenient-interface|便捷接口* +brimm-armors-tactical-military-armors +@the-shadow-tapes +starter-bunker +shortbow-revamped +places@ +pets-pillage +ultimine-rewind@|连锁撤回* +project-time-master + +let-me-click-and-send-for-server@ +extra-hoppers@ +quick-pack@ +armored-paws-backport@wolf-armor-backport + + +create-mining-laser +create-who-touched-my-train@|机械动力:谁动了我的列车! (Create: Who Touched My Train!) +@so-lets-set-the-ore-on-fire +tab +armor-tooltip +modefite-item-definition-backport@ +hobbit-hill-village@ +alexs-caves-better-combat@ + +mc-modstheimbaium +storage-drawers-extra-reloaded +startup-qol +modern-startup-qol +domestication-innovation-fixed|驯养革新修复版* +@clear-despawn-reworked +spartancataclysm@ +@metalrender +nirvana-library@ + +animated-mojang-logo@ +hardcoreastagesitem +@morbid-reborn-reborn-reborn + +fullbright-ultimate +fast-pipes@ +immersive-enchanting@|沉浸式附魔* + +boss-checklist@ +fire-resistance-tiers +cursedinfuserbycage|诅咒之笼注入 (CursedInfuserCage) +enchantedsails@ +pale-monarch@pale-monarch-boss-mod +firmanutrition@ +calypsos-night-vision-goggles|卡里普索的夜视仪 (Calypso's Night Vision Goggles) +the-apiarists-terminal +bee-mastery@ +nonupdate-retro@|不再有更新:Retro 版 (NonUpdate-Retro) +custom-message|自定义消息* +kjsutils + +create-access-denied@ +create-better-motors@ +@create-cc-total-logistics +create-fuel-motor@ +create-extra-casing@ +@create-meta-logistics|机械动力:元物流 (Create: Meta Logistics) +bush-master-core +@tritiumconfiguration|氚配置 (TritiumConfiguration) + +@mob-effect-display-fix|状态效果显示修复 (Mob Effect Display Fix) + + +anvilcraft-pigsplus@anvilcraftpigsplus|铁砧工艺:猪+ (AnvilCraft: PigsPlus) + + + +@teas-official-random-things|Eh5の茶会:随意作品 (Teas Official Random Things) + + +@chatscreen|聊天屏幕 (Chat Screen) + + +sunny-riptide-trident@ +@lastwords|鸳鸯董卓 (LastWords) + +@bubble-column-tweaks|气泡柱调整 (Bubble Column Tweaks) + +@villager-inventory-hwyla-plugin +blessfulled +irregular-implements@ +sounds-be-gone@soundsbegone +@copper-grates-bubble +@fireworkdurability +create-colored-chain-conveyor@ +create-dimension-steamworks-realm@create-dimension,-steamworks-realm +punchy@punchy-fpa +@quickswap|快速切换物品栏 (Quick Swap) +@blockdynamics_mod +armor-durability-hud +g3-doors +arcane-convergence|奥法归一* +ae2-ftbquest-detector|AE2-FTB任务检测器 (AE2-Ftbquest-Detector) +better-library@ + +dimensions-of-alexs-caves@|Alex的洞穴:维度 (Dimensions-of-Alex's-Caves) +better-combat-particle@|更好的战斗粒子* +vsplit@|V裂 (VSplit) +taczcurios@|Tacz饰品 (TaczCurios) +jeai@ + + +unofficial-variedcommodities +literal-sky-block-neo +cnpc-cobblemon-integration +cnpc-gecko-addon@cnpc-geckolib-addon +minecards@minecards-forge|我的卡牌* + +construction-wand-kots@|建筑手杖-耕云钓月版 (Construction Wand - KOTS) +thaumcraft-nei-plugin-patched@ +replication-rs2-bridge + +player-attribute-commands@|玩家属性指令* +lumis-lots + +omnisearch@|万象搜索* +ftb-unearthed +@colored-obsidian +collectors-caravan@ + +@itemagnetic + +soul-candles@ +lunchbox-sustenance-project|好好吃饭* +just-some-more-crops|农务多多* +cobblemon-spawn-alerts@|方块宝可梦生成提醒* +hex-text@ +hand-snow-ball|给我一个雪球* +extended-shaders +titles-and-timing@renxi.ch|称号与计时 (Titles and Timing) +biomespy|群系间谍 (BiomeSpy) +random-dungeon-spawners|随机地牢刷怪笼* +versionerreborn|整合包版本工具:重生 (VersionerReborn) +critical-strike@ +ghostblocks@ghostblock|幽灵方块 (GhostBlock) +@crossbow_scoping +create-threaded-trains@ +raing-row@raing_row|雨后生草 (Rain Grow Mod) + +@seafloor_gardening|海底花园 (Seafloor Gardening) +nemos-vertical-slabs@ +electric-advantage|电力优势* +froglightsreimagined@ +flower-pots-plus +diversity-legacy +@sturdy-farmland +bettervannilafishes|更好的原版鱼类 (Better Vannila Fishes) + +ezunclear-reactor-big-explosion-preventer|易研电炸/核抑危 (EzUnclear) + +kubejs-goety +create-stuff-and-addition-tank-fix@ +create-ratatouille-fried-delights|机械动力:齿轮与麦穗的炸物拓展 (Create:Ratatouille Fried Delights) +spellcasters-archives|施法者档案馆 (Spellcaster's Archives) +enders-spells-and-stuff-requiem +ironrust@ +web@webs +letmecc@|让我看看 (Let Me See See) + + + +fancy-trail|梦幻刀光* +caster-curios-bonus|灿奇术饰* +anyblade@|万物皆为刃 (Anything SlashBlade) + + +tongda-railway|通达铁路 (TongDa Railway) +crystcursed-dragon|晶咒龙* +brutal-mobs|狂暴生物* +super-headcrab|超级猎头蟹* + +@simple-locations|简单位置 (Simple Locations) +lios-seafaring-dungeons@ + +inductive-automation +steam-advantage|蒸汽优势* +@better-block-entities + +rs-logical +industrial-revolution-by-redstone-rebooted +the-void-legacy +omoshiroi-kamo +death-taxes@death-and-taxes +@emotional-support-creeper +ly-ender-compass +walking-crops +harken-scythe-resharpened@ + + +resonant-engine +@liquid-xp|液体经验 (Liquid XP) + +xlvs-locks|Xlv 的锁 (Xlv's Locks) +the-legend-of-2900-core + +graphene-extension +dr-stone-kingdom-of-science@dr.-stone-kingdom-of-science +kinetic-pixel-dup-bug-fix +@custom-shutdown|自选关机 (Custom Shutdown) + +formatless|格逝* +invisiblocks-1-2-1|隐形方块 (Invisiblocks) +mage +spore-addon-nightmare|孢子扩展包:噩梦 (Spore Addon: Nightmare) +the-biomass-must-grow|生物质必须生长 (The biomass must grow) +create-nuka-cola@create-nuka +dyson-cube-project|戴森立方计划* + +annoying-villagers-herobrine-invasion@|烦人的村民 (Annoying Villagers: Herobrine Invasion) +@net-music-peripheral|网络音乐机:电脑外设 (Net Music: Peripheral) + +tacz-golem-compat@|傀儡装配-战术人形 (TaCZ Golem Compat) +customizable-player-health@|可自定义的玩家生命值* + +goety-twilight|诡厄暮色* +@achievement-attribute-rewards|成就属性提升 (Attribute Achievement Rewards) + +@easyentitylib +coolcatlib +myths-and-monsters +@chronograph|计时仪 (Chronograph) +cnpc-additions@ +complicated-bees-genetic-industry +squish@ +tameableminisheep@ +tameablemossbloom@ +wolf-armor-and-storage-legacy@ + +@mcedia + +phonk-edit@ +ae2addonlib +infinity-mending-datapack-mod +epic-knights-ores-and-alloys +epic-knights-dark-ages-addon +indestructible-server-fix|坚不可摧服务端修复* +chromosome-lib@ +turtlebois-core-library +simple-voice-chat-group-player-names@svc-group-player-names +cinematic-zoom-neoforge@cinematiczoom +busy-villagers + +ars-affinity +ars-hex-unity +pretty-in-pink@ +chatcalc-neoforge@chatcalc +dust-and-ore@dust-and-ores +matrix-minecraft-bridge + +@entity-chat +jei-copy-recipe-json|JEI随意复制 (JEI Copy Recipe Json) +configured-burn-time@|可配置的燃烧时间 (Configurable Burn Time) +time-controller@ +@improved-snow-melting +ridebattle-lib@ridebattlelib|骑士战争库 (RideBattleLib) + +gamma-tweaks@ + +wandering-trader-express|流浪速递* +stewme|铁锅炖自己 (Stew Me) +kubejs-powah-reborn +inheritable-coat-color@|可继承的绵羊毛色* +pouch-of-unknown-nirvana|未知之袋:涅槃 (Pouch of Unknown: Nirvana) +ae2-auto-pattern-upload@|AE2自动样板上传 (AE2 Auto Pattern Upload) + + +petite-inventory@|真实物品栏 / 娇小物品栏* +cipher-wright +short-players@|矮玩家* +backported-spears@ +ufo-future +copper-age-backport@backport-copper-age +animal-garden-sea-otter@ +animal-garden-porcupine@ +animal-garden-red-panda@ +animal-garden-owl@ +bytebuddies +entity-enchantment|实体附魔* +smoothwater +insanity-assorted-magics +bonded@ +indexer@ +dimensional-worldborder-continuation +dimensional-worldborder +damage-tracker +the-growling-man +mapwright@ + +wandering-ribbit@ +kinetic-minecart@ + +classic-pipes@ + +dust-and-ore-raw-ore-expansion +@minecraft-sigma +@pricks-n-planks +archogenum +giant-drops +kubejs-data-component +quantum-things@ +simple-emotes@ +flux-ducts +valley@valleys + +wizard-tower +the-dyson-sphere-project@dyson-sphere-project|戴森球计划* +bronze-mod@bronze|青铜 (Bronze) +lib39@ +companions-dogfolk@ +the-block-keeps-ticking@ +better-pet-teleporting@|更好的宠物传送* +beacon-aura@ +create-ender-storage +portalfarm-despawn-fix@ +@kitchen-projectiles +eternally-overburdened +robot-dinos +stellar-decay|繁星枯朽* + +acceleratedblade@|拔刀剑:加速渲染兼容 (SlashBlade: AcceleratedRendering) +heartbladeunoffical|心之所向非官方版 (HeartBladeUnOffical) +slashblade-japanese-addon-pack-adder|拔刀剑·日系附属·扩展包 (SJAPA·The SlashBlade EXtra) +project-saligia-reborn|炼金重铸计划·七罪新约* +lostbladereborn|遗忘之刃重生 (LostBladeReborn) +slashblade-shinku-and-blood-katana|炼狱真红之刃 (SlashBlade: Shinku And Blood Katana) +foxbladeextrare|狐月刀改·重生 (FoxBladeExtraRe) +spelunkers-palette@ +@seasons-api|季节 API (Seasons API) +mavity-lib@ +void-lib@ +easy-platform|简易平台* +dont-get-hurt|无伤大雅* + +flight-ring@|飞行戒指* + +autorepairhandler|坚不可摧 (AutoRepairHandler) +wonderous-sea-endlessocean@wonderoussea +@bettertp|更好的传送 (BetterTP) +ftb-echoes +makeup-aesthetics-decor@ +opencomputers-rescaled +entityguardian +deep-dark-dimensional-dungeons +infinite-light@|无限光源* +@list-core +thermal-tweaks-dynamic-temperature-survival +debloat@ +orthocamera-unofficial-neoforge-port@orthocamera-(unofficial-neoforge-port) +crossroads-legacy +@wilder-enderscapes +charcoalplus@ +modern-decor-outdoor@ +shadeless-blocks@ +modern-decor-wooden@ +shadeless-mobs@ +herobrine-old-edition@herobrine-mod-alpha-version +multiplayer-spawners@ +compact-crafting-fork + +custom-machinery-ars-nouveau +peaceful-hunger@ +ftb-ez-crystals + +falling-blocks-anim +immersive-overlays@ +volcano + +nbtstructurelib@ +@enderlib +coalapi@ +homo-http-router@homohttprouter|野兽先辈的HTTP路由器* +fastconfigapi@ +@awakeapi +@anti-offhand +@so-tuff + +moreeventsjs +spudiversity-more-and-more-potato|土豆盛宴-更多的土豆! (Spudiversity - More And More Potato) + +flans-mod-tyrants-and-plebians-edition +connected-caves@ +invasion-code-red-reborn@ +mecha-pillager-boss +@zg-mobs +invincibleconquest +@arknights-races|明日方舟种族 (Arknights origins races) +gulliver-lite@gulliverlite +mojo-redmetal +logistics-bridge|Logistics Bridge - 原版¨logistics-bridge-dmnedition|Logistics Bridge - 修复版 +sourceblock@|源方块 (Source Block) +aec|以太工艺 (AetherCraft) +animal-garden-sugar-glider@ +animal-garden-meerkat@ +just-sit@just-sit! +tameable-endermen +vanilla-shears@vanillashears +logibots +always-eat@ +spice-of-life-valheim-reforged@ +enchantedstorage@|附魔收纳 (EnchantedStorage) +ultimine-addition +immersive-tips@|沉浸式提示* +lightspeedre-launch-optimizations +enhancedgunsounds +@mcsr-ranked +@create-cafe-unofficial|机械动力:咖啡馆 非官方分支 (Create Cafe Unofficial Version) +realm-rpg-treasure-balloons + +@alexs-caves-delight-(-farmers-delight-alexs-caves-add-on) +kitchen-tools@kitchen-tools-tc6-addon|厨师宝具* +camphor@ +jade-magnifying-glass@ +c418-music-restored@ + +crab-claws@ +draconic-evolution-render-patcher@ +touhou-little-maid-epistalove@|车万女仆:纸短情长 (Touhou Little Maid: Epistalove) +omnicore@ +@voxelbridge +tritium@optitritium|氚* +immersive-fixes@|沉浸工程修复* +schematic-energistics@ +boss-rush@boss-rush-mod|老板冲* +mekanism-bigger-teleporters@ +@carpet-igny-addition +registry-blocker@ +more-ornamental-plants@|观赏植物* +presence-footsteps-neoforge|脚步声NeoForge版 (Presence Footsteps (NeoForge)) +@villager-translator|村民语言翻译器 (Villager translator) +ai-player@|AI玩家 (AI Player) +aaron@ +@hextech-lib|海克斯 (Hextech Lib) + +missing-trees@ +pk-craft +easy-craft-tweaker@easy-crafttweaker +wenxins-warp-pipes@ +forgelin-legacy¨@forgelin-legacy|Forgelin: Legacy - 审核中 +anothertips@ + +copper-drops +cooks-collection@ + +no-end|我的末地呢?!* +ome-tweaks@ +clutter-no-more@ +decocraft-nature@deconature +frex@ +open-terrain-generator-continued@ +duplicationless|复演结/コピーの終結* + +custom-mob-targets + +bopintegration-catized@bopic|超多生物群系集成:非官方版 (BOPIntegration - Catifized) +@rawmouseinput + +buildnotes@|建筑笔记 (BuildNotes) + + +hbms-nuclear-tech-modernized@|HBM的核科技现代化 (HBM's Nuclear Tech Modernized) +mtr-france-addon@ +minions-remastered +@applied-enough-items|应用定整 (Applied Enough Items) +milkspray|牛奶喷雾 (Milk Spray) +@road-to-evolution|进化之路 (Road to Evolution) +@totem-of-homing|归家图腾 (Totem of Homing) +better-foliage-legacy-edition@ + + +@adventure-platform-mod +pedestal-crafting +pedestal-crafting-patched@ +putbuildings@|建筑放置 (PutBuildings) +@no-air-mining-penalty|去除浮空挖掘惩罚 (No Air Mining Penalty) + + +reader + +improved-cleanroom-relauncher@ +qbithop + +wilder-flowers@ +render-held-item-while-a-wolf-witchery-addon +economicalmilktea +unnamed-monster@ +half-bite +merry-snow@|瑞雪物语* + +hdd-continuation@ +headdowndisplay@hdd +refined-equivalence + +amtweaker + + +fluidityfoodstuffs +chisel-modern +rmscore@ +magic-coins-tweak +irons-apothic-invaders@ +mekagenjei-mekanism-generator-addon +boatfly|船只飞行 (BoatFly) +ibicf +kube-utils@ +laputan +lecovian +pops@ +bad-ores +reactor-plus + + + +petrock-mod +bettertalk +autocrafter2000 + +baby-mobs-rebrushed@ +animal-garden-mouse@ +better-sleeping|更好的睡眠* +amtgenerators + +extrabees-community|更多蜜蜂:社区版 (ExtraBees: Community Edition) + +better-farming@better-farming-plus +evil-seagull@|邪恶海鸥* +hbm-galacticraft-companion +gamingbarns-guns +jas-compatability-addon + +ad-astra-auto-lander +@createfkcrafter|机械动力:解放合成器 (Create: FuckCrafter) +create-cobblemon-balls-overhaul +create-air-ducts@ +grimoire-of-gaia-redux + +create-wrapped +create-cogs-invaders +notenoughscaffold +ore-trees-reboot +re-barked +composing +antique-armory|匠魂盔甲怀古* +cataclysm-ignis-soulfires +just-dyna-things +metal-detector +replication-addon-tiered + + +savethehorse|哦齁齁齁斯 (SaveTheHorse) +hamsters-delight@|仓鼠乐事 (Hamster's Delight) +the-ultimate-ore-mod +animal-garden-fennec-fox@ +animal-garden-common-raven@ +@nether-portal-remastered +redstone-recharged@recharged +toil-and-trouble@ +basicweapons@|基础武器 (Basic Weapons) + +youkaishomecomingcurios|妖怪们的归家:饰品兼容 (Youkais Home Coming Curios) +drop-force@|掉落力* +sacrifice|祭品/掉落不死亡* + +getoutofmyway|别挡我的路! (GetOutOfMyWay) +stronger-snowballs@ + + +simply-swords-epicfied|简易刀剑 - 史诗战斗 (Epic Fight - Simply Swords EpicFied) +cotton-resources + +talents +rain-rot@ +lothryas-spell-additiions +shershavy@|谢尔沙维* +craycrafting +@rain-world-weaponry +customtitlescreen +unusual-prehistory-2@ +shield-api@ +brick-lib-api@ +whatversioning|申必版本号 (WhatVersioning) +yourender|完了 (YourEnder) +@mana-jade|以玉定植 (Mana Jade) +villagerapi@ +peeping-angels@ +simple-teleporters-reforged@ +structures-tweaker@ +tooltip-overhaul@ + +pre-menu-screen +titlescreennotice +spellboundskiescustom +kind-of-nice-weapon +copperized + +comes-alive-reworked@|凡家物语:重制* +better-animal-feeding@ +tiny-dragons@ +@eternal_winter_mirage|永冬幻境 (Eternal Winter Mirage) +tightfire@ +wax-item-frames@ +obese-crops@ +@frame-changer + + + +tfcgurman@tfc_gurman +fungi-delight@ +leaves-youll-go +yagens-attributes|Yagen的属性 (Yagen's Attributes) +terrarium-world@terrarium-earth +daylightchangerstruggle@ + +@eclipses-tweakeroo-additions +utamis-futon@ +@not-enough-furniture +ceruleans-mystical-smithery +mutated-mobs-mod +ftbxaerocompat +aesthetic-windows +bagels-palette-blocks +astrochem@ +dynamic-portals +heralds-ii@ +industrialized-architecture +better-starting-armors@ +@slashblade-frostbite-snowbloom|拔刀剑:雪蚀绽霜 (SlashBlade: Frostbite Snowbloom) +raspberry-core@ +advancedcamera-by-lostmortal +classic-peripherals@classicperipherals +sbm-punt-animal +sbm-baggable-mobs +sbm-helm-bucket +animal-garden-spotted-hyena@ +animal-garden-western-gorilla@ +more-concrete@ +lukis-strongholds +lukis-woodland-mansions +thaumic-model +gtse +ancore@ +xdonplugins + + +expanded-storage-kots|扩展存储 - 耕云钓月版 (Expanded Storage - KOTS) +ragdolls@ +mar-mars-enhanced-ore-variety@ +superflat-progression@ +@tinkers-origin|始源匠心 (Tinker's Origin) +renxin-music-player@|荏心音乐机* +slotify +textile@textile-lib +clash +morebatsvariants +hxcperipherals +yori3os-grappling-hooks@ +seasonal-decorations@|🎄 季节性装饰 (🎄 Seasonal Decorations) +neon-sentry-keeper + +maid-please-help-me-forge|女仆,请帮我打铁* + + +chemcraft-more-backpacks|更多林业背包 (More Backpacks) +e-flux +thaumcraft-extras|神秘扩展* + +peacefulpack +sulfur-and-nitre +combat-evolution +shaderfixerragecraftver +brokenentity-0 +the-cardboard-box-extension +kubejs-cc@kubejs+cc-tweaked +sbm-elevators +sbm-cacti-bucket +sbm-grappling-hook +sbm-hand-held-piston +petting@ +datapatched@ +no-villages +hbms-nuclear-tech-leafias-cursed-edition +cell-chemistry +ae2-all-encompassing@|AE2:包罗万象 (AE2: All Encompassing) +tiptool +burrowers@ +skyislands +fastflyblockbreaking-reworked +aether-ruined-portals + +@more-backpack-upgrades|更多背包升级 (More Backpack Upgrades) +@daedalus-enchantments|代达罗斯附魔 (DaedalusEnchantments) + +better-lock-on@|史诗战斗 - 更好的锁定 (Epic Fight - Better Lock On) +nightfall-invade|史诗战斗:夜幕入侵 (NightFall : Invade) + +copper-friend-backport|铜器时代的小家伙* +slab-topographic|自然台阶 (slab topographic) +tag-editor|贴标签* + +omnitool@omnitools|全能工具 (OmniTools) + + +@syncmatica-revolution|共享原理图增强 (Syncmatica Revolution) +quantumhue-visualizer|量子色域 视界 (QuantumHue Visualizer) +@thip_expansion|特摄英雄补完计划-附属 +sbw-drr|[SBW]龙之崛起:重铸 (DragonRise: Reforge) +nine-nether-regions@|九幽引 (Nine-nether Regions) +handheld-moon@|掌中明月* +carpenters-blocks-ce|木匠方块-社区版 (Carpenter's Blocks-CE) + +@yos-coins +ae-infinity-disk@|AE无限磁盘 (AE Infinity Disk) +survivalplus-primer¨survivalplus-primer-fabric +northstar-redux@|机械动力:北极星 - 重制版 (Create: Northstar - Redux) +create-igneodynamics@ +create-numismatics-utils@ +create-vintage-unofficial-port@create-vintage-(unofficial-port) +create-bits-n-bobs +ccb-tweaks@ccbt +gregtechceu-modern-extra-unofficial@gtceu_extra +geotetraarmor +more-more-tooltips +more-tooltips +elemental-creepers-redux +mc-modsthecreepers +wikiful@ +atlatls@ +@itemaccessrestrictor|物品存取限制器 (Item Access Restrictor) + + +ahh-pvp@ +toggle-sneak-sprint +monstie-hunter-baby-monster-collector@monstie-hunter-baby-monster-collector! +enchants +hubris +custom-nether-portals@custom-nether-portal +damagenumberlegacy +helios-divinity +item-marks|物品标记* +mossylib@ + + +@better-trading-menu|更好的交易菜单 (Better Trading Menu) +easy-npc-config-ui@ +evolved-mekanism-extras + +backups +inventory-particles@|物品栏粒子* +backport-recipes@ +primal-survival@ +electrical-age-eln@electrical-age-jrddunbr-edition +@showfps|显示FPS (ShowFPS) +magia-daemonica +death-n-taxes + +super-factory-manager-unofficial-backport-sfm-ub +cart-complete +veilings + +narutoloading +visual-keymap@|可视化键位图* + + +grindstoneplus|更好的砂轮 (GrindstonePlus) +anvilcraft-transducers|铁砧工艺:并入电网 (AnvilCraft Transducers) +confluence-weapon-modification@confluence_weaponmodification|汇流来世:武器修改 (Confluence: Weapon Modification) +some-useless-things|无用之物* +hmag-spells|藏妻术式 (HMaG Spells) +tsb-tetra-spell-book|Tetra法术书 (Tetra Spell Book) + +gilded-krmor + +traces-of-the-fallen +lycanite-mobs-additions +revmobs +kaleidoscope-cookery-refabricated@|森罗物语厨房:重织* +timelesslib@ +norfrieds-eldritch-takeover +deliverymanconfig|殖民地现代化更新 (Minecolonies Modernization Update) +setcraftjs|套装效果 (SetCraftJS) +xymultichat|聊天分栏 (XYMultiChat) +mc-mods-of-slashblades + +texture-locale-redirector@ +eidolon-edoni@|幻梦:欢宴 (Eidolon : Edoni) +mekanism-morecapacity@mekanismmorecapacity +modular-machinery-cryogenic-simulation-edition +sbm-wooden-rails +project-fruit|水果地形* + +tlm-native-power|车万女仆:原生的力量 (TLM: Native POWER) +hogcraft +awesome-sheep-swell@ +@casks|桶 (Casks) +luxurious-misfortunes +transparent-entities@ +paintings-n-discs-backport +fallen-elves +@pv-addon-flashback + +ae2fluidcraft-rework-unofficial|AE2流体合成套件重置非官方版 (AE2 Fluid Craft Rework Unofficial) +annihilation-recreated +simple-kelpies@ +tacz-turrets@ + + +health-scoring +supearlposition +daisy@ +no-fall-mod +@farmers-assortment +foggy-world + +thieves +witt +techrot@ +create-easyfilling@ +mortar-and-pastels@mortar +@delicate_little_garbage|精致小垃圾 (Delicate Little Garbage) +elemental-arsenal|属性锻造 (Elemental Arsenal / ElementalCraft) + +kubejs-recipes-create|kubejs配方生成* + +showercore|洗浴核心 (Shower Core) +playsound-pro|音频播放专业版*¨@playsound_pro|音频播放专业版 (Playsound Pro) - 审核中 +omnisprint@|四向疾跑 (OmniSprint) +@totem-of-luck|幸运图腾 (Totem of Luck) +infinite-provider@|无限供应器* +annuus@ +myosotis@ + + + + + + + +magistics +littlefix +@legacyvoicechat +fast-recipe-search|快速配方查找* + +speech-recognition +faster-block-placement@|更快的方块放置* +instantsleep +chunk-save-fix@|区块保存修复* +peacefulsurface¨peacefulsurface-fabric +mazons-quarks +lucisrenderlib@|光 渲染库 (LucisRenderLib) +no-ai-guess@|禁止 AI 胡扯 (No AI Guess) +itemnamecopier|复制物品名称 (ItemNameCopier) +ping-like-r2 +super-bosses@ +slashblade-patchouli@ +ender-dragon-suit +ecliptic-seasons-lets-do +lets-do-hearth-timber@ +immersive-vehicles-vdeco@vdeco +netherized-kedition +wizardry-and-fire +pepsied-item-name-recolorer +pepsied-auto-droppers +pepsied-copper-item-pipes +pepsied-exp-vacuum +pepsied-pocket-dimension +pepsied-mining-quarries +sbm-red-cow +fangs-textiles-and-trinkets + + + +saurialib +riftlibrary@ +clean-portals-randomportals-cleanroom-edition +spinning-items-displayer-mod +attack-range-attribute@ +kryptonfnp-patcher@ +riautomobility +cyberware-reforged +project-plastic + + + +me-placement-tool|ME成型工具¨@me-placement-tool|ME成型工具 - 审核中 +pepsied-slow-hopper +nocubes-food-desire +@dense-flowers +prehistoric-rift@ + + +more-slashblade-ex-enchantment-effects|超多附魔增效 (More SlashBlade Ex Enchantment Effects) +tinkers-emc|匠魂EMC (Tinker's EMC) +@tacz-advanced-deathinfo +summon-totem|召唤图腾* + + +christmas-music-discs@ +friends-for-life@ + +wool-pressure-plates@ +protect-mobs-from-daylight@ +pepsied-more-professions +pepsied-chaos-core +pepsied-auto-bridger +custom-sapling|自定义树苗* + +patternprovidersave@|样板供应器保存 (PatternProviderSave) +create-lazytick|机械动力:懒惰刻 (Create:LazyTick) - 注意号¨@createlazytick|机械动力:懒惰刻 (Create:LazyTick) \ No newline at end of file diff --git a/Plain Craft Launcher 2/Resources/System.ValueTuple.dll b/Plain Craft Launcher 2/Resources/System.ValueTuple.dll new file mode 100644 index 0000000..fec98f3 Binary files /dev/null and b/Plain Craft Launcher 2/Resources/System.ValueTuple.dll differ